Home | History | Annotate | Download | only in database
      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      */
     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      */
    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 
    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 
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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          */
   1013         public InsertHelper(SQLiteDatabase db, String tableName) {
   1014             mDb = db;
   1015             mTableName = tableName;
   1016         }
   1017 
   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 
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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      */
   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      */
   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      */
   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      */
   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