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