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