| /* |
| * Copyright (C) 2006 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.database; |
| |
| import org.apache.commons.codec.binary.Hex; |
| |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.OperationApplicationException; |
| import android.database.sqlite.SQLiteAbortException; |
| import android.database.sqlite.SQLiteConstraintException; |
| import android.database.sqlite.SQLiteDatabase; |
| import android.database.sqlite.SQLiteDatabaseCorruptException; |
| import android.database.sqlite.SQLiteDiskIOException; |
| import android.database.sqlite.SQLiteException; |
| import android.database.sqlite.SQLiteFullException; |
| import android.database.sqlite.SQLiteProgram; |
| import android.database.sqlite.SQLiteStatement; |
| import android.os.Parcel; |
| import android.text.TextUtils; |
| import android.util.Config; |
| import android.util.Log; |
| |
| import java.io.FileNotFoundException; |
| import java.io.PrintStream; |
| import java.text.Collator; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** |
| * Static utility methods for dealing with databases and {@link Cursor}s. |
| */ |
| public class DatabaseUtils { |
| private static final String TAG = "DatabaseUtils"; |
| |
| private static final boolean DEBUG = false; |
| private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV; |
| |
| private static final String[] countProjection = new String[]{"count(*)"}; |
| |
| /** |
| * Special function for writing an exception result at the header of |
| * a parcel, to be used when returning an exception from a transaction. |
| * exception will be re-thrown by the function in another process |
| * @param reply Parcel to write to |
| * @param e The Exception to be written. |
| * @see Parcel#writeNoException |
| * @see Parcel#writeException |
| */ |
| public static final void writeExceptionToParcel(Parcel reply, Exception e) { |
| int code = 0; |
| boolean logException = true; |
| if (e instanceof FileNotFoundException) { |
| code = 1; |
| logException = false; |
| } else if (e instanceof IllegalArgumentException) { |
| code = 2; |
| } else if (e instanceof UnsupportedOperationException) { |
| code = 3; |
| } else if (e instanceof SQLiteAbortException) { |
| code = 4; |
| } else if (e instanceof SQLiteConstraintException) { |
| code = 5; |
| } else if (e instanceof SQLiteDatabaseCorruptException) { |
| code = 6; |
| } else if (e instanceof SQLiteFullException) { |
| code = 7; |
| } else if (e instanceof SQLiteDiskIOException) { |
| code = 8; |
| } else if (e instanceof SQLiteException) { |
| code = 9; |
| } else if (e instanceof OperationApplicationException) { |
| code = 10; |
| } else { |
| reply.writeException(e); |
| Log.e(TAG, "Writing exception to parcel", e); |
| return; |
| } |
| reply.writeInt(code); |
| reply.writeString(e.getMessage()); |
| |
| if (logException) { |
| Log.e(TAG, "Writing exception to parcel", e); |
| } |
| } |
| |
| /** |
| * Special function for reading an exception result from the header of |
| * a parcel, to be used after receiving the result of a transaction. This |
| * will throw the exception for you if it had been written to the Parcel, |
| * otherwise return and let you read the normal result data from the Parcel. |
| * @param reply Parcel to read from |
| * @see Parcel#writeNoException |
| * @see Parcel#readException |
| */ |
| public static final void readExceptionFromParcel(Parcel reply) { |
| int code = reply.readInt(); |
| if (code == 0) return; |
| String msg = reply.readString(); |
| DatabaseUtils.readExceptionFromParcel(reply, msg, code); |
| } |
| |
| public static void readExceptionWithFileNotFoundExceptionFromParcel( |
| Parcel reply) throws FileNotFoundException { |
| int code = reply.readInt(); |
| if (code == 0) return; |
| String msg = reply.readString(); |
| if (code == 1) { |
| throw new FileNotFoundException(msg); |
| } else { |
| DatabaseUtils.readExceptionFromParcel(reply, msg, code); |
| } |
| } |
| |
| public static void readExceptionWithOperationApplicationExceptionFromParcel( |
| Parcel reply) throws OperationApplicationException { |
| int code = reply.readInt(); |
| if (code == 0) return; |
| String msg = reply.readString(); |
| if (code == 10) { |
| throw new OperationApplicationException(msg); |
| } else { |
| DatabaseUtils.readExceptionFromParcel(reply, msg, code); |
| } |
| } |
| |
| private static final void readExceptionFromParcel(Parcel reply, String msg, int code) { |
| switch (code) { |
| case 2: |
| throw new IllegalArgumentException(msg); |
| case 3: |
| throw new UnsupportedOperationException(msg); |
| case 4: |
| throw new SQLiteAbortException(msg); |
| case 5: |
| throw new SQLiteConstraintException(msg); |
| case 6: |
| throw new SQLiteDatabaseCorruptException(msg); |
| case 7: |
| throw new SQLiteFullException(msg); |
| case 8: |
| throw new SQLiteDiskIOException(msg); |
| case 9: |
| throw new SQLiteException(msg); |
| default: |
| reply.readException(code, msg); |
| } |
| } |
| |
| /** |
| * Binds the given Object to the given SQLiteProgram using the proper |
| * typing. For example, bind numbers as longs/doubles, and everything else |
| * as a string by call toString() on it. |
| * |
| * @param prog the program to bind the object to |
| * @param index the 1-based index to bind at |
| * @param value the value to bind |
| */ |
| public static void bindObjectToProgram(SQLiteProgram prog, int index, |
| Object value) { |
| if (value == null) { |
| prog.bindNull(index); |
| } else if (value instanceof Double || value instanceof Float) { |
| prog.bindDouble(index, ((Number)value).doubleValue()); |
| } else if (value instanceof Number) { |
| prog.bindLong(index, ((Number)value).longValue()); |
| } else if (value instanceof Boolean) { |
| Boolean bool = (Boolean)value; |
| if (bool) { |
| prog.bindLong(index, 1); |
| } else { |
| prog.bindLong(index, 0); |
| } |
| } else if (value instanceof byte[]){ |
| prog.bindBlob(index, (byte[]) value); |
| } else { |
| prog.bindString(index, value.toString()); |
| } |
| } |
| |
| /** |
| * Appends an SQL string to the given StringBuilder, including the opening |
| * and closing single quotes. Any single quotes internal to sqlString will |
| * be escaped. |
| * |
| * This method is deprecated because we want to encourage everyone |
| * to use the "?" binding form. However, when implementing a |
| * ContentProvider, one may want to add WHERE clauses that were |
| * not provided by the caller. Since "?" is a positional form, |
| * using it in this case could break the caller because the |
| * indexes would be shifted to accomodate the ContentProvider's |
| * internal bindings. In that case, it may be necessary to |
| * construct a WHERE clause manually. This method is useful for |
| * those cases. |
| * |
| * @param sb the StringBuilder that the SQL string will be appended to |
| * @param sqlString the raw string to be appended, which may contain single |
| * quotes |
| */ |
| public static void appendEscapedSQLString(StringBuilder sb, String sqlString) { |
| sb.append('\''); |
| if (sqlString.indexOf('\'') != -1) { |
| int length = sqlString.length(); |
| for (int i = 0; i < length; i++) { |
| char c = sqlString.charAt(i); |
| if (c == '\'') { |
| sb.append('\''); |
| } |
| sb.append(c); |
| } |
| } else |
| sb.append(sqlString); |
| sb.append('\''); |
| } |
| |
| /** |
| * SQL-escape a string. |
| */ |
| public static String sqlEscapeString(String value) { |
| StringBuilder escaper = new StringBuilder(); |
| |
| DatabaseUtils.appendEscapedSQLString(escaper, value); |
| |
| return escaper.toString(); |
| } |
| |
| /** |
| * Appends an Object to an SQL string with the proper escaping, etc. |
| */ |
| public static final void appendValueToSql(StringBuilder sql, Object value) { |
| if (value == null) { |
| sql.append("NULL"); |
| } else if (value instanceof Boolean) { |
| Boolean bool = (Boolean)value; |
| if (bool) { |
| sql.append('1'); |
| } else { |
| sql.append('0'); |
| } |
| } else { |
| appendEscapedSQLString(sql, value.toString()); |
| } |
| } |
| |
| /** |
| * Concatenates two SQL WHERE clauses, handling empty or null values. |
| * @hide |
| */ |
| public static String concatenateWhere(String a, String b) { |
| if (TextUtils.isEmpty(a)) { |
| return b; |
| } |
| if (TextUtils.isEmpty(b)) { |
| return a; |
| } |
| |
| return "(" + a + ") AND (" + b + ")"; |
| } |
| |
| /** |
| * return the collation key |
| * @param name |
| * @return the collation key |
| */ |
| public static String getCollationKey(String name) { |
| byte [] arr = getCollationKeyInBytes(name); |
| try { |
| return new String(arr, 0, getKeyLen(arr), "ISO8859_1"); |
| } catch (Exception ex) { |
| return ""; |
| } |
| } |
| |
| /** |
| * return the collation key in hex format |
| * @param name |
| * @return the collation key in hex format |
| */ |
| public static String getHexCollationKey(String name) { |
| byte [] arr = getCollationKeyInBytes(name); |
| char[] keys = Hex.encodeHex(arr); |
| return new String(keys, 0, getKeyLen(arr) * 2); |
| } |
| |
| private static int getKeyLen(byte[] arr) { |
| if (arr[arr.length - 1] != 0) { |
| return arr.length; |
| } else { |
| // remove zero "termination" |
| return arr.length-1; |
| } |
| } |
| |
| private static byte[] getCollationKeyInBytes(String name) { |
| if (mColl == null) { |
| mColl = Collator.getInstance(); |
| mColl.setStrength(Collator.PRIMARY); |
| } |
| return mColl.getCollationKey(name).toByteArray(); |
| } |
| |
| private static Collator mColl = null; |
| /** |
| * Prints the contents of a Cursor to System.out. The position is restored |
| * after printing. |
| * |
| * @param cursor the cursor to print |
| */ |
| public static void dumpCursor(Cursor cursor) { |
| dumpCursor(cursor, System.out); |
| } |
| |
| /** |
| * Prints the contents of a Cursor to a PrintSteam. The position is restored |
| * after printing. |
| * |
| * @param cursor the cursor to print |
| * @param stream the stream to print to |
| */ |
| public static void dumpCursor(Cursor cursor, PrintStream stream) { |
| stream.println(">>>>> Dumping cursor " + cursor); |
| if (cursor != null) { |
| int startPos = cursor.getPosition(); |
| |
| cursor.moveToPosition(-1); |
| while (cursor.moveToNext()) { |
| dumpCurrentRow(cursor, stream); |
| } |
| cursor.moveToPosition(startPos); |
| } |
| stream.println("<<<<<"); |
| } |
| |
| /** |
| * Prints the contents of a Cursor to a StringBuilder. The position |
| * is restored after printing. |
| * |
| * @param cursor the cursor to print |
| * @param sb the StringBuilder to print to |
| */ |
| public static void dumpCursor(Cursor cursor, StringBuilder sb) { |
| sb.append(">>>>> Dumping cursor " + cursor + "\n"); |
| if (cursor != null) { |
| int startPos = cursor.getPosition(); |
| |
| cursor.moveToPosition(-1); |
| while (cursor.moveToNext()) { |
| dumpCurrentRow(cursor, sb); |
| } |
| cursor.moveToPosition(startPos); |
| } |
| sb.append("<<<<<\n"); |
| } |
| |
| /** |
| * Prints the contents of a Cursor to a String. The position is restored |
| * after printing. |
| * |
| * @param cursor the cursor to print |
| * @return a String that contains the dumped cursor |
| */ |
| public static String dumpCursorToString(Cursor cursor) { |
| StringBuilder sb = new StringBuilder(); |
| dumpCursor(cursor, sb); |
| return sb.toString(); |
| } |
| |
| /** |
| * Prints the contents of a Cursor's current row to System.out. |
| * |
| * @param cursor the cursor to print from |
| */ |
| public static void dumpCurrentRow(Cursor cursor) { |
| dumpCurrentRow(cursor, System.out); |
| } |
| |
| /** |
| * Prints the contents of a Cursor's current row to a PrintSteam. |
| * |
| * @param cursor the cursor to print |
| * @param stream the stream to print to |
| */ |
| public static void dumpCurrentRow(Cursor cursor, PrintStream stream) { |
| String[] cols = cursor.getColumnNames(); |
| stream.println("" + cursor.getPosition() + " {"); |
| int length = cols.length; |
| for (int i = 0; i< length; i++) { |
| String value; |
| try { |
| value = cursor.getString(i); |
| } catch (SQLiteException e) { |
| // assume that if the getString threw this exception then the column is not |
| // representable by a string, e.g. it is a BLOB. |
| value = "<unprintable>"; |
| } |
| stream.println(" " + cols[i] + '=' + value); |
| } |
| stream.println("}"); |
| } |
| |
| /** |
| * Prints the contents of a Cursor's current row to a StringBuilder. |
| * |
| * @param cursor the cursor to print |
| * @param sb the StringBuilder to print to |
| */ |
| public static void dumpCurrentRow(Cursor cursor, StringBuilder sb) { |
| String[] cols = cursor.getColumnNames(); |
| sb.append("" + cursor.getPosition() + " {\n"); |
| int length = cols.length; |
| for (int i = 0; i < length; i++) { |
| String value; |
| try { |
| value = cursor.getString(i); |
| } catch (SQLiteException e) { |
| // assume that if the getString threw this exception then the column is not |
| // representable by a string, e.g. it is a BLOB. |
| value = "<unprintable>"; |
| } |
| sb.append(" " + cols[i] + '=' + value + "\n"); |
| } |
| sb.append("}\n"); |
| } |
| |
| /** |
| * Dump the contents of a Cursor's current row to a String. |
| * |
| * @param cursor the cursor to print |
| * @return a String that contains the dumped cursor row |
| */ |
| public static String dumpCurrentRowToString(Cursor cursor) { |
| StringBuilder sb = new StringBuilder(); |
| dumpCurrentRow(cursor, sb); |
| return sb.toString(); |
| } |
| |
| /** |
| * Reads a String out of a field in a Cursor and writes it to a Map. |
| * |
| * @param cursor The cursor to read from |
| * @param field The TEXT field to read |
| * @param values The {@link ContentValues} to put the value into, with the field as the key |
| */ |
| public static void cursorStringToContentValues(Cursor cursor, String field, |
| ContentValues values) { |
| cursorStringToContentValues(cursor, field, values, field); |
| } |
| |
| /** |
| * Reads a String out of a field in a Cursor and writes it to an InsertHelper. |
| * |
| * @param cursor The cursor to read from |
| * @param field The TEXT field to read |
| * @param inserter The InsertHelper to bind into |
| * @param index the index of the bind entry in the InsertHelper |
| */ |
| public static void cursorStringToInsertHelper(Cursor cursor, String field, |
| InsertHelper inserter, int index) { |
| inserter.bind(index, cursor.getString(cursor.getColumnIndexOrThrow(field))); |
| } |
| |
| /** |
| * Reads a String out of a field in a Cursor and writes it to a Map. |
| * |
| * @param cursor The cursor to read from |
| * @param field The TEXT field to read |
| * @param values The {@link ContentValues} to put the value into, with the field as the key |
| * @param key The key to store the value with in the map |
| */ |
| public static void cursorStringToContentValues(Cursor cursor, String field, |
| ContentValues values, String key) { |
| values.put(key, cursor.getString(cursor.getColumnIndexOrThrow(field))); |
| } |
| |
| /** |
| * Reads an Integer out of a field in a Cursor and writes it to a Map. |
| * |
| * @param cursor The cursor to read from |
| * @param field The INTEGER field to read |
| * @param values The {@link ContentValues} to put the value into, with the field as the key |
| */ |
| public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values) { |
| cursorIntToContentValues(cursor, field, values, field); |
| } |
| |
| /** |
| * Reads a Integer out of a field in a Cursor and writes it to a Map. |
| * |
| * @param cursor The cursor to read from |
| * @param field The INTEGER field to read |
| * @param values The {@link ContentValues} to put the value into, with the field as the key |
| * @param key The key to store the value with in the map |
| */ |
| public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values, |
| String key) { |
| int colIndex = cursor.getColumnIndex(field); |
| if (!cursor.isNull(colIndex)) { |
| values.put(key, cursor.getInt(colIndex)); |
| } else { |
| values.put(key, (Integer) null); |
| } |
| } |
| |
| /** |
| * Reads a Long out of a field in a Cursor and writes it to a Map. |
| * |
| * @param cursor The cursor to read from |
| * @param field The INTEGER field to read |
| * @param values The {@link ContentValues} to put the value into, with the field as the key |
| */ |
| public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values) |
| { |
| cursorLongToContentValues(cursor, field, values, field); |
| } |
| |
| /** |
| * Reads a Long out of a field in a Cursor and writes it to a Map. |
| * |
| * @param cursor The cursor to read from |
| * @param field The INTEGER field to read |
| * @param values The {@link ContentValues} to put the value into |
| * @param key The key to store the value with in the map |
| */ |
| public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values, |
| String key) { |
| int colIndex = cursor.getColumnIndex(field); |
| if (!cursor.isNull(colIndex)) { |
| Long value = Long.valueOf(cursor.getLong(colIndex)); |
| values.put(key, value); |
| } else { |
| values.put(key, (Long) null); |
| } |
| } |
| |
| /** |
| * Reads a Double out of a field in a Cursor and writes it to a Map. |
| * |
| * @param cursor The cursor to read from |
| * @param field The REAL field to read |
| * @param values The {@link ContentValues} to put the value into |
| */ |
| public static void cursorDoubleToCursorValues(Cursor cursor, String field, ContentValues values) |
| { |
| cursorDoubleToContentValues(cursor, field, values, field); |
| } |
| |
| /** |
| * Reads a Double out of a field in a Cursor and writes it to a Map. |
| * |
| * @param cursor The cursor to read from |
| * @param field The REAL field to read |
| * @param values The {@link ContentValues} to put the value into |
| * @param key The key to store the value with in the map |
| */ |
| public static void cursorDoubleToContentValues(Cursor cursor, String field, |
| ContentValues values, String key) { |
| int colIndex = cursor.getColumnIndex(field); |
| if (!cursor.isNull(colIndex)) { |
| values.put(key, cursor.getDouble(colIndex)); |
| } else { |
| values.put(key, (Double) null); |
| } |
| } |
| |
| /** |
| * Read the entire contents of a cursor row and store them in a ContentValues. |
| * |
| * @param cursor the cursor to read from. |
| * @param values the {@link ContentValues} to put the row into. |
| */ |
| public static void cursorRowToContentValues(Cursor cursor, ContentValues values) { |
| AbstractWindowedCursor awc = |
| (cursor instanceof AbstractWindowedCursor) ? (AbstractWindowedCursor) cursor : null; |
| |
| String[] columns = cursor.getColumnNames(); |
| int length = columns.length; |
| for (int i = 0; i < length; i++) { |
| if (awc != null && awc.isBlob(i)) { |
| values.put(columns[i], cursor.getBlob(i)); |
| } else { |
| values.put(columns[i], cursor.getString(i)); |
| } |
| } |
| } |
| |
| /** |
| * Query the table for the number of rows in the table. |
| * @param db the database the table is in |
| * @param table the name of the table to query |
| * @return the number of rows in the table |
| */ |
| public static long queryNumEntries(SQLiteDatabase db, String table) { |
| Cursor cursor = db.query(table, countProjection, |
| null, null, null, null, null); |
| try { |
| cursor.moveToFirst(); |
| return cursor.getLong(0); |
| } finally { |
| cursor.close(); |
| } |
| } |
| |
| /** |
| * Utility method to run the query on the db and return the value in the |
| * first column of the first row. |
| */ |
| public static long longForQuery(SQLiteDatabase db, String query, String[] selectionArgs) { |
| SQLiteStatement prog = db.compileStatement(query); |
| try { |
| return longForQuery(prog, selectionArgs); |
| } finally { |
| prog.close(); |
| } |
| } |
| |
| /** |
| * Utility method to run the pre-compiled query and return the value in the |
| * first column of the first row. |
| */ |
| public static long longForQuery(SQLiteStatement prog, String[] selectionArgs) { |
| if (selectionArgs != null) { |
| int size = selectionArgs.length; |
| for (int i = 0; i < size; i++) { |
| bindObjectToProgram(prog, i + 1, selectionArgs[i]); |
| } |
| } |
| long value = prog.simpleQueryForLong(); |
| return value; |
| } |
| |
| /** |
| * Utility method to run the query on the db and return the value in the |
| * first column of the first row. |
| */ |
| public static String stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs) { |
| SQLiteStatement prog = db.compileStatement(query); |
| try { |
| return stringForQuery(prog, selectionArgs); |
| } finally { |
| prog.close(); |
| } |
| } |
| |
| /** |
| * Utility method to run the pre-compiled query and return the value in the |
| * first column of the first row. |
| */ |
| public static String stringForQuery(SQLiteStatement prog, String[] selectionArgs) { |
| if (selectionArgs != null) { |
| int size = selectionArgs.length; |
| for (int i = 0; i < size; i++) { |
| bindObjectToProgram(prog, i + 1, selectionArgs[i]); |
| } |
| } |
| String value = prog.simpleQueryForString(); |
| return value; |
| } |
| |
| /** |
| * This class allows users to do multiple inserts into a table but |
| * compile the SQL insert statement only once, which may increase |
| * performance. |
| */ |
| public static class InsertHelper { |
| private final SQLiteDatabase mDb; |
| private final String mTableName; |
| private HashMap<String, Integer> mColumns; |
| private String mInsertSQL = null; |
| private SQLiteStatement mInsertStatement = null; |
| private SQLiteStatement mReplaceStatement = null; |
| private SQLiteStatement mPreparedStatement = null; |
| |
| /** |
| * {@hide} |
| * |
| * These are the columns returned by sqlite's "PRAGMA |
| * table_info(...)" command that we depend on. |
| */ |
| public static final int TABLE_INFO_PRAGMA_COLUMNNAME_INDEX = 1; |
| public static final int TABLE_INFO_PRAGMA_DEFAULT_INDEX = 4; |
| |
| /** |
| * @param db the SQLiteDatabase to insert into |
| * @param tableName the name of the table to insert into |
| */ |
| public InsertHelper(SQLiteDatabase db, String tableName) { |
| mDb = db; |
| mTableName = tableName; |
| } |
| |
| private void buildSQL() throws SQLException { |
| StringBuilder sb = new StringBuilder(128); |
| sb.append("INSERT INTO "); |
| sb.append(mTableName); |
| sb.append(" ("); |
| |
| StringBuilder sbv = new StringBuilder(128); |
| sbv.append("VALUES ("); |
| |
| int i = 1; |
| Cursor cur = null; |
| try { |
| cur = mDb.rawQuery("PRAGMA table_info(" + mTableName + ")", null); |
| mColumns = new HashMap<String, Integer>(cur.getCount()); |
| while (cur.moveToNext()) { |
| String columnName = cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX); |
| String defaultValue = cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX); |
| |
| mColumns.put(columnName, i); |
| sb.append("'"); |
| sb.append(columnName); |
| sb.append("'"); |
| |
| if (defaultValue == null) { |
| sbv.append("?"); |
| } else { |
| sbv.append("COALESCE(?, "); |
| sbv.append(defaultValue); |
| sbv.append(")"); |
| } |
| |
| sb.append(i == cur.getCount() ? ") " : ", "); |
| sbv.append(i == cur.getCount() ? ");" : ", "); |
| ++i; |
| } |
| } finally { |
| if (cur != null) cur.close(); |
| } |
| |
| sb.append(sbv); |
| |
| mInsertSQL = sb.toString(); |
| if (LOCAL_LOGV) Log.v(TAG, "insert statement is " + mInsertSQL); |
| } |
| |
| private SQLiteStatement getStatement(boolean allowReplace) throws SQLException { |
| if (allowReplace) { |
| if (mReplaceStatement == null) { |
| if (mInsertSQL == null) buildSQL(); |
| // chop "INSERT" off the front and prepend "INSERT OR REPLACE" instead. |
| String replaceSQL = "INSERT OR REPLACE" + mInsertSQL.substring(6); |
| mReplaceStatement = mDb.compileStatement(replaceSQL); |
| } |
| return mReplaceStatement; |
| } else { |
| if (mInsertStatement == null) { |
| if (mInsertSQL == null) buildSQL(); |
| mInsertStatement = mDb.compileStatement(mInsertSQL); |
| } |
| return mInsertStatement; |
| } |
| } |
| |
| /** |
| * Performs an insert, adding a new row with the given values. |
| * |
| * @param values the set of values with which to populate the |
| * new row |
| * @param allowReplace if true, the statement does "INSERT OR |
| * REPLACE" instead of "INSERT", silently deleting any |
| * previously existing rows that would cause a conflict |
| * |
| * @return the row ID of the newly inserted row, or -1 if an |
| * error occurred |
| */ |
| private synchronized long insertInternal(ContentValues values, boolean allowReplace) { |
| try { |
| SQLiteStatement stmt = getStatement(allowReplace); |
| stmt.clearBindings(); |
| if (LOCAL_LOGV) Log.v(TAG, "--- inserting in table " + mTableName); |
| for (Map.Entry<String, Object> e: values.valueSet()) { |
| final String key = e.getKey(); |
| int i = getColumnIndex(key); |
| DatabaseUtils.bindObjectToProgram(stmt, i, e.getValue()); |
| if (LOCAL_LOGV) { |
| Log.v(TAG, "binding " + e.getValue() + " to column " + |
| i + " (" + key + ")"); |
| } |
| } |
| return stmt.executeInsert(); |
| } catch (SQLException e) { |
| Log.e(TAG, "Error inserting " + values + " into table " + mTableName, e); |
| return -1; |
| } |
| } |
| |
| /** |
| * Returns the index of the specified column. This is index is suitagble for use |
| * in calls to bind(). |
| * @param key the column name |
| * @return the index of the column |
| */ |
| public int getColumnIndex(String key) { |
| getStatement(false); |
| final Integer index = mColumns.get(key); |
| if (index == null) { |
| throw new IllegalArgumentException("column '" + key + "' is invalid"); |
| } |
| return index; |
| } |
| |
| /** |
| * Bind the value to an index. A prepareForInsert() or prepareForReplace() |
| * without a matching execute() must have already have been called. |
| * @param index the index of the slot to which to bind |
| * @param value the value to bind |
| */ |
| public void bind(int index, double value) { |
| mPreparedStatement.bindDouble(index, value); |
| } |
| |
| /** |
| * Bind the value to an index. A prepareForInsert() or prepareForReplace() |
| * without a matching execute() must have already have been called. |
| * @param index the index of the slot to which to bind |
| * @param value the value to bind |
| */ |
| public void bind(int index, float value) { |
| mPreparedStatement.bindDouble(index, value); |
| } |
| |
| /** |
| * Bind the value to an index. A prepareForInsert() or prepareForReplace() |
| * without a matching execute() must have already have been called. |
| * @param index the index of the slot to which to bind |
| * @param value the value to bind |
| */ |
| public void bind(int index, long value) { |
| mPreparedStatement.bindLong(index, value); |
| } |
| |
| /** |
| * Bind the value to an index. A prepareForInsert() or prepareForReplace() |
| * without a matching execute() must have already have been called. |
| * @param index the index of the slot to which to bind |
| * @param value the value to bind |
| */ |
| public void bind(int index, int value) { |
| mPreparedStatement.bindLong(index, value); |
| } |
| |
| /** |
| * Bind the value to an index. A prepareForInsert() or prepareForReplace() |
| * without a matching execute() must have already have been called. |
| * @param index the index of the slot to which to bind |
| * @param value the value to bind |
| */ |
| public void bind(int index, boolean value) { |
| mPreparedStatement.bindLong(index, value ? 1 : 0); |
| } |
| |
| /** |
| * Bind null to an index. A prepareForInsert() or prepareForReplace() |
| * without a matching execute() must have already have been called. |
| * @param index the index of the slot to which to bind |
| */ |
| public void bindNull(int index) { |
| mPreparedStatement.bindNull(index); |
| } |
| |
| /** |
| * Bind the value to an index. A prepareForInsert() or prepareForReplace() |
| * without a matching execute() must have already have been called. |
| * @param index the index of the slot to which to bind |
| * @param value the value to bind |
| */ |
| public void bind(int index, byte[] value) { |
| if (value == null) { |
| mPreparedStatement.bindNull(index); |
| } else { |
| mPreparedStatement.bindBlob(index, value); |
| } |
| } |
| |
| /** |
| * Bind the value to an index. A prepareForInsert() or prepareForReplace() |
| * without a matching execute() must have already have been called. |
| * @param index the index of the slot to which to bind |
| * @param value the value to bind |
| */ |
| public void bind(int index, String value) { |
| if (value == null) { |
| mPreparedStatement.bindNull(index); |
| } else { |
| mPreparedStatement.bindString(index, value); |
| } |
| } |
| |
| /** |
| * Performs an insert, adding a new row with the given values. |
| * If the table contains conflicting rows, an error is |
| * returned. |
| * |
| * @param values the set of values with which to populate the |
| * new row |
| * |
| * @return the row ID of the newly inserted row, or -1 if an |
| * error occurred |
| */ |
| public long insert(ContentValues values) { |
| return insertInternal(values, false); |
| } |
| |
| /** |
| * Execute the previously prepared insert or replace using the bound values |
| * since the last call to prepareForInsert or prepareForReplace. |
| * |
| * <p>Note that calling bind() and then execute() is not thread-safe. The only thread-safe |
| * way to use this class is to call insert() or replace(). |
| * |
| * @return the row ID of the newly inserted row, or -1 if an |
| * error occurred |
| */ |
| public long execute() { |
| if (mPreparedStatement == null) { |
| throw new IllegalStateException("you must prepare this inserter before calling " |
| + "execute"); |
| } |
| try { |
| if (LOCAL_LOGV) Log.v(TAG, "--- doing insert or replace in table " + mTableName); |
| return mPreparedStatement.executeInsert(); |
| } catch (SQLException e) { |
| Log.e(TAG, "Error executing InsertHelper with table " + mTableName, e); |
| return -1; |
| } finally { |
| // you can only call this once per prepare |
| mPreparedStatement = null; |
| } |
| } |
| |
| /** |
| * Prepare the InsertHelper for an insert. The pattern for this is: |
| * <ul> |
| * <li>prepareForInsert() |
| * <li>bind(index, value); |
| * <li>bind(index, value); |
| * <li>... |
| * <li>bind(index, value); |
| * <li>execute(); |
| * </ul> |
| */ |
| public void prepareForInsert() { |
| mPreparedStatement = getStatement(false); |
| mPreparedStatement.clearBindings(); |
| } |
| |
| /** |
| * Prepare the InsertHelper for a replace. The pattern for this is: |
| * <ul> |
| * <li>prepareForReplace() |
| * <li>bind(index, value); |
| * <li>bind(index, value); |
| * <li>... |
| * <li>bind(index, value); |
| * <li>execute(); |
| * </ul> |
| */ |
| public void prepareForReplace() { |
| mPreparedStatement = getStatement(true); |
| mPreparedStatement.clearBindings(); |
| } |
| |
| /** |
| * Performs an insert, adding a new row with the given values. |
| * If the table contains conflicting rows, they are deleted |
| * and replaced with the new row. |
| * |
| * @param values the set of values with which to populate the |
| * new row |
| * |
| * @return the row ID of the newly inserted row, or -1 if an |
| * error occurred |
| */ |
| public long replace(ContentValues values) { |
| return insertInternal(values, true); |
| } |
| |
| /** |
| * Close this object and release any resources associated with |
| * it. The behavior of calling <code>insert()</code> after |
| * calling this method is undefined. |
| */ |
| public void close() { |
| if (mInsertStatement != null) { |
| mInsertStatement.close(); |
| mInsertStatement = null; |
| } |
| if (mReplaceStatement != null) { |
| mReplaceStatement.close(); |
| mReplaceStatement = null; |
| } |
| mInsertSQL = null; |
| mColumns = null; |
| } |
| } |
| |
| /** |
| * Creates a db and populates it with the sql statements in sqlStatements. |
| * |
| * @param context the context to use to create the db |
| * @param dbName the name of the db to create |
| * @param dbVersion the version to set on the db |
| * @param sqlStatements the statements to use to populate the db. This should be a single string |
| * of the form returned by sqlite3's <tt>.dump</tt> command (statements separated by |
| * semicolons) |
| */ |
| static public void createDbFromSqlStatements( |
| Context context, String dbName, int dbVersion, String sqlStatements) { |
| SQLiteDatabase db = context.openOrCreateDatabase(dbName, 0, null); |
| // TODO: this is not quite safe since it assumes that all semicolons at the end of a line |
| // terminate statements. It is possible that a text field contains ;\n. We will have to fix |
| // this if that turns out to be a problem. |
| String[] statements = TextUtils.split(sqlStatements, ";\n"); |
| for (String statement : statements) { |
| if (TextUtils.isEmpty(statement)) continue; |
| db.execSQL(statement); |
| } |
| db.setVersion(dbVersion); |
| db.close(); |
| } |
| } |