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 AbstractWindowedCursor awc = 732 (cursor instanceof AbstractWindowedCursor) ? (AbstractWindowedCursor) cursor : null; 733 734 String[] columns = cursor.getColumnNames(); 735 int length = columns.length; 736 for (int i = 0; i < length; i++) { 737 if (awc != null && awc.isBlob(i)) { 738 values.put(columns[i], cursor.getBlob(i)); 739 } else { 740 values.put(columns[i], cursor.getString(i)); 741 } 742 } 743 } 744 745 /** 746 * Picks a start position for {@link Cursor#fillWindow} such that the 747 * window will contain the requested row and a useful range of rows 748 * around it. 749 * 750 * When the data set is too large to fit in a cursor window, seeking the 751 * cursor can become a very expensive operation since we have to run the 752 * query again when we move outside the bounds of the current window. 753 * 754 * We try to choose a start position for the cursor window such that 755 * 1/3 of the window's capacity is used to hold rows before the requested 756 * position and 2/3 of the window's capacity is used to hold rows after the 757 * requested position. 758 * 759 * @param cursorPosition The row index of the row we want to get. 760 * @param cursorWindowCapacity The estimated number of rows that can fit in 761 * a cursor window, or 0 if unknown. 762 * @return The recommended start position, always less than or equal to 763 * the requested row. 764 * @hide 765 */ cursorPickFillWindowStartPosition( int cursorPosition, int cursorWindowCapacity)766 public static int cursorPickFillWindowStartPosition( 767 int cursorPosition, int cursorWindowCapacity) { 768 return Math.max(cursorPosition - cursorWindowCapacity / 3, 0); 769 } 770 771 /** 772 * Query the table for the number of rows in the table. 773 * @param db the database the table is in 774 * @param table the name of the table to query 775 * @return the number of rows in the table 776 */ queryNumEntries(SQLiteDatabase db, String table)777 public static long queryNumEntries(SQLiteDatabase db, String table) { 778 return queryNumEntries(db, table, null, null); 779 } 780 781 /** 782 * Query the table for the number of rows in the table. 783 * @param db the database the table is in 784 * @param table the name of the table to query 785 * @param selection A filter declaring which rows to return, 786 * formatted as an SQL WHERE clause (excluding the WHERE itself). 787 * Passing null will count all rows for the given table 788 * @return the number of rows in the table filtered by the selection 789 */ queryNumEntries(SQLiteDatabase db, String table, String selection)790 public static long queryNumEntries(SQLiteDatabase db, String table, String selection) { 791 return queryNumEntries(db, table, selection, null); 792 } 793 794 /** 795 * Query the table for the number of rows in the table. 796 * @param db the database the table is in 797 * @param table the name of the table to query 798 * @param selection A filter declaring which rows to return, 799 * formatted as an SQL WHERE clause (excluding the WHERE itself). 800 * Passing null will count all rows for the given table 801 * @param selectionArgs You may include ?s in selection, 802 * which will be replaced by the values from selectionArgs, 803 * in order that they appear in the selection. 804 * The values will be bound as Strings. 805 * @return the number of rows in the table filtered by the selection 806 */ queryNumEntries(SQLiteDatabase db, String table, String selection, String[] selectionArgs)807 public static long queryNumEntries(SQLiteDatabase db, String table, String selection, 808 String[] selectionArgs) { 809 String s = (!TextUtils.isEmpty(selection)) ? " where " + selection : ""; 810 return longForQuery(db, "select count(*) from " + table + s, 811 selectionArgs); 812 } 813 814 /** 815 * Query the table to check whether a table is empty or not 816 * @param db the database the table is in 817 * @param table the name of the table to query 818 * @return True if the table is empty 819 * @hide 820 */ queryIsEmpty(SQLiteDatabase db, String table)821 public static boolean queryIsEmpty(SQLiteDatabase db, String table) { 822 long isEmpty = longForQuery(db, "select exists(select 1 from " + table + ")", null); 823 return isEmpty == 0; 824 } 825 826 /** 827 * Utility method to run the query on the db and return the value in the 828 * first column of the first row. 829 */ longForQuery(SQLiteDatabase db, String query, String[] selectionArgs)830 public static long longForQuery(SQLiteDatabase db, String query, String[] selectionArgs) { 831 SQLiteStatement prog = db.compileStatement(query); 832 try { 833 return longForQuery(prog, selectionArgs); 834 } finally { 835 prog.close(); 836 } 837 } 838 839 /** 840 * Utility method to run the pre-compiled query and return the value in the 841 * first column of the first row. 842 */ longForQuery(SQLiteStatement prog, String[] selectionArgs)843 public static long longForQuery(SQLiteStatement prog, String[] selectionArgs) { 844 prog.bindAllArgsAsStrings(selectionArgs); 845 return prog.simpleQueryForLong(); 846 } 847 848 /** 849 * Utility method to run the query on the db and return the value in the 850 * first column of the first row. 851 */ stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs)852 public static String stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs) { 853 SQLiteStatement prog = db.compileStatement(query); 854 try { 855 return stringForQuery(prog, selectionArgs); 856 } finally { 857 prog.close(); 858 } 859 } 860 861 /** 862 * Utility method to run the pre-compiled query and return the value in the 863 * first column of the first row. 864 */ stringForQuery(SQLiteStatement prog, String[] selectionArgs)865 public static String stringForQuery(SQLiteStatement prog, String[] selectionArgs) { 866 prog.bindAllArgsAsStrings(selectionArgs); 867 return prog.simpleQueryForString(); 868 } 869 870 /** 871 * Utility method to run the query on the db and return the blob value in the 872 * first column of the first row. 873 * 874 * @return A read-only file descriptor for a copy of the blob value. 875 */ blobFileDescriptorForQuery(SQLiteDatabase db, String query, String[] selectionArgs)876 public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteDatabase db, 877 String query, String[] selectionArgs) { 878 SQLiteStatement prog = db.compileStatement(query); 879 try { 880 return blobFileDescriptorForQuery(prog, selectionArgs); 881 } finally { 882 prog.close(); 883 } 884 } 885 886 /** 887 * Utility method to run the pre-compiled query and return the blob value in the 888 * first column of the first row. 889 * 890 * @return A read-only file descriptor for a copy of the blob value. 891 */ blobFileDescriptorForQuery(SQLiteStatement prog, String[] selectionArgs)892 public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteStatement prog, 893 String[] selectionArgs) { 894 prog.bindAllArgsAsStrings(selectionArgs); 895 return prog.simpleQueryForBlobFileDescriptor(); 896 } 897 898 /** 899 * Reads a String 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 */ cursorStringToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)906 public static void cursorStringToContentValuesIfPresent(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.getString(index)); 911 } 912 } 913 914 /** 915 * Reads a Long 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 */ cursorLongToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)922 public static void cursorLongToContentValuesIfPresent(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.getLong(index)); 927 } 928 } 929 930 /** 931 * Reads a Short 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 */ cursorShortToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)938 public static void cursorShortToContentValuesIfPresent(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.getShort(index)); 943 } 944 } 945 946 /** 947 * Reads a Integer 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 */ cursorIntToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)954 public static void cursorIntToContentValuesIfPresent(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.getInt(index)); 959 } 960 } 961 962 /** 963 * Reads a Float out of a column in a Cursor and writes it to a ContentValues. 964 * Adds nothing to the ContentValues if the column isn't present or if its value is null. 965 * 966 * @param cursor The cursor to read from 967 * @param column The column to read 968 * @param values The {@link ContentValues} to put the value into 969 */ cursorFloatToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)970 public static void cursorFloatToContentValuesIfPresent(Cursor cursor, ContentValues values, 971 String column) { 972 final int index = cursor.getColumnIndex(column); 973 if (index != -1 && !cursor.isNull(index)) { 974 values.put(column, cursor.getFloat(index)); 975 } 976 } 977 978 /** 979 * Reads a Double out of a column in a Cursor and writes it to a ContentValues. 980 * Adds nothing to the ContentValues if the column isn't present or if its value is null. 981 * 982 * @param cursor The cursor to read from 983 * @param column The column to read 984 * @param values The {@link ContentValues} to put the value into 985 */ cursorDoubleToContentValuesIfPresent(Cursor cursor, ContentValues values, String column)986 public static void cursorDoubleToContentValuesIfPresent(Cursor cursor, ContentValues values, 987 String column) { 988 final int index = cursor.getColumnIndex(column); 989 if (index != -1 && !cursor.isNull(index)) { 990 values.put(column, cursor.getDouble(index)); 991 } 992 } 993 994 /** 995 * This class allows users to do multiple inserts into a table using 996 * the same statement. 997 * <p> 998 * This class is not thread-safe. 999 * </p> 1000 * 1001 * @deprecated Use {@link SQLiteStatement} instead. 1002 */ 1003 @Deprecated 1004 public static class InsertHelper { 1005 private final SQLiteDatabase mDb; 1006 private final String mTableName; 1007 private HashMap<String, Integer> mColumns; 1008 private String mInsertSQL = null; 1009 private SQLiteStatement mInsertStatement = null; 1010 private SQLiteStatement mReplaceStatement = null; 1011 private SQLiteStatement mPreparedStatement = null; 1012 1013 /** 1014 * {@hide} 1015 * 1016 * These are the columns returned by sqlite's "PRAGMA 1017 * table_info(...)" command that we depend on. 1018 */ 1019 public static final int TABLE_INFO_PRAGMA_COLUMNNAME_INDEX = 1; 1020 1021 /** 1022 * This field was accidentally exposed in earlier versions of the platform 1023 * so we can hide it but we can't remove it. 1024 * 1025 * @hide 1026 */ 1027 public static final int TABLE_INFO_PRAGMA_DEFAULT_INDEX = 4; 1028 1029 /** 1030 * @param db the SQLiteDatabase to insert into 1031 * @param tableName the name of the table to insert into 1032 */ InsertHelper(SQLiteDatabase db, String tableName)1033 public InsertHelper(SQLiteDatabase db, String tableName) { 1034 mDb = db; 1035 mTableName = tableName; 1036 } 1037 buildSQL()1038 private void buildSQL() throws SQLException { 1039 StringBuilder sb = new StringBuilder(128); 1040 sb.append("INSERT INTO "); 1041 sb.append(mTableName); 1042 sb.append(" ("); 1043 1044 StringBuilder sbv = new StringBuilder(128); 1045 sbv.append("VALUES ("); 1046 1047 int i = 1; 1048 Cursor cur = null; 1049 try { 1050 cur = mDb.rawQuery("PRAGMA table_info(" + mTableName + ")", null); 1051 mColumns = new HashMap<String, Integer>(cur.getCount()); 1052 while (cur.moveToNext()) { 1053 String columnName = cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX); 1054 String defaultValue = cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX); 1055 1056 mColumns.put(columnName, i); 1057 sb.append("'"); 1058 sb.append(columnName); 1059 sb.append("'"); 1060 1061 if (defaultValue == null) { 1062 sbv.append("?"); 1063 } else { 1064 sbv.append("COALESCE(?, "); 1065 sbv.append(defaultValue); 1066 sbv.append(")"); 1067 } 1068 1069 sb.append(i == cur.getCount() ? ") " : ", "); 1070 sbv.append(i == cur.getCount() ? ");" : ", "); 1071 ++i; 1072 } 1073 } finally { 1074 if (cur != null) cur.close(); 1075 } 1076 1077 sb.append(sbv); 1078 1079 mInsertSQL = sb.toString(); 1080 if (DEBUG) Log.v(TAG, "insert statement is " + mInsertSQL); 1081 } 1082 getStatement(boolean allowReplace)1083 private SQLiteStatement getStatement(boolean allowReplace) throws SQLException { 1084 if (allowReplace) { 1085 if (mReplaceStatement == null) { 1086 if (mInsertSQL == null) buildSQL(); 1087 // chop "INSERT" off the front and prepend "INSERT OR REPLACE" instead. 1088 String replaceSQL = "INSERT OR REPLACE" + mInsertSQL.substring(6); 1089 mReplaceStatement = mDb.compileStatement(replaceSQL); 1090 } 1091 return mReplaceStatement; 1092 } else { 1093 if (mInsertStatement == null) { 1094 if (mInsertSQL == null) buildSQL(); 1095 mInsertStatement = mDb.compileStatement(mInsertSQL); 1096 } 1097 return mInsertStatement; 1098 } 1099 } 1100 1101 /** 1102 * Performs an insert, adding a new row with the given values. 1103 * 1104 * @param values the set of values with which to populate the 1105 * new row 1106 * @param allowReplace if true, the statement does "INSERT OR 1107 * REPLACE" instead of "INSERT", silently deleting any 1108 * previously existing rows that would cause a conflict 1109 * 1110 * @return the row ID of the newly inserted row, or -1 if an 1111 * error occurred 1112 */ insertInternal(ContentValues values, boolean allowReplace)1113 private long insertInternal(ContentValues values, boolean allowReplace) { 1114 // Start a transaction even though we don't really need one. 1115 // This is to help maintain compatibility with applications that 1116 // access InsertHelper from multiple threads even though they never should have. 1117 // The original code used to lock the InsertHelper itself which was prone 1118 // to deadlocks. Starting a transaction achieves the same mutual exclusion 1119 // effect as grabbing a lock but without the potential for deadlocks. 1120 mDb.beginTransactionNonExclusive(); 1121 try { 1122 SQLiteStatement stmt = getStatement(allowReplace); 1123 stmt.clearBindings(); 1124 if (DEBUG) Log.v(TAG, "--- inserting in table " + mTableName); 1125 for (Map.Entry<String, Object> e: values.valueSet()) { 1126 final String key = e.getKey(); 1127 int i = getColumnIndex(key); 1128 DatabaseUtils.bindObjectToProgram(stmt, i, e.getValue()); 1129 if (DEBUG) { 1130 Log.v(TAG, "binding " + e.getValue() + " to column " + 1131 i + " (" + key + ")"); 1132 } 1133 } 1134 long result = stmt.executeInsert(); 1135 mDb.setTransactionSuccessful(); 1136 return result; 1137 } catch (SQLException e) { 1138 Log.e(TAG, "Error inserting " + values + " into table " + mTableName, e); 1139 return -1; 1140 } finally { 1141 mDb.endTransaction(); 1142 } 1143 } 1144 1145 /** 1146 * Returns the index of the specified column. This is index is suitagble for use 1147 * in calls to bind(). 1148 * @param key the column name 1149 * @return the index of the column 1150 */ getColumnIndex(String key)1151 public int getColumnIndex(String key) { 1152 getStatement(false); 1153 final Integer index = mColumns.get(key); 1154 if (index == null) { 1155 throw new IllegalArgumentException("column '" + key + "' is invalid"); 1156 } 1157 return index; 1158 } 1159 1160 /** 1161 * Bind the value to an index. A prepareForInsert() or prepareForReplace() 1162 * without a matching execute() must have already have been called. 1163 * @param index the index of the slot to which to bind 1164 * @param value the value to bind 1165 */ bind(int index, double value)1166 public void bind(int index, double value) { 1167 mPreparedStatement.bindDouble(index, value); 1168 } 1169 1170 /** 1171 * Bind the value to an index. A prepareForInsert() or prepareForReplace() 1172 * without a matching execute() must have already have been called. 1173 * @param index the index of the slot to which to bind 1174 * @param value the value to bind 1175 */ bind(int index, float value)1176 public void bind(int index, float value) { 1177 mPreparedStatement.bindDouble(index, value); 1178 } 1179 1180 /** 1181 * Bind the value to an index. A prepareForInsert() or prepareForReplace() 1182 * without a matching execute() must have already have been called. 1183 * @param index the index of the slot to which to bind 1184 * @param value the value to bind 1185 */ bind(int index, long value)1186 public void bind(int index, long value) { 1187 mPreparedStatement.bindLong(index, value); 1188 } 1189 1190 /** 1191 * Bind the value to an index. A prepareForInsert() or prepareForReplace() 1192 * without a matching execute() must have already have been called. 1193 * @param index the index of the slot to which to bind 1194 * @param value the value to bind 1195 */ bind(int index, int value)1196 public void bind(int index, int value) { 1197 mPreparedStatement.bindLong(index, value); 1198 } 1199 1200 /** 1201 * Bind the value to an index. A prepareForInsert() or prepareForReplace() 1202 * without a matching execute() must have already have been called. 1203 * @param index the index of the slot to which to bind 1204 * @param value the value to bind 1205 */ bind(int index, boolean value)1206 public void bind(int index, boolean value) { 1207 mPreparedStatement.bindLong(index, value ? 1 : 0); 1208 } 1209 1210 /** 1211 * Bind null to an index. A prepareForInsert() or prepareForReplace() 1212 * without a matching execute() must have already have been called. 1213 * @param index the index of the slot to which to bind 1214 */ bindNull(int index)1215 public void bindNull(int index) { 1216 mPreparedStatement.bindNull(index); 1217 } 1218 1219 /** 1220 * Bind the value to an index. A prepareForInsert() or prepareForReplace() 1221 * without a matching execute() must have already have been called. 1222 * @param index the index of the slot to which to bind 1223 * @param value the value to bind 1224 */ bind(int index, byte[] value)1225 public void bind(int index, byte[] value) { 1226 if (value == null) { 1227 mPreparedStatement.bindNull(index); 1228 } else { 1229 mPreparedStatement.bindBlob(index, value); 1230 } 1231 } 1232 1233 /** 1234 * Bind the value to an index. A prepareForInsert() or prepareForReplace() 1235 * without a matching execute() must have already have been called. 1236 * @param index the index of the slot to which to bind 1237 * @param value the value to bind 1238 */ bind(int index, String value)1239 public void bind(int index, String value) { 1240 if (value == null) { 1241 mPreparedStatement.bindNull(index); 1242 } else { 1243 mPreparedStatement.bindString(index, value); 1244 } 1245 } 1246 1247 /** 1248 * Performs an insert, adding a new row with the given values. 1249 * If the table contains conflicting rows, an error is 1250 * returned. 1251 * 1252 * @param values the set of values with which to populate the 1253 * new row 1254 * 1255 * @return the row ID of the newly inserted row, or -1 if an 1256 * error occurred 1257 */ insert(ContentValues values)1258 public long insert(ContentValues values) { 1259 return insertInternal(values, false); 1260 } 1261 1262 /** 1263 * Execute the previously prepared insert or replace using the bound values 1264 * since the last call to prepareForInsert or prepareForReplace. 1265 * 1266 * <p>Note that calling bind() and then execute() is not thread-safe. The only thread-safe 1267 * way to use this class is to call insert() or replace(). 1268 * 1269 * @return the row ID of the newly inserted row, or -1 if an 1270 * error occurred 1271 */ execute()1272 public long execute() { 1273 if (mPreparedStatement == null) { 1274 throw new IllegalStateException("you must prepare this inserter before calling " 1275 + "execute"); 1276 } 1277 try { 1278 if (DEBUG) Log.v(TAG, "--- doing insert or replace in table " + mTableName); 1279 return mPreparedStatement.executeInsert(); 1280 } catch (SQLException e) { 1281 Log.e(TAG, "Error executing InsertHelper with table " + mTableName, e); 1282 return -1; 1283 } finally { 1284 // you can only call this once per prepare 1285 mPreparedStatement = null; 1286 } 1287 } 1288 1289 /** 1290 * Prepare the InsertHelper for an insert. The pattern for this is: 1291 * <ul> 1292 * <li>prepareForInsert() 1293 * <li>bind(index, value); 1294 * <li>bind(index, value); 1295 * <li>... 1296 * <li>bind(index, value); 1297 * <li>execute(); 1298 * </ul> 1299 */ prepareForInsert()1300 public void prepareForInsert() { 1301 mPreparedStatement = getStatement(false); 1302 mPreparedStatement.clearBindings(); 1303 } 1304 1305 /** 1306 * Prepare the InsertHelper for a replace. The pattern for this is: 1307 * <ul> 1308 * <li>prepareForReplace() 1309 * <li>bind(index, value); 1310 * <li>bind(index, value); 1311 * <li>... 1312 * <li>bind(index, value); 1313 * <li>execute(); 1314 * </ul> 1315 */ prepareForReplace()1316 public void prepareForReplace() { 1317 mPreparedStatement = getStatement(true); 1318 mPreparedStatement.clearBindings(); 1319 } 1320 1321 /** 1322 * Performs an insert, adding a new row with the given values. 1323 * If the table contains conflicting rows, they are deleted 1324 * and replaced with the new row. 1325 * 1326 * @param values the set of values with which to populate the 1327 * new row 1328 * 1329 * @return the row ID of the newly inserted row, or -1 if an 1330 * error occurred 1331 */ replace(ContentValues values)1332 public long replace(ContentValues values) { 1333 return insertInternal(values, true); 1334 } 1335 1336 /** 1337 * Close this object and release any resources associated with 1338 * it. The behavior of calling <code>insert()</code> after 1339 * calling this method is undefined. 1340 */ close()1341 public void close() { 1342 if (mInsertStatement != null) { 1343 mInsertStatement.close(); 1344 mInsertStatement = null; 1345 } 1346 if (mReplaceStatement != null) { 1347 mReplaceStatement.close(); 1348 mReplaceStatement = null; 1349 } 1350 mInsertSQL = null; 1351 mColumns = null; 1352 } 1353 } 1354 1355 /** 1356 * Creates a db and populates it with the sql statements in sqlStatements. 1357 * 1358 * @param context the context to use to create the db 1359 * @param dbName the name of the db to create 1360 * @param dbVersion the version to set on the db 1361 * @param sqlStatements the statements to use to populate the db. This should be a single string 1362 * of the form returned by sqlite3's <tt>.dump</tt> command (statements separated by 1363 * semicolons) 1364 */ createDbFromSqlStatements( Context context, String dbName, int dbVersion, String sqlStatements)1365 static public void createDbFromSqlStatements( 1366 Context context, String dbName, int dbVersion, String sqlStatements) { 1367 SQLiteDatabase db = context.openOrCreateDatabase(dbName, 0, null); 1368 // TODO: this is not quite safe since it assumes that all semicolons at the end of a line 1369 // terminate statements. It is possible that a text field contains ;\n. We will have to fix 1370 // this if that turns out to be a problem. 1371 String[] statements = TextUtils.split(sqlStatements, ";\n"); 1372 for (String statement : statements) { 1373 if (TextUtils.isEmpty(statement)) continue; 1374 db.execSQL(statement); 1375 } 1376 db.setVersion(dbVersion); 1377 db.close(); 1378 } 1379 1380 /** 1381 * Returns one of the following which represent the type of the given SQL statement. 1382 * <ol> 1383 * <li>{@link #STATEMENT_SELECT}</li> 1384 * <li>{@link #STATEMENT_UPDATE}</li> 1385 * <li>{@link #STATEMENT_ATTACH}</li> 1386 * <li>{@link #STATEMENT_BEGIN}</li> 1387 * <li>{@link #STATEMENT_COMMIT}</li> 1388 * <li>{@link #STATEMENT_ABORT}</li> 1389 * <li>{@link #STATEMENT_OTHER}</li> 1390 * </ol> 1391 * @param sql the SQL statement whose type is returned by this method 1392 * @return one of the values listed above 1393 */ getSqlStatementType(String sql)1394 public static int getSqlStatementType(String sql) { 1395 sql = sql.trim(); 1396 if (sql.length() < 3) { 1397 return STATEMENT_OTHER; 1398 } 1399 String prefixSql = sql.substring(0, 3).toUpperCase(Locale.ROOT); 1400 if (prefixSql.equals("SEL")) { 1401 return STATEMENT_SELECT; 1402 } else if (prefixSql.equals("INS") || 1403 prefixSql.equals("UPD") || 1404 prefixSql.equals("REP") || 1405 prefixSql.equals("DEL")) { 1406 return STATEMENT_UPDATE; 1407 } else if (prefixSql.equals("ATT")) { 1408 return STATEMENT_ATTACH; 1409 } else if (prefixSql.equals("COM")) { 1410 return STATEMENT_COMMIT; 1411 } else if (prefixSql.equals("END")) { 1412 return STATEMENT_COMMIT; 1413 } else if (prefixSql.equals("ROL")) { 1414 return STATEMENT_ABORT; 1415 } else if (prefixSql.equals("BEG")) { 1416 return STATEMENT_BEGIN; 1417 } else if (prefixSql.equals("PRA")) { 1418 return STATEMENT_PRAGMA; 1419 } else if (prefixSql.equals("CRE") || prefixSql.equals("DRO") || 1420 prefixSql.equals("ALT")) { 1421 return STATEMENT_DDL; 1422 } else if (prefixSql.equals("ANA") || prefixSql.equals("DET")) { 1423 return STATEMENT_UNPREPARED; 1424 } 1425 return STATEMENT_OTHER; 1426 } 1427 1428 /** 1429 * Appends one set of selection args to another. This is useful when adding a selection 1430 * argument to a user provided set. 1431 */ appendSelectionArgs(String[] originalValues, String[] newValues)1432 public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) { 1433 if (originalValues == null || originalValues.length == 0) { 1434 return newValues; 1435 } 1436 String[] result = new String[originalValues.length + newValues.length ]; 1437 System.arraycopy(originalValues, 0, result, 0, originalValues.length); 1438 System.arraycopy(newValues, 0, result, originalValues.length, newValues.length); 1439 return result; 1440 } 1441 1442 /** 1443 * Returns column index of "_id" column, or -1 if not found. 1444 * @hide 1445 */ findRowIdColumnIndex(String[] columnNames)1446 public static int findRowIdColumnIndex(String[] columnNames) { 1447 int length = columnNames.length; 1448 for (int i = 0; i < length; i++) { 1449 if (columnNames[i].equals("_id")) { 1450 return i; 1451 } 1452 } 1453 return -1; 1454 } 1455 } 1456