• 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      * Utility method to run the query on the db and return the value in the
796      * first column of the first row.
797      */
longForQuery(SQLiteDatabase db, String query, String[] selectionArgs)798     public static long longForQuery(SQLiteDatabase db, String query, String[] selectionArgs) {
799         SQLiteStatement prog = db.compileStatement(query);
800         try {
801             return longForQuery(prog, selectionArgs);
802         } finally {
803             prog.close();
804         }
805     }
806 
807     /**
808      * Utility method to run the pre-compiled query and return the value in the
809      * first column of the first row.
810      */
longForQuery(SQLiteStatement prog, String[] selectionArgs)811     public static long longForQuery(SQLiteStatement prog, String[] selectionArgs) {
812         prog.bindAllArgsAsStrings(selectionArgs);
813         return prog.simpleQueryForLong();
814     }
815 
816     /**
817      * Utility method to run the query on the db and return the value in the
818      * first column of the first row.
819      */
stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs)820     public static String stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs) {
821         SQLiteStatement prog = db.compileStatement(query);
822         try {
823             return stringForQuery(prog, selectionArgs);
824         } finally {
825             prog.close();
826         }
827     }
828 
829     /**
830      * Utility method to run the pre-compiled query and return the value in the
831      * first column of the first row.
832      */
stringForQuery(SQLiteStatement prog, String[] selectionArgs)833     public static String stringForQuery(SQLiteStatement prog, String[] selectionArgs) {
834         prog.bindAllArgsAsStrings(selectionArgs);
835         return prog.simpleQueryForString();
836     }
837 
838     /**
839      * Utility method to run the query on the db and return the blob value in the
840      * first column of the first row.
841      *
842      * @return A read-only file descriptor for a copy of the blob value.
843      */
blobFileDescriptorForQuery(SQLiteDatabase db, String query, String[] selectionArgs)844     public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteDatabase db,
845             String query, String[] selectionArgs) {
846         SQLiteStatement prog = db.compileStatement(query);
847         try {
848             return blobFileDescriptorForQuery(prog, selectionArgs);
849         } finally {
850             prog.close();
851         }
852     }
853 
854     /**
855      * Utility method to run the pre-compiled query and return the blob value in the
856      * first column of the first row.
857      *
858      * @return A read-only file descriptor for a copy of the blob value.
859      */
blobFileDescriptorForQuery(SQLiteStatement prog, String[] selectionArgs)860     public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteStatement prog,
861             String[] selectionArgs) {
862         prog.bindAllArgsAsStrings(selectionArgs);
863         return prog.simpleQueryForBlobFileDescriptor();
864     }
865 
866     /**
867      * Reads a String out of a column in a Cursor and writes it to a ContentValues.
868      * Adds nothing to the ContentValues if the column isn't present or if its value is null.
869      *
870      * @param cursor The cursor to read from
871      * @param column The column to read
872      * @param values The {@link ContentValues} to put the value into
873      */
cursorStringToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)874     public static void cursorStringToContentValuesIfPresent(Cursor cursor, ContentValues values,
875             String column) {
876         final int index = cursor.getColumnIndex(column);
877         if (index != -1 && !cursor.isNull(index)) {
878             values.put(column, cursor.getString(index));
879         }
880     }
881 
882     /**
883      * Reads a Long out of a column in a Cursor and writes it to a ContentValues.
884      * Adds nothing to the ContentValues if the column isn't present or if its value is null.
885      *
886      * @param cursor The cursor to read from
887      * @param column The column to read
888      * @param values The {@link ContentValues} to put the value into
889      */
cursorLongToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)890     public static void cursorLongToContentValuesIfPresent(Cursor cursor, ContentValues values,
891             String column) {
892         final int index = cursor.getColumnIndex(column);
893         if (index != -1 && !cursor.isNull(index)) {
894             values.put(column, cursor.getLong(index));
895         }
896     }
897 
898     /**
899      * Reads a Short 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      */
cursorShortToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)906     public static void cursorShortToContentValuesIfPresent(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.getShort(index));
911         }
912     }
913 
914     /**
915      * Reads a Integer 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      */
cursorIntToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)922     public static void cursorIntToContentValuesIfPresent(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.getInt(index));
927         }
928     }
929 
930     /**
931      * Reads a Float 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      */
cursorFloatToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)938     public static void cursorFloatToContentValuesIfPresent(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.getFloat(index));
943         }
944     }
945 
946     /**
947      * Reads a Double 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      */
cursorDoubleToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)954     public static void cursorDoubleToContentValuesIfPresent(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.getDouble(index));
959         }
960     }
961 
962     /**
963      * This class allows users to do multiple inserts into a table using
964      * the same statement.
965      * <p>
966      * This class is not thread-safe.
967      * </p>
968      *
969      * @deprecated Use {@link SQLiteStatement} instead.
970      */
971     @Deprecated
972     public static class InsertHelper {
973         private final SQLiteDatabase mDb;
974         private final String mTableName;
975         private HashMap<String, Integer> mColumns;
976         private String mInsertSQL = null;
977         private SQLiteStatement mInsertStatement = null;
978         private SQLiteStatement mReplaceStatement = null;
979         private SQLiteStatement mPreparedStatement = null;
980 
981         /**
982          * {@hide}
983          *
984          * These are the columns returned by sqlite's "PRAGMA
985          * table_info(...)" command that we depend on.
986          */
987         public static final int TABLE_INFO_PRAGMA_COLUMNNAME_INDEX = 1;
988 
989         /**
990          * This field was accidentally exposed in earlier versions of the platform
991          * so we can hide it but we can't remove it.
992          *
993          * @hide
994          */
995         public static final int TABLE_INFO_PRAGMA_DEFAULT_INDEX = 4;
996 
997         /**
998          * @param db the SQLiteDatabase to insert into
999          * @param tableName the name of the table to insert into
1000          */
InsertHelper(SQLiteDatabase db, String tableName)1001         public InsertHelper(SQLiteDatabase db, String tableName) {
1002             mDb = db;
1003             mTableName = tableName;
1004         }
1005 
buildSQL()1006         private void buildSQL() throws SQLException {
1007             StringBuilder sb = new StringBuilder(128);
1008             sb.append("INSERT INTO ");
1009             sb.append(mTableName);
1010             sb.append(" (");
1011 
1012             StringBuilder sbv = new StringBuilder(128);
1013             sbv.append("VALUES (");
1014 
1015             int i = 1;
1016             Cursor cur = null;
1017             try {
1018                 cur = mDb.rawQuery("PRAGMA table_info(" + mTableName + ")", null);
1019                 mColumns = new HashMap<String, Integer>(cur.getCount());
1020                 while (cur.moveToNext()) {
1021                     String columnName = cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX);
1022                     String defaultValue = cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX);
1023 
1024                     mColumns.put(columnName, i);
1025                     sb.append("'");
1026                     sb.append(columnName);
1027                     sb.append("'");
1028 
1029                     if (defaultValue == null) {
1030                         sbv.append("?");
1031                     } else {
1032                         sbv.append("COALESCE(?, ");
1033                         sbv.append(defaultValue);
1034                         sbv.append(")");
1035                     }
1036 
1037                     sb.append(i == cur.getCount() ? ") " : ", ");
1038                     sbv.append(i == cur.getCount() ? ");" : ", ");
1039                     ++i;
1040                 }
1041             } finally {
1042                 if (cur != null) cur.close();
1043             }
1044 
1045             sb.append(sbv);
1046 
1047             mInsertSQL = sb.toString();
1048             if (DEBUG) Log.v(TAG, "insert statement is " + mInsertSQL);
1049         }
1050 
getStatement(boolean allowReplace)1051         private SQLiteStatement getStatement(boolean allowReplace) throws SQLException {
1052             if (allowReplace) {
1053                 if (mReplaceStatement == null) {
1054                     if (mInsertSQL == null) buildSQL();
1055                     // chop "INSERT" off the front and prepend "INSERT OR REPLACE" instead.
1056                     String replaceSQL = "INSERT OR REPLACE" + mInsertSQL.substring(6);
1057                     mReplaceStatement = mDb.compileStatement(replaceSQL);
1058                 }
1059                 return mReplaceStatement;
1060             } else {
1061                 if (mInsertStatement == null) {
1062                     if (mInsertSQL == null) buildSQL();
1063                     mInsertStatement = mDb.compileStatement(mInsertSQL);
1064                 }
1065                 return mInsertStatement;
1066             }
1067         }
1068 
1069         /**
1070          * Performs an insert, adding a new row with the given values.
1071          *
1072          * @param values the set of values with which  to populate the
1073          * new row
1074          * @param allowReplace if true, the statement does "INSERT OR
1075          *   REPLACE" instead of "INSERT", silently deleting any
1076          *   previously existing rows that would cause a conflict
1077          *
1078          * @return the row ID of the newly inserted row, or -1 if an
1079          * error occurred
1080          */
insertInternal(ContentValues values, boolean allowReplace)1081         private long insertInternal(ContentValues values, boolean allowReplace) {
1082             // Start a transaction even though we don't really need one.
1083             // This is to help maintain compatibility with applications that
1084             // access InsertHelper from multiple threads even though they never should have.
1085             // The original code used to lock the InsertHelper itself which was prone
1086             // to deadlocks.  Starting a transaction achieves the same mutual exclusion
1087             // effect as grabbing a lock but without the potential for deadlocks.
1088             mDb.beginTransactionNonExclusive();
1089             try {
1090                 SQLiteStatement stmt = getStatement(allowReplace);
1091                 stmt.clearBindings();
1092                 if (DEBUG) Log.v(TAG, "--- inserting in table " + mTableName);
1093                 for (Map.Entry<String, Object> e: values.valueSet()) {
1094                     final String key = e.getKey();
1095                     int i = getColumnIndex(key);
1096                     DatabaseUtils.bindObjectToProgram(stmt, i, e.getValue());
1097                     if (DEBUG) {
1098                         Log.v(TAG, "binding " + e.getValue() + " to column " +
1099                               i + " (" + key + ")");
1100                     }
1101                 }
1102                 long result = stmt.executeInsert();
1103                 mDb.setTransactionSuccessful();
1104                 return result;
1105             } catch (SQLException e) {
1106                 Log.e(TAG, "Error inserting " + values + " into table  " + mTableName, e);
1107                 return -1;
1108             } finally {
1109                 mDb.endTransaction();
1110             }
1111         }
1112 
1113         /**
1114          * Returns the index of the specified column. This is index is suitagble for use
1115          * in calls to bind().
1116          * @param key the column name
1117          * @return the index of the column
1118          */
getColumnIndex(String key)1119         public int getColumnIndex(String key) {
1120             getStatement(false);
1121             final Integer index = mColumns.get(key);
1122             if (index == null) {
1123                 throw new IllegalArgumentException("column '" + key + "' is invalid");
1124             }
1125             return index;
1126         }
1127 
1128         /**
1129          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1130          * without a matching execute() must have already have been called.
1131          * @param index the index of the slot to which to bind
1132          * @param value the value to bind
1133          */
bind(int index, double value)1134         public void bind(int index, double value) {
1135             mPreparedStatement.bindDouble(index, value);
1136         }
1137 
1138         /**
1139          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1140          * without a matching execute() must have already have been called.
1141          * @param index the index of the slot to which to bind
1142          * @param value the value to bind
1143          */
bind(int index, float value)1144         public void bind(int index, float value) {
1145             mPreparedStatement.bindDouble(index, value);
1146         }
1147 
1148         /**
1149          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1150          * without a matching execute() must have already have been called.
1151          * @param index the index of the slot to which to bind
1152          * @param value the value to bind
1153          */
bind(int index, long value)1154         public void bind(int index, long value) {
1155             mPreparedStatement.bindLong(index, value);
1156         }
1157 
1158         /**
1159          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1160          * without a matching execute() must have already have been called.
1161          * @param index the index of the slot to which to bind
1162          * @param value the value to bind
1163          */
bind(int index, int value)1164         public void bind(int index, int value) {
1165             mPreparedStatement.bindLong(index, value);
1166         }
1167 
1168         /**
1169          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1170          * without a matching execute() must have already have been called.
1171          * @param index the index of the slot to which to bind
1172          * @param value the value to bind
1173          */
bind(int index, boolean value)1174         public void bind(int index, boolean value) {
1175             mPreparedStatement.bindLong(index, value ? 1 : 0);
1176         }
1177 
1178         /**
1179          * Bind null to an index. A prepareForInsert() or prepareForReplace()
1180          * without a matching execute() must have already have been called.
1181          * @param index the index of the slot to which to bind
1182          */
bindNull(int index)1183         public void bindNull(int index) {
1184             mPreparedStatement.bindNull(index);
1185         }
1186 
1187         /**
1188          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1189          * without a matching execute() must have already have been called.
1190          * @param index the index of the slot to which to bind
1191          * @param value the value to bind
1192          */
bind(int index, byte[] value)1193         public void bind(int index, byte[] value) {
1194             if (value == null) {
1195                 mPreparedStatement.bindNull(index);
1196             } else {
1197                 mPreparedStatement.bindBlob(index, value);
1198             }
1199         }
1200 
1201         /**
1202          * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1203          * without a matching execute() must have already have been called.
1204          * @param index the index of the slot to which to bind
1205          * @param value the value to bind
1206          */
bind(int index, String value)1207         public void bind(int index, String value) {
1208             if (value == null) {
1209                 mPreparedStatement.bindNull(index);
1210             } else {
1211                 mPreparedStatement.bindString(index, value);
1212             }
1213         }
1214 
1215         /**
1216          * Performs an insert, adding a new row with the given values.
1217          * If the table contains conflicting rows, an error is
1218          * returned.
1219          *
1220          * @param values the set of values with which to populate the
1221          * new row
1222          *
1223          * @return the row ID of the newly inserted row, or -1 if an
1224          * error occurred
1225          */
insert(ContentValues values)1226         public long insert(ContentValues values) {
1227             return insertInternal(values, false);
1228         }
1229 
1230         /**
1231          * Execute the previously prepared insert or replace using the bound values
1232          * since the last call to prepareForInsert or prepareForReplace.
1233          *
1234          * <p>Note that calling bind() and then execute() is not thread-safe. The only thread-safe
1235          * way to use this class is to call insert() or replace().
1236          *
1237          * @return the row ID of the newly inserted row, or -1 if an
1238          * error occurred
1239          */
execute()1240         public long execute() {
1241             if (mPreparedStatement == null) {
1242                 throw new IllegalStateException("you must prepare this inserter before calling "
1243                         + "execute");
1244             }
1245             try {
1246                 if (DEBUG) Log.v(TAG, "--- doing insert or replace in table " + mTableName);
1247                 return mPreparedStatement.executeInsert();
1248             } catch (SQLException e) {
1249                 Log.e(TAG, "Error executing InsertHelper with table " + mTableName, e);
1250                 return -1;
1251             } finally {
1252                 // you can only call this once per prepare
1253                 mPreparedStatement = null;
1254             }
1255         }
1256 
1257         /**
1258          * Prepare the InsertHelper for an insert. The pattern for this is:
1259          * <ul>
1260          * <li>prepareForInsert()
1261          * <li>bind(index, value);
1262          * <li>bind(index, value);
1263          * <li>...
1264          * <li>bind(index, value);
1265          * <li>execute();
1266          * </ul>
1267          */
prepareForInsert()1268         public void prepareForInsert() {
1269             mPreparedStatement = getStatement(false);
1270             mPreparedStatement.clearBindings();
1271         }
1272 
1273         /**
1274          * Prepare the InsertHelper for a replace. The pattern for this is:
1275          * <ul>
1276          * <li>prepareForReplace()
1277          * <li>bind(index, value);
1278          * <li>bind(index, value);
1279          * <li>...
1280          * <li>bind(index, value);
1281          * <li>execute();
1282          * </ul>
1283          */
prepareForReplace()1284         public void prepareForReplace() {
1285             mPreparedStatement = getStatement(true);
1286             mPreparedStatement.clearBindings();
1287         }
1288 
1289         /**
1290          * Performs an insert, adding a new row with the given values.
1291          * If the table contains conflicting rows, they are deleted
1292          * and replaced with the new row.
1293          *
1294          * @param values the set of values with which to populate the
1295          * new row
1296          *
1297          * @return the row ID of the newly inserted row, or -1 if an
1298          * error occurred
1299          */
replace(ContentValues values)1300         public long replace(ContentValues values) {
1301             return insertInternal(values, true);
1302         }
1303 
1304         /**
1305          * Close this object and release any resources associated with
1306          * it.  The behavior of calling <code>insert()</code> after
1307          * calling this method is undefined.
1308          */
close()1309         public void close() {
1310             if (mInsertStatement != null) {
1311                 mInsertStatement.close();
1312                 mInsertStatement = null;
1313             }
1314             if (mReplaceStatement != null) {
1315                 mReplaceStatement.close();
1316                 mReplaceStatement = null;
1317             }
1318             mInsertSQL = null;
1319             mColumns = null;
1320         }
1321     }
1322 
1323     /**
1324      * Creates a db and populates it with the sql statements in sqlStatements.
1325      *
1326      * @param context the context to use to create the db
1327      * @param dbName the name of the db to create
1328      * @param dbVersion the version to set on the db
1329      * @param sqlStatements the statements to use to populate the db. This should be a single string
1330      *   of the form returned by sqlite3's <tt>.dump</tt> command (statements separated by
1331      *   semicolons)
1332      */
createDbFromSqlStatements( Context context, String dbName, int dbVersion, String sqlStatements)1333     static public void createDbFromSqlStatements(
1334             Context context, String dbName, int dbVersion, String sqlStatements) {
1335         SQLiteDatabase db = context.openOrCreateDatabase(dbName, 0, null);
1336         // TODO: this is not quite safe since it assumes that all semicolons at the end of a line
1337         // terminate statements. It is possible that a text field contains ;\n. We will have to fix
1338         // this if that turns out to be a problem.
1339         String[] statements = TextUtils.split(sqlStatements, ";\n");
1340         for (String statement : statements) {
1341             if (TextUtils.isEmpty(statement)) continue;
1342             db.execSQL(statement);
1343         }
1344         db.setVersion(dbVersion);
1345         db.close();
1346     }
1347 
1348     /**
1349      * Returns one of the following which represent the type of the given SQL statement.
1350      * <ol>
1351      *   <li>{@link #STATEMENT_SELECT}</li>
1352      *   <li>{@link #STATEMENT_UPDATE}</li>
1353      *   <li>{@link #STATEMENT_ATTACH}</li>
1354      *   <li>{@link #STATEMENT_BEGIN}</li>
1355      *   <li>{@link #STATEMENT_COMMIT}</li>
1356      *   <li>{@link #STATEMENT_ABORT}</li>
1357      *   <li>{@link #STATEMENT_OTHER}</li>
1358      * </ol>
1359      * @param sql the SQL statement whose type is returned by this method
1360      * @return one of the values listed above
1361      */
getSqlStatementType(String sql)1362     public static int getSqlStatementType(String sql) {
1363         sql = sql.trim();
1364         if (sql.length() < 3) {
1365             return STATEMENT_OTHER;
1366         }
1367         String prefixSql = sql.substring(0, 3).toUpperCase(Locale.US);
1368         if (prefixSql.equals("SEL")) {
1369             return STATEMENT_SELECT;
1370         } else if (prefixSql.equals("INS") ||
1371                 prefixSql.equals("UPD") ||
1372                 prefixSql.equals("REP") ||
1373                 prefixSql.equals("DEL")) {
1374             return STATEMENT_UPDATE;
1375         } else if (prefixSql.equals("ATT")) {
1376             return STATEMENT_ATTACH;
1377         } else if (prefixSql.equals("COM")) {
1378             return STATEMENT_COMMIT;
1379         } else if (prefixSql.equals("END")) {
1380             return STATEMENT_COMMIT;
1381         } else if (prefixSql.equals("ROL")) {
1382             return STATEMENT_ABORT;
1383         } else if (prefixSql.equals("BEG")) {
1384             return STATEMENT_BEGIN;
1385         } else if (prefixSql.equals("PRA")) {
1386             return STATEMENT_PRAGMA;
1387         } else if (prefixSql.equals("CRE") || prefixSql.equals("DRO") ||
1388                 prefixSql.equals("ALT")) {
1389             return STATEMENT_DDL;
1390         } else if (prefixSql.equals("ANA") || prefixSql.equals("DET")) {
1391             return STATEMENT_UNPREPARED;
1392         }
1393         return STATEMENT_OTHER;
1394     }
1395 
1396     /**
1397      * Appends one set of selection args to another. This is useful when adding a selection
1398      * argument to a user provided set.
1399      */
appendSelectionArgs(String[] originalValues, String[] newValues)1400     public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
1401         if (originalValues == null || originalValues.length == 0) {
1402             return newValues;
1403         }
1404         String[] result = new String[originalValues.length + newValues.length ];
1405         System.arraycopy(originalValues, 0, result, 0, originalValues.length);
1406         System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
1407         return result;
1408     }
1409 
1410     /**
1411      * Returns column index of "_id" column, or -1 if not found.
1412      * @hide
1413      */
findRowIdColumnIndex(String[] columnNames)1414     public static int findRowIdColumnIndex(String[] columnNames) {
1415         int length = columnNames.length;
1416         for (int i = 0; i < length; i++) {
1417             if (columnNames[i].equals("_id")) {
1418                 return i;
1419             }
1420         }
1421         return -1;
1422     }
1423 }
1424