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      * Utility method to run the query on the db and return the value in the
    796      * first column of the first row.
    797      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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          */
   1001         public InsertHelper(SQLiteDatabase db, String tableName) {
   1002             mDb = db;
   1003             mTableName = tableName;
   1004         }
   1005 
   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 
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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          */
   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      */
   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      */
   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      */
   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      */
   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