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