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