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