SQLiteConnection.java revision 47847f3f4dcf2a0dbea0bc0e4f02528e21d37a88
1/* 2 * Copyright (C) 2011 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 17package android.database.sqlite; 18 19import dalvik.system.BlockGuard; 20import dalvik.system.CloseGuard; 21 22import android.content.CancellationSignal; 23import android.content.OperationCanceledException; 24import android.database.Cursor; 25import android.database.CursorWindow; 26import android.database.DatabaseUtils; 27import android.database.sqlite.SQLiteDebug.DbStats; 28import android.os.ParcelFileDescriptor; 29import android.util.Log; 30import android.util.LruCache; 31import android.util.Printer; 32 33import java.sql.Date; 34import java.text.SimpleDateFormat; 35import java.util.ArrayList; 36import java.util.Map; 37import java.util.regex.Pattern; 38 39/** 40 * Represents a SQLite database connection. 41 * Each connection wraps an instance of a native <code>sqlite3</code> object. 42 * <p> 43 * When database connection pooling is enabled, there can be multiple active 44 * connections to the same database. Otherwise there is typically only one 45 * connection per database. 46 * </p><p> 47 * When the SQLite WAL feature is enabled, multiple readers and one writer 48 * can concurrently access the database. Without WAL, readers and writers 49 * are mutually exclusive. 50 * </p> 51 * 52 * <h2>Ownership and concurrency guarantees</h2> 53 * <p> 54 * Connection objects are not thread-safe. They are acquired as needed to 55 * perform a database operation and are then returned to the pool. At any 56 * given time, a connection is either owned and used by a {@link SQLiteSession} 57 * object or the {@link SQLiteConnectionPool}. Those classes are 58 * responsible for serializing operations to guard against concurrent 59 * use of a connection. 60 * </p><p> 61 * The guarantee of having a single owner allows this class to be implemented 62 * without locks and greatly simplifies resource management. 63 * </p> 64 * 65 * <h2>Encapsulation guarantees</h2> 66 * <p> 67 * The connection object object owns *all* of the SQLite related native 68 * objects that are associated with the connection. What's more, there are 69 * no other objects in the system that are capable of obtaining handles to 70 * those native objects. Consequently, when the connection is closed, we do 71 * not have to worry about what other components might have references to 72 * its associated SQLite state -- there are none. 73 * </p><p> 74 * Encapsulation is what ensures that the connection object's 75 * lifecycle does not become a tortured mess of finalizers and reference 76 * queues. 77 * </p> 78 * 79 * <h2>Reentrance</h2> 80 * <p> 81 * This class must tolerate reentrant execution of SQLite operations because 82 * triggers may call custom SQLite functions that perform additional queries. 83 * </p> 84 * 85 * @hide 86 */ 87public final class SQLiteConnection implements CancellationSignal.OnCancelListener { 88 private static final String TAG = "SQLiteConnection"; 89 private static final boolean DEBUG = false; 90 91 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 92 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 93 94 private static final Pattern TRIM_SQL_PATTERN = Pattern.compile("[\\s]*\\n+[\\s]*"); 95 96 private final CloseGuard mCloseGuard = CloseGuard.get(); 97 98 private final SQLiteConnectionPool mPool; 99 private final SQLiteDatabaseConfiguration mConfiguration; 100 private final int mConnectionId; 101 private final boolean mIsPrimaryConnection; 102 private final boolean mIsReadOnlyConnection; 103 private final PreparedStatementCache mPreparedStatementCache; 104 private PreparedStatement mPreparedStatementPool; 105 106 // The recent operations log. 107 private final OperationLog mRecentOperations = new OperationLog(); 108 109 // The native SQLiteConnection pointer. (FOR INTERNAL USE ONLY) 110 private int mConnectionPtr; 111 112 private boolean mOnlyAllowReadOnlyOperations; 113 114 // The number of times attachCancellationSignal has been called. 115 // Because SQLite statement execution can be reentrant, we keep track of how many 116 // times we have attempted to attach a cancellation signal to the connection so that 117 // we can ensure that we detach the signal at the right time. 118 private int mCancellationSignalAttachCount; 119 120 private static native int nativeOpen(String path, int openFlags, String label, 121 boolean enableTrace, boolean enableProfile); 122 private static native void nativeClose(int connectionPtr); 123 private static native void nativeRegisterCustomFunction(int connectionPtr, 124 SQLiteCustomFunction function); 125 private static native void nativeRegisterLocalizedCollators(int connectionPtr, String locale); 126 private static native int nativePrepareStatement(int connectionPtr, String sql); 127 private static native void nativeFinalizeStatement(int connectionPtr, int statementPtr); 128 private static native int nativeGetParameterCount(int connectionPtr, int statementPtr); 129 private static native boolean nativeIsReadOnly(int connectionPtr, int statementPtr); 130 private static native int nativeGetColumnCount(int connectionPtr, int statementPtr); 131 private static native String nativeGetColumnName(int connectionPtr, int statementPtr, 132 int index); 133 private static native void nativeBindNull(int connectionPtr, int statementPtr, 134 int index); 135 private static native void nativeBindLong(int connectionPtr, int statementPtr, 136 int index, long value); 137 private static native void nativeBindDouble(int connectionPtr, int statementPtr, 138 int index, double value); 139 private static native void nativeBindString(int connectionPtr, int statementPtr, 140 int index, String value); 141 private static native void nativeBindBlob(int connectionPtr, int statementPtr, 142 int index, byte[] value); 143 private static native void nativeResetStatementAndClearBindings( 144 int connectionPtr, int statementPtr); 145 private static native void nativeExecute(int connectionPtr, int statementPtr); 146 private static native long nativeExecuteForLong(int connectionPtr, int statementPtr); 147 private static native String nativeExecuteForString(int connectionPtr, int statementPtr); 148 private static native int nativeExecuteForBlobFileDescriptor( 149 int connectionPtr, int statementPtr); 150 private static native int nativeExecuteForChangedRowCount(int connectionPtr, int statementPtr); 151 private static native long nativeExecuteForLastInsertedRowId( 152 int connectionPtr, int statementPtr); 153 private static native long nativeExecuteForCursorWindow( 154 int connectionPtr, int statementPtr, int windowPtr, 155 int startPos, int requiredPos, boolean countAllRows); 156 private static native int nativeGetDbLookaside(int connectionPtr); 157 private static native void nativeCancel(int connectionPtr); 158 private static native void nativeResetCancel(int connectionPtr, boolean cancelable); 159 160 private SQLiteConnection(SQLiteConnectionPool pool, 161 SQLiteDatabaseConfiguration configuration, 162 int connectionId, boolean primaryConnection) { 163 mPool = pool; 164 mConfiguration = new SQLiteDatabaseConfiguration(configuration); 165 mConnectionId = connectionId; 166 mIsPrimaryConnection = primaryConnection; 167 mIsReadOnlyConnection = (configuration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0; 168 mPreparedStatementCache = new PreparedStatementCache( 169 mConfiguration.maxSqlCacheSize); 170 mCloseGuard.open("close"); 171 } 172 173 @Override 174 protected void finalize() throws Throwable { 175 try { 176 if (mPool != null && mConnectionPtr != 0) { 177 mPool.onConnectionLeaked(); 178 } 179 180 dispose(true); 181 } finally { 182 super.finalize(); 183 } 184 } 185 186 // Called by SQLiteConnectionPool only. 187 static SQLiteConnection open(SQLiteConnectionPool pool, 188 SQLiteDatabaseConfiguration configuration, 189 int connectionId, boolean primaryConnection) { 190 SQLiteConnection connection = new SQLiteConnection(pool, configuration, 191 connectionId, primaryConnection); 192 try { 193 connection.open(); 194 return connection; 195 } catch (SQLiteException ex) { 196 connection.dispose(false); 197 throw ex; 198 } 199 } 200 201 // Called by SQLiteConnectionPool only. 202 // Closes the database closes and releases all of its associated resources. 203 // Do not call methods on the connection after it is closed. It will probably crash. 204 void close() { 205 dispose(false); 206 } 207 208 private void open() { 209 mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags, 210 mConfiguration.label, 211 SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME); 212 213 setPageSize(); 214 setWalModeFromConfiguration(); 215 setJournalSizeLimit(); 216 setAutoCheckpointInterval(); 217 setLocaleFromConfiguration(); 218 } 219 220 private void dispose(boolean finalized) { 221 if (mCloseGuard != null) { 222 if (finalized) { 223 mCloseGuard.warnIfOpen(); 224 } 225 mCloseGuard.close(); 226 } 227 228 if (mConnectionPtr != 0) { 229 final int cookie = mRecentOperations.beginOperation("close", null, null); 230 try { 231 mPreparedStatementCache.evictAll(); 232 nativeClose(mConnectionPtr); 233 mConnectionPtr = 0; 234 } finally { 235 mRecentOperations.endOperation(cookie); 236 } 237 } 238 } 239 240 private void setPageSize() { 241 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { 242 final long newValue = SQLiteGlobal.getDefaultPageSize(); 243 long value = executeForLong("PRAGMA page_size", null, null); 244 if (value != newValue) { 245 execute("PRAGMA page_size=" + newValue, null, null); 246 } 247 } 248 } 249 250 private void setAutoCheckpointInterval() { 251 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { 252 final long newValue = SQLiteGlobal.getWALAutoCheckpoint(); 253 long value = executeForLong("PRAGMA wal_autocheckpoint", null, null); 254 if (value != newValue) { 255 executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null); 256 } 257 } 258 } 259 260 private void setJournalSizeLimit() { 261 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { 262 final long newValue = SQLiteGlobal.getJournalSizeLimit(); 263 long value = executeForLong("PRAGMA journal_size_limit", null, null); 264 if (value != newValue) { 265 executeForLong("PRAGMA journal_size_limit=" + newValue, null, null); 266 } 267 } 268 } 269 270 private void setWalModeFromConfiguration() { 271 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { 272 if ((mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) { 273 setJournalMode("WAL"); 274 setSyncMode(SQLiteGlobal.getWALSyncMode()); 275 } else { 276 setJournalMode(SQLiteGlobal.getDefaultJournalMode()); 277 setSyncMode(SQLiteGlobal.getDefaultSyncMode()); 278 } 279 } 280 } 281 282 private void setSyncMode(String newValue) { 283 String value = executeForString("PRAGMA synchronous", null, null); 284 if (!canonicalizeSyncMode(value).equalsIgnoreCase( 285 canonicalizeSyncMode(newValue))) { 286 execute("PRAGMA synchronous=" + newValue, null, null); 287 } 288 } 289 290 private static String canonicalizeSyncMode(String value) { 291 if (value.equals("0")) { 292 return "OFF"; 293 } else if (value.equals("1")) { 294 return "NORMAL"; 295 } else if (value.equals("2")) { 296 return "FULL"; 297 } 298 return value; 299 } 300 301 private void setJournalMode(String newValue) { 302 String value = executeForString("PRAGMA journal_mode", null, null); 303 if (!value.equalsIgnoreCase(newValue)) { 304 try { 305 String result = executeForString("PRAGMA journal_mode=" + newValue, null, null); 306 if (result.equalsIgnoreCase(newValue)) { 307 return; 308 } 309 // PRAGMA journal_mode silently fails and returns the original journal 310 // mode in some cases if the journal mode could not be changed. 311 } catch (SQLiteDatabaseLockedException ex) { 312 // This error (SQLITE_BUSY) occurs if one connection has the database 313 // open in WAL mode and another tries to change it to non-WAL. 314 } 315 // Because we always disable WAL mode when a database is first opened 316 // (even if we intend to re-enable it), we can encounter problems if 317 // there is another open connection to the database somewhere. 318 // This can happen for a variety of reasons such as an application opening 319 // the same database in multiple processes at the same time or if there is a 320 // crashing content provider service that the ActivityManager has 321 // removed from its registry but whose process hasn't quite died yet 322 // by the time it is restarted in a new process. 323 // 324 // If we don't change the journal mode, nothing really bad happens. 325 // In the worst case, an application that enables WAL might not actually 326 // get it, although it can still use connection pooling. 327 Log.w(TAG, "Could not change the database journal mode of '" 328 + mConfiguration.label + "' from '" + value + "' to '" + newValue 329 + "' because the database is locked. This usually means that " 330 + "there are other open connections to the database which prevents " 331 + "the database from enabling or disabling write-ahead logging mode. " 332 + "Proceeding without changing the journal mode."); 333 } 334 } 335 336 private void setLocaleFromConfiguration() { 337 if ((mConfiguration.openFlags & SQLiteDatabase.NO_LOCALIZED_COLLATORS) != 0) { 338 return; 339 } 340 341 // Register the localized collators. 342 final String newLocale = mConfiguration.locale.toString(); 343 nativeRegisterLocalizedCollators(mConnectionPtr, newLocale); 344 345 // If the database is read-only, we cannot modify the android metadata table 346 // or existing indexes. 347 if (mIsReadOnlyConnection) { 348 return; 349 } 350 351 try { 352 // Ensure the android metadata table exists. 353 execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null); 354 355 // Check whether the locale was actually changed. 356 final String oldLocale = executeForString("SELECT locale FROM android_metadata " 357 + "UNION SELECT NULL ORDER BY locale DESC LIMIT 1", null, null); 358 if (oldLocale != null && oldLocale.equals(newLocale)) { 359 return; 360 } 361 362 // Go ahead and update the indexes using the new locale. 363 execute("BEGIN", null, null); 364 boolean success = false; 365 try { 366 execute("DELETE FROM android_metadata", null, null); 367 execute("INSERT INTO android_metadata (locale) VALUES(?)", 368 new Object[] { newLocale }, null); 369 execute("REINDEX LOCALIZED", null, null); 370 success = true; 371 } finally { 372 execute(success ? "COMMIT" : "ROLLBACK", null, null); 373 } 374 } catch (RuntimeException ex) { 375 throw new SQLiteException("Failed to change locale for db '" + mConfiguration.label 376 + "' to '" + newLocale + "'.", ex); 377 } 378 } 379 380 // Called by SQLiteConnectionPool only. 381 void reconfigure(SQLiteDatabaseConfiguration configuration) { 382 // Register custom functions. 383 final int functionCount = configuration.customFunctions.size(); 384 for (int i = 0; i < functionCount; i++) { 385 SQLiteCustomFunction function = configuration.customFunctions.get(i); 386 if (!mConfiguration.customFunctions.contains(function)) { 387 nativeRegisterCustomFunction(mConnectionPtr, function); 388 } 389 } 390 391 // Remember what changed. 392 boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags) 393 & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0; 394 boolean localeChanged = !configuration.locale.equals(mConfiguration.locale); 395 396 // Update configuration parameters. 397 mConfiguration.updateParametersFrom(configuration); 398 399 // Update prepared statement cache size. 400 mPreparedStatementCache.resize(configuration.maxSqlCacheSize); 401 402 // Update WAL. 403 if (walModeChanged) { 404 setWalModeFromConfiguration(); 405 } 406 407 // Update locale. 408 if (localeChanged) { 409 setLocaleFromConfiguration(); 410 } 411 } 412 413 // Called by SQLiteConnectionPool only. 414 // When set to true, executing write operations will throw SQLiteException. 415 // Preparing statements that might write is ok, just don't execute them. 416 void setOnlyAllowReadOnlyOperations(boolean readOnly) { 417 mOnlyAllowReadOnlyOperations = readOnly; 418 } 419 420 // Called by SQLiteConnectionPool only. 421 // Returns true if the prepared statement cache contains the specified SQL. 422 boolean isPreparedStatementInCache(String sql) { 423 return mPreparedStatementCache.get(sql) != null; 424 } 425 426 /** 427 * Gets the unique id of this connection. 428 * @return The connection id. 429 */ 430 public int getConnectionId() { 431 return mConnectionId; 432 } 433 434 /** 435 * Returns true if this is the primary database connection. 436 * @return True if this is the primary database connection. 437 */ 438 public boolean isPrimaryConnection() { 439 return mIsPrimaryConnection; 440 } 441 442 /** 443 * Prepares a statement for execution but does not bind its parameters or execute it. 444 * <p> 445 * This method can be used to check for syntax errors during compilation 446 * prior to execution of the statement. If the {@code outStatementInfo} argument 447 * is not null, the provided {@link SQLiteStatementInfo} object is populated 448 * with information about the statement. 449 * </p><p> 450 * A prepared statement makes no reference to the arguments that may eventually 451 * be bound to it, consequently it it possible to cache certain prepared statements 452 * such as SELECT or INSERT/UPDATE statements. If the statement is cacheable, 453 * then it will be stored in the cache for later. 454 * </p><p> 455 * To take advantage of this behavior as an optimization, the connection pool 456 * provides a method to acquire a connection that already has a given SQL statement 457 * in its prepared statement cache so that it is ready for execution. 458 * </p> 459 * 460 * @param sql The SQL statement to prepare. 461 * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate 462 * with information about the statement, or null if none. 463 * 464 * @throws SQLiteException if an error occurs, such as a syntax error. 465 */ 466 public void prepare(String sql, SQLiteStatementInfo outStatementInfo) { 467 if (sql == null) { 468 throw new IllegalArgumentException("sql must not be null."); 469 } 470 471 final int cookie = mRecentOperations.beginOperation("prepare", sql, null); 472 try { 473 final PreparedStatement statement = acquirePreparedStatement(sql); 474 try { 475 if (outStatementInfo != null) { 476 outStatementInfo.numParameters = statement.mNumParameters; 477 outStatementInfo.readOnly = statement.mReadOnly; 478 479 final int columnCount = nativeGetColumnCount( 480 mConnectionPtr, statement.mStatementPtr); 481 if (columnCount == 0) { 482 outStatementInfo.columnNames = EMPTY_STRING_ARRAY; 483 } else { 484 outStatementInfo.columnNames = new String[columnCount]; 485 for (int i = 0; i < columnCount; i++) { 486 outStatementInfo.columnNames[i] = nativeGetColumnName( 487 mConnectionPtr, statement.mStatementPtr, i); 488 } 489 } 490 } 491 } finally { 492 releasePreparedStatement(statement); 493 } 494 } catch (RuntimeException ex) { 495 mRecentOperations.failOperation(cookie, ex); 496 throw ex; 497 } finally { 498 mRecentOperations.endOperation(cookie); 499 } 500 } 501 502 /** 503 * Executes a statement that does not return a result. 504 * 505 * @param sql The SQL statement to execute. 506 * @param bindArgs The arguments to bind, or null if none. 507 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 508 * 509 * @throws SQLiteException if an error occurs, such as a syntax error 510 * or invalid number of bind arguments. 511 * @throws OperationCanceledException if the operation was canceled. 512 */ 513 public void execute(String sql, Object[] bindArgs, 514 CancellationSignal cancellationSignal) { 515 if (sql == null) { 516 throw new IllegalArgumentException("sql must not be null."); 517 } 518 519 final int cookie = mRecentOperations.beginOperation("execute", sql, bindArgs); 520 try { 521 final PreparedStatement statement = acquirePreparedStatement(sql); 522 try { 523 throwIfStatementForbidden(statement); 524 bindArguments(statement, bindArgs); 525 applyBlockGuardPolicy(statement); 526 attachCancellationSignal(cancellationSignal); 527 try { 528 nativeExecute(mConnectionPtr, statement.mStatementPtr); 529 } finally { 530 detachCancellationSignal(cancellationSignal); 531 } 532 } finally { 533 releasePreparedStatement(statement); 534 } 535 } catch (RuntimeException ex) { 536 mRecentOperations.failOperation(cookie, ex); 537 throw ex; 538 } finally { 539 mRecentOperations.endOperation(cookie); 540 } 541 } 542 543 /** 544 * Executes a statement that returns a single <code>long</code> result. 545 * 546 * @param sql The SQL statement to execute. 547 * @param bindArgs The arguments to bind, or null if none. 548 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 549 * @return The value of the first column in the first row of the result set 550 * as a <code>long</code>, or zero if none. 551 * 552 * @throws SQLiteException if an error occurs, such as a syntax error 553 * or invalid number of bind arguments. 554 * @throws OperationCanceledException if the operation was canceled. 555 */ 556 public long executeForLong(String sql, Object[] bindArgs, 557 CancellationSignal cancellationSignal) { 558 if (sql == null) { 559 throw new IllegalArgumentException("sql must not be null."); 560 } 561 562 final int cookie = mRecentOperations.beginOperation("executeForLong", sql, bindArgs); 563 try { 564 final PreparedStatement statement = acquirePreparedStatement(sql); 565 try { 566 throwIfStatementForbidden(statement); 567 bindArguments(statement, bindArgs); 568 applyBlockGuardPolicy(statement); 569 attachCancellationSignal(cancellationSignal); 570 try { 571 return nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr); 572 } finally { 573 detachCancellationSignal(cancellationSignal); 574 } 575 } finally { 576 releasePreparedStatement(statement); 577 } 578 } catch (RuntimeException ex) { 579 mRecentOperations.failOperation(cookie, ex); 580 throw ex; 581 } finally { 582 mRecentOperations.endOperation(cookie); 583 } 584 } 585 586 /** 587 * Executes a statement that returns a single {@link String} result. 588 * 589 * @param sql The SQL statement to execute. 590 * @param bindArgs The arguments to bind, or null if none. 591 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 592 * @return The value of the first column in the first row of the result set 593 * as a <code>String</code>, or null if none. 594 * 595 * @throws SQLiteException if an error occurs, such as a syntax error 596 * or invalid number of bind arguments. 597 * @throws OperationCanceledException if the operation was canceled. 598 */ 599 public String executeForString(String sql, Object[] bindArgs, 600 CancellationSignal cancellationSignal) { 601 if (sql == null) { 602 throw new IllegalArgumentException("sql must not be null."); 603 } 604 605 final int cookie = mRecentOperations.beginOperation("executeForString", sql, bindArgs); 606 try { 607 final PreparedStatement statement = acquirePreparedStatement(sql); 608 try { 609 throwIfStatementForbidden(statement); 610 bindArguments(statement, bindArgs); 611 applyBlockGuardPolicy(statement); 612 attachCancellationSignal(cancellationSignal); 613 try { 614 return nativeExecuteForString(mConnectionPtr, statement.mStatementPtr); 615 } finally { 616 detachCancellationSignal(cancellationSignal); 617 } 618 } finally { 619 releasePreparedStatement(statement); 620 } 621 } catch (RuntimeException ex) { 622 mRecentOperations.failOperation(cookie, ex); 623 throw ex; 624 } finally { 625 mRecentOperations.endOperation(cookie); 626 } 627 } 628 629 /** 630 * Executes a statement that returns a single BLOB result as a 631 * file descriptor to a shared memory region. 632 * 633 * @param sql The SQL statement to execute. 634 * @param bindArgs The arguments to bind, or null if none. 635 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 636 * @return The file descriptor for a shared memory region that contains 637 * the value of the first column in the first row of the result set as a BLOB, 638 * or null if none. 639 * 640 * @throws SQLiteException if an error occurs, such as a syntax error 641 * or invalid number of bind arguments. 642 * @throws OperationCanceledException if the operation was canceled. 643 */ 644 public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs, 645 CancellationSignal cancellationSignal) { 646 if (sql == null) { 647 throw new IllegalArgumentException("sql must not be null."); 648 } 649 650 final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor", 651 sql, bindArgs); 652 try { 653 final PreparedStatement statement = acquirePreparedStatement(sql); 654 try { 655 throwIfStatementForbidden(statement); 656 bindArguments(statement, bindArgs); 657 applyBlockGuardPolicy(statement); 658 attachCancellationSignal(cancellationSignal); 659 try { 660 int fd = nativeExecuteForBlobFileDescriptor( 661 mConnectionPtr, statement.mStatementPtr); 662 return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null; 663 } finally { 664 detachCancellationSignal(cancellationSignal); 665 } 666 } finally { 667 releasePreparedStatement(statement); 668 } 669 } catch (RuntimeException ex) { 670 mRecentOperations.failOperation(cookie, ex); 671 throw ex; 672 } finally { 673 mRecentOperations.endOperation(cookie); 674 } 675 } 676 677 /** 678 * Executes a statement that returns a count of the number of rows 679 * that were changed. Use for UPDATE or DELETE SQL statements. 680 * 681 * @param sql The SQL statement to execute. 682 * @param bindArgs The arguments to bind, or null if none. 683 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 684 * @return The number of rows that were changed. 685 * 686 * @throws SQLiteException if an error occurs, such as a syntax error 687 * or invalid number of bind arguments. 688 * @throws OperationCanceledException if the operation was canceled. 689 */ 690 public int executeForChangedRowCount(String sql, Object[] bindArgs, 691 CancellationSignal cancellationSignal) { 692 if (sql == null) { 693 throw new IllegalArgumentException("sql must not be null."); 694 } 695 696 final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount", 697 sql, bindArgs); 698 try { 699 final PreparedStatement statement = acquirePreparedStatement(sql); 700 try { 701 throwIfStatementForbidden(statement); 702 bindArguments(statement, bindArgs); 703 applyBlockGuardPolicy(statement); 704 attachCancellationSignal(cancellationSignal); 705 try { 706 return nativeExecuteForChangedRowCount( 707 mConnectionPtr, statement.mStatementPtr); 708 } finally { 709 detachCancellationSignal(cancellationSignal); 710 } 711 } finally { 712 releasePreparedStatement(statement); 713 } 714 } catch (RuntimeException ex) { 715 mRecentOperations.failOperation(cookie, ex); 716 throw ex; 717 } finally { 718 mRecentOperations.endOperation(cookie); 719 } 720 } 721 722 /** 723 * Executes a statement that returns the row id of the last row inserted 724 * by the statement. Use for INSERT SQL statements. 725 * 726 * @param sql The SQL statement to execute. 727 * @param bindArgs The arguments to bind, or null if none. 728 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 729 * @return The row id of the last row that was inserted, or 0 if none. 730 * 731 * @throws SQLiteException if an error occurs, such as a syntax error 732 * or invalid number of bind arguments. 733 * @throws OperationCanceledException if the operation was canceled. 734 */ 735 public long executeForLastInsertedRowId(String sql, Object[] bindArgs, 736 CancellationSignal cancellationSignal) { 737 if (sql == null) { 738 throw new IllegalArgumentException("sql must not be null."); 739 } 740 741 final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId", 742 sql, bindArgs); 743 try { 744 final PreparedStatement statement = acquirePreparedStatement(sql); 745 try { 746 throwIfStatementForbidden(statement); 747 bindArguments(statement, bindArgs); 748 applyBlockGuardPolicy(statement); 749 attachCancellationSignal(cancellationSignal); 750 try { 751 return nativeExecuteForLastInsertedRowId( 752 mConnectionPtr, statement.mStatementPtr); 753 } finally { 754 detachCancellationSignal(cancellationSignal); 755 } 756 } finally { 757 releasePreparedStatement(statement); 758 } 759 } catch (RuntimeException ex) { 760 mRecentOperations.failOperation(cookie, ex); 761 throw ex; 762 } finally { 763 mRecentOperations.endOperation(cookie); 764 } 765 } 766 767 /** 768 * Executes a statement and populates the specified {@link CursorWindow} 769 * with a range of results. Returns the number of rows that were counted 770 * during query execution. 771 * 772 * @param sql The SQL statement to execute. 773 * @param bindArgs The arguments to bind, or null if none. 774 * @param window The cursor window to clear and fill. 775 * @param startPos The start position for filling the window. 776 * @param requiredPos The position of a row that MUST be in the window. 777 * If it won't fit, then the query should discard part of what it filled 778 * so that it does. Must be greater than or equal to <code>startPos</code>. 779 * @param countAllRows True to count all rows that the query would return 780 * regagless of whether they fit in the window. 781 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 782 * @return The number of rows that were counted during query execution. Might 783 * not be all rows in the result set unless <code>countAllRows</code> is true. 784 * 785 * @throws SQLiteException if an error occurs, such as a syntax error 786 * or invalid number of bind arguments. 787 * @throws OperationCanceledException if the operation was canceled. 788 */ 789 public int executeForCursorWindow(String sql, Object[] bindArgs, 790 CursorWindow window, int startPos, int requiredPos, boolean countAllRows, 791 CancellationSignal cancellationSignal) { 792 if (sql == null) { 793 throw new IllegalArgumentException("sql must not be null."); 794 } 795 if (window == null) { 796 throw new IllegalArgumentException("window must not be null."); 797 } 798 799 window.acquireReference(); 800 try { 801 int actualPos = -1; 802 int countedRows = -1; 803 int filledRows = -1; 804 final int cookie = mRecentOperations.beginOperation("executeForCursorWindow", 805 sql, bindArgs); 806 try { 807 final PreparedStatement statement = acquirePreparedStatement(sql); 808 try { 809 throwIfStatementForbidden(statement); 810 bindArguments(statement, bindArgs); 811 applyBlockGuardPolicy(statement); 812 attachCancellationSignal(cancellationSignal); 813 try { 814 final long result = nativeExecuteForCursorWindow( 815 mConnectionPtr, statement.mStatementPtr, window.mWindowPtr, 816 startPos, requiredPos, countAllRows); 817 actualPos = (int)(result >> 32); 818 countedRows = (int)result; 819 filledRows = window.getNumRows(); 820 window.setStartPosition(actualPos); 821 return countedRows; 822 } finally { 823 detachCancellationSignal(cancellationSignal); 824 } 825 } finally { 826 releasePreparedStatement(statement); 827 } 828 } catch (RuntimeException ex) { 829 mRecentOperations.failOperation(cookie, ex); 830 throw ex; 831 } finally { 832 if (mRecentOperations.endOperationDeferLog(cookie)) { 833 mRecentOperations.logOperation(cookie, "window='" + window 834 + "', startPos=" + startPos 835 + ", actualPos=" + actualPos 836 + ", filledRows=" + filledRows 837 + ", countedRows=" + countedRows); 838 } 839 } 840 } finally { 841 window.releaseReference(); 842 } 843 } 844 845 private PreparedStatement acquirePreparedStatement(String sql) { 846 PreparedStatement statement = mPreparedStatementCache.get(sql); 847 boolean skipCache = false; 848 if (statement != null) { 849 if (!statement.mInUse) { 850 return statement; 851 } 852 // The statement is already in the cache but is in use (this statement appears 853 // to be not only re-entrant but recursive!). So prepare a new copy of the 854 // statement but do not cache it. 855 skipCache = true; 856 } 857 858 final int statementPtr = nativePrepareStatement(mConnectionPtr, sql); 859 try { 860 final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr); 861 final int type = DatabaseUtils.getSqlStatementType(sql); 862 final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr); 863 statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly); 864 if (!skipCache && isCacheable(type)) { 865 mPreparedStatementCache.put(sql, statement); 866 statement.mInCache = true; 867 } 868 } catch (RuntimeException ex) { 869 // Finalize the statement if an exception occurred and we did not add 870 // it to the cache. If it is already in the cache, then leave it there. 871 if (statement == null || !statement.mInCache) { 872 nativeFinalizeStatement(mConnectionPtr, statementPtr); 873 } 874 throw ex; 875 } 876 statement.mInUse = true; 877 return statement; 878 } 879 880 private void releasePreparedStatement(PreparedStatement statement) { 881 statement.mInUse = false; 882 if (statement.mInCache) { 883 try { 884 nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr); 885 } catch (SQLiteException ex) { 886 // The statement could not be reset due to an error. Remove it from the cache. 887 // When remove() is called, the cache will invoke its entryRemoved() callback, 888 // which will in turn call finalizePreparedStatement() to finalize and 889 // recycle the statement. 890 if (DEBUG) { 891 Log.d(TAG, "Could not reset prepared statement due to an exception. " 892 + "Removing it from the cache. SQL: " 893 + trimSqlForDisplay(statement.mSql), ex); 894 } 895 896 mPreparedStatementCache.remove(statement.mSql); 897 } 898 } else { 899 finalizePreparedStatement(statement); 900 } 901 } 902 903 private void finalizePreparedStatement(PreparedStatement statement) { 904 nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr); 905 recyclePreparedStatement(statement); 906 } 907 908 private void attachCancellationSignal(CancellationSignal cancellationSignal) { 909 if (cancellationSignal != null) { 910 cancellationSignal.throwIfCanceled(); 911 912 mCancellationSignalAttachCount += 1; 913 if (mCancellationSignalAttachCount == 1) { 914 // Reset cancellation flag before executing the statement. 915 nativeResetCancel(mConnectionPtr, true /*cancelable*/); 916 917 // After this point, onCancel() may be called concurrently. 918 cancellationSignal.setOnCancelListener(this); 919 } 920 } 921 } 922 923 private void detachCancellationSignal(CancellationSignal cancellationSignal) { 924 if (cancellationSignal != null) { 925 assert mCancellationSignalAttachCount > 0; 926 927 mCancellationSignalAttachCount -= 1; 928 if (mCancellationSignalAttachCount == 0) { 929 // After this point, onCancel() cannot be called concurrently. 930 cancellationSignal.setOnCancelListener(null); 931 932 // Reset cancellation flag after executing the statement. 933 nativeResetCancel(mConnectionPtr, false /*cancelable*/); 934 } 935 } 936 } 937 938 // CancellationSignal.OnCancelListener callback. 939 // This method may be called on a different thread than the executing statement. 940 // However, it will only be called between calls to attachCancellationSignal and 941 // detachCancellationSignal, while a statement is executing. We can safely assume 942 // that the SQLite connection is still alive. 943 @Override 944 public void onCancel() { 945 nativeCancel(mConnectionPtr); 946 } 947 948 private void bindArguments(PreparedStatement statement, Object[] bindArgs) { 949 final int count = bindArgs != null ? bindArgs.length : 0; 950 if (count != statement.mNumParameters) { 951 throw new SQLiteBindOrColumnIndexOutOfRangeException( 952 "Expected " + statement.mNumParameters + " bind arguments but " 953 + bindArgs.length + " were provided."); 954 } 955 if (count == 0) { 956 return; 957 } 958 959 final int statementPtr = statement.mStatementPtr; 960 for (int i = 0; i < count; i++) { 961 final Object arg = bindArgs[i]; 962 switch (DatabaseUtils.getTypeOfObject(arg)) { 963 case Cursor.FIELD_TYPE_NULL: 964 nativeBindNull(mConnectionPtr, statementPtr, i + 1); 965 break; 966 case Cursor.FIELD_TYPE_INTEGER: 967 nativeBindLong(mConnectionPtr, statementPtr, i + 1, 968 ((Number)arg).longValue()); 969 break; 970 case Cursor.FIELD_TYPE_FLOAT: 971 nativeBindDouble(mConnectionPtr, statementPtr, i + 1, 972 ((Number)arg).doubleValue()); 973 break; 974 case Cursor.FIELD_TYPE_BLOB: 975 nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg); 976 break; 977 case Cursor.FIELD_TYPE_STRING: 978 default: 979 if (arg instanceof Boolean) { 980 // Provide compatibility with legacy applications which may pass 981 // Boolean values in bind args. 982 nativeBindLong(mConnectionPtr, statementPtr, i + 1, 983 ((Boolean)arg).booleanValue() ? 1 : 0); 984 } else { 985 nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString()); 986 } 987 break; 988 } 989 } 990 } 991 992 private void throwIfStatementForbidden(PreparedStatement statement) { 993 if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) { 994 throw new SQLiteException("Cannot execute this statement because it " 995 + "might modify the database but the connection is read-only."); 996 } 997 } 998 999 private static boolean isCacheable(int statementType) { 1000 if (statementType == DatabaseUtils.STATEMENT_UPDATE 1001 || statementType == DatabaseUtils.STATEMENT_SELECT) { 1002 return true; 1003 } 1004 return false; 1005 } 1006 1007 private void applyBlockGuardPolicy(PreparedStatement statement) { 1008 if (!mConfiguration.isInMemoryDb()) { 1009 if (statement.mReadOnly) { 1010 BlockGuard.getThreadPolicy().onReadFromDisk(); 1011 } else { 1012 BlockGuard.getThreadPolicy().onWriteToDisk(); 1013 } 1014 } 1015 } 1016 1017 /** 1018 * Dumps debugging information about this connection. 1019 * 1020 * @param printer The printer to receive the dump, not null. 1021 * @param verbose True to dump more verbose information. 1022 */ 1023 public void dump(Printer printer, boolean verbose) { 1024 dumpUnsafe(printer, verbose); 1025 } 1026 1027 /** 1028 * Dumps debugging information about this connection, in the case where the 1029 * caller might not actually own the connection. 1030 * 1031 * This function is written so that it may be called by a thread that does not 1032 * own the connection. We need to be very careful because the connection state is 1033 * not synchronized. 1034 * 1035 * At worst, the method may return stale or slightly wrong data, however 1036 * it should not crash. This is ok as it is only used for diagnostic purposes. 1037 * 1038 * @param printer The printer to receive the dump, not null. 1039 * @param verbose True to dump more verbose information. 1040 */ 1041 void dumpUnsafe(Printer printer, boolean verbose) { 1042 printer.println("Connection #" + mConnectionId + ":"); 1043 if (verbose) { 1044 printer.println(" connectionPtr: 0x" + Integer.toHexString(mConnectionPtr)); 1045 } 1046 printer.println(" isPrimaryConnection: " + mIsPrimaryConnection); 1047 printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations); 1048 1049 mRecentOperations.dump(printer); 1050 1051 if (verbose) { 1052 mPreparedStatementCache.dump(printer); 1053 } 1054 } 1055 1056 /** 1057 * Describes the currently executing operation, in the case where the 1058 * caller might not actually own the connection. 1059 * 1060 * This function is written so that it may be called by a thread that does not 1061 * own the connection. We need to be very careful because the connection state is 1062 * not synchronized. 1063 * 1064 * At worst, the method may return stale or slightly wrong data, however 1065 * it should not crash. This is ok as it is only used for diagnostic purposes. 1066 * 1067 * @return A description of the current operation including how long it has been running, 1068 * or null if none. 1069 */ 1070 String describeCurrentOperationUnsafe() { 1071 return mRecentOperations.describeCurrentOperation(); 1072 } 1073 1074 /** 1075 * Collects statistics about database connection memory usage. 1076 * 1077 * @param dbStatsList The list to populate. 1078 */ 1079 void collectDbStats(ArrayList<DbStats> dbStatsList) { 1080 // Get information about the main database. 1081 int lookaside = nativeGetDbLookaside(mConnectionPtr); 1082 long pageCount = 0; 1083 long pageSize = 0; 1084 try { 1085 pageCount = executeForLong("PRAGMA page_count;", null, null); 1086 pageSize = executeForLong("PRAGMA page_size;", null, null); 1087 } catch (SQLiteException ex) { 1088 // Ignore. 1089 } 1090 dbStatsList.add(getMainDbStatsUnsafe(lookaside, pageCount, pageSize)); 1091 1092 // Get information about attached databases. 1093 // We ignore the first row in the database list because it corresponds to 1094 // the main database which we have already described. 1095 CursorWindow window = new CursorWindow("collectDbStats"); 1096 try { 1097 executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null); 1098 for (int i = 1; i < window.getNumRows(); i++) { 1099 String name = window.getString(i, 1); 1100 String path = window.getString(i, 2); 1101 pageCount = 0; 1102 pageSize = 0; 1103 try { 1104 pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null); 1105 pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null); 1106 } catch (SQLiteException ex) { 1107 // Ignore. 1108 } 1109 String label = " (attached) " + name; 1110 if (!path.isEmpty()) { 1111 label += ": " + path; 1112 } 1113 dbStatsList.add(new DbStats(label, pageCount, pageSize, 0, 0, 0, 0)); 1114 } 1115 } catch (SQLiteException ex) { 1116 // Ignore. 1117 } finally { 1118 window.close(); 1119 } 1120 } 1121 1122 /** 1123 * Collects statistics about database connection memory usage, in the case where the 1124 * caller might not actually own the connection. 1125 * 1126 * @return The statistics object, never null. 1127 */ 1128 void collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList) { 1129 dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0)); 1130 } 1131 1132 private DbStats getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize) { 1133 // The prepared statement cache is thread-safe so we can access its statistics 1134 // even if we do not own the database connection. 1135 String label = mConfiguration.path; 1136 if (!mIsPrimaryConnection) { 1137 label += " (" + mConnectionId + ")"; 1138 } 1139 return new DbStats(label, pageCount, pageSize, lookaside, 1140 mPreparedStatementCache.hitCount(), 1141 mPreparedStatementCache.missCount(), 1142 mPreparedStatementCache.size()); 1143 } 1144 1145 @Override 1146 public String toString() { 1147 return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")"; 1148 } 1149 1150 private PreparedStatement obtainPreparedStatement(String sql, int statementPtr, 1151 int numParameters, int type, boolean readOnly) { 1152 PreparedStatement statement = mPreparedStatementPool; 1153 if (statement != null) { 1154 mPreparedStatementPool = statement.mPoolNext; 1155 statement.mPoolNext = null; 1156 statement.mInCache = false; 1157 } else { 1158 statement = new PreparedStatement(); 1159 } 1160 statement.mSql = sql; 1161 statement.mStatementPtr = statementPtr; 1162 statement.mNumParameters = numParameters; 1163 statement.mType = type; 1164 statement.mReadOnly = readOnly; 1165 return statement; 1166 } 1167 1168 private void recyclePreparedStatement(PreparedStatement statement) { 1169 statement.mSql = null; 1170 statement.mPoolNext = mPreparedStatementPool; 1171 mPreparedStatementPool = statement; 1172 } 1173 1174 private static String trimSqlForDisplay(String sql) { 1175 return TRIM_SQL_PATTERN.matcher(sql).replaceAll(" "); 1176 } 1177 1178 /** 1179 * Holder type for a prepared statement. 1180 * 1181 * Although this object holds a pointer to a native statement object, it 1182 * does not have a finalizer. This is deliberate. The {@link SQLiteConnection} 1183 * owns the statement object and will take care of freeing it when needed. 1184 * In particular, closing the connection requires a guarantee of deterministic 1185 * resource disposal because all native statement objects must be freed before 1186 * the native database object can be closed. So no finalizers here. 1187 */ 1188 private static final class PreparedStatement { 1189 // Next item in pool. 1190 public PreparedStatement mPoolNext; 1191 1192 // The SQL from which the statement was prepared. 1193 public String mSql; 1194 1195 // The native sqlite3_stmt object pointer. 1196 // Lifetime is managed explicitly by the connection. 1197 public int mStatementPtr; 1198 1199 // The number of parameters that the prepared statement has. 1200 public int mNumParameters; 1201 1202 // The statement type. 1203 public int mType; 1204 1205 // True if the statement is read-only. 1206 public boolean mReadOnly; 1207 1208 // True if the statement is in the cache. 1209 public boolean mInCache; 1210 1211 // True if the statement is in use (currently executing). 1212 // We need this flag because due to the use of custom functions in triggers, it's 1213 // possible for SQLite calls to be re-entrant. Consequently we need to prevent 1214 // in use statements from being finalized until they are no longer in use. 1215 public boolean mInUse; 1216 } 1217 1218 private final class PreparedStatementCache 1219 extends LruCache<String, PreparedStatement> { 1220 public PreparedStatementCache(int size) { 1221 super(size); 1222 } 1223 1224 @Override 1225 protected void entryRemoved(boolean evicted, String key, 1226 PreparedStatement oldValue, PreparedStatement newValue) { 1227 oldValue.mInCache = false; 1228 if (!oldValue.mInUse) { 1229 finalizePreparedStatement(oldValue); 1230 } 1231 } 1232 1233 public void dump(Printer printer) { 1234 printer.println(" Prepared statement cache:"); 1235 Map<String, PreparedStatement> cache = snapshot(); 1236 if (!cache.isEmpty()) { 1237 int i = 0; 1238 for (Map.Entry<String, PreparedStatement> entry : cache.entrySet()) { 1239 PreparedStatement statement = entry.getValue(); 1240 if (statement.mInCache) { // might be false due to a race with entryRemoved 1241 String sql = entry.getKey(); 1242 printer.println(" " + i + ": statementPtr=0x" 1243 + Integer.toHexString(statement.mStatementPtr) 1244 + ", numParameters=" + statement.mNumParameters 1245 + ", type=" + statement.mType 1246 + ", readOnly=" + statement.mReadOnly 1247 + ", sql=\"" + trimSqlForDisplay(sql) + "\""); 1248 } 1249 i += 1; 1250 } 1251 } else { 1252 printer.println(" <none>"); 1253 } 1254 } 1255 } 1256 1257 private static final class OperationLog { 1258 private static final int MAX_RECENT_OPERATIONS = 20; 1259 private static final int COOKIE_GENERATION_SHIFT = 8; 1260 private static final int COOKIE_INDEX_MASK = 0xff; 1261 1262 private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS]; 1263 private int mIndex; 1264 private int mGeneration; 1265 1266 public int beginOperation(String kind, String sql, Object[] bindArgs) { 1267 synchronized (mOperations) { 1268 final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS; 1269 Operation operation = mOperations[index]; 1270 if (operation == null) { 1271 operation = new Operation(); 1272 mOperations[index] = operation; 1273 } else { 1274 operation.mFinished = false; 1275 operation.mException = null; 1276 if (operation.mBindArgs != null) { 1277 operation.mBindArgs.clear(); 1278 } 1279 } 1280 operation.mStartTime = System.currentTimeMillis(); 1281 operation.mKind = kind; 1282 operation.mSql = sql; 1283 if (bindArgs != null) { 1284 if (operation.mBindArgs == null) { 1285 operation.mBindArgs = new ArrayList<Object>(); 1286 } else { 1287 operation.mBindArgs.clear(); 1288 } 1289 for (int i = 0; i < bindArgs.length; i++) { 1290 final Object arg = bindArgs[i]; 1291 if (arg != null && arg instanceof byte[]) { 1292 // Don't hold onto the real byte array longer than necessary. 1293 operation.mBindArgs.add(EMPTY_BYTE_ARRAY); 1294 } else { 1295 operation.mBindArgs.add(arg); 1296 } 1297 } 1298 } 1299 operation.mCookie = newOperationCookieLocked(index); 1300 mIndex = index; 1301 return operation.mCookie; 1302 } 1303 } 1304 1305 public void failOperation(int cookie, Exception ex) { 1306 synchronized (mOperations) { 1307 final Operation operation = getOperationLocked(cookie); 1308 if (operation != null) { 1309 operation.mException = ex; 1310 } 1311 } 1312 } 1313 1314 public void endOperation(int cookie) { 1315 synchronized (mOperations) { 1316 if (endOperationDeferLogLocked(cookie)) { 1317 logOperationLocked(cookie, null); 1318 } 1319 } 1320 } 1321 1322 public boolean endOperationDeferLog(int cookie) { 1323 synchronized (mOperations) { 1324 return endOperationDeferLogLocked(cookie); 1325 } 1326 } 1327 1328 public void logOperation(int cookie, String detail) { 1329 synchronized (mOperations) { 1330 logOperationLocked(cookie, detail); 1331 } 1332 } 1333 1334 private boolean endOperationDeferLogLocked(int cookie) { 1335 final Operation operation = getOperationLocked(cookie); 1336 if (operation != null) { 1337 operation.mEndTime = System.currentTimeMillis(); 1338 operation.mFinished = true; 1339 return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery( 1340 operation.mEndTime - operation.mStartTime); 1341 } 1342 return false; 1343 } 1344 1345 private void logOperationLocked(int cookie, String detail) { 1346 final Operation operation = getOperationLocked(cookie); 1347 StringBuilder msg = new StringBuilder(); 1348 operation.describe(msg); 1349 if (detail != null) { 1350 msg.append(", ").append(detail); 1351 } 1352 Log.d(TAG, msg.toString()); 1353 } 1354 1355 private int newOperationCookieLocked(int index) { 1356 final int generation = mGeneration++; 1357 return generation << COOKIE_GENERATION_SHIFT | index; 1358 } 1359 1360 private Operation getOperationLocked(int cookie) { 1361 final int index = cookie & COOKIE_INDEX_MASK; 1362 final Operation operation = mOperations[index]; 1363 return operation.mCookie == cookie ? operation : null; 1364 } 1365 1366 public String describeCurrentOperation() { 1367 synchronized (mOperations) { 1368 final Operation operation = mOperations[mIndex]; 1369 if (operation != null && !operation.mFinished) { 1370 StringBuilder msg = new StringBuilder(); 1371 operation.describe(msg); 1372 return msg.toString(); 1373 } 1374 return null; 1375 } 1376 } 1377 1378 public void dump(Printer printer) { 1379 synchronized (mOperations) { 1380 printer.println(" Most recently executed operations:"); 1381 int index = mIndex; 1382 Operation operation = mOperations[index]; 1383 if (operation != null) { 1384 int n = 0; 1385 do { 1386 StringBuilder msg = new StringBuilder(); 1387 msg.append(" ").append(n).append(": ["); 1388 msg.append(operation.getFormattedStartTime()); 1389 msg.append("] "); 1390 operation.describe(msg); 1391 printer.println(msg.toString()); 1392 1393 if (index > 0) { 1394 index -= 1; 1395 } else { 1396 index = MAX_RECENT_OPERATIONS - 1; 1397 } 1398 n += 1; 1399 operation = mOperations[index]; 1400 } while (operation != null && n < MAX_RECENT_OPERATIONS); 1401 } else { 1402 printer.println(" <none>"); 1403 } 1404 } 1405 } 1406 } 1407 1408 private static final class Operation { 1409 private static final SimpleDateFormat sDateFormat = 1410 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 1411 1412 public long mStartTime; 1413 public long mEndTime; 1414 public String mKind; 1415 public String mSql; 1416 public ArrayList<Object> mBindArgs; 1417 public boolean mFinished; 1418 public Exception mException; 1419 public int mCookie; 1420 1421 public void describe(StringBuilder msg) { 1422 msg.append(mKind); 1423 if (mFinished) { 1424 msg.append(" took ").append(mEndTime - mStartTime).append("ms"); 1425 } else { 1426 msg.append(" started ").append(System.currentTimeMillis() - mStartTime) 1427 .append("ms ago"); 1428 } 1429 msg.append(" - ").append(getStatus()); 1430 if (mSql != null) { 1431 msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\""); 1432 } 1433 if (mBindArgs != null && mBindArgs.size() != 0) { 1434 msg.append(", bindArgs=["); 1435 final int count = mBindArgs.size(); 1436 for (int i = 0; i < count; i++) { 1437 final Object arg = mBindArgs.get(i); 1438 if (i != 0) { 1439 msg.append(", "); 1440 } 1441 if (arg == null) { 1442 msg.append("null"); 1443 } else if (arg instanceof byte[]) { 1444 msg.append("<byte[]>"); 1445 } else if (arg instanceof String) { 1446 msg.append("\"").append((String)arg).append("\""); 1447 } else { 1448 msg.append(arg); 1449 } 1450 } 1451 msg.append("]"); 1452 } 1453 if (mException != null) { 1454 msg.append(", exception=\"").append(mException.getMessage()).append("\""); 1455 } 1456 } 1457 1458 private String getStatus() { 1459 if (!mFinished) { 1460 return "running"; 1461 } 1462 return mException != null ? "failed" : "succeeded"; 1463 } 1464 1465 private String getFormattedStartTime() { 1466 return sDateFormat.format(new Date(mStartTime)); 1467 } 1468 } 1469} 1470