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