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