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