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