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