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