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