• 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.text.TextUtils;
35 import android.util.Config;
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 = DEBUG ? Config.LOGD : Config.LOGV;
52 
53     private static final String[] countProjection = new String[]{"count(*)"};
54 
55     /**
56      * Special function for writing an exception result at the header of
57      * a parcel, to be used when returning an exception from a transaction.
58      * exception will be re-thrown by the function in another process
59      * @param reply Parcel to write to
60      * @param e The Exception to be written.
61      * @see Parcel#writeNoException
62      * @see Parcel#writeException
63      */
writeExceptionToParcel(Parcel reply, Exception e)64     public static final void writeExceptionToParcel(Parcel reply, Exception e) {
65         int code = 0;
66         boolean logException = true;
67         if (e instanceof FileNotFoundException) {
68             code = 1;
69             logException = false;
70         } else if (e instanceof IllegalArgumentException) {
71             code = 2;
72         } else if (e instanceof UnsupportedOperationException) {
73             code = 3;
74         } else if (e instanceof SQLiteAbortException) {
75             code = 4;
76         } else if (e instanceof SQLiteConstraintException) {
77             code = 5;
78         } else if (e instanceof SQLiteDatabaseCorruptException) {
79             code = 6;
80         } else if (e instanceof SQLiteFullException) {
81             code = 7;
82         } else if (e instanceof SQLiteDiskIOException) {
83             code = 8;
84         } else if (e instanceof SQLiteException) {
85             code = 9;
86         } else if (e instanceof OperationApplicationException) {
87             code = 10;
88         } else {
89             reply.writeException(e);
90             Log.e(TAG, "Writing exception to parcel", e);
91             return;
92         }
93         reply.writeInt(code);
94         reply.writeString(e.getMessage());
95 
96         if (logException) {
97             Log.e(TAG, "Writing exception to parcel", e);
98         }
99     }
100 
101     /**
102      * Special function for reading an exception result from the header of
103      * a parcel, to be used after receiving the result of a transaction.  This
104      * will throw the exception for you if it had been written to the Parcel,
105      * otherwise return and let you read the normal result data from the Parcel.
106      * @param reply Parcel to read from
107      * @see Parcel#writeNoException
108      * @see Parcel#readException
109      */
readExceptionFromParcel(Parcel reply)110     public static final void readExceptionFromParcel(Parcel reply) {
111         int code = reply.readInt();
112         if (code == 0) return;
113         String msg = reply.readString();
114         DatabaseUtils.readExceptionFromParcel(reply, msg, code);
115     }
116 
readExceptionWithFileNotFoundExceptionFromParcel( Parcel reply)117     public static void readExceptionWithFileNotFoundExceptionFromParcel(
118             Parcel reply) throws FileNotFoundException {
119         int code = reply.readInt();
120         if (code == 0) return;
121         String msg = reply.readString();
122         if (code == 1) {
123             throw new FileNotFoundException(msg);
124         } else {
125             DatabaseUtils.readExceptionFromParcel(reply, msg, code);
126         }
127     }
128 
readExceptionWithOperationApplicationExceptionFromParcel( Parcel reply)129     public static void readExceptionWithOperationApplicationExceptionFromParcel(
130             Parcel reply) throws OperationApplicationException {
131         int code = reply.readInt();
132         if (code == 0) return;
133         String msg = reply.readString();
134         if (code == 10) {
135             throw new OperationApplicationException(msg);
136         } else {
137             DatabaseUtils.readExceptionFromParcel(reply, msg, code);
138         }
139     }
140 
readExceptionFromParcel(Parcel reply, String msg, int code)141     private static final void readExceptionFromParcel(Parcel reply, String msg, int code) {
142         switch (code) {
143             case 2:
144                 throw new IllegalArgumentException(msg);
145             case 3:
146                 throw new UnsupportedOperationException(msg);
147             case 4:
148                 throw new SQLiteAbortException(msg);
149             case 5:
150                 throw new SQLiteConstraintException(msg);
151             case 6:
152                 throw new SQLiteDatabaseCorruptException(msg);
153             case 7:
154                 throw new SQLiteFullException(msg);
155             case 8:
156                 throw new SQLiteDiskIOException(msg);
157             case 9:
158                 throw new SQLiteException(msg);
159             default:
160                 reply.readException(code, msg);
161         }
162     }
163 
164     /**
165      * Binds the given Object to the given SQLiteProgram using the proper
166      * typing. For example, bind numbers as longs/doubles, and everything else
167      * as a string by call toString() on it.
168      *
169      * @param prog the program to bind the object to
170      * @param index the 1-based index to bind at
171      * @param value the value to bind
172      */
bindObjectToProgram(SQLiteProgram prog, int index, Object value)173     public static void bindObjectToProgram(SQLiteProgram prog, int index,
174             Object value) {
175         if (value == null) {
176             prog.bindNull(index);
177         } else if (value instanceof Double || value instanceof Float) {
178             prog.bindDouble(index, ((Number)value).doubleValue());
179         } else if (value instanceof Number) {
180             prog.bindLong(index, ((Number)value).longValue());
181         } else if (value instanceof Boolean) {
182             Boolean bool = (Boolean)value;
183             if (bool) {
184                 prog.bindLong(index, 1);
185             } else {
186                 prog.bindLong(index, 0);
187             }
188         } else if (value instanceof byte[]){
189             prog.bindBlob(index, (byte[]) value);
190         } else {
191             prog.bindString(index, value.toString());
192         }
193     }
194 
195     /**
196      * Appends an SQL string to the given StringBuilder, including the opening
197      * and closing single quotes. Any single quotes internal to sqlString will
198      * be escaped.
199      *
200      * This method is deprecated because we want to encourage everyone
201      * to use the "?" binding form.  However, when implementing a
202      * ContentProvider, one may want to add WHERE clauses that were
203      * not provided by the caller.  Since "?" is a positional form,
204      * using it in this case could break the caller because the
205      * indexes would be shifted to accomodate the ContentProvider's
206      * internal bindings.  In that case, it may be necessary to
207      * construct a WHERE clause manually.  This method is useful for
208      * those cases.
209      *
210      * @param sb the StringBuilder that the SQL string will be appended to
211      * @param sqlString the raw string to be appended, which may contain single
212      *                  quotes
213      */
appendEscapedSQLString(StringBuilder sb, String sqlString)214     public static void appendEscapedSQLString(StringBuilder sb, String sqlString) {
215         sb.append('\'');
216         if (sqlString.indexOf('\'') != -1) {
217             int length = sqlString.length();
218             for (int i = 0; i < length; i++) {
219                 char c = sqlString.charAt(i);
220                 if (c == '\'') {
221                     sb.append('\'');
222                 }
223                 sb.append(c);
224             }
225         } else
226             sb.append(sqlString);
227         sb.append('\'');
228     }
229 
230     /**
231      * SQL-escape a string.
232      */
sqlEscapeString(String value)233     public static String sqlEscapeString(String value) {
234         StringBuilder escaper = new StringBuilder();
235 
236         DatabaseUtils.appendEscapedSQLString(escaper, value);
237 
238         return escaper.toString();
239     }
240 
241     /**
242      * Appends an Object to an SQL string with the proper escaping, etc.
243      */
appendValueToSql(StringBuilder sql, Object value)244     public static final void appendValueToSql(StringBuilder sql, Object value) {
245         if (value == null) {
246             sql.append("NULL");
247         } else if (value instanceof Boolean) {
248             Boolean bool = (Boolean)value;
249             if (bool) {
250                 sql.append('1');
251             } else {
252                 sql.append('0');
253             }
254         } else {
255             appendEscapedSQLString(sql, value.toString());
256         }
257     }
258 
259     /**
260      * Concatenates two SQL WHERE clauses, handling empty or null values.
261      * @hide
262      */
concatenateWhere(String a, String b)263     public static String concatenateWhere(String a, String b) {
264         if (TextUtils.isEmpty(a)) {
265             return b;
266         }
267         if (TextUtils.isEmpty(b)) {
268             return a;
269         }
270 
271         return "(" + a + ") AND (" + b + ")";
272     }
273 
274     /**
275      * return the collation key
276      * @param name
277      * @return the collation key
278      */
getCollationKey(String name)279     public static String getCollationKey(String name) {
280         byte [] arr = getCollationKeyInBytes(name);
281         try {
282             return new String(arr, 0, getKeyLen(arr), "ISO8859_1");
283         } catch (Exception ex) {
284             return "";
285         }
286     }
287 
288     /**
289      * return the collation key in hex format
290      * @param name
291      * @return the collation key in hex format
292      */
getHexCollationKey(String name)293     public static String getHexCollationKey(String name) {
294         byte [] arr = getCollationKeyInBytes(name);
295         char[] keys = Hex.encodeHex(arr);
296         return new String(keys, 0, getKeyLen(arr) * 2);
297     }
298 
getKeyLen(byte[] arr)299     private static int getKeyLen(byte[] arr) {
300         if (arr[arr.length - 1] != 0) {
301             return arr.length;
302         } else {
303             // remove zero "termination"
304             return arr.length-1;
305         }
306     }
307 
getCollationKeyInBytes(String name)308     private static byte[] getCollationKeyInBytes(String name) {
309         if (mColl == null) {
310             mColl = Collator.getInstance();
311             mColl.setStrength(Collator.PRIMARY);
312         }
313         return mColl.getCollationKey(name).toByteArray();
314     }
315 
316     private static Collator mColl = null;
317     /**
318      * Prints the contents of a Cursor to System.out. The position is restored
319      * after printing.
320      *
321      * @param cursor the cursor to print
322      */
dumpCursor(Cursor cursor)323     public static void dumpCursor(Cursor cursor) {
324         dumpCursor(cursor, System.out);
325     }
326 
327     /**
328      * Prints the contents of a Cursor to a PrintSteam. The position is restored
329      * after printing.
330      *
331      * @param cursor the cursor to print
332      * @param stream the stream to print to
333      */
dumpCursor(Cursor cursor, PrintStream stream)334     public static void dumpCursor(Cursor cursor, PrintStream stream) {
335         stream.println(">>>>> Dumping cursor " + cursor);
336         if (cursor != null) {
337             int startPos = cursor.getPosition();
338 
339             cursor.moveToPosition(-1);
340             while (cursor.moveToNext()) {
341                 dumpCurrentRow(cursor, stream);
342             }
343             cursor.moveToPosition(startPos);
344         }
345         stream.println("<<<<<");
346     }
347 
348     /**
349      * Prints the contents of a Cursor to a StringBuilder. The position
350      * is restored after printing.
351      *
352      * @param cursor the cursor to print
353      * @param sb the StringBuilder to print to
354      */
dumpCursor(Cursor cursor, StringBuilder sb)355     public static void dumpCursor(Cursor cursor, StringBuilder sb) {
356         sb.append(">>>>> Dumping cursor " + cursor + "\n");
357         if (cursor != null) {
358             int startPos = cursor.getPosition();
359 
360             cursor.moveToPosition(-1);
361             while (cursor.moveToNext()) {
362                 dumpCurrentRow(cursor, sb);
363             }
364             cursor.moveToPosition(startPos);
365         }
366         sb.append("<<<<<\n");
367     }
368 
369     /**
370      * Prints the contents of a Cursor to a String. The position is restored
371      * after printing.
372      *
373      * @param cursor the cursor to print
374      * @return a String that contains the dumped cursor
375      */
dumpCursorToString(Cursor cursor)376     public static String dumpCursorToString(Cursor cursor) {
377         StringBuilder sb = new StringBuilder();
378         dumpCursor(cursor, sb);
379         return sb.toString();
380     }
381 
382     /**
383      * Prints the contents of a Cursor's current row to System.out.
384      *
385      * @param cursor the cursor to print from
386      */
dumpCurrentRow(Cursor cursor)387     public static void dumpCurrentRow(Cursor cursor) {
388         dumpCurrentRow(cursor, System.out);
389     }
390 
391     /**
392      * Prints the contents of a Cursor's current row to a PrintSteam.
393      *
394      * @param cursor the cursor to print
395      * @param stream the stream to print to
396      */
dumpCurrentRow(Cursor cursor, PrintStream stream)397     public static void dumpCurrentRow(Cursor cursor, PrintStream stream) {
398         String[] cols = cursor.getColumnNames();
399         stream.println("" + cursor.getPosition() + " {");
400         int length = cols.length;
401         for (int i = 0; i< length; i++) {
402             String value;
403             try {
404                 value = cursor.getString(i);
405             } catch (SQLiteException e) {
406                 // assume that if the getString threw this exception then the column is not
407                 // representable by a string, e.g. it is a BLOB.
408                 value = "<unprintable>";
409             }
410             stream.println("   " + cols[i] + '=' + value);
411         }
412         stream.println("}");
413     }
414 
415     /**
416      * Prints the contents of a Cursor's current row to a StringBuilder.
417      *
418      * @param cursor the cursor to print
419      * @param sb the StringBuilder to print to
420      */
dumpCurrentRow(Cursor cursor, StringBuilder sb)421     public static void dumpCurrentRow(Cursor cursor, StringBuilder sb) {
422         String[] cols = cursor.getColumnNames();
423         sb.append("" + cursor.getPosition() + " {\n");
424         int length = cols.length;
425         for (int i = 0; i < length; i++) {
426             String value;
427             try {
428                 value = cursor.getString(i);
429             } catch (SQLiteException e) {
430                 // assume that if the getString threw this exception then the column is not
431                 // representable by a string, e.g. it is a BLOB.
432                 value = "<unprintable>";
433             }
434             sb.append("   " + cols[i] + '=' + value + "\n");
435         }
436         sb.append("}\n");
437     }
438 
439     /**
440      * Dump the contents of a Cursor's current row to a String.
441      *
442      * @param cursor the cursor to print
443      * @return a String that contains the dumped cursor row
444      */
dumpCurrentRowToString(Cursor cursor)445     public static String dumpCurrentRowToString(Cursor cursor) {
446         StringBuilder sb = new StringBuilder();
447         dumpCurrentRow(cursor, sb);
448         return sb.toString();
449     }
450 
451     /**
452      * Reads a String out of a field in a Cursor and writes it to a Map.
453      *
454      * @param cursor The cursor to read from
455      * @param field The TEXT field to read
456      * @param values The {@link ContentValues} to put the value into, with the field as the key
457      */
cursorStringToContentValues(Cursor cursor, String field, ContentValues values)458     public static void cursorStringToContentValues(Cursor cursor, String field,
459             ContentValues values) {
460         cursorStringToContentValues(cursor, field, values, field);
461     }
462 
463     /**
464      * Reads a String out of a field in a Cursor and writes it to an InsertHelper.
465      *
466      * @param cursor The cursor to read from
467      * @param field The TEXT field to read
468      * @param inserter The InsertHelper to bind into
469      * @param index the index of the bind entry in the InsertHelper
470      */
cursorStringToInsertHelper(Cursor cursor, String field, InsertHelper inserter, int index)471     public static void cursorStringToInsertHelper(Cursor cursor, String field,
472             InsertHelper inserter, int index) {
473         inserter.bind(index, cursor.getString(cursor.getColumnIndexOrThrow(field)));
474     }
475 
476     /**
477      * Reads a String out of a field in a Cursor and writes it to a Map.
478      *
479      * @param cursor The cursor to read from
480      * @param field The TEXT field to read
481      * @param values The {@link ContentValues} to put the value into, with the field as the key
482      * @param key The key to store the value with in the map
483      */
cursorStringToContentValues(Cursor cursor, String field, ContentValues values, String key)484     public static void cursorStringToContentValues(Cursor cursor, String field,
485             ContentValues values, String key) {
486         values.put(key, cursor.getString(cursor.getColumnIndexOrThrow(field)));
487     }
488 
489     /**
490      * Reads an Integer out of a field in a Cursor and writes it to a Map.
491      *
492      * @param cursor The cursor to read from
493      * @param field The INTEGER field to read
494      * @param values The {@link ContentValues} to put the value into, with the field as the key
495      */
cursorIntToContentValues(Cursor cursor, String field, ContentValues values)496     public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values) {
497         cursorIntToContentValues(cursor, field, values, field);
498     }
499 
500     /**
501      * Reads a Integer out of a field in a Cursor and writes it to a Map.
502      *
503      * @param cursor The cursor to read from
504      * @param field The INTEGER field to read
505      * @param values The {@link ContentValues} to put the value into, with the field as the key
506      * @param key The key to store the value with in the map
507      */
cursorIntToContentValues(Cursor cursor, String field, ContentValues values, String key)508     public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values,
509             String key) {
510         int colIndex = cursor.getColumnIndex(field);
511         if (!cursor.isNull(colIndex)) {
512             values.put(key, cursor.getInt(colIndex));
513         } else {
514             values.put(key, (Integer) null);
515         }
516     }
517 
518     /**
519      * Reads a Long out of a field in a Cursor and writes it to a Map.
520      *
521      * @param cursor The cursor to read from
522      * @param field The INTEGER field to read
523      * @param values The {@link ContentValues} to put the value into, with the field as the key
524      */
cursorLongToContentValues(Cursor cursor, String field, ContentValues values)525     public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values)
526     {
527         cursorLongToContentValues(cursor, field, values, field);
528     }
529 
530     /**
531      * Reads a Long out of a field in a Cursor and writes it to a Map.
532      *
533      * @param cursor The cursor to read from
534      * @param field The INTEGER field to read
535      * @param values The {@link ContentValues} to put the value into
536      * @param key The key to store the value with in the map
537      */
cursorLongToContentValues(Cursor cursor, String field, ContentValues values, String key)538     public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values,
539             String key) {
540         int colIndex = cursor.getColumnIndex(field);
541         if (!cursor.isNull(colIndex)) {
542             Long value = Long.valueOf(cursor.getLong(colIndex));
543             values.put(key, value);
544         } else {
545             values.put(key, (Long) null);
546         }
547     }
548 
549     /**
550      * Reads a Double out of a field in a Cursor and writes it to a Map.
551      *
552      * @param cursor The cursor to read from
553      * @param field The REAL field to read
554      * @param values The {@link ContentValues} to put the value into
555      */
cursorDoubleToCursorValues(Cursor cursor, String field, ContentValues values)556     public static void cursorDoubleToCursorValues(Cursor cursor, String field, ContentValues values)
557     {
558         cursorDoubleToContentValues(cursor, field, values, field);
559     }
560 
561     /**
562      * Reads a Double out of a field in a Cursor and writes it to a Map.
563      *
564      * @param cursor The cursor to read from
565      * @param field The REAL field to read
566      * @param values The {@link ContentValues} to put the value into
567      * @param key The key to store the value with in the map
568      */
cursorDoubleToContentValues(Cursor cursor, String field, ContentValues values, String key)569     public static void cursorDoubleToContentValues(Cursor cursor, String field,
570             ContentValues values, String key) {
571         int colIndex = cursor.getColumnIndex(field);
572         if (!cursor.isNull(colIndex)) {
573             values.put(key, cursor.getDouble(colIndex));
574         } else {
575             values.put(key, (Double) null);
576         }
577     }
578 
579     /**
580      * Read the entire contents of a cursor row and store them in a ContentValues.
581      *
582      * @param cursor the cursor to read from.
583      * @param values the {@link ContentValues} to put the row into.
584      */
cursorRowToContentValues(Cursor cursor, ContentValues values)585     public static void cursorRowToContentValues(Cursor cursor, ContentValues values) {
586         AbstractWindowedCursor awc =
587                 (cursor instanceof AbstractWindowedCursor) ? (AbstractWindowedCursor) cursor : null;
588 
589         String[] columns = cursor.getColumnNames();
590         int length = columns.length;
591         for (int i = 0; i < length; i++) {
592             if (awc != null && awc.isBlob(i)) {
593                 values.put(columns[i], cursor.getBlob(i));
594             } else {
595                 values.put(columns[i], cursor.getString(i));
596             }
597         }
598     }
599 
600     /**
601      * Query the table for the number of rows in the table.
602      * @param db the database the table is in
603      * @param table the name of the table to query
604      * @return the number of rows in the table
605      */
queryNumEntries(SQLiteDatabase db, String table)606     public static long queryNumEntries(SQLiteDatabase db, String table) {
607         Cursor cursor = db.query(table, countProjection,
608                 null, null, null, null, null);
609         try {
610             cursor.moveToFirst();
611             return cursor.getLong(0);
612         } finally {
613             cursor.close();
614         }
615     }
616 
617     /**
618      * Utility method to run the query on the db and return the value in the
619      * first column of the first row.
620      */
longForQuery(SQLiteDatabase db, String query, String[] selectionArgs)621     public static long longForQuery(SQLiteDatabase db, String query, String[] selectionArgs) {
622         SQLiteStatement prog = db.compileStatement(query);
623         try {
624             return longForQuery(prog, selectionArgs);
625         } finally {
626             prog.close();
627         }
628     }
629 
630     /**
631      * Utility method to run the pre-compiled query and return the value in the
632      * first column of the first row.
633      */
longForQuery(SQLiteStatement prog, String[] selectionArgs)634     public static long longForQuery(SQLiteStatement prog, String[] selectionArgs) {
635         if (selectionArgs != null) {
636             int size = selectionArgs.length;
637             for (int i = 0; i < size; i++) {
638                 bindObjectToProgram(prog, i + 1, selectionArgs[i]);
639             }
640         }
641         long value = prog.simpleQueryForLong();
642         return value;
643     }
644 
645     /**
646      * Utility method to run the query on the db and return the value in the
647      * first column of the first row.
648      */
stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs)649     public static String stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs) {
650         SQLiteStatement prog = db.compileStatement(query);
651         try {
652             return stringForQuery(prog, selectionArgs);
653         } finally {
654             prog.close();
655         }
656     }
657 
658     /**
659      * Utility method to run the pre-compiled query and return the value in the
660      * first column of the first row.
661      */
stringForQuery(SQLiteStatement prog, String[] selectionArgs)662     public static String stringForQuery(SQLiteStatement prog, String[] selectionArgs) {
663         if (selectionArgs != null) {
664             int size = selectionArgs.length;
665             for (int i = 0; i < size; i++) {
666                 bindObjectToProgram(prog, i + 1, selectionArgs[i]);
667             }
668         }
669         String value = prog.simpleQueryForString();
670         return value;
671     }
672 
673     /**
674      * This class allows users to do multiple inserts into a table but
675      * compile the SQL insert statement only once, which may increase
676      * performance.
677      */
678     public static class InsertHelper {
679         private final SQLiteDatabase mDb;
680         private final String mTableName;
681         private HashMap<String, Integer> mColumns;
682         private String mInsertSQL = null;
683         private SQLiteStatement mInsertStatement = null;
684         private SQLiteStatement mReplaceStatement = null;
685         private SQLiteStatement mPreparedStatement = null;
686 
687         /**
688          * {@hide}
689          *
690          * These are the columns returned by sqlite's "PRAGMA
691          * table_info(...)" command that we depend on.
692          */
693         public static final int TABLE_INFO_PRAGMA_COLUMNNAME_INDEX = 1;
694         public static final int TABLE_INFO_PRAGMA_DEFAULT_INDEX = 4;
695 
696         /**
697          * @param db the SQLiteDatabase to insert into
698          * @param tableName the name of the table to insert into
699          */
InsertHelper(SQLiteDatabase db, String tableName)700         public InsertHelper(SQLiteDatabase db, String tableName) {
701             mDb = db;
702             mTableName = tableName;
703         }
704 
buildSQL()705         private void buildSQL() throws SQLException {
706             StringBuilder sb = new StringBuilder(128);
707             sb.append("INSERT INTO ");
708             sb.append(mTableName);
709             sb.append(" (");
710 
711             StringBuilder sbv = new StringBuilder(128);
712             sbv.append("VALUES (");
713 
714             int i = 1;
715             Cursor cur = null;
716             try {
717                 cur = mDb.rawQuery("PRAGMA table_info(" + mTableName + ")", null);
718                 mColumns = new HashMap<String, Integer>(cur.getCount());
719                 while (cur.moveToNext()) {
720                     String columnName = cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX);
721                     String defaultValue = cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX);
722 
723                     mColumns.put(columnName, i);
724                     sb.append("'");
725                     sb.append(columnName);
726                     sb.append("'");
727 
728                     if (defaultValue == null) {
729                         sbv.append("?");
730                     } else {
731                         sbv.append("COALESCE(?, ");
732                         sbv.append(defaultValue);
733                         sbv.append(")");
734                     }
735 
736                     sb.append(i == cur.getCount() ? ") " : ", ");
737                     sbv.append(i == cur.getCount() ? ");" : ", ");
738                     ++i;
739                 }
740             } finally {
741                 if (cur != null) cur.close();
742             }
743 
744             sb.append(sbv);
745 
746             mInsertSQL = sb.toString();
747             if (LOCAL_LOGV) Log.v(TAG, "insert statement is " + mInsertSQL);
748         }
749 
getStatement(boolean allowReplace)750         private SQLiteStatement getStatement(boolean allowReplace) throws SQLException {
751             if (allowReplace) {
752                 if (mReplaceStatement == null) {
753                     if (mInsertSQL == null) buildSQL();
754                     // chop "INSERT" off the front and prepend "INSERT OR REPLACE" instead.
755                     String replaceSQL = "INSERT OR REPLACE" + mInsertSQL.substring(6);
756                     mReplaceStatement = mDb.compileStatement(replaceSQL);
757                 }
758                 return mReplaceStatement;
759             } else {
760                 if (mInsertStatement == null) {
761                     if (mInsertSQL == null) buildSQL();
762                     mInsertStatement = mDb.compileStatement(mInsertSQL);
763                 }
764                 return mInsertStatement;
765             }
766         }
767 
768         /**
769          * Performs an insert, adding a new row with the given values.
770          *
771          * @param values the set of values with which  to populate the
772          * new row
773          * @param allowReplace if true, the statement does "INSERT OR
774          *   REPLACE" instead of "INSERT", silently deleting any
775          *   previously existing rows that would cause a conflict
776          *
777          * @return the row ID of the newly inserted row, or -1 if an
778          * error occurred
779          */
insertInternal(ContentValues values, boolean allowReplace)780         private synchronized long insertInternal(ContentValues values, boolean allowReplace) {
781             try {
782                 SQLiteStatement stmt = getStatement(allowReplace);
783                 stmt.clearBindings();
784                 if (LOCAL_LOGV) Log.v(TAG, "--- inserting in table " + mTableName);
785                 for (Map.Entry<String, Object> e: values.valueSet()) {
786                     final String key = e.getKey();
787                     int i = getColumnIndex(key);
788                     DatabaseUtils.bindObjectToProgram(stmt, i, e.getValue());
789                     if (LOCAL_LOGV) {
790                         Log.v(TAG, "binding " + e.getValue() + " to column " +
791                               i + " (" + key + ")");
792                     }
793                 }
794                 return stmt.executeInsert();
795             } catch (SQLException e) {
796                 Log.e(TAG, "Error inserting " + values + " into table  " + mTableName, e);
797                 return -1;
798             }
799         }
800 
801         /**
802          * Returns the index of the specified column. This is index is suitagble for use
803          * in calls to bind().
804          * @param key the column name
805          * @return the index of the column
806          */
getColumnIndex(String key)807         public int getColumnIndex(String key) {
808             getStatement(false);
809             final Integer index = mColumns.get(key);
810             if (index == null) {
811                 throw new IllegalArgumentException("column '" + key + "' is invalid");
812             }
813             return index;
814         }
815 
816         /**
817          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
818          * without a matching execute() must have already have been called.
819          * @param index the index of the slot to which to bind
820          * @param value the value to bind
821          */
bind(int index, double value)822         public void bind(int index, double value) {
823             mPreparedStatement.bindDouble(index, value);
824         }
825 
826         /**
827          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
828          * without a matching execute() must have already have been called.
829          * @param index the index of the slot to which to bind
830          * @param value the value to bind
831          */
bind(int index, float value)832         public void bind(int index, float value) {
833             mPreparedStatement.bindDouble(index, value);
834         }
835 
836         /**
837          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
838          * without a matching execute() must have already have been called.
839          * @param index the index of the slot to which to bind
840          * @param value the value to bind
841          */
bind(int index, long value)842         public void bind(int index, long value) {
843             mPreparedStatement.bindLong(index, value);
844         }
845 
846         /**
847          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
848          * without a matching execute() must have already have been called.
849          * @param index the index of the slot to which to bind
850          * @param value the value to bind
851          */
bind(int index, int value)852         public void bind(int index, int value) {
853             mPreparedStatement.bindLong(index, value);
854         }
855 
856         /**
857          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
858          * without a matching execute() must have already have been called.
859          * @param index the index of the slot to which to bind
860          * @param value the value to bind
861          */
bind(int index, boolean value)862         public void bind(int index, boolean value) {
863             mPreparedStatement.bindLong(index, value ? 1 : 0);
864         }
865 
866         /**
867          * Bind null to an index. A prepareForInsert() or prepareForReplace()
868          * without a matching execute() must have already have been called.
869          * @param index the index of the slot to which to bind
870          */
bindNull(int index)871         public void bindNull(int index) {
872             mPreparedStatement.bindNull(index);
873         }
874 
875         /**
876          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
877          * without a matching execute() must have already have been called.
878          * @param index the index of the slot to which to bind
879          * @param value the value to bind
880          */
bind(int index, byte[] value)881         public void bind(int index, byte[] value) {
882             if (value == null) {
883                 mPreparedStatement.bindNull(index);
884             } else {
885                 mPreparedStatement.bindBlob(index, value);
886             }
887         }
888 
889         /**
890          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
891          * without a matching execute() must have already have been called.
892          * @param index the index of the slot to which to bind
893          * @param value the value to bind
894          */
bind(int index, String value)895         public void bind(int index, String value) {
896             if (value == null) {
897                 mPreparedStatement.bindNull(index);
898             } else {
899                 mPreparedStatement.bindString(index, value);
900             }
901         }
902 
903         /**
904          * Performs an insert, adding a new row with the given values.
905          * If the table contains conflicting rows, an error is
906          * returned.
907          *
908          * @param values the set of values with which to populate the
909          * new row
910          *
911          * @return the row ID of the newly inserted row, or -1 if an
912          * error occurred
913          */
insert(ContentValues values)914         public long insert(ContentValues values) {
915             return insertInternal(values, false);
916         }
917 
918         /**
919          * Execute the previously prepared insert or replace using the bound values
920          * since the last call to prepareForInsert or prepareForReplace.
921          *
922          * <p>Note that calling bind() and then execute() is not thread-safe. The only thread-safe
923          * way to use this class is to call insert() or replace().
924          *
925          * @return the row ID of the newly inserted row, or -1 if an
926          * error occurred
927          */
execute()928         public long execute() {
929             if (mPreparedStatement == null) {
930                 throw new IllegalStateException("you must prepare this inserter before calling "
931                         + "execute");
932             }
933             try {
934                 if (LOCAL_LOGV) Log.v(TAG, "--- doing insert or replace in table " + mTableName);
935                 return mPreparedStatement.executeInsert();
936             } catch (SQLException e) {
937                 Log.e(TAG, "Error executing InsertHelper with table " + mTableName, e);
938                 return -1;
939             } finally {
940                 // you can only call this once per prepare
941                 mPreparedStatement = null;
942             }
943         }
944 
945         /**
946          * Prepare the InsertHelper for an insert. The pattern for this is:
947          * <ul>
948          * <li>prepareForInsert()
949          * <li>bind(index, value);
950          * <li>bind(index, value);
951          * <li>...
952          * <li>bind(index, value);
953          * <li>execute();
954          * </ul>
955          */
prepareForInsert()956         public void prepareForInsert() {
957             mPreparedStatement = getStatement(false);
958             mPreparedStatement.clearBindings();
959         }
960 
961         /**
962          * Prepare the InsertHelper for a replace. The pattern for this is:
963          * <ul>
964          * <li>prepareForReplace()
965          * <li>bind(index, value);
966          * <li>bind(index, value);
967          * <li>...
968          * <li>bind(index, value);
969          * <li>execute();
970          * </ul>
971          */
prepareForReplace()972         public void prepareForReplace() {
973             mPreparedStatement = getStatement(true);
974             mPreparedStatement.clearBindings();
975         }
976 
977         /**
978          * Performs an insert, adding a new row with the given values.
979          * If the table contains conflicting rows, they are deleted
980          * and replaced with the new row.
981          *
982          * @param values the set of values with which to populate the
983          * new row
984          *
985          * @return the row ID of the newly inserted row, or -1 if an
986          * error occurred
987          */
replace(ContentValues values)988         public long replace(ContentValues values) {
989             return insertInternal(values, true);
990         }
991 
992         /**
993          * Close this object and release any resources associated with
994          * it.  The behavior of calling <code>insert()</code> after
995          * calling this method is undefined.
996          */
close()997         public void close() {
998             if (mInsertStatement != null) {
999                 mInsertStatement.close();
1000                 mInsertStatement = null;
1001             }
1002             if (mReplaceStatement != null) {
1003                 mReplaceStatement.close();
1004                 mReplaceStatement = null;
1005             }
1006             mInsertSQL = null;
1007             mColumns = null;
1008         }
1009     }
1010 
1011     /**
1012      * Creates a db and populates it with the sql statements in sqlStatements.
1013      *
1014      * @param context the context to use to create the db
1015      * @param dbName the name of the db to create
1016      * @param dbVersion the version to set on the db
1017      * @param sqlStatements the statements to use to populate the db. This should be a single string
1018      *   of the form returned by sqlite3's <tt>.dump</tt> command (statements separated by
1019      *   semicolons)
1020      */
createDbFromSqlStatements( Context context, String dbName, int dbVersion, String sqlStatements)1021     static public void createDbFromSqlStatements(
1022             Context context, String dbName, int dbVersion, String sqlStatements) {
1023         SQLiteDatabase db = context.openOrCreateDatabase(dbName, 0, null);
1024         // TODO: this is not quite safe since it assumes that all semicolons at the end of a line
1025         // terminate statements. It is possible that a text field contains ;\n. We will have to fix
1026         // this if that turns out to be a problem.
1027         String[] statements = TextUtils.split(sqlStatements, ";\n");
1028         for (String statement : statements) {
1029             if (TextUtils.isEmpty(statement)) continue;
1030             db.execSQL(statement);
1031         }
1032         db.setVersion(dbVersion);
1033         db.close();
1034     }
1035 }
1036