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