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