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