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