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