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