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