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