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