1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.database;
18 
19 import android.content.ContentValues;
20 import android.content.Context;
21 import android.content.OperationApplicationException;
22 import android.database.sqlite.SQLiteAbortException;
23 import android.database.sqlite.SQLiteConstraintException;
24 import android.database.sqlite.SQLiteDatabase;
25 import android.database.sqlite.SQLiteDatabaseCorruptException;
26 import android.database.sqlite.SQLiteDiskIOException;
27 import android.database.sqlite.SQLiteException;
28 import android.database.sqlite.SQLiteFullException;
29 import android.database.sqlite.SQLiteProgram;
30 import android.database.sqlite.SQLiteStatement;
31 import android.os.OperationCanceledException;
32 import android.os.Parcel;
33 import android.os.ParcelFileDescriptor;
34 import android.text.TextUtils;
35 import android.util.Log;
36 
37 import java.io.FileNotFoundException;
38 import java.io.PrintStream;
39 import java.text.Collator;
40 import java.util.HashMap;
41 import java.util.Locale;
42 import java.util.Map;
43 
44 /**
45  * Static utility methods for dealing with databases and {@link Cursor}s.
46  */
47 public class DatabaseUtils {
48     private static final String TAG = "DatabaseUtils";
49 
50     private static final boolean DEBUG = false;
51 
52     /** One of the values returned by {@link #getSqlStatementType(String)}. */
53     public static final int STATEMENT_SELECT = 1;
54     /** One of the values returned by {@link #getSqlStatementType(String)}. */
55     public static final int STATEMENT_UPDATE = 2;
56     /** One of the values returned by {@link #getSqlStatementType(String)}. */
57     public static final int STATEMENT_ATTACH = 3;
58     /** One of the values returned by {@link #getSqlStatementType(String)}. */
59     public static final int STATEMENT_BEGIN = 4;
60     /** One of the values returned by {@link #getSqlStatementType(String)}. */
61     public static final int STATEMENT_COMMIT = 5;
62     /** One of the values returned by {@link #getSqlStatementType(String)}. */
63     public static final int STATEMENT_ABORT = 6;
64     /** One of the values returned by {@link #getSqlStatementType(String)}. */
65     public static final int STATEMENT_PRAGMA = 7;
66     /** One of the values returned by {@link #getSqlStatementType(String)}. */
67     public static final int STATEMENT_DDL = 8;
68     /** One of the values returned by {@link #getSqlStatementType(String)}. */
69     public static final int STATEMENT_UNPREPARED = 9;
70     /** One of the values returned by {@link #getSqlStatementType(String)}. */
71     public static final int STATEMENT_OTHER = 99;
72 
73     /**
74      * Special function for writing an exception result at the header of
75      * a parcel, to be used when returning an exception from a transaction.
76      * exception will be re-thrown by the function in another process
77      * @param reply Parcel to write to
78      * @param e The Exception to be written.
79      * @see Parcel#writeNoException
80      * @see Parcel#writeException
81      */
writeExceptionToParcel(Parcel reply, Exception e)82     public static final void writeExceptionToParcel(Parcel reply, Exception e) {
83         int code = 0;
84         boolean logException = true;
85         if (e instanceof FileNotFoundException) {
86             code = 1;
87             logException = false;
88         } else if (e instanceof IllegalArgumentException) {
89             code = 2;
90         } else if (e instanceof UnsupportedOperationException) {
91             code = 3;
92         } else if (e instanceof SQLiteAbortException) {
93             code = 4;
94         } else if (e instanceof SQLiteConstraintException) {
95             code = 5;
96         } else if (e instanceof SQLiteDatabaseCorruptException) {
97             code = 6;
98         } else if (e instanceof SQLiteFullException) {
99             code = 7;
100         } else if (e instanceof SQLiteDiskIOException) {
101             code = 8;
102         } else if (e instanceof SQLiteException) {
103             code = 9;
104         } else if (e instanceof OperationApplicationException) {
105             code = 10;
106         } else if (e instanceof OperationCanceledException) {
107             code = 11;
108             logException = false;
109         } else {
110             reply.writeException(e);
111             Log.e(TAG, "Writing exception to parcel", e);
112             return;
113         }
114         reply.writeInt(code);
115         reply.writeString(e.getMessage());
116 
117         if (logException) {
118             Log.e(TAG, "Writing exception to parcel", e);
119         }
120     }
121 
122     /**
123      * Special function for reading an exception result from the header of
124      * a parcel, to be used after receiving the result of a transaction.  This
125      * will throw the exception for you if it had been written to the Parcel,
126      * otherwise return and let you read the normal result data from the Parcel.
127      * @param reply Parcel to read from
128      * @see Parcel#writeNoException
129      * @see Parcel#readException
130      */
readExceptionFromParcel(Parcel reply)131     public static final void readExceptionFromParcel(Parcel reply) {
132         int code = reply.readExceptionCode();
133         if (code == 0) return;
134         String msg = reply.readString();
135         DatabaseUtils.readExceptionFromParcel(reply, msg, code);
136     }
137 
readExceptionWithFileNotFoundExceptionFromParcel( Parcel reply)138     public static void readExceptionWithFileNotFoundExceptionFromParcel(
139             Parcel reply) throws FileNotFoundException {
140         int code = reply.readExceptionCode();
141         if (code == 0) return;
142         String msg = reply.readString();
143         if (code == 1) {
144             throw new FileNotFoundException(msg);
145         } else {
146             DatabaseUtils.readExceptionFromParcel(reply, msg, code);
147         }
148     }
149 
readExceptionWithOperationApplicationExceptionFromParcel( Parcel reply)150     public static void readExceptionWithOperationApplicationExceptionFromParcel(
151             Parcel reply) throws OperationApplicationException {
152         int code = reply.readExceptionCode();
153         if (code == 0) return;
154         String msg = reply.readString();
155         if (code == 10) {
156             throw new OperationApplicationException(msg);
157         } else {
158             DatabaseUtils.readExceptionFromParcel(reply, msg, code);
159         }
160     }
161 
readExceptionFromParcel(Parcel reply, String msg, int code)162     private static final void readExceptionFromParcel(Parcel reply, String msg, int code) {
163         switch (code) {
164             case 2:
165                 throw new IllegalArgumentException(msg);
166             case 3:
167                 throw new UnsupportedOperationException(msg);
168             case 4:
169                 throw new SQLiteAbortException(msg);
170             case 5:
171                 throw new SQLiteConstraintException(msg);
172             case 6:
173                 throw new SQLiteDatabaseCorruptException(msg);
174             case 7:
175                 throw new SQLiteFullException(msg);
176             case 8:
177                 throw new SQLiteDiskIOException(msg);
178             case 9:
179                 throw new SQLiteException(msg);
180             case 11:
181                 throw new OperationCanceledException(msg);
182             default:
183                 reply.readException(code, msg);
184         }
185     }
186 
187     /**
188      * Binds the given Object to the given SQLiteProgram using the proper
189      * typing. For example, bind numbers as longs/doubles, and everything else
190      * as a string by call toString() on it.
191      *
192      * @param prog the program to bind the object to
193      * @param index the 1-based index to bind at
194      * @param value the value to bind
195      */
bindObjectToProgram(SQLiteProgram prog, int index, Object value)196     public static void bindObjectToProgram(SQLiteProgram prog, int index,
197             Object value) {
198         if (value == null) {
199             prog.bindNull(index);
200         } else if (value instanceof Double || value instanceof Float) {
201             prog.bindDouble(index, ((Number)value).doubleValue());
202         } else if (value instanceof Number) {
203             prog.bindLong(index, ((Number)value).longValue());
204         } else if (value instanceof Boolean) {
205             Boolean bool = (Boolean)value;
206             if (bool) {
207                 prog.bindLong(index, 1);
208             } else {
209                 prog.bindLong(index, 0);
210             }
211         } else if (value instanceof byte[]){
212             prog.bindBlob(index, (byte[]) value);
213         } else {
214             prog.bindString(index, value.toString());
215         }
216     }
217 
218     /**
219      * Returns data type of the given object's value.
220      *<p>
221      * Returned values are
222      * <ul>
223      *   <li>{@link Cursor#FIELD_TYPE_NULL}</li>
224      *   <li>{@link Cursor#FIELD_TYPE_INTEGER}</li>
225      *   <li>{@link Cursor#FIELD_TYPE_FLOAT}</li>
226      *   <li>{@link Cursor#FIELD_TYPE_STRING}</li>
227      *   <li>{@link Cursor#FIELD_TYPE_BLOB}</li>
228      *</ul>
229      *</p>
230      *
231      * @param obj the object whose value type is to be returned
232      * @return object value type
233      * @hide
234      */
getTypeOfObject(Object obj)235     public static int getTypeOfObject(Object obj) {
236         if (obj == null) {
237             return Cursor.FIELD_TYPE_NULL;
238         } else if (obj instanceof byte[]) {
239             return Cursor.FIELD_TYPE_BLOB;
240         } else if (obj instanceof Float || obj instanceof Double) {
241             return Cursor.FIELD_TYPE_FLOAT;
242         } else if (obj instanceof Long || obj instanceof Integer
243                 || obj instanceof Short || obj instanceof Byte) {
244             return Cursor.FIELD_TYPE_INTEGER;
245         } else {
246             return Cursor.FIELD_TYPE_STRING;
247         }
248     }
249 
250     /**
251      * Fills the specified cursor window by iterating over the contents of the cursor.
252      * The window is filled until the cursor is exhausted or the window runs out
253      * of space.
254      *
255      * The original position of the cursor is left unchanged by this operation.
256      *
257      * @param cursor The cursor that contains the data to put in the window.
258      * @param position The start position for filling the window.
259      * @param window The window to fill.
260      * @hide
261      */
cursorFillWindow(final Cursor cursor, int position, final CursorWindow window)262     public static void cursorFillWindow(final Cursor cursor,
263             int position, final CursorWindow window) {
264         if (position < 0 || position >= cursor.getCount()) {
265             return;
266         }
267         final int oldPos = cursor.getPosition();
268         final int numColumns = cursor.getColumnCount();
269         window.clear();
270         window.setStartPosition(position);
271         window.setNumColumns(numColumns);
272         if (cursor.moveToPosition(position)) {
273             rowloop: do {
274                 if (!window.allocRow()) {
275                     break;
276                 }
277                 for (int i = 0; i < numColumns; i++) {
278                     final int type = cursor.getType(i);
279                     final boolean success;
280                     switch (type) {
281                         case Cursor.FIELD_TYPE_NULL:
282                             success = window.putNull(position, i);
283                             break;
284 
285                         case Cursor.FIELD_TYPE_INTEGER:
286                             success = window.putLong(cursor.getLong(i), position, i);
287                             break;
288 
289                         case Cursor.FIELD_TYPE_FLOAT:
290                             success = window.putDouble(cursor.getDouble(i), position, i);
291                             break;
292 
293                         case Cursor.FIELD_TYPE_BLOB: {
294                             final byte[] value = cursor.getBlob(i);
295                             success = value != null ? window.putBlob(value, position, i)
296                                     : window.putNull(position, i);
297                             break;
298                         }
299 
300                         default: // assume value is convertible to String
301                         case Cursor.FIELD_TYPE_STRING: {
302                             final String value = cursor.getString(i);
303                             success = value != null ? window.putString(value, position, i)
304                                     : window.putNull(position, i);
305                             break;
306                         }
307                     }
308                     if (!success) {
309                         window.freeLastRow();
310                         break rowloop;
311                     }
312                 }
313                 position += 1;
314             } while (cursor.moveToNext());
315         }
316         cursor.moveToPosition(oldPos);
317     }
318 
319     /**
320      * Appends an SQL string to the given StringBuilder, including the opening
321      * and closing single quotes. Any single quotes internal to sqlString will
322      * be escaped.
323      *
324      * This method is deprecated because we want to encourage everyone
325      * to use the "?" binding form.  However, when implementing a
326      * ContentProvider, one may want to add WHERE clauses that were
327      * not provided by the caller.  Since "?" is a positional form,
328      * using it in this case could break the caller because the
329      * indexes would be shifted to accomodate the ContentProvider's
330      * internal bindings.  In that case, it may be necessary to
331      * construct a WHERE clause manually.  This method is useful for
332      * those cases.
333      *
334      * @param sb the StringBuilder that the SQL string will be appended to
335      * @param sqlString the raw string to be appended, which may contain single
336      *                  quotes
337      */
appendEscapedSQLString(StringBuilder sb, String sqlString)338     public static void appendEscapedSQLString(StringBuilder sb, String sqlString) {
339         sb.append('\'');
340         if (sqlString.indexOf('\'') != -1) {
341             int length = sqlString.length();
342             for (int i = 0; i < length; i++) {
343                 char c = sqlString.charAt(i);
344                 if (c == '\'') {
345                     sb.append('\'');
346                 }
347                 sb.append(c);
348             }
349         } else
350             sb.append(sqlString);
351         sb.append('\'');
352     }
353 
354     /**
355      * SQL-escape a string.
356      */
sqlEscapeString(String value)357     public static String sqlEscapeString(String value) {
358         StringBuilder escaper = new StringBuilder();
359 
360         DatabaseUtils.appendEscapedSQLString(escaper, value);
361 
362         return escaper.toString();
363     }
364 
365     /**
366      * Appends an Object to an SQL string with the proper escaping, etc.
367      */
appendValueToSql(StringBuilder sql, Object value)368     public static final void appendValueToSql(StringBuilder sql, Object value) {
369         if (value == null) {
370             sql.append("NULL");
371         } else if (value instanceof Boolean) {
372             Boolean bool = (Boolean)value;
373             if (bool) {
374                 sql.append('1');
375             } else {
376                 sql.append('0');
377             }
378         } else {
379             appendEscapedSQLString(sql, value.toString());
380         }
381     }
382 
383     /**
384      * Concatenates two SQL WHERE clauses, handling empty or null values.
385      */
concatenateWhere(String a, String b)386     public static String concatenateWhere(String a, String b) {
387         if (TextUtils.isEmpty(a)) {
388             return b;
389         }
390         if (TextUtils.isEmpty(b)) {
391             return a;
392         }
393 
394         return "(" + a + ") AND (" + b + ")";
395     }
396 
397     /**
398      * return the collation key
399      * @param name
400      * @return the collation key
401      */
getCollationKey(String name)402     public static String getCollationKey(String name) {
403         byte [] arr = getCollationKeyInBytes(name);
404         try {
405             return new String(arr, 0, getKeyLen(arr), "ISO8859_1");
406         } catch (Exception ex) {
407             return "";
408         }
409     }
410 
411     /**
412      * return the collation key in hex format
413      * @param name
414      * @return the collation key in hex format
415      */
getHexCollationKey(String name)416     public static String getHexCollationKey(String name) {
417         byte[] arr = getCollationKeyInBytes(name);
418         char[] keys = encodeHex(arr);
419         return new String(keys, 0, getKeyLen(arr) * 2);
420     }
421 
422 
423     /**
424      * Used building output as Hex
425      */
426     private static final char[] DIGITS = {
427             '0', '1', '2', '3', '4', '5', '6', '7',
428             '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
429     };
430 
encodeHex(byte[] input)431     private static char[] encodeHex(byte[] input) {
432         int l = input.length;
433         char[] out = new char[l << 1];
434 
435         // two characters form the hex value.
436         for (int i = 0, j = 0; i < l; i++) {
437             out[j++] = DIGITS[(0xF0 & input[i]) >>> 4 ];
438             out[j++] = DIGITS[ 0x0F & input[i] ];
439         }
440 
441         return out;
442     }
443 
getKeyLen(byte[] arr)444     private static int getKeyLen(byte[] arr) {
445         if (arr[arr.length - 1] != 0) {
446             return arr.length;
447         } else {
448             // remove zero "termination"
449             return arr.length-1;
450         }
451     }
452 
getCollationKeyInBytes(String name)453     private static byte[] getCollationKeyInBytes(String name) {
454         if (mColl == null) {
455             mColl = Collator.getInstance();
456             mColl.setStrength(Collator.PRIMARY);
457         }
458         return mColl.getCollationKey(name).toByteArray();
459     }
460 
461     private static Collator mColl = null;
462     /**
463      * Prints the contents of a Cursor to System.out. The position is restored
464      * after printing.
465      *
466      * @param cursor the cursor to print
467      */
dumpCursor(Cursor cursor)468     public static void dumpCursor(Cursor cursor) {
469         dumpCursor(cursor, System.out);
470     }
471 
472     /**
473      * Prints the contents of a Cursor to a PrintSteam. The position is restored
474      * after printing.
475      *
476      * @param cursor the cursor to print
477      * @param stream the stream to print to
478      */
dumpCursor(Cursor cursor, PrintStream stream)479     public static void dumpCursor(Cursor cursor, PrintStream stream) {
480         stream.println(">>>>> Dumping cursor " + cursor);
481         if (cursor != null) {
482             int startPos = cursor.getPosition();
483 
484             cursor.moveToPosition(-1);
485             while (cursor.moveToNext()) {
486                 dumpCurrentRow(cursor, stream);
487             }
488             cursor.moveToPosition(startPos);
489         }
490         stream.println("<<<<<");
491     }
492 
493     /**
494      * Prints the contents of a Cursor to a StringBuilder. The position
495      * is restored after printing.
496      *
497      * @param cursor the cursor to print
498      * @param sb the StringBuilder to print to
499      */
dumpCursor(Cursor cursor, StringBuilder sb)500     public static void dumpCursor(Cursor cursor, StringBuilder sb) {
501         sb.append(">>>>> Dumping cursor " + cursor + "\n");
502         if (cursor != null) {
503             int startPos = cursor.getPosition();
504 
505             cursor.moveToPosition(-1);
506             while (cursor.moveToNext()) {
507                 dumpCurrentRow(cursor, sb);
508             }
509             cursor.moveToPosition(startPos);
510         }
511         sb.append("<<<<<\n");
512     }
513 
514     /**
515      * Prints the contents of a Cursor to a String. The position is restored
516      * after printing.
517      *
518      * @param cursor the cursor to print
519      * @return a String that contains the dumped cursor
520      */
dumpCursorToString(Cursor cursor)521     public static String dumpCursorToString(Cursor cursor) {
522         StringBuilder sb = new StringBuilder();
523         dumpCursor(cursor, sb);
524         return sb.toString();
525     }
526 
527     /**
528      * Prints the contents of a Cursor's current row to System.out.
529      *
530      * @param cursor the cursor to print from
531      */
dumpCurrentRow(Cursor cursor)532     public static void dumpCurrentRow(Cursor cursor) {
533         dumpCurrentRow(cursor, System.out);
534     }
535 
536     /**
537      * Prints the contents of a Cursor's current row to a PrintSteam.
538      *
539      * @param cursor the cursor to print
540      * @param stream the stream to print to
541      */
dumpCurrentRow(Cursor cursor, PrintStream stream)542     public static void dumpCurrentRow(Cursor cursor, PrintStream stream) {
543         String[] cols = cursor.getColumnNames();
544         stream.println("" + cursor.getPosition() + " {");
545         int length = cols.length;
546         for (int i = 0; i< length; i++) {
547             String value;
548             try {
549                 value = cursor.getString(i);
550             } catch (SQLiteException e) {
551                 // assume that if the getString threw this exception then the column is not
552                 // representable by a string, e.g. it is a BLOB.
553                 value = "<unprintable>";
554             }
555             stream.println("   " + cols[i] + '=' + value);
556         }
557         stream.println("}");
558     }
559 
560     /**
561      * Prints the contents of a Cursor's current row to a StringBuilder.
562      *
563      * @param cursor the cursor to print
564      * @param sb the StringBuilder to print to
565      */
dumpCurrentRow(Cursor cursor, StringBuilder sb)566     public static void dumpCurrentRow(Cursor cursor, StringBuilder sb) {
567         String[] cols = cursor.getColumnNames();
568         sb.append("" + cursor.getPosition() + " {\n");
569         int length = cols.length;
570         for (int i = 0; i < length; i++) {
571             String value;
572             try {
573                 value = cursor.getString(i);
574             } catch (SQLiteException e) {
575                 // assume that if the getString threw this exception then the column is not
576                 // representable by a string, e.g. it is a BLOB.
577                 value = "<unprintable>";
578             }
579             sb.append("   " + cols[i] + '=' + value + "\n");
580         }
581         sb.append("}\n");
582     }
583 
584     /**
585      * Dump the contents of a Cursor's current row to a String.
586      *
587      * @param cursor the cursor to print
588      * @return a String that contains the dumped cursor row
589      */
dumpCurrentRowToString(Cursor cursor)590     public static String dumpCurrentRowToString(Cursor cursor) {
591         StringBuilder sb = new StringBuilder();
592         dumpCurrentRow(cursor, sb);
593         return sb.toString();
594     }
595 
596     /**
597      * Reads a String out of a field in a Cursor and writes it to a Map.
598      *
599      * @param cursor The cursor to read from
600      * @param field The TEXT field to read
601      * @param values The {@link ContentValues} to put the value into, with the field as the key
602      */
cursorStringToContentValues(Cursor cursor, String field, ContentValues values)603     public static void cursorStringToContentValues(Cursor cursor, String field,
604             ContentValues values) {
605         cursorStringToContentValues(cursor, field, values, field);
606     }
607 
608     /**
609      * Reads a String out of a field in a Cursor and writes it to an InsertHelper.
610      *
611      * @param cursor The cursor to read from
612      * @param field The TEXT field to read
613      * @param inserter The InsertHelper to bind into
614      * @param index the index of the bind entry in the InsertHelper
615      */
cursorStringToInsertHelper(Cursor cursor, String field, InsertHelper inserter, int index)616     public static void cursorStringToInsertHelper(Cursor cursor, String field,
617             InsertHelper inserter, int index) {
618         inserter.bind(index, cursor.getString(cursor.getColumnIndexOrThrow(field)));
619     }
620 
621     /**
622      * Reads a String out of a field in a Cursor and writes it to a Map.
623      *
624      * @param cursor The cursor to read from
625      * @param field The TEXT field to read
626      * @param values The {@link ContentValues} to put the value into, with the field as the key
627      * @param key The key to store the value with in the map
628      */
cursorStringToContentValues(Cursor cursor, String field, ContentValues values, String key)629     public static void cursorStringToContentValues(Cursor cursor, String field,
630             ContentValues values, String key) {
631         values.put(key, cursor.getString(cursor.getColumnIndexOrThrow(field)));
632     }
633 
634     /**
635      * Reads an Integer out of a field in a Cursor and writes it to a Map.
636      *
637      * @param cursor The cursor to read from
638      * @param field The INTEGER field to read
639      * @param values The {@link ContentValues} to put the value into, with the field as the key
640      */
cursorIntToContentValues(Cursor cursor, String field, ContentValues values)641     public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values) {
642         cursorIntToContentValues(cursor, field, values, field);
643     }
644 
645     /**
646      * Reads a Integer out of a field in a Cursor and writes it to a Map.
647      *
648      * @param cursor The cursor to read from
649      * @param field The INTEGER field to read
650      * @param values The {@link ContentValues} to put the value into, with the field as the key
651      * @param key The key to store the value with in the map
652      */
cursorIntToContentValues(Cursor cursor, String field, ContentValues values, String key)653     public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values,
654             String key) {
655         int colIndex = cursor.getColumnIndex(field);
656         if (!cursor.isNull(colIndex)) {
657             values.put(key, cursor.getInt(colIndex));
658         } else {
659             values.put(key, (Integer) null);
660         }
661     }
662 
663     /**
664      * Reads a Long out of a field in a Cursor and writes it to a Map.
665      *
666      * @param cursor The cursor to read from
667      * @param field The INTEGER field to read
668      * @param values The {@link ContentValues} to put the value into, with the field as the key
669      */
cursorLongToContentValues(Cursor cursor, String field, ContentValues values)670     public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values)
671     {
672         cursorLongToContentValues(cursor, field, values, field);
673     }
674 
675     /**
676      * Reads a Long out of a field in a Cursor and writes it to a Map.
677      *
678      * @param cursor The cursor to read from
679      * @param field The INTEGER field to read
680      * @param values The {@link ContentValues} to put the value into
681      * @param key The key to store the value with in the map
682      */
cursorLongToContentValues(Cursor cursor, String field, ContentValues values, String key)683     public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values,
684             String key) {
685         int colIndex = cursor.getColumnIndex(field);
686         if (!cursor.isNull(colIndex)) {
687             Long value = Long.valueOf(cursor.getLong(colIndex));
688             values.put(key, value);
689         } else {
690             values.put(key, (Long) null);
691         }
692     }
693 
694     /**
695      * Reads a Double out of a field in a Cursor and writes it to a Map.
696      *
697      * @param cursor The cursor to read from
698      * @param field The REAL field to read
699      * @param values The {@link ContentValues} to put the value into
700      */
cursorDoubleToCursorValues(Cursor cursor, String field, ContentValues values)701     public static void cursorDoubleToCursorValues(Cursor cursor, String field, ContentValues values)
702     {
703         cursorDoubleToContentValues(cursor, field, values, field);
704     }
705 
706     /**
707      * Reads a Double out of a field in a Cursor and writes it to a Map.
708      *
709      * @param cursor The cursor to read from
710      * @param field The REAL field to read
711      * @param values The {@link ContentValues} to put the value into
712      * @param key The key to store the value with in the map
713      */
cursorDoubleToContentValues(Cursor cursor, String field, ContentValues values, String key)714     public static void cursorDoubleToContentValues(Cursor cursor, String field,
715             ContentValues values, String key) {
716         int colIndex = cursor.getColumnIndex(field);
717         if (!cursor.isNull(colIndex)) {
718             values.put(key, cursor.getDouble(colIndex));
719         } else {
720             values.put(key, (Double) null);
721         }
722     }
723 
724     /**
725      * Read the entire contents of a cursor row and store them in a ContentValues.
726      *
727      * @param cursor the cursor to read from.
728      * @param values the {@link ContentValues} to put the row into.
729      */
cursorRowToContentValues(Cursor cursor, ContentValues values)730     public static void cursorRowToContentValues(Cursor cursor, ContentValues values) {
731         String[] columns = cursor.getColumnNames();
732         int length = columns.length;
733         for (int i = 0; i < length; i++) {
734             if (cursor.getType(i) == Cursor.FIELD_TYPE_BLOB) {
735                 values.put(columns[i], cursor.getBlob(i));
736             } else {
737                 values.put(columns[i], cursor.getString(i));
738             }
739         }
740     }
741 
742     /**
743      * Picks a start position for {@link Cursor#fillWindow} such that the
744      * window will contain the requested row and a useful range of rows
745      * around it.
746      *
747      * When the data set is too large to fit in a cursor window, seeking the
748      * cursor can become a very expensive operation since we have to run the
749      * query again when we move outside the bounds of the current window.
750      *
751      * We try to choose a start position for the cursor window such that
752      * 1/3 of the window's capacity is used to hold rows before the requested
753      * position and 2/3 of the window's capacity is used to hold rows after the
754      * requested position.
755      *
756      * @param cursorPosition The row index of the row we want to get.
757      * @param cursorWindowCapacity The estimated number of rows that can fit in
758      * a cursor window, or 0 if unknown.
759      * @return The recommended start position, always less than or equal to
760      * the requested row.
761      * @hide
762      */
cursorPickFillWindowStartPosition( int cursorPosition, int cursorWindowCapacity)763     public static int cursorPickFillWindowStartPosition(
764             int cursorPosition, int cursorWindowCapacity) {
765         return Math.max(cursorPosition - cursorWindowCapacity / 3, 0);
766     }
767 
768     /**
769      * Query the table for the number of rows in the table.
770      * @param db the database the table is in
771      * @param table the name of the table to query
772      * @return the number of rows in the table
773      */
queryNumEntries(SQLiteDatabase db, String table)774     public static long queryNumEntries(SQLiteDatabase db, String table) {
775         return queryNumEntries(db, table, null, null);
776     }
777 
778     /**
779      * Query the table for the number of rows in the table.
780      * @param db the database the table is in
781      * @param table the name of the table to query
782      * @param selection A filter declaring which rows to return,
783      *              formatted as an SQL WHERE clause (excluding the WHERE itself).
784      *              Passing null will count all rows for the given table
785      * @return the number of rows in the table filtered by the selection
786      */
queryNumEntries(SQLiteDatabase db, String table, String selection)787     public static long queryNumEntries(SQLiteDatabase db, String table, String selection) {
788         return queryNumEntries(db, table, selection, null);
789     }
790 
791     /**
792      * Query the table for the number of rows in the table.
793      * @param db the database the table is in
794      * @param table the name of the table to query
795      * @param selection A filter declaring which rows to return,
796      *              formatted as an SQL WHERE clause (excluding the WHERE itself).
797      *              Passing null will count all rows for the given table
798      * @param selectionArgs You may include ?s in selection,
799      *              which will be replaced by the values from selectionArgs,
800      *              in order that they appear in the selection.
801      *              The values will be bound as Strings.
802      * @return the number of rows in the table filtered by the selection
803      */
queryNumEntries(SQLiteDatabase db, String table, String selection, String[] selectionArgs)804     public static long queryNumEntries(SQLiteDatabase db, String table, String selection,
805             String[] selectionArgs) {
806         String s = (!TextUtils.isEmpty(selection)) ? " where " + selection : "";
807         return longForQuery(db, "select count(*) from " + table + s,
808                     selectionArgs);
809     }
810 
811     /**
812      * Query the table to check whether a table is empty or not
813      * @param db the database the table is in
814      * @param table the name of the table to query
815      * @return True if the table is empty
816      * @hide
817      */
queryIsEmpty(SQLiteDatabase db, String table)818     public static boolean queryIsEmpty(SQLiteDatabase db, String table) {
819         long isEmpty = longForQuery(db, "select exists(select 1 from " + table + ")", null);
820         return isEmpty == 0;
821     }
822 
823     /**
824      * Utility method to run the query on the db and return the value in the
825      * first column of the first row.
826      */
longForQuery(SQLiteDatabase db, String query, String[] selectionArgs)827     public static long longForQuery(SQLiteDatabase db, String query, String[] selectionArgs) {
828         SQLiteStatement prog = db.compileStatement(query);
829         try {
830             return longForQuery(prog, selectionArgs);
831         } finally {
832             prog.close();
833         }
834     }
835 
836     /**
837      * Utility method to run the pre-compiled query and return the value in the
838      * first column of the first row.
839      */
longForQuery(SQLiteStatement prog, String[] selectionArgs)840     public static long longForQuery(SQLiteStatement prog, String[] selectionArgs) {
841         prog.bindAllArgsAsStrings(selectionArgs);
842         return prog.simpleQueryForLong();
843     }
844 
845     /**
846      * Utility method to run the query on the db and return the value in the
847      * first column of the first row.
848      */
stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs)849     public static String stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs) {
850         SQLiteStatement prog = db.compileStatement(query);
851         try {
852             return stringForQuery(prog, selectionArgs);
853         } finally {
854             prog.close();
855         }
856     }
857 
858     /**
859      * Utility method to run the pre-compiled query and return the value in the
860      * first column of the first row.
861      */
stringForQuery(SQLiteStatement prog, String[] selectionArgs)862     public static String stringForQuery(SQLiteStatement prog, String[] selectionArgs) {
863         prog.bindAllArgsAsStrings(selectionArgs);
864         return prog.simpleQueryForString();
865     }
866 
867     /**
868      * Utility method to run the query on the db and return the blob value in the
869      * first column of the first row.
870      *
871      * @return A read-only file descriptor for a copy of the blob value.
872      */
blobFileDescriptorForQuery(SQLiteDatabase db, String query, String[] selectionArgs)873     public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteDatabase db,
874             String query, String[] selectionArgs) {
875         SQLiteStatement prog = db.compileStatement(query);
876         try {
877             return blobFileDescriptorForQuery(prog, selectionArgs);
878         } finally {
879             prog.close();
880         }
881     }
882 
883     /**
884      * Utility method to run the pre-compiled query and return the blob value in the
885      * first column of the first row.
886      *
887      * @return A read-only file descriptor for a copy of the blob value.
888      */
blobFileDescriptorForQuery(SQLiteStatement prog, String[] selectionArgs)889     public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteStatement prog,
890             String[] selectionArgs) {
891         prog.bindAllArgsAsStrings(selectionArgs);
892         return prog.simpleQueryForBlobFileDescriptor();
893     }
894 
895     /**
896      * Reads a String out of a column in a Cursor and writes it to a ContentValues.
897      * Adds nothing to the ContentValues if the column isn't present or if its value is null.
898      *
899      * @param cursor The cursor to read from
900      * @param column The column to read
901      * @param values The {@link ContentValues} to put the value into
902      */
cursorStringToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)903     public static void cursorStringToContentValuesIfPresent(Cursor cursor, ContentValues values,
904             String column) {
905         final int index = cursor.getColumnIndex(column);
906         if (index != -1 && !cursor.isNull(index)) {
907             values.put(column, cursor.getString(index));
908         }
909     }
910 
911     /**
912      * Reads a Long out of a column in a Cursor and writes it to a ContentValues.
913      * Adds nothing to the ContentValues if the column isn't present or if its value is null.
914      *
915      * @param cursor The cursor to read from
916      * @param column The column to read
917      * @param values The {@link ContentValues} to put the value into
918      */
cursorLongToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)919     public static void cursorLongToContentValuesIfPresent(Cursor cursor, ContentValues values,
920             String column) {
921         final int index = cursor.getColumnIndex(column);
922         if (index != -1 && !cursor.isNull(index)) {
923             values.put(column, cursor.getLong(index));
924         }
925     }
926 
927     /**
928      * Reads a Short out of a column in a Cursor and writes it to a ContentValues.
929      * Adds nothing to the ContentValues if the column isn't present or if its value is null.
930      *
931      * @param cursor The cursor to read from
932      * @param column The column to read
933      * @param values The {@link ContentValues} to put the value into
934      */
cursorShortToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)935     public static void cursorShortToContentValuesIfPresent(Cursor cursor, ContentValues values,
936             String column) {
937         final int index = cursor.getColumnIndex(column);
938         if (index != -1 && !cursor.isNull(index)) {
939             values.put(column, cursor.getShort(index));
940         }
941     }
942 
943     /**
944      * Reads a Integer out of a column in a Cursor and writes it to a ContentValues.
945      * Adds nothing to the ContentValues if the column isn't present or if its value is null.
946      *
947      * @param cursor The cursor to read from
948      * @param column The column to read
949      * @param values The {@link ContentValues} to put the value into
950      */
cursorIntToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)951     public static void cursorIntToContentValuesIfPresent(Cursor cursor, ContentValues values,
952             String column) {
953         final int index = cursor.getColumnIndex(column);
954         if (index != -1 && !cursor.isNull(index)) {
955             values.put(column, cursor.getInt(index));
956         }
957     }
958 
959     /**
960      * Reads a Float out of a column in a Cursor and writes it to a ContentValues.
961      * Adds nothing to the ContentValues if the column isn't present or if its value is null.
962      *
963      * @param cursor The cursor to read from
964      * @param column The column to read
965      * @param values The {@link ContentValues} to put the value into
966      */
cursorFloatToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)967     public static void cursorFloatToContentValuesIfPresent(Cursor cursor, ContentValues values,
968             String column) {
969         final int index = cursor.getColumnIndex(column);
970         if (index != -1 && !cursor.isNull(index)) {
971             values.put(column, cursor.getFloat(index));
972         }
973     }
974 
975     /**
976      * Reads a Double out of a column in a Cursor and writes it to a ContentValues.
977      * Adds nothing to the ContentValues if the column isn't present or if its value is null.
978      *
979      * @param cursor The cursor to read from
980      * @param column The column to read
981      * @param values The {@link ContentValues} to put the value into
982      */
cursorDoubleToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)983     public static void cursorDoubleToContentValuesIfPresent(Cursor cursor, ContentValues values,
984             String column) {
985         final int index = cursor.getColumnIndex(column);
986         if (index != -1 && !cursor.isNull(index)) {
987             values.put(column, cursor.getDouble(index));
988         }
989     }
990 
991     /**
992      * This class allows users to do multiple inserts into a table using
993      * the same statement.
994      * <p>
995      * This class is not thread-safe.
996      * </p>
997      *
998      * @deprecated Use {@link SQLiteStatement} instead.
999      */
1000     @Deprecated
1001     public static class InsertHelper {
1002         private final SQLiteDatabase mDb;
1003         private final String mTableName;
1004         private HashMap<String, Integer> mColumns;
1005         private String mInsertSQL = null;
1006         private SQLiteStatement mInsertStatement = null;
1007         private SQLiteStatement mReplaceStatement = null;
1008         private SQLiteStatement mPreparedStatement = null;
1009 
1010         /**
1011          * {@hide}
1012          *
1013          * These are the columns returned by sqlite's "PRAGMA
1014          * table_info(...)" command that we depend on.
1015          */
1016         public static final int TABLE_INFO_PRAGMA_COLUMNNAME_INDEX = 1;
1017 
1018         /**
1019          * This field was accidentally exposed in earlier versions of the platform
1020          * so we can hide it but we can't remove it.
1021          *
1022          * @hide
1023          */
1024         public static final int TABLE_INFO_PRAGMA_DEFAULT_INDEX = 4;
1025 
1026         /**
1027          * @param db the SQLiteDatabase to insert into
1028          * @param tableName the name of the table to insert into
1029          */
InsertHelper(SQLiteDatabase db, String tableName)1030         public InsertHelper(SQLiteDatabase db, String tableName) {
1031             mDb = db;
1032             mTableName = tableName;
1033         }
1034 
buildSQL()1035         private void buildSQL() throws SQLException {
1036             StringBuilder sb = new StringBuilder(128);
1037             sb.append("INSERT INTO ");
1038             sb.append(mTableName);
1039             sb.append(" (");
1040 
1041             StringBuilder sbv = new StringBuilder(128);
1042             sbv.append("VALUES (");
1043 
1044             int i = 1;
1045             Cursor cur = null;
1046             try {
1047                 cur = mDb.rawQuery("PRAGMA table_info(" + mTableName + ")", null);
1048                 mColumns = new HashMap<String, Integer>(cur.getCount());
1049                 while (cur.moveToNext()) {
1050                     String columnName = cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX);
1051                     String defaultValue = cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX);
1052 
1053                     mColumns.put(columnName, i);
1054                     sb.append("'");
1055                     sb.append(columnName);
1056                     sb.append("'");
1057 
1058                     if (defaultValue == null) {
1059                         sbv.append("?");
1060                     } else {
1061                         sbv.append("COALESCE(?, ");
1062                         sbv.append(defaultValue);
1063                         sbv.append(")");
1064                     }
1065 
1066                     sb.append(i == cur.getCount() ? ") " : ", ");
1067                     sbv.append(i == cur.getCount() ? ");" : ", ");
1068                     ++i;
1069                 }
1070             } finally {
1071                 if (cur != null) cur.close();
1072             }
1073 
1074             sb.append(sbv);
1075 
1076             mInsertSQL = sb.toString();
1077             if (DEBUG) Log.v(TAG, "insert statement is " + mInsertSQL);
1078         }
1079 
getStatement(boolean allowReplace)1080         private SQLiteStatement getStatement(boolean allowReplace) throws SQLException {
1081             if (allowReplace) {
1082                 if (mReplaceStatement == null) {
1083                     if (mInsertSQL == null) buildSQL();
1084                     // chop "INSERT" off the front and prepend "INSERT OR REPLACE" instead.
1085                     String replaceSQL = "INSERT OR REPLACE" + mInsertSQL.substring(6);
1086                     mReplaceStatement = mDb.compileStatement(replaceSQL);
1087                 }
1088                 return mReplaceStatement;
1089             } else {
1090                 if (mInsertStatement == null) {
1091                     if (mInsertSQL == null) buildSQL();
1092                     mInsertStatement = mDb.compileStatement(mInsertSQL);
1093                 }
1094                 return mInsertStatement;
1095             }
1096         }
1097 
1098         /**
1099          * Performs an insert, adding a new row with the given values.
1100          *
1101          * @param values the set of values with which  to populate the
1102          * new row
1103          * @param allowReplace if true, the statement does "INSERT OR
1104          *   REPLACE" instead of "INSERT", silently deleting any
1105          *   previously existing rows that would cause a conflict
1106          *
1107          * @return the row ID of the newly inserted row, or -1 if an
1108          * error occurred
1109          */
insertInternal(ContentValues values, boolean allowReplace)1110         private long insertInternal(ContentValues values, boolean allowReplace) {
1111             // Start a transaction even though we don't really need one.
1112             // This is to help maintain compatibility with applications that
1113             // access InsertHelper from multiple threads even though they never should have.
1114             // The original code used to lock the InsertHelper itself which was prone
1115             // to deadlocks.  Starting a transaction achieves the same mutual exclusion
1116             // effect as grabbing a lock but without the potential for deadlocks.
1117             mDb.beginTransactionNonExclusive();
1118             try {
1119                 SQLiteStatement stmt = getStatement(allowReplace);
1120                 stmt.clearBindings();
1121                 if (DEBUG) Log.v(TAG, "--- inserting in table " + mTableName);
1122                 for (Map.Entry<String, Object> e: values.valueSet()) {
1123                     final String key = e.getKey();
1124                     int i = getColumnIndex(key);
1125                     DatabaseUtils.bindObjectToProgram(stmt, i, e.getValue());
1126                     if (DEBUG) {
1127                         Log.v(TAG, "binding " + e.getValue() + " to column " +
1128                               i + " (" + key + ")");
1129                     }
1130                 }
1131                 long result = stmt.executeInsert();
1132                 mDb.setTransactionSuccessful();
1133                 return result;
1134             } catch (SQLException e) {
1135                 Log.e(TAG, "Error inserting " + values + " into table  " + mTableName, e);
1136                 return -1;
1137             } finally {
1138                 mDb.endTransaction();
1139             }
1140         }
1141 
1142         /**
1143          * Returns the index of the specified column. This is index is suitagble for use
1144          * in calls to bind().
1145          * @param key the column name
1146          * @return the index of the column
1147          */
getColumnIndex(String key)1148         public int getColumnIndex(String key) {
1149             getStatement(false);
1150             final Integer index = mColumns.get(key);
1151             if (index == null) {
1152                 throw new IllegalArgumentException("column '" + key + "' is invalid");
1153             }
1154             return index;
1155         }
1156 
1157         /**
1158          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1159          * without a matching execute() must have already have been called.
1160          * @param index the index of the slot to which to bind
1161          * @param value the value to bind
1162          */
bind(int index, double value)1163         public void bind(int index, double value) {
1164             mPreparedStatement.bindDouble(index, value);
1165         }
1166 
1167         /**
1168          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1169          * without a matching execute() must have already have been called.
1170          * @param index the index of the slot to which to bind
1171          * @param value the value to bind
1172          */
bind(int index, float value)1173         public void bind(int index, float value) {
1174             mPreparedStatement.bindDouble(index, value);
1175         }
1176 
1177         /**
1178          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1179          * without a matching execute() must have already have been called.
1180          * @param index the index of the slot to which to bind
1181          * @param value the value to bind
1182          */
bind(int index, long value)1183         public void bind(int index, long value) {
1184             mPreparedStatement.bindLong(index, value);
1185         }
1186 
1187         /**
1188          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1189          * without a matching execute() must have already have been called.
1190          * @param index the index of the slot to which to bind
1191          * @param value the value to bind
1192          */
bind(int index, int value)1193         public void bind(int index, int value) {
1194             mPreparedStatement.bindLong(index, value);
1195         }
1196 
1197         /**
1198          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1199          * without a matching execute() must have already have been called.
1200          * @param index the index of the slot to which to bind
1201          * @param value the value to bind
1202          */
bind(int index, boolean value)1203         public void bind(int index, boolean value) {
1204             mPreparedStatement.bindLong(index, value ? 1 : 0);
1205         }
1206 
1207         /**
1208          * Bind null to an index. A prepareForInsert() or prepareForReplace()
1209          * without a matching execute() must have already have been called.
1210          * @param index the index of the slot to which to bind
1211          */
bindNull(int index)1212         public void bindNull(int index) {
1213             mPreparedStatement.bindNull(index);
1214         }
1215 
1216         /**
1217          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1218          * without a matching execute() must have already have been called.
1219          * @param index the index of the slot to which to bind
1220          * @param value the value to bind
1221          */
bind(int index, byte[] value)1222         public void bind(int index, byte[] value) {
1223             if (value == null) {
1224                 mPreparedStatement.bindNull(index);
1225             } else {
1226                 mPreparedStatement.bindBlob(index, value);
1227             }
1228         }
1229 
1230         /**
1231          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1232          * without a matching execute() must have already have been called.
1233          * @param index the index of the slot to which to bind
1234          * @param value the value to bind
1235          */
bind(int index, String value)1236         public void bind(int index, String value) {
1237             if (value == null) {
1238                 mPreparedStatement.bindNull(index);
1239             } else {
1240                 mPreparedStatement.bindString(index, value);
1241             }
1242         }
1243 
1244         /**
1245          * Performs an insert, adding a new row with the given values.
1246          * If the table contains conflicting rows, an error is
1247          * returned.
1248          *
1249          * @param values the set of values with which to populate the
1250          * new row
1251          *
1252          * @return the row ID of the newly inserted row, or -1 if an
1253          * error occurred
1254          */
insert(ContentValues values)1255         public long insert(ContentValues values) {
1256             return insertInternal(values, false);
1257         }
1258 
1259         /**
1260          * Execute the previously prepared insert or replace using the bound values
1261          * since the last call to prepareForInsert or prepareForReplace.
1262          *
1263          * <p>Note that calling bind() and then execute() is not thread-safe. The only thread-safe
1264          * way to use this class is to call insert() or replace().
1265          *
1266          * @return the row ID of the newly inserted row, or -1 if an
1267          * error occurred
1268          */
execute()1269         public long execute() {
1270             if (mPreparedStatement == null) {
1271                 throw new IllegalStateException("you must prepare this inserter before calling "
1272                         + "execute");
1273             }
1274             try {
1275                 if (DEBUG) Log.v(TAG, "--- doing insert or replace in table " + mTableName);
1276                 return mPreparedStatement.executeInsert();
1277             } catch (SQLException e) {
1278                 Log.e(TAG, "Error executing InsertHelper with table " + mTableName, e);
1279                 return -1;
1280             } finally {
1281                 // you can only call this once per prepare
1282                 mPreparedStatement = null;
1283             }
1284         }
1285 
1286         /**
1287          * Prepare the InsertHelper for an insert. The pattern for this is:
1288          * <ul>
1289          * <li>prepareForInsert()
1290          * <li>bind(index, value);
1291          * <li>bind(index, value);
1292          * <li>...
1293          * <li>bind(index, value);
1294          * <li>execute();
1295          * </ul>
1296          */
prepareForInsert()1297         public void prepareForInsert() {
1298             mPreparedStatement = getStatement(false);
1299             mPreparedStatement.clearBindings();
1300         }
1301 
1302         /**
1303          * Prepare the InsertHelper for a replace. The pattern for this is:
1304          * <ul>
1305          * <li>prepareForReplace()
1306          * <li>bind(index, value);
1307          * <li>bind(index, value);
1308          * <li>...
1309          * <li>bind(index, value);
1310          * <li>execute();
1311          * </ul>
1312          */
prepareForReplace()1313         public void prepareForReplace() {
1314             mPreparedStatement = getStatement(true);
1315             mPreparedStatement.clearBindings();
1316         }
1317 
1318         /**
1319          * Performs an insert, adding a new row with the given values.
1320          * If the table contains conflicting rows, they are deleted
1321          * and replaced with the new row.
1322          *
1323          * @param values the set of values with which to populate the
1324          * new row
1325          *
1326          * @return the row ID of the newly inserted row, or -1 if an
1327          * error occurred
1328          */
replace(ContentValues values)1329         public long replace(ContentValues values) {
1330             return insertInternal(values, true);
1331         }
1332 
1333         /**
1334          * Close this object and release any resources associated with
1335          * it.  The behavior of calling <code>insert()</code> after
1336          * calling this method is undefined.
1337          */
close()1338         public void close() {
1339             if (mInsertStatement != null) {
1340                 mInsertStatement.close();
1341                 mInsertStatement = null;
1342             }
1343             if (mReplaceStatement != null) {
1344                 mReplaceStatement.close();
1345                 mReplaceStatement = null;
1346             }
1347             mInsertSQL = null;
1348             mColumns = null;
1349         }
1350     }
1351 
1352     /**
1353      * Creates a db and populates it with the sql statements in sqlStatements.
1354      *
1355      * @param context the context to use to create the db
1356      * @param dbName the name of the db to create
1357      * @param dbVersion the version to set on the db
1358      * @param sqlStatements the statements to use to populate the db. This should be a single string
1359      *   of the form returned by sqlite3's <tt>.dump</tt> command (statements separated by
1360      *   semicolons)
1361      */
createDbFromSqlStatements( Context context, String dbName, int dbVersion, String sqlStatements)1362     static public void createDbFromSqlStatements(
1363             Context context, String dbName, int dbVersion, String sqlStatements) {
1364         SQLiteDatabase db = context.openOrCreateDatabase(dbName, 0, null);
1365         // TODO: this is not quite safe since it assumes that all semicolons at the end of a line
1366         // terminate statements. It is possible that a text field contains ;\n. We will have to fix
1367         // this if that turns out to be a problem.
1368         String[] statements = TextUtils.split(sqlStatements, ";\n");
1369         for (String statement : statements) {
1370             if (TextUtils.isEmpty(statement)) continue;
1371             db.execSQL(statement);
1372         }
1373         db.setVersion(dbVersion);
1374         db.close();
1375     }
1376 
1377     /**
1378      * Returns one of the following which represent the type of the given SQL statement.
1379      * <ol>
1380      *   <li>{@link #STATEMENT_SELECT}</li>
1381      *   <li>{@link #STATEMENT_UPDATE}</li>
1382      *   <li>{@link #STATEMENT_ATTACH}</li>
1383      *   <li>{@link #STATEMENT_BEGIN}</li>
1384      *   <li>{@link #STATEMENT_COMMIT}</li>
1385      *   <li>{@link #STATEMENT_ABORT}</li>
1386      *   <li>{@link #STATEMENT_OTHER}</li>
1387      * </ol>
1388      * @param sql the SQL statement whose type is returned by this method
1389      * @return one of the values listed above
1390      */
getSqlStatementType(String sql)1391     public static int getSqlStatementType(String sql) {
1392         sql = sql.trim();
1393         if (sql.length() < 3) {
1394             return STATEMENT_OTHER;
1395         }
1396         String prefixSql = sql.substring(0, 3).toUpperCase(Locale.ROOT);
1397         if (prefixSql.equals("SEL")) {
1398             return STATEMENT_SELECT;
1399         } else if (prefixSql.equals("INS") ||
1400                 prefixSql.equals("UPD") ||
1401                 prefixSql.equals("REP") ||
1402                 prefixSql.equals("DEL")) {
1403             return STATEMENT_UPDATE;
1404         } else if (prefixSql.equals("ATT")) {
1405             return STATEMENT_ATTACH;
1406         } else if (prefixSql.equals("COM")) {
1407             return STATEMENT_COMMIT;
1408         } else if (prefixSql.equals("END")) {
1409             return STATEMENT_COMMIT;
1410         } else if (prefixSql.equals("ROL")) {
1411             boolean isRollbackToSavepoint = sql.toUpperCase(Locale.ROOT).contains(" TO ");
1412             if (isRollbackToSavepoint) {
1413                 Log.w(TAG, "Statement '" + sql
1414                         + "' may not work on API levels 16-27, use ';" + sql + "' instead");
1415                 return STATEMENT_OTHER;
1416             }
1417             return STATEMENT_ABORT;
1418         } else if (prefixSql.equals("BEG")) {
1419             return STATEMENT_BEGIN;
1420         } else if (prefixSql.equals("PRA")) {
1421             return STATEMENT_PRAGMA;
1422         } else if (prefixSql.equals("CRE") || prefixSql.equals("DRO") ||
1423                 prefixSql.equals("ALT")) {
1424             return STATEMENT_DDL;
1425         } else if (prefixSql.equals("ANA") || prefixSql.equals("DET")) {
1426             return STATEMENT_UNPREPARED;
1427         }
1428         return STATEMENT_OTHER;
1429     }
1430 
1431     /**
1432      * Appends one set of selection args to another. This is useful when adding a selection
1433      * argument to a user provided set.
1434      */
appendSelectionArgs(String[] originalValues, String[] newValues)1435     public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
1436         if (originalValues == null || originalValues.length == 0) {
1437             return newValues;
1438         }
1439         String[] result = new String[originalValues.length + newValues.length ];
1440         System.arraycopy(originalValues, 0, result, 0, originalValues.length);
1441         System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
1442         return result;
1443     }
1444 
1445     /**
1446      * Returns column index of "_id" column, or -1 if not found.
1447      * @hide
1448      */
findRowIdColumnIndex(String[] columnNames)1449     public static int findRowIdColumnIndex(String[] columnNames) {
1450         int length = columnNames.length;
1451         for (int i = 0; i < length; i++) {
1452             if (columnNames[i].equals("_id")) {
1453                 return i;
1454             }
1455         }
1456         return -1;
1457     }
1458 }
1459