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.annotation.NonNull; 20 import com.android.internal.annotations.GuardedBy; 21 22 import android.database.Cursor; 23 import android.database.CursorWindow; 24 import android.database.DatabaseUtils; 25 import android.database.sqlite.SQLiteDebug.DbStats; 26 import android.database.sqlite.SQLiteDebug.NoPreloadHolder; 27 import android.os.CancellationSignal; 28 import android.os.OperationCanceledException; 29 import android.os.ParcelFileDescriptor; 30 import android.os.SystemClock; 31 import android.os.Trace; 32 import android.text.TextUtils; 33 import android.util.Log; 34 import android.util.LruCache; 35 import android.util.Pair; 36 import android.util.Printer; 37 import com.android.internal.util.RingBuffer; 38 import dalvik.system.BlockGuard; 39 import dalvik.system.CloseGuard; 40 41 import java.io.File; 42 import java.io.IOException; 43 import java.lang.ref.Reference; 44 import java.nio.file.FileSystems; 45 import java.nio.file.Files; 46 import java.nio.file.Path; 47 import java.time.Instant; 48 import java.time.ZoneId; 49 import java.time.format.DateTimeFormatter; 50 import java.util.ArrayList; 51 import java.util.Date; 52 import java.util.Locale; 53 import java.util.Map; 54 import java.util.function.BinaryOperator; 55 import java.util.function.UnaryOperator; 56 57 /** 58 * Represents a SQLite database connection. 59 * Each connection wraps an instance of a native <code>sqlite3</code> object. 60 * <p> 61 * When database connection pooling is enabled, there can be multiple active 62 * connections to the same database. Otherwise there is typically only one 63 * connection per database. 64 * </p><p> 65 * When the SQLite WAL feature is enabled, multiple readers and one writer 66 * can concurrently access the database. Without WAL, readers and writers 67 * are mutually exclusive. 68 * </p> 69 * 70 * <h2>Ownership and concurrency guarantees</h2> 71 * <p> 72 * Connection objects are not thread-safe. They are acquired as needed to 73 * perform a database operation and are then returned to the pool. At any 74 * given time, a connection is either owned and used by a {@link SQLiteSession} 75 * object or the {@link SQLiteConnectionPool}. Those classes are 76 * responsible for serializing operations to guard against concurrent 77 * use of a connection. 78 * </p><p> 79 * The guarantee of having a single owner allows this class to be implemented 80 * without locks and greatly simplifies resource management. 81 * </p> 82 * 83 * <h2>Encapsulation guarantees</h2> 84 * <p> 85 * The connection object object owns *all* of the SQLite related native 86 * objects that are associated with the connection. What's more, there are 87 * no other objects in the system that are capable of obtaining handles to 88 * those native objects. Consequently, when the connection is closed, we do 89 * not have to worry about what other components might have references to 90 * its associated SQLite state -- there are none. 91 * </p><p> 92 * Encapsulation is what ensures that the connection object's 93 * lifecycle does not become a tortured mess of finalizers and reference 94 * queues. 95 * </p> 96 * 97 * <h2>Reentrance</h2> 98 * <p> 99 * This class must tolerate reentrant execution of SQLite operations because 100 * triggers may call custom SQLite functions that perform additional queries. 101 * </p> 102 * 103 * @hide 104 */ 105 public final class SQLiteConnection implements CancellationSignal.OnCancelListener { 106 private static final String TAG = "SQLiteConnection"; 107 private static final boolean DEBUG = false; 108 109 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 110 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 111 112 private final CloseGuard mCloseGuard = CloseGuard.get(); 113 114 private final SQLiteConnectionPool mPool; 115 private final SQLiteDatabaseConfiguration mConfiguration; 116 private final int mConnectionId; 117 private final boolean mIsPrimaryConnection; 118 private final boolean mIsReadOnlyConnection; 119 private PreparedStatement mPreparedStatementPool; 120 121 private final PreparedStatementCache mPreparedStatementCache; 122 123 // The recent operations log. 124 private final OperationLog mRecentOperations; 125 126 // The native SQLiteConnection pointer. (FOR INTERNAL USE ONLY) 127 private long mConnectionPtr; 128 129 // Restrict this connection to read-only operations. 130 private boolean mOnlyAllowReadOnlyOperations; 131 132 // The number of times attachCancellationSignal has been called. 133 // Because SQLite statement execution can be reentrant, we keep track of how many 134 // times we have attempted to attach a cancellation signal to the connection so that 135 // we can ensure that we detach the signal at the right time. 136 private int mCancellationSignalAttachCount; 137 nativeOpen(String path, int openFlags, String label, boolean enableTrace, boolean enableProfile, int lookasideSlotSize, int lookasideSlotCount)138 private static native long nativeOpen(String path, int openFlags, String label, 139 boolean enableTrace, boolean enableProfile, int lookasideSlotSize, 140 int lookasideSlotCount); nativeClose(long connectionPtr, boolean fast)141 private static native void nativeClose(long connectionPtr, boolean fast); nativeRegisterCustomScalarFunction(long connectionPtr, String name, UnaryOperator<String> function)142 private static native void nativeRegisterCustomScalarFunction(long connectionPtr, 143 String name, UnaryOperator<String> function); nativeRegisterCustomAggregateFunction(long connectionPtr, String name, BinaryOperator<String> function)144 private static native void nativeRegisterCustomAggregateFunction(long connectionPtr, 145 String name, BinaryOperator<String> function); nativeRegisterLocalizedCollators(long connectionPtr, String locale)146 private static native void nativeRegisterLocalizedCollators(long connectionPtr, String locale); nativePrepareStatement(long connectionPtr, String sql)147 private static native long nativePrepareStatement(long connectionPtr, String sql); nativeFinalizeStatement(long connectionPtr, long statementPtr)148 private static native void nativeFinalizeStatement(long connectionPtr, long statementPtr); nativeGetParameterCount(long connectionPtr, long statementPtr)149 private static native int nativeGetParameterCount(long connectionPtr, long statementPtr); nativeIsReadOnly(long connectionPtr, long statementPtr)150 private static native boolean nativeIsReadOnly(long connectionPtr, long statementPtr); nativeUpdatesTempOnly(long connectionPtr, long statementPtr)151 private static native boolean nativeUpdatesTempOnly(long connectionPtr, long statementPtr); nativeGetColumnCount(long connectionPtr, long statementPtr)152 private static native int nativeGetColumnCount(long connectionPtr, long statementPtr); nativeGetColumnName(long connectionPtr, long statementPtr, int index)153 private static native String nativeGetColumnName(long connectionPtr, long statementPtr, 154 int index); nativeBindNull(long connectionPtr, long statementPtr, int index)155 private static native void nativeBindNull(long connectionPtr, long statementPtr, 156 int index); nativeBindLong(long connectionPtr, long statementPtr, int index, long value)157 private static native void nativeBindLong(long connectionPtr, long statementPtr, 158 int index, long value); nativeBindDouble(long connectionPtr, long statementPtr, int index, double value)159 private static native void nativeBindDouble(long connectionPtr, long statementPtr, 160 int index, double value); nativeBindString(long connectionPtr, long statementPtr, int index, String value)161 private static native void nativeBindString(long connectionPtr, long statementPtr, 162 int index, String value); nativeBindBlob(long connectionPtr, long statementPtr, int index, byte[] value)163 private static native void nativeBindBlob(long connectionPtr, long statementPtr, 164 int index, byte[] value); nativeResetStatementAndClearBindings( long connectionPtr, long statementPtr)165 private static native void nativeResetStatementAndClearBindings( 166 long connectionPtr, long statementPtr); nativeExecute(long connectionPtr, long statementPtr, boolean isPragmaStmt)167 private static native void nativeExecute(long connectionPtr, long statementPtr, 168 boolean isPragmaStmt); nativeExecuteForLong(long connectionPtr, long statementPtr)169 private static native long nativeExecuteForLong(long connectionPtr, long statementPtr); nativeExecuteForString(long connectionPtr, long statementPtr)170 private static native String nativeExecuteForString(long connectionPtr, long statementPtr); nativeExecuteForBlobFileDescriptor( long connectionPtr, long statementPtr)171 private static native int nativeExecuteForBlobFileDescriptor( 172 long connectionPtr, long statementPtr); nativeExecuteForChangedRowCount(long connectionPtr, long statementPtr)173 private static native int nativeExecuteForChangedRowCount(long connectionPtr, long statementPtr); nativeExecuteForLastInsertedRowId( long connectionPtr, long statementPtr)174 private static native long nativeExecuteForLastInsertedRowId( 175 long connectionPtr, long statementPtr); nativeExecuteForCursorWindow( long connectionPtr, long statementPtr, long windowPtr, int startPos, int requiredPos, boolean countAllRows)176 private static native long nativeExecuteForCursorWindow( 177 long connectionPtr, long statementPtr, long windowPtr, 178 int startPos, int requiredPos, boolean countAllRows); nativeGetDbLookaside(long connectionPtr)179 private static native int nativeGetDbLookaside(long connectionPtr); nativeCancel(long connectionPtr)180 private static native void nativeCancel(long connectionPtr); nativeResetCancel(long connectionPtr, boolean cancelable)181 private static native void nativeResetCancel(long connectionPtr, boolean cancelable); nativeLastInsertRowId(long connectionPtr)182 private static native int nativeLastInsertRowId(long connectionPtr); nativeChanges(long connectionPtr)183 private static native long nativeChanges(long connectionPtr); nativeTotalChanges(long connectionPtr)184 private static native long nativeTotalChanges(long connectionPtr); 185 SQLiteConnection(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration, int connectionId, boolean primaryConnection)186 private SQLiteConnection(SQLiteConnectionPool pool, 187 SQLiteDatabaseConfiguration configuration, 188 int connectionId, boolean primaryConnection) { 189 mPool = pool; 190 mRecentOperations = new OperationLog(); 191 mConfiguration = new SQLiteDatabaseConfiguration(configuration); 192 mConnectionId = connectionId; 193 mIsPrimaryConnection = primaryConnection; 194 mIsReadOnlyConnection = mConfiguration.isReadOnlyDatabase(); 195 mPreparedStatementCache = new PreparedStatementCache( 196 mConfiguration.maxSqlCacheSize); 197 mCloseGuard.open("SQLiteConnection.close"); 198 } 199 200 @Override finalize()201 protected void finalize() throws Throwable { 202 try { 203 if (mPool != null && mConnectionPtr != 0) { 204 mPool.onConnectionLeaked(); 205 } 206 207 dispose(true); 208 } finally { 209 super.finalize(); 210 } 211 } 212 213 // Called by SQLiteConnectionPool only. open(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration, int connectionId, boolean primaryConnection)214 static SQLiteConnection open(SQLiteConnectionPool pool, 215 SQLiteDatabaseConfiguration configuration, 216 int connectionId, boolean primaryConnection) { 217 SQLiteConnection connection = new SQLiteConnection(pool, configuration, 218 connectionId, primaryConnection); 219 try { 220 connection.open(); 221 return connection; 222 } catch (SQLiteException ex) { 223 connection.dispose(false); 224 throw ex; 225 } 226 } 227 228 // Called by SQLiteConnectionPool only. 229 // Closes the database closes and releases all of its associated resources. 230 // Do not call methods on the connection after it is closed. It will probably crash. close()231 void close() { 232 dispose(false); 233 } 234 open()235 private void open() { 236 final String file = mConfiguration.path; 237 final int cookie = mRecentOperations.beginOperation("open", null, null); 238 try { 239 mConnectionPtr = nativeOpen(file, mConfiguration.openFlags, 240 mConfiguration.label, 241 NoPreloadHolder.DEBUG_SQL_STATEMENTS, NoPreloadHolder.DEBUG_SQL_TIME, 242 mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount); 243 } catch (SQLiteCantOpenDatabaseException e) { 244 final StringBuilder message = new StringBuilder("Cannot open database ") 245 .append("[").append(e.getMessage()).append("]") 246 .append(" '").append(file).append("'") 247 .append(" with flags 0x") 248 .append(Integer.toHexString(mConfiguration.openFlags)); 249 250 try { 251 // Try to diagnose for common reasons. If something fails in here, that's fine; 252 // just swallow the exception. 253 254 final Path path = FileSystems.getDefault().getPath(file); 255 final Path dir = path.getParent(); 256 if (dir == null) { 257 message.append(": Directory not specified in the file path"); 258 } else if (!Files.isDirectory(dir)) { 259 message.append(": Directory ").append(dir).append(" doesn't exist"); 260 } else if (!Files.exists(path)) { 261 message.append(": File ").append(path).append( 262 " doesn't exist"); 263 if ((mConfiguration.openFlags & SQLiteDatabase.CREATE_IF_NECESSARY) != 0) { 264 message.append( 265 " and CREATE_IF_NECESSARY is set, check directory permissions"); 266 } 267 } else if (!Files.isReadable(path)) { 268 message.append(": File ").append(path).append(" is not readable"); 269 } else if (Files.isDirectory(path)) { 270 message.append(": Path ").append(path).append(" is a directory"); 271 } 272 } catch (Throwable th) { 273 // Ignore any exceptions generated whilst attempting to create extended diagnostic 274 // messages. 275 } 276 throw new SQLiteCantOpenDatabaseException(message.toString(), e); 277 } finally { 278 mRecentOperations.endOperation(cookie); 279 } 280 setPageSize(); 281 setForeignKeyModeFromConfiguration(); 282 setJournalFromConfiguration(); 283 setSyncModeFromConfiguration(); 284 setJournalSizeLimit(); 285 setAutoCheckpointInterval(); 286 setLocaleFromConfiguration(); 287 setCustomFunctionsFromConfiguration(); 288 executePerConnectionSqlFromConfiguration(0); 289 } 290 dispose(boolean finalized)291 private void dispose(boolean finalized) { 292 if (mCloseGuard != null) { 293 if (finalized) { 294 mCloseGuard.warnIfOpen(); 295 } 296 mCloseGuard.close(); 297 } 298 299 if (mConnectionPtr != 0) { 300 final int cookie = mRecentOperations.beginOperation("close", null, null); 301 try { 302 mPreparedStatementCache.evictAll(); 303 nativeClose(mConnectionPtr, finalized && Flags.noCheckpointOnFinalize()); 304 mConnectionPtr = 0; 305 } finally { 306 mRecentOperations.endOperation(cookie); 307 } 308 } 309 } 310 311 /** Record the start of a transaction for logging and debugging. */ recordBeginTransaction(String mode)312 void recordBeginTransaction(String mode) { 313 mRecentOperations.beginTransaction(mode); 314 } 315 316 /** Record the end of a transaction for logging and debugging. */ recordEndTransaction(boolean successful)317 void recordEndTransaction(boolean successful) { 318 mRecentOperations.endTransaction(successful); 319 } 320 setPageSize()321 private void setPageSize() { 322 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { 323 final long newValue = SQLiteGlobal.getDefaultPageSize(); 324 long value = executeForLong("PRAGMA page_size", null, null); 325 if (value != newValue) { 326 execute("PRAGMA page_size=" + newValue, null, null); 327 } 328 } 329 } 330 setAutoCheckpointInterval()331 private void setAutoCheckpointInterval() { 332 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { 333 final long newValue = SQLiteGlobal.getWALAutoCheckpoint(); 334 long value = executeForLong("PRAGMA wal_autocheckpoint", null, null); 335 if (value != newValue) { 336 executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null); 337 } 338 } 339 } 340 setJournalSizeLimit()341 private void setJournalSizeLimit() { 342 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { 343 final long newValue = SQLiteGlobal.getJournalSizeLimit(); 344 long value = executeForLong("PRAGMA journal_size_limit", null, null); 345 if (value != newValue) { 346 executeForLong("PRAGMA journal_size_limit=" + newValue, null, null); 347 } 348 } 349 } 350 setForeignKeyModeFromConfiguration()351 private void setForeignKeyModeFromConfiguration() { 352 if (!mIsReadOnlyConnection) { 353 final long newValue = mConfiguration.foreignKeyConstraintsEnabled ? 1 : 0; 354 long value = executeForLong("PRAGMA foreign_keys", null, null); 355 if (value != newValue) { 356 execute("PRAGMA foreign_keys=" + newValue, null, null); 357 } 358 } 359 } 360 setJournalFromConfiguration()361 private void setJournalFromConfiguration() { 362 if (!mIsReadOnlyConnection) { 363 setJournalMode(mConfiguration.resolveJournalMode()); 364 maybeTruncateWalFile(); 365 } else { 366 // No need to truncate for read only databases. 367 mConfiguration.shouldTruncateWalFile = false; 368 } 369 } 370 setSyncModeFromConfiguration()371 private void setSyncModeFromConfiguration() { 372 if (!mIsReadOnlyConnection) { 373 setSyncMode(mConfiguration.resolveSyncMode()); 374 } 375 } 376 377 /** 378 * If the WAL file exists and larger than a threshold, truncate it by executing 379 * PRAGMA wal_checkpoint. 380 */ maybeTruncateWalFile()381 private void maybeTruncateWalFile() { 382 if (!mConfiguration.shouldTruncateWalFile) { 383 return; 384 } 385 386 final long threshold = SQLiteGlobal.getWALTruncateSize(); 387 if (DEBUG) { 388 Log.d(TAG, "Truncate threshold=" + threshold); 389 } 390 if (threshold == 0) { 391 return; 392 } 393 394 final File walFile = new File(mConfiguration.path + "-wal"); 395 if (!walFile.isFile()) { 396 return; 397 } 398 final long size = walFile.length(); 399 if (size < threshold) { 400 if (DEBUG) { 401 Log.d(TAG, walFile.getAbsolutePath() + " " + size + " bytes: No need to truncate"); 402 } 403 return; 404 } 405 406 try { 407 executeForString("PRAGMA wal_checkpoint(TRUNCATE)", null, null); 408 mConfiguration.shouldTruncateWalFile = false; 409 } catch (SQLiteException e) { 410 Log.w(TAG, "Failed to truncate the -wal file", e); 411 } 412 } 413 setSyncMode(@QLiteDatabase.SyncMode String newValue)414 private void setSyncMode(@SQLiteDatabase.SyncMode String newValue) { 415 if (TextUtils.isEmpty(newValue)) { 416 // No change to the sync mode is intended 417 return; 418 } 419 String value = executeForString("PRAGMA synchronous", null, null); 420 if (!canonicalizeSyncMode(value).equalsIgnoreCase( 421 canonicalizeSyncMode(newValue))) { 422 execute("PRAGMA synchronous=" + newValue, null, null); 423 } 424 } 425 canonicalizeSyncMode(String value)426 private static @SQLiteDatabase.SyncMode String canonicalizeSyncMode(String value) { 427 switch (value) { 428 case "0": return SQLiteDatabase.SYNC_MODE_OFF; 429 case "1": return SQLiteDatabase.SYNC_MODE_NORMAL; 430 case "2": return SQLiteDatabase.SYNC_MODE_FULL; 431 case "3": return SQLiteDatabase.SYNC_MODE_EXTRA; 432 } 433 return value; 434 } 435 setJournalMode(@QLiteDatabase.JournalMode String newValue)436 private void setJournalMode(@SQLiteDatabase.JournalMode String newValue) { 437 if (TextUtils.isEmpty(newValue)) { 438 // No change to the journal mode is intended 439 return; 440 } 441 String value = executeForString("PRAGMA journal_mode", null, null); 442 if (!value.equalsIgnoreCase(newValue)) { 443 try { 444 String result = executeForString("PRAGMA journal_mode=" + newValue, null, null); 445 if (result.equalsIgnoreCase(newValue)) { 446 return; 447 } 448 // PRAGMA journal_mode silently fails and returns the original journal 449 // mode in some cases if the journal mode could not be changed. 450 } catch (SQLiteDatabaseLockedException ex) { 451 // This error (SQLITE_BUSY) occurs if one connection has the database 452 // open in WAL mode and another tries to change it to non-WAL. 453 } 454 // Because we always disable WAL mode when a database is first opened 455 // (even if we intend to re-enable it), we can encounter problems if 456 // there is another open connection to the database somewhere. 457 // This can happen for a variety of reasons such as an application opening 458 // the same database in multiple processes at the same time or if there is a 459 // crashing content provider service that the ActivityManager has 460 // removed from its registry but whose process hasn't quite died yet 461 // by the time it is restarted in a new process. 462 // 463 // If we don't change the journal mode, nothing really bad happens. 464 // In the worst case, an application that enables WAL might not actually 465 // get it, although it can still use connection pooling. 466 Log.w(TAG, "Could not change the database journal mode of '" 467 + mConfiguration.label + "' from '" + value + "' to '" + newValue 468 + "' because the database is locked. This usually means that " 469 + "there are other open connections to the database which prevents " 470 + "the database from enabling or disabling write-ahead logging mode. " 471 + "Proceeding without changing the journal mode."); 472 } 473 } 474 setLocaleFromConfiguration()475 private void setLocaleFromConfiguration() { 476 if ((mConfiguration.openFlags & SQLiteDatabase.NO_LOCALIZED_COLLATORS) != 0) { 477 return; 478 } 479 480 // Register the localized collators. 481 final String newLocale = mConfiguration.locale.toString(); 482 nativeRegisterLocalizedCollators(mConnectionPtr, newLocale); 483 484 if (!mConfiguration.isInMemoryDb()) { 485 checkDatabaseWiped(); 486 } 487 488 // If the database is read-only, we cannot modify the android metadata table 489 // or existing indexes. 490 if (mIsReadOnlyConnection) { 491 return; 492 } 493 494 try { 495 // Ensure the android metadata table exists. 496 execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null); 497 498 // Check whether the locale was actually changed. 499 final String oldLocale = executeForString("SELECT locale FROM android_metadata " 500 + "UNION SELECT NULL ORDER BY locale DESC LIMIT 1", null, null); 501 if (oldLocale != null && oldLocale.equals(newLocale)) { 502 return; 503 } 504 505 // Go ahead and update the indexes using the new locale. 506 execute("BEGIN", null, null); 507 boolean success = false; 508 try { 509 execute("DELETE FROM android_metadata", null, null); 510 execute("INSERT INTO android_metadata (locale) VALUES(?)", 511 new Object[] { newLocale }, null); 512 execute("REINDEX LOCALIZED", null, null); 513 success = true; 514 } finally { 515 execute(success ? "COMMIT" : "ROLLBACK", null, null); 516 } 517 } catch (SQLiteException ex) { 518 throw ex; 519 } catch (RuntimeException ex) { 520 throw new SQLiteException("Failed to change locale for db '" + mConfiguration.label 521 + "' to '" + newLocale + "'.", ex); 522 } 523 } 524 setCustomFunctionsFromConfiguration()525 private void setCustomFunctionsFromConfiguration() { 526 for (int i = 0; i < mConfiguration.customScalarFunctions.size(); i++) { 527 nativeRegisterCustomScalarFunction(mConnectionPtr, 528 mConfiguration.customScalarFunctions.keyAt(i), 529 mConfiguration.customScalarFunctions.valueAt(i)); 530 } 531 for (int i = 0; i < mConfiguration.customAggregateFunctions.size(); i++) { 532 nativeRegisterCustomAggregateFunction(mConnectionPtr, 533 mConfiguration.customAggregateFunctions.keyAt(i), 534 mConfiguration.customAggregateFunctions.valueAt(i)); 535 } 536 } 537 executePerConnectionSqlFromConfiguration(int startIndex)538 private void executePerConnectionSqlFromConfiguration(int startIndex) { 539 for (int i = startIndex; i < mConfiguration.perConnectionSql.size(); i++) { 540 final Pair<String, Object[]> statement = mConfiguration.perConnectionSql.get(i); 541 final int type = DatabaseUtils.getSqlStatementType(statement.first); 542 switch (type) { 543 case DatabaseUtils.STATEMENT_SELECT: 544 executeForString(statement.first, statement.second, null); 545 break; 546 case DatabaseUtils.STATEMENT_PRAGMA: 547 execute(statement.first, statement.second, null); 548 break; 549 default: 550 throw new IllegalArgumentException( 551 "Unsupported configuration statement: " + statement); 552 } 553 } 554 } 555 checkDatabaseWiped()556 private void checkDatabaseWiped() { 557 if (!SQLiteGlobal.checkDbWipe()) { 558 return; 559 } 560 try { 561 final File checkFile = new File(mConfiguration.path 562 + SQLiteGlobal.WIPE_CHECK_FILE_SUFFIX); 563 564 final boolean hasMetadataTable = executeForLong( 565 "SELECT count(*) FROM sqlite_master" 566 + " WHERE type='table' AND name='android_metadata'", null, null) > 0; 567 final boolean hasCheckFile = checkFile.exists(); 568 569 if (!mIsReadOnlyConnection && !hasCheckFile) { 570 // Create the check file, unless it's a readonly connection, 571 // in which case we can't create the metadata table anyway. 572 checkFile.createNewFile(); 573 } 574 575 if (!hasMetadataTable && hasCheckFile) { 576 // Bad. The DB is gone unexpectedly. 577 SQLiteDatabase.wipeDetected(mConfiguration.path, "unknown"); 578 } 579 580 } catch (RuntimeException | IOException ex) { 581 SQLiteDatabase.wtfAsSystemServer(TAG, 582 "Unexpected exception while checking for wipe", ex); 583 } 584 } 585 586 // Called by SQLiteConnectionPool only. reconfigure(SQLiteDatabaseConfiguration configuration)587 void reconfigure(SQLiteDatabaseConfiguration configuration) { 588 mOnlyAllowReadOnlyOperations = false; 589 590 // Remember what changed. 591 boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled 592 != mConfiguration.foreignKeyConstraintsEnabled; 593 boolean localeChanged = !configuration.locale.equals(mConfiguration.locale); 594 boolean customScalarFunctionsChanged = !configuration.customScalarFunctions 595 .equals(mConfiguration.customScalarFunctions); 596 boolean customAggregateFunctionsChanged = !configuration.customAggregateFunctions 597 .equals(mConfiguration.customAggregateFunctions); 598 final int oldSize = mConfiguration.perConnectionSql.size(); 599 final int newSize = configuration.perConnectionSql.size(); 600 boolean perConnectionSqlChanged = newSize > oldSize; 601 boolean journalModeChanged = !configuration.resolveJournalMode().equalsIgnoreCase( 602 mConfiguration.resolveJournalMode()); 603 boolean syncModeChanged = 604 !configuration.resolveSyncMode().equalsIgnoreCase(mConfiguration.resolveSyncMode()); 605 606 // Update configuration parameters. 607 mConfiguration.updateParametersFrom(configuration); 608 609 // Update prepared statement cache size. 610 mPreparedStatementCache.resize(configuration.maxSqlCacheSize); 611 612 if (foreignKeyModeChanged) { 613 setForeignKeyModeFromConfiguration(); 614 } 615 616 if (journalModeChanged) { 617 setJournalFromConfiguration(); 618 } 619 620 if (syncModeChanged) { 621 setSyncModeFromConfiguration(); 622 } 623 624 if (localeChanged) { 625 setLocaleFromConfiguration(); 626 } 627 if (customScalarFunctionsChanged || customAggregateFunctionsChanged) { 628 setCustomFunctionsFromConfiguration(); 629 } 630 if (perConnectionSqlChanged) { 631 executePerConnectionSqlFromConfiguration(oldSize); 632 } 633 } 634 635 // Called by SQLiteConnectionPool only. 636 // When set to true, executing write operations will throw SQLiteException. 637 // Preparing statements that might write is ok, just don't execute them. setOnlyAllowReadOnlyOperations(boolean readOnly)638 void setOnlyAllowReadOnlyOperations(boolean readOnly) { 639 mOnlyAllowReadOnlyOperations = readOnly; 640 } 641 642 // Called by SQLiteConnectionPool only to decide if this connection has the desired statement 643 // already prepared. Returns true if the prepared statement cache contains the specified SQL. 644 // The statement may be stale, but that will be a rare occurrence and affects performance only 645 // a tiny bit, and only when database schema changes. isPreparedStatementInCache(String sql)646 boolean isPreparedStatementInCache(String sql) { 647 return mPreparedStatementCache.get(sql) != null; 648 } 649 650 /** 651 * Gets the unique id of this connection. 652 * @return The connection id. 653 */ getConnectionId()654 public int getConnectionId() { 655 return mConnectionId; 656 } 657 658 /** 659 * Returns true if this is the primary database connection. 660 * @return True if this is the primary database connection. 661 */ isPrimaryConnection()662 public boolean isPrimaryConnection() { 663 return mIsPrimaryConnection; 664 } 665 666 /** 667 * Prepares a statement for execution but does not bind its parameters or execute it. 668 * <p> 669 * This method can be used to check for syntax errors during compilation 670 * prior to execution of the statement. If the {@code outStatementInfo} argument 671 * is not null, the provided {@link SQLiteStatementInfo} object is populated 672 * with information about the statement. 673 * </p><p> 674 * A prepared statement makes no reference to the arguments that may eventually 675 * be bound to it, consequently it it possible to cache certain prepared statements 676 * such as SELECT or INSERT/UPDATE statements. If the statement is cacheable, 677 * then it will be stored in the cache for later. 678 * </p><p> 679 * To take advantage of this behavior as an optimization, the connection pool 680 * provides a method to acquire a connection that already has a given SQL statement 681 * in its prepared statement cache so that it is ready for execution. 682 * </p> 683 * 684 * @param sql The SQL statement to prepare. 685 * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate 686 * with information about the statement, or null if none. 687 * 688 * @throws SQLiteException if an error occurs, such as a syntax error. 689 */ prepare(String sql, SQLiteStatementInfo outStatementInfo)690 public void prepare(String sql, SQLiteStatementInfo outStatementInfo) { 691 if (sql == null) { 692 throw new IllegalArgumentException("sql must not be null."); 693 } 694 695 final int cookie = mRecentOperations.beginOperation("prepare", sql, null); 696 try { 697 final PreparedStatement statement = acquirePreparedStatement(sql); 698 try { 699 if (outStatementInfo != null) { 700 outStatementInfo.numParameters = statement.mNumParameters; 701 outStatementInfo.readOnly = statement.mReadOnly; 702 703 final int columnCount = nativeGetColumnCount( 704 mConnectionPtr, statement.mStatementPtr); 705 if (columnCount == 0) { 706 outStatementInfo.columnNames = EMPTY_STRING_ARRAY; 707 } else { 708 outStatementInfo.columnNames = new String[columnCount]; 709 for (int i = 0; i < columnCount; i++) { 710 outStatementInfo.columnNames[i] = nativeGetColumnName( 711 mConnectionPtr, statement.mStatementPtr, i); 712 } 713 } 714 } 715 } finally { 716 releasePreparedStatement(statement); 717 } 718 } catch (RuntimeException ex) { 719 mRecentOperations.failOperation(cookie, ex); 720 throw ex; 721 } finally { 722 mRecentOperations.endOperation(cookie); 723 } 724 } 725 726 /** 727 * Executes a statement that does not return a result. 728 * 729 * @param sql The SQL statement to execute. 730 * @param bindArgs The arguments to bind, or null if none. 731 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 732 * 733 * @throws SQLiteException if an error occurs, such as a syntax error 734 * or invalid number of bind arguments. 735 * @throws OperationCanceledException if the operation was canceled. 736 */ execute(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)737 public void execute(String sql, Object[] bindArgs, 738 CancellationSignal cancellationSignal) { 739 if (sql == null) { 740 throw new IllegalArgumentException("sql must not be null."); 741 } 742 743 final int cookie = mRecentOperations.beginOperation("execute", sql, bindArgs); 744 try { 745 final boolean isPragmaStmt = 746 DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_PRAGMA; 747 final PreparedStatement statement = acquirePreparedStatement(sql); 748 try { 749 throwIfStatementForbidden(statement); 750 bindArguments(statement, bindArgs); 751 applyBlockGuardPolicy(statement); 752 attachCancellationSignal(cancellationSignal); 753 try { 754 nativeExecute(mConnectionPtr, statement.mStatementPtr, isPragmaStmt); 755 } finally { 756 detachCancellationSignal(cancellationSignal); 757 } 758 } finally { 759 releasePreparedStatement(statement); 760 } 761 } catch (RuntimeException ex) { 762 mRecentOperations.failOperation(cookie, ex); 763 throw ex; 764 } finally { 765 mRecentOperations.endOperation(cookie); 766 } 767 } 768 769 /** 770 * Executes a statement that returns a single <code>long</code> result. 771 * 772 * @param sql The SQL statement to execute. 773 * @param bindArgs The arguments to bind, or null if none. 774 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 775 * @return The value of the first column in the first row of the result set 776 * as a <code>long</code>, or zero if none. 777 * 778 * @throws SQLiteException if an error occurs, such as a syntax error 779 * or invalid number of bind arguments. 780 * @throws OperationCanceledException if the operation was canceled. 781 */ executeForLong(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)782 public long executeForLong(String sql, Object[] bindArgs, 783 CancellationSignal cancellationSignal) { 784 if (sql == null) { 785 throw new IllegalArgumentException("sql must not be null."); 786 } 787 788 final int cookie = mRecentOperations.beginOperation("executeForLong", sql, bindArgs); 789 try { 790 final PreparedStatement statement = acquirePreparedStatement(sql); 791 try { 792 throwIfStatementForbidden(statement); 793 bindArguments(statement, bindArgs); 794 applyBlockGuardPolicy(statement); 795 attachCancellationSignal(cancellationSignal); 796 try { 797 long ret = nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr); 798 mRecentOperations.setResult(ret); 799 return ret; 800 } finally { 801 detachCancellationSignal(cancellationSignal); 802 } 803 } finally { 804 releasePreparedStatement(statement); 805 } 806 } catch (RuntimeException ex) { 807 mRecentOperations.failOperation(cookie, ex); 808 throw ex; 809 } finally { 810 mRecentOperations.endOperation(cookie); 811 } 812 } 813 814 /** 815 * Executes a statement that returns a single {@link String} result. 816 * 817 * @param sql The SQL statement to execute. 818 * @param bindArgs The arguments to bind, or null if none. 819 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 820 * @return The value of the first column in the first row of the result set 821 * as a <code>String</code>, or null if none. 822 * 823 * @throws SQLiteException if an error occurs, such as a syntax error 824 * or invalid number of bind arguments. 825 * @throws OperationCanceledException if the operation was canceled. 826 */ executeForString(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)827 public String executeForString(String sql, Object[] bindArgs, 828 CancellationSignal cancellationSignal) { 829 if (sql == null) { 830 throw new IllegalArgumentException("sql must not be null."); 831 } 832 833 final int cookie = mRecentOperations.beginOperation("executeForString", sql, bindArgs); 834 try { 835 final PreparedStatement statement = acquirePreparedStatement(sql); 836 try { 837 throwIfStatementForbidden(statement); 838 bindArguments(statement, bindArgs); 839 applyBlockGuardPolicy(statement); 840 attachCancellationSignal(cancellationSignal); 841 try { 842 String ret = nativeExecuteForString(mConnectionPtr, statement.mStatementPtr); 843 mRecentOperations.setResult(ret); 844 return ret; 845 } finally { 846 detachCancellationSignal(cancellationSignal); 847 } 848 } finally { 849 releasePreparedStatement(statement); 850 } 851 } catch (RuntimeException ex) { 852 mRecentOperations.failOperation(cookie, ex); 853 throw ex; 854 } finally { 855 mRecentOperations.endOperation(cookie); 856 } 857 } 858 859 /** 860 * Executes a statement that returns a single BLOB result as a 861 * file descriptor to a shared memory region. 862 * 863 * @param sql The SQL statement to execute. 864 * @param bindArgs The arguments to bind, or null if none. 865 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 866 * @return The file descriptor for a shared memory region that contains 867 * the value of the first column in the first row of the result set as a BLOB, 868 * or null if none. 869 * 870 * @throws SQLiteException if an error occurs, such as a syntax error 871 * or invalid number of bind arguments. 872 * @throws OperationCanceledException if the operation was canceled. 873 */ executeForBlobFileDescriptor(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)874 public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs, 875 CancellationSignal cancellationSignal) { 876 if (sql == null) { 877 throw new IllegalArgumentException("sql must not be null."); 878 } 879 880 final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor", 881 sql, bindArgs); 882 try { 883 final PreparedStatement statement = acquirePreparedStatement(sql); 884 try { 885 throwIfStatementForbidden(statement); 886 bindArguments(statement, bindArgs); 887 applyBlockGuardPolicy(statement); 888 attachCancellationSignal(cancellationSignal); 889 try { 890 int fd = nativeExecuteForBlobFileDescriptor( 891 mConnectionPtr, statement.mStatementPtr); 892 return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null; 893 } finally { 894 detachCancellationSignal(cancellationSignal); 895 } 896 } finally { 897 releasePreparedStatement(statement); 898 } 899 } catch (RuntimeException ex) { 900 mRecentOperations.failOperation(cookie, ex); 901 throw ex; 902 } finally { 903 mRecentOperations.endOperation(cookie); 904 } 905 } 906 907 /** 908 * Executes a statement that returns a count of the number of rows 909 * that were changed. Use for UPDATE or DELETE SQL statements. 910 * 911 * @param sql The SQL statement to execute. 912 * @param bindArgs The arguments to bind, or null if none. 913 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 914 * @return The number of rows that were changed. 915 * 916 * @throws SQLiteException if an error occurs, such as a syntax error 917 * or invalid number of bind arguments. 918 * @throws OperationCanceledException if the operation was canceled. 919 */ executeForChangedRowCount(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)920 public int executeForChangedRowCount(String sql, Object[] bindArgs, 921 CancellationSignal cancellationSignal) { 922 if (sql == null) { 923 throw new IllegalArgumentException("sql must not be null."); 924 } 925 926 int changedRows = 0; 927 final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount", 928 sql, bindArgs); 929 try { 930 final PreparedStatement statement = acquirePreparedStatement(sql); 931 try { 932 throwIfStatementForbidden(statement); 933 bindArguments(statement, bindArgs); 934 applyBlockGuardPolicy(statement); 935 attachCancellationSignal(cancellationSignal); 936 try { 937 changedRows = nativeExecuteForChangedRowCount( 938 mConnectionPtr, statement.mStatementPtr); 939 return changedRows; 940 } finally { 941 detachCancellationSignal(cancellationSignal); 942 } 943 } finally { 944 releasePreparedStatement(statement); 945 } 946 } catch (RuntimeException ex) { 947 mRecentOperations.failOperation(cookie, ex); 948 throw ex; 949 } finally { 950 if (mRecentOperations.endOperationDeferLog(cookie)) { 951 mRecentOperations.logOperation(cookie, "changedRows=" + changedRows); 952 } 953 } 954 } 955 956 /** 957 * Executes a statement that returns the row id of the last row inserted 958 * by the statement. Use for INSERT SQL statements. 959 * 960 * @param sql The SQL statement to execute. 961 * @param bindArgs The arguments to bind, or null if none. 962 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 963 * @return The row id of the last row that was inserted, or 0 if none. 964 * 965 * @throws SQLiteException if an error occurs, such as a syntax error 966 * or invalid number of bind arguments. 967 * @throws OperationCanceledException if the operation was canceled. 968 */ executeForLastInsertedRowId(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)969 public long executeForLastInsertedRowId(String sql, Object[] bindArgs, 970 CancellationSignal cancellationSignal) { 971 if (sql == null) { 972 throw new IllegalArgumentException("sql must not be null."); 973 } 974 975 final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId", 976 sql, bindArgs); 977 try { 978 final PreparedStatement statement = acquirePreparedStatement(sql); 979 try { 980 throwIfStatementForbidden(statement); 981 bindArguments(statement, bindArgs); 982 applyBlockGuardPolicy(statement); 983 attachCancellationSignal(cancellationSignal); 984 try { 985 return nativeExecuteForLastInsertedRowId( 986 mConnectionPtr, statement.mStatementPtr); 987 } finally { 988 detachCancellationSignal(cancellationSignal); 989 } 990 } finally { 991 releasePreparedStatement(statement); 992 } 993 } catch (RuntimeException ex) { 994 mRecentOperations.failOperation(cookie, ex); 995 throw ex; 996 } finally { 997 mRecentOperations.endOperation(cookie); 998 } 999 } 1000 1001 /** 1002 * Executes a statement and populates the specified {@link CursorWindow} 1003 * with a range of results. Returns the number of rows that were counted 1004 * during query execution. 1005 * 1006 * @param sql The SQL statement to execute. 1007 * @param bindArgs The arguments to bind, or null if none. 1008 * @param window The cursor window to clear and fill. 1009 * @param startPos The start position for filling the window. 1010 * @param requiredPos The position of a row that MUST be in the window. 1011 * If it won't fit, then the query should discard part of what it filled 1012 * so that it does. Must be greater than or equal to <code>startPos</code>. 1013 * @param countAllRows True to count all rows that the query would return 1014 * regagless of whether they fit in the window. 1015 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. 1016 * @return The number of rows that were counted during query execution. Might 1017 * not be all rows in the result set unless <code>countAllRows</code> is true. 1018 * 1019 * @throws SQLiteException if an error occurs, such as a syntax error 1020 * or invalid number of bind arguments. 1021 * @throws OperationCanceledException if the operation was canceled. 1022 */ executeForCursorWindow(String sql, Object[] bindArgs, CursorWindow window, int startPos, int requiredPos, boolean countAllRows, CancellationSignal cancellationSignal)1023 public int executeForCursorWindow(String sql, Object[] bindArgs, 1024 CursorWindow window, int startPos, int requiredPos, boolean countAllRows, 1025 CancellationSignal cancellationSignal) { 1026 if (sql == null) { 1027 throw new IllegalArgumentException("sql must not be null."); 1028 } 1029 if (window == null) { 1030 throw new IllegalArgumentException("window must not be null."); 1031 } 1032 1033 window.acquireReference(); 1034 try { 1035 int actualPos = -1; 1036 int countedRows = -1; 1037 int filledRows = -1; 1038 final int cookie = mRecentOperations.beginOperation("executeForCursorWindow", 1039 sql, bindArgs); 1040 try { 1041 final PreparedStatement statement = acquirePreparedStatement(sql); 1042 try { 1043 throwIfStatementForbidden(statement); 1044 bindArguments(statement, bindArgs); 1045 applyBlockGuardPolicy(statement); 1046 attachCancellationSignal(cancellationSignal); 1047 try { 1048 final long result = nativeExecuteForCursorWindow( 1049 mConnectionPtr, statement.mStatementPtr, window.mWindowPtr, 1050 startPos, requiredPos, countAllRows); 1051 actualPos = (int)(result >> 32); 1052 countedRows = (int)result; 1053 filledRows = window.getNumRows(); 1054 window.setStartPosition(actualPos); 1055 return countedRows; 1056 } finally { 1057 detachCancellationSignal(cancellationSignal); 1058 } 1059 } finally { 1060 releasePreparedStatement(statement); 1061 } 1062 } catch (RuntimeException ex) { 1063 mRecentOperations.failOperation(cookie, ex); 1064 throw ex; 1065 } finally { 1066 if (mRecentOperations.endOperationDeferLog(cookie)) { 1067 mRecentOperations.logOperation(cookie, "window='" + window 1068 + "', startPos=" + startPos 1069 + ", actualPos=" + actualPos 1070 + ", filledRows=" + filledRows 1071 + ", countedRows=" + countedRows); 1072 } 1073 } 1074 } finally { 1075 window.releaseReference(); 1076 } 1077 } 1078 1079 /** 1080 * Return a {@link #PreparedStatement}, possibly from the cache. 1081 */ acquirePreparedStatementLI(String sql)1082 private PreparedStatement acquirePreparedStatementLI(String sql) { 1083 ++mPool.mTotalPrepareStatements; 1084 PreparedStatement statement = mPreparedStatementCache.getStatement(sql); 1085 long seqNum = mPreparedStatementCache.getLastSeqNum(); 1086 1087 boolean skipCache = false; 1088 if (statement != null) { 1089 if (!statement.mInUse) { 1090 if (statement.mSeqNum == seqNum) { 1091 // This is a valid statement. Claim it and return it. 1092 statement.mInUse = true; 1093 return statement; 1094 } else { 1095 // This is a stale statement. Remove it from the cache. Treat this as if the 1096 // statement was never found, which means we should not skip the cache. 1097 mPreparedStatementCache.remove(sql); 1098 statement = null; 1099 // Leave skipCache == false. 1100 } 1101 } else { 1102 // The statement is already in the cache but is in use (this statement appears to 1103 // be not only re-entrant but recursive!). So prepare a new copy of the statement 1104 // but do not cache it. 1105 skipCache = true; 1106 } 1107 } 1108 ++mPool.mTotalPrepareStatementCacheMiss; 1109 final long statementPtr = mPreparedStatementCache.createStatement(sql); 1110 seqNum = mPreparedStatementCache.getLastSeqNum(); 1111 try { 1112 final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr); 1113 final int type = DatabaseUtils.getSqlStatementTypeExtended(sql); 1114 boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr); 1115 statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly, 1116 seqNum); 1117 if (!skipCache && isCacheable(type)) { 1118 mPreparedStatementCache.put(sql, statement); 1119 statement.mInCache = true; 1120 } 1121 } catch (RuntimeException ex) { 1122 // Finalize the statement if an exception occurred and we did not add 1123 // it to the cache. If it is already in the cache, then leave it there. 1124 if (statement == null || !statement.mInCache) { 1125 nativeFinalizeStatement(mConnectionPtr, statementPtr); 1126 } 1127 throw ex; 1128 } 1129 statement.mInUse = true; 1130 return statement; 1131 } 1132 1133 /** 1134 * Return a {@link #PreparedStatement}, possibly from the cache. 1135 */ acquirePreparedStatement(String sql)1136 PreparedStatement acquirePreparedStatement(String sql) { 1137 return acquirePreparedStatementLI(sql); 1138 } 1139 1140 /** 1141 * Release a {@link #PreparedStatement} that was originally supplied by this connection. 1142 */ releasePreparedStatementLI(PreparedStatement statement)1143 private void releasePreparedStatementLI(PreparedStatement statement) { 1144 statement.mInUse = false; 1145 if (statement.mInCache) { 1146 try { 1147 nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr); 1148 } catch (SQLiteException ex) { 1149 // The statement could not be reset due to an error. Remove it from the cache. 1150 // When remove() is called, the cache will invoke its entryRemoved() callback, 1151 // which will in turn call finalizePreparedStatement() to finalize and 1152 // recycle the statement. 1153 if (DEBUG) { 1154 Log.d(TAG, "Could not reset prepared statement due to an exception. " 1155 + "Removing it from the cache. SQL: " 1156 + trimSqlForDisplay(statement.mSql), ex); 1157 } 1158 1159 mPreparedStatementCache.remove(statement.mSql); 1160 } 1161 } else { 1162 finalizePreparedStatement(statement); 1163 } 1164 } 1165 1166 /** 1167 * Release a {@link #PreparedStatement} that was originally supplied by this connection. 1168 */ releasePreparedStatement(PreparedStatement statement)1169 void releasePreparedStatement(PreparedStatement statement) { 1170 releasePreparedStatementLI(statement); 1171 } 1172 finalizePreparedStatement(PreparedStatement statement)1173 private void finalizePreparedStatement(PreparedStatement statement) { 1174 nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr); 1175 recyclePreparedStatement(statement); 1176 } 1177 1178 /** 1179 * Return a prepared statement for use by {@link SQLiteRawStatement}. This throws if the 1180 * prepared statement is incompatible with this connection. 1181 */ acquirePersistentStatement(@onNull String sql)1182 PreparedStatement acquirePersistentStatement(@NonNull String sql) { 1183 final int cookie = mRecentOperations.beginOperation("prepare", sql, null); 1184 try { 1185 final PreparedStatement statement = acquirePreparedStatement(sql); 1186 throwIfStatementForbidden(statement); 1187 return statement; 1188 } catch (RuntimeException e) { 1189 mRecentOperations.failOperation(cookie, e); 1190 throw e; 1191 } finally { 1192 mRecentOperations.endOperation(cookie); 1193 } 1194 } 1195 attachCancellationSignal(CancellationSignal cancellationSignal)1196 private void attachCancellationSignal(CancellationSignal cancellationSignal) { 1197 if (cancellationSignal != null) { 1198 cancellationSignal.throwIfCanceled(); 1199 1200 mCancellationSignalAttachCount += 1; 1201 if (mCancellationSignalAttachCount == 1) { 1202 // Reset cancellation flag before executing the statement. 1203 nativeResetCancel(mConnectionPtr, true /*cancelable*/); 1204 1205 // After this point, onCancel() may be called concurrently. 1206 cancellationSignal.setOnCancelListener(this); 1207 } 1208 } 1209 } 1210 detachCancellationSignal(CancellationSignal cancellationSignal)1211 private void detachCancellationSignal(CancellationSignal cancellationSignal) { 1212 if (cancellationSignal != null) { 1213 assert mCancellationSignalAttachCount > 0; 1214 1215 mCancellationSignalAttachCount -= 1; 1216 if (mCancellationSignalAttachCount == 0) { 1217 // After this point, onCancel() cannot be called concurrently. 1218 cancellationSignal.setOnCancelListener(null); 1219 1220 // Reset cancellation flag after executing the statement. 1221 nativeResetCancel(mConnectionPtr, false /*cancelable*/); 1222 } 1223 } 1224 } 1225 1226 // CancellationSignal.OnCancelListener callback. 1227 // This method may be called on a different thread than the executing statement. 1228 // However, it will only be called between calls to attachCancellationSignal and 1229 // detachCancellationSignal, while a statement is executing. We can safely assume 1230 // that the SQLite connection is still alive. 1231 @Override onCancel()1232 public void onCancel() { 1233 nativeCancel(mConnectionPtr); 1234 } 1235 bindArguments(PreparedStatement statement, Object[] bindArgs)1236 private void bindArguments(PreparedStatement statement, Object[] bindArgs) { 1237 final int count = bindArgs != null ? bindArgs.length : 0; 1238 if (count != statement.mNumParameters) { 1239 throw new SQLiteBindOrColumnIndexOutOfRangeException( 1240 "Expected " + statement.mNumParameters + " bind arguments but " 1241 + count + " were provided."); 1242 } 1243 if (count == 0) { 1244 return; 1245 } 1246 1247 final long statementPtr = statement.mStatementPtr; 1248 for (int i = 0; i < count; i++) { 1249 final Object arg = bindArgs[i]; 1250 switch (DatabaseUtils.getTypeOfObject(arg)) { 1251 case Cursor.FIELD_TYPE_NULL: 1252 nativeBindNull(mConnectionPtr, statementPtr, i + 1); 1253 break; 1254 case Cursor.FIELD_TYPE_INTEGER: 1255 nativeBindLong(mConnectionPtr, statementPtr, i + 1, 1256 ((Number)arg).longValue()); 1257 break; 1258 case Cursor.FIELD_TYPE_FLOAT: 1259 nativeBindDouble(mConnectionPtr, statementPtr, i + 1, 1260 ((Number)arg).doubleValue()); 1261 break; 1262 case Cursor.FIELD_TYPE_BLOB: 1263 nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg); 1264 break; 1265 case Cursor.FIELD_TYPE_STRING: 1266 default: 1267 if (arg instanceof Boolean) { 1268 // Provide compatibility with legacy applications which may pass 1269 // Boolean values in bind args. 1270 nativeBindLong(mConnectionPtr, statementPtr, i + 1, 1271 ((Boolean)arg).booleanValue() ? 1 : 0); 1272 } else { 1273 nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString()); 1274 } 1275 break; 1276 } 1277 } 1278 } 1279 1280 /** 1281 * Verify that the statement is read-only, if the connection only allows read-only 1282 * operations. If the statement is not read-only, then check if the statement only modifies 1283 * temp tables, in which case it is treated the same as a read-only statement and is allowed. 1284 * @param statement The statement to check. 1285 * @throws SQLiteException if the statement could update the database inside a read-only 1286 * transaction. 1287 */ throwIfStatementForbidden(PreparedStatement statement)1288 void throwIfStatementForbidden(PreparedStatement statement) { 1289 if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) { 1290 statement.mReadOnly = 1291 nativeUpdatesTempOnly(mConnectionPtr, statement.mStatementPtr); 1292 if (statement.mReadOnly) return; 1293 1294 throw new SQLiteException("Cannot execute this statement because it " 1295 + "might modify the database but the connection is read-only."); 1296 } 1297 } 1298 isCacheable(int statementType)1299 private static boolean isCacheable(int statementType) { 1300 if (statementType == DatabaseUtils.STATEMENT_UPDATE 1301 || statementType == DatabaseUtils.STATEMENT_SELECT 1302 || statementType == DatabaseUtils.STATEMENT_WITH) { 1303 return true; 1304 } 1305 return false; 1306 } 1307 applyBlockGuardPolicy(PreparedStatement statement)1308 private void applyBlockGuardPolicy(PreparedStatement statement) { 1309 if (!mConfiguration.isInMemoryDb()) { 1310 if (statement.mReadOnly) { 1311 BlockGuard.getThreadPolicy().onReadFromDisk(); 1312 } else { 1313 BlockGuard.getThreadPolicy().onWriteToDisk(); 1314 } 1315 } 1316 } 1317 1318 /** 1319 * Dumps debugging information about this connection. 1320 * 1321 * @param printer The printer to receive the dump, not null. 1322 * @param verbose True to dump more verbose information. 1323 */ dump(Printer printer, boolean verbose)1324 public void dump(Printer printer, boolean verbose) { 1325 dumpUnsafe(printer, verbose); 1326 } 1327 1328 /** 1329 * Dumps debugging information about this connection, in the case where the 1330 * caller might not actually own the connection. 1331 * 1332 * This function is written so that it may be called by a thread that does not 1333 * own the connection. We need to be very careful because the connection state is 1334 * not synchronized. 1335 * 1336 * At worst, the method may return stale or slightly wrong data, however 1337 * it should not crash. This is ok as it is only used for diagnostic purposes. 1338 * 1339 * @param printer The printer to receive the dump, not null. 1340 * @param verbose True to dump more verbose information. 1341 */ dumpUnsafe(Printer printer, boolean verbose)1342 void dumpUnsafe(Printer printer, boolean verbose) { 1343 printer.println("Connection #" + mConnectionId + ":"); 1344 if (verbose) { 1345 printer.println(" connectionPtr: 0x" + Long.toHexString(mConnectionPtr)); 1346 } 1347 printer.println(" isPrimaryConnection: " + mIsPrimaryConnection); 1348 printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations); 1349 printer.println(" totalLongOperations: " + mRecentOperations.getTotalLongOperations()); 1350 1351 mRecentOperations.dump(printer); 1352 1353 if (verbose) { 1354 mPreparedStatementCache.dump(printer); 1355 } 1356 } 1357 1358 /** 1359 * Describes the currently executing operation, in the case where the 1360 * caller might not actually own the connection. 1361 * 1362 * This function is written so that it may be called by a thread that does not 1363 * own the connection. We need to be very careful because the connection state is 1364 * not synchronized. 1365 * 1366 * At worst, the method may return stale or slightly wrong data, however 1367 * it should not crash. This is ok as it is only used for diagnostic purposes. 1368 * 1369 * @return A description of the current operation including how long it has been running, 1370 * or null if none. 1371 */ describeCurrentOperationUnsafe()1372 String describeCurrentOperationUnsafe() { 1373 return mRecentOperations.describeCurrentOperation(); 1374 } 1375 1376 /** 1377 * Collects statistics about database connection memory usage. 1378 * 1379 * @param dbStatsList The list to populate. 1380 */ collectDbStats(ArrayList<DbStats> dbStatsList)1381 void collectDbStats(ArrayList<DbStats> dbStatsList) { 1382 // Get information about the main database. 1383 int lookaside = nativeGetDbLookaside(mConnectionPtr); 1384 long pageCount = 0; 1385 long pageSize = 0; 1386 try { 1387 pageCount = executeForLong("PRAGMA page_count;", null, null); 1388 pageSize = executeForLong("PRAGMA page_size;", null, null); 1389 } catch (SQLiteException ex) { 1390 // Ignore. 1391 } 1392 dbStatsList.add(getMainDbStatsUnsafe(lookaside, pageCount, pageSize)); 1393 1394 // Get information about attached databases. 1395 // We ignore the first row in the database list because it corresponds to 1396 // the main database which we have already described. 1397 CursorWindow window = new CursorWindow("collectDbStats"); 1398 try { 1399 executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null); 1400 for (int i = 1; i < window.getNumRows(); i++) { 1401 String name = window.getString(i, 1); 1402 String path = window.getString(i, 2); 1403 pageCount = 0; 1404 pageSize = 0; 1405 try { 1406 pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null); 1407 pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null); 1408 } catch (SQLiteException ex) { 1409 // Ignore. 1410 } 1411 StringBuilder label = new StringBuilder(" (attached) ").append(name); 1412 if (!path.isEmpty()) { 1413 label.append(": ").append(path); 1414 } 1415 dbStatsList.add( 1416 new DbStats(label.toString(), pageCount, pageSize, 0, 0, 0, 0, false)); 1417 } 1418 } catch (SQLiteException ex) { 1419 // Ignore. 1420 } finally { 1421 window.close(); 1422 } 1423 } 1424 1425 /** 1426 * Collects statistics about database connection memory usage, in the case where the 1427 * caller might not actually own the connection. 1428 * 1429 * @return The statistics object, never null. 1430 */ collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList)1431 void collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList) { 1432 dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0)); 1433 } 1434 getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize)1435 private DbStats getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize) { 1436 // The prepared statement cache is thread-safe so we can access its statistics 1437 // even if we do not own the database connection. 1438 String label; 1439 if (mIsPrimaryConnection) { 1440 label = mConfiguration.path; 1441 } else { 1442 label = mConfiguration.path + " (" + mConnectionId + ")"; 1443 } 1444 return new DbStats(label, pageCount, pageSize, lookaside, 1445 mPreparedStatementCache.hitCount(), mPreparedStatementCache.missCount(), 1446 mPreparedStatementCache.size(), false); 1447 } 1448 1449 @Override toString()1450 public String toString() { 1451 return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")"; 1452 } 1453 obtainPreparedStatement(String sql, long statementPtr, int numParameters, int type, boolean readOnly, long seqNum)1454 private PreparedStatement obtainPreparedStatement(String sql, long statementPtr, 1455 int numParameters, int type, boolean readOnly, long seqNum) { 1456 PreparedStatement statement = mPreparedStatementPool; 1457 if (statement != null) { 1458 mPreparedStatementPool = statement.mPoolNext; 1459 statement.mPoolNext = null; 1460 statement.mInCache = false; 1461 } else { 1462 statement = new PreparedStatement(); 1463 } 1464 statement.mSql = sql; 1465 statement.mStatementPtr = statementPtr; 1466 statement.mNumParameters = numParameters; 1467 statement.mType = type; 1468 statement.mReadOnly = readOnly; 1469 statement.mSeqNum = seqNum; 1470 return statement; 1471 } 1472 recyclePreparedStatement(PreparedStatement statement)1473 private void recyclePreparedStatement(PreparedStatement statement) { 1474 statement.mSql = null; 1475 statement.mPoolNext = mPreparedStatementPool; 1476 mPreparedStatementPool = statement; 1477 } 1478 trimSqlForDisplay(String sql)1479 private static String trimSqlForDisplay(String sql) { 1480 // Note: Creating and caching a regular expression is expensive at preload-time 1481 // and stops compile-time initialization. This pattern is only used when 1482 // dumping the connection, which is a rare (mainly error) case. So: 1483 // DO NOT CACHE. 1484 return sql.replaceAll("[\\s]*\\n+[\\s]*", " "); 1485 } 1486 1487 // Update the database sequence number. This number is stored in the prepared statement 1488 // cache. setDatabaseSeqNum(long n)1489 void setDatabaseSeqNum(long n) { 1490 mPreparedStatementCache.setDatabaseSeqNum(n); 1491 } 1492 1493 /** 1494 * Holder type for a prepared statement. 1495 * 1496 * Although this object holds a pointer to a native statement object, it 1497 * does not have a finalizer. This is deliberate. The {@link SQLiteConnection} 1498 * owns the statement object and will take care of freeing it when needed. 1499 * In particular, closing the connection requires a guarantee of deterministic 1500 * resource disposal because all native statement objects must be freed before 1501 * the native database object can be closed. So no finalizers here. 1502 * 1503 * The class is package-visible so that {@link SQLiteRawStatement} can use it. 1504 */ 1505 static final class PreparedStatement { 1506 // Next item in pool. 1507 public PreparedStatement mPoolNext; 1508 1509 // The SQL from which the statement was prepared. 1510 public String mSql; 1511 1512 // The native sqlite3_stmt object pointer. 1513 // Lifetime is managed explicitly by the connection. 1514 public long mStatementPtr; 1515 1516 // The number of parameters that the prepared statement has. 1517 public int mNumParameters; 1518 1519 // The statement type. 1520 public int mType; 1521 1522 // True if the statement is read-only. 1523 public boolean mReadOnly; 1524 1525 // True if the statement is in the cache. 1526 public boolean mInCache; 1527 1528 // The database schema ID at the time this statement was created. The ID is left zero for 1529 // statements that are not cached. This value is meaningful only if mInCache is true. 1530 public long mSeqNum; 1531 1532 // True if the statement is in use (currently executing). 1533 // We need this flag because due to the use of custom functions in triggers, it's 1534 // possible for SQLite calls to be re-entrant. Consequently we need to prevent 1535 // in use statements from being finalized until they are no longer in use. 1536 public boolean mInUse; 1537 } 1538 1539 private final class PreparedStatementCache extends LruCache<String, PreparedStatement> { 1540 // The database sequence number. This changes every time the database schema changes. 1541 private long mDatabaseSeqNum = 0; 1542 1543 // The database sequence number from the last getStatement() or createStatement() 1544 // call. The proper use of this variable depends on the caller being single threaded. 1545 private long mLastSeqNum = 0; 1546 PreparedStatementCache(int size)1547 public PreparedStatementCache(int size) { 1548 super(size); 1549 } 1550 setDatabaseSeqNum(long n)1551 public synchronized void setDatabaseSeqNum(long n) { 1552 mDatabaseSeqNum = n; 1553 } 1554 1555 // Return the last database sequence number. getLastSeqNum()1556 public long getLastSeqNum() { 1557 return mLastSeqNum; 1558 } 1559 1560 // Return a statement from the cache. Save the database sequence number for the caller. getStatement(String sql)1561 public synchronized PreparedStatement getStatement(String sql) { 1562 mLastSeqNum = mDatabaseSeqNum; 1563 return get(sql); 1564 } 1565 1566 // Return a new native prepared statement and save the database sequence number for the 1567 // caller. This does not modify the cache in any way. However, by being synchronized, 1568 // callers are guaranteed that the sequence number did not change across the native 1569 // preparation step. createStatement(String sql)1570 public synchronized long createStatement(String sql) { 1571 mLastSeqNum = mDatabaseSeqNum; 1572 return nativePrepareStatement(mConnectionPtr, sql); 1573 } 1574 1575 @Override entryRemoved(boolean evicted, String key, PreparedStatement oldValue, PreparedStatement newValue)1576 protected void entryRemoved(boolean evicted, String key, 1577 PreparedStatement oldValue, PreparedStatement newValue) { 1578 oldValue.mInCache = false; 1579 if (!oldValue.mInUse) { 1580 finalizePreparedStatement(oldValue); 1581 } 1582 } 1583 dump(Printer printer)1584 public void dump(Printer printer) { 1585 printer.println(" Prepared statement cache:"); 1586 Map<String, PreparedStatement> cache = snapshot(); 1587 if (!cache.isEmpty()) { 1588 int i = 0; 1589 for (Map.Entry<String, PreparedStatement> entry : cache.entrySet()) { 1590 PreparedStatement statement = entry.getValue(); 1591 if (statement.mInCache) { // might be false due to a race with entryRemoved 1592 String sql = entry.getKey(); 1593 printer.println(" " + i + ": statementPtr=0x" 1594 + Long.toHexString(statement.mStatementPtr) 1595 + ", numParameters=" + statement.mNumParameters 1596 + ", type=" + statement.mType 1597 + ", readOnly=" + statement.mReadOnly 1598 + ", sql=\"" + trimSqlForDisplay(sql) + "\""); 1599 } 1600 i += 1; 1601 } 1602 } else { 1603 printer.println(" <none>"); 1604 } 1605 } 1606 } 1607 1608 /** 1609 * This class implements a leaky bucket strategy to rate-limit operations. A client 1610 * accumulates one credit every <n> milliseconds; a credit allows the client execute an 1611 * operation (which then deducts the credit). Credits accumulate up to a maximum amount after 1612 * which they no longer accumulate. The strategy allows a client to execute an operation 1613 * every <n> milliseconds, or to execute a burst, after a period of no operations. 1614 */ 1615 private static class RateLimiter { 1616 // When the bucket was created, in ms. 1617 private final long mCreationUptimeMs; 1618 // The time required to accumulate a single credit. 1619 private final long mMsPerCredit; 1620 // The maximum number of credits the process can accumulate. 1621 private final int mMaxCredits; 1622 // Total credits consumed so far. 1623 private long mSpent = 0; 1624 RateLimiter(long msPerCredit, int maxCredits)1625 RateLimiter(long msPerCredit, int maxCredits) { 1626 mMsPerCredit = msPerCredit; 1627 mMaxCredits = maxCredits; 1628 mCreationUptimeMs = SystemClock.uptimeMillis() - (mMsPerCredit * mMaxCredits); 1629 } 1630 1631 /** Return true if there is a credit available (and consume that credit). */ tryAcquire()1632 boolean tryAcquire() { 1633 final long now = SystemClock.uptimeMillis(); 1634 long credits = (now - mCreationUptimeMs) / mMsPerCredit; 1635 1636 long available = credits - mSpent; 1637 if (available > mMaxCredits) { 1638 mSpent += available - mMaxCredits; 1639 available = credits - mSpent; 1640 } 1641 if (available > 0) { 1642 mSpent++; 1643 return true; 1644 } else { 1645 return false; 1646 } 1647 } 1648 } 1649 1650 private final class OperationLog { 1651 private static final int MAX_RECENT_OPERATIONS = 20; 1652 private static final int COOKIE_GENERATION_SHIFT = 8; 1653 private static final int COOKIE_INDEX_MASK = 0xff; 1654 1655 // Operations over 2s are long. Save the last ten. 1656 private static final long LONG_OPERATION_THRESHOLD_MS = 2_000; 1657 private static final int MAX_LONG_OPERATIONS = 10; 1658 1659 private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS]; 1660 private int mIndex = -1; 1661 private int mGeneration = 0; 1662 private final Operation mTransaction = new Operation(); 1663 private long mResultLong = Long.MIN_VALUE; 1664 private String mResultString; 1665 1666 private final RingBuffer<Operation> mLongOperations = 1667 new RingBuffer<>(()->{return new Operation();}, 1668 (n) ->{return new Operation[n];}, 1669 MAX_LONG_OPERATIONS); 1670 private int mTotalLongOperations = 0; 1671 1672 // Limit log messages to one every 5 minutes, except that a burst may be 10 messages long. 1673 private final RateLimiter mLongLimiter = new RateLimiter(300_000, 10); 1674 beginOperation(String kind, String sql, Object[] bindArgs)1675 public int beginOperation(String kind, String sql, Object[] bindArgs) { 1676 mResultLong = Long.MIN_VALUE; 1677 mResultString = null; 1678 1679 synchronized (mOperations) { 1680 Operation operation = newOperationLocked(); 1681 operation.mKind = kind; 1682 operation.mSql = sql; 1683 if (bindArgs != null) { 1684 if (operation.mBindArgs == null) { 1685 operation.mBindArgs = new ArrayList<Object>(); 1686 } 1687 for (int i = 0; i < bindArgs.length; i++) { 1688 final Object arg = bindArgs[i]; 1689 if (arg != null && arg instanceof byte[]) { 1690 // Don't hold onto the real byte array longer than necessary. 1691 operation.mBindArgs.add(EMPTY_BYTE_ARRAY); 1692 } else { 1693 operation.mBindArgs.add(arg); 1694 } 1695 } 1696 } 1697 operation.mTraced = Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE); 1698 if (operation.mTraced) { 1699 Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(), 1700 operation.mCookie); 1701 } 1702 return operation.mCookie; 1703 } 1704 } 1705 beginTransaction(String kind)1706 public void beginTransaction(String kind) { 1707 synchronized (mOperations) { 1708 Operation operation = newOperationLocked(); 1709 operation.mKind = kind; 1710 mTransaction.copyFrom(operation); 1711 1712 if (operation.mTraced) { 1713 Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(), 1714 operation.mCookie); 1715 } 1716 } 1717 } 1718 1719 /** 1720 * Fetch a new operation from the ring buffer. The operation is properly initialized. 1721 * This advances mIndex to point to the next element. 1722 */ newOperationLocked()1723 private Operation newOperationLocked() { 1724 final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS; 1725 Operation operation = mOperations[index]; 1726 if (operation == null) { 1727 mOperations[index] = new Operation(); 1728 operation = mOperations[index]; 1729 } 1730 operation.start(); 1731 operation.mCookie = newOperationCookieLocked(index); 1732 mIndex = index; 1733 return operation; 1734 } 1735 failOperation(int cookie, Exception ex)1736 public void failOperation(int cookie, Exception ex) { 1737 synchronized (mOperations) { 1738 final Operation operation = getOperationLocked(cookie); 1739 if (operation != null) { 1740 operation.mException = ex; 1741 } 1742 } 1743 } 1744 endOperation(int cookie)1745 public void endOperation(int cookie) { 1746 synchronized (mOperations) { 1747 if (endOperationDeferLogLocked(cookie)) { 1748 logOperationLocked(cookie, null); 1749 } 1750 } 1751 } 1752 endOperationDeferLog(int cookie)1753 public boolean endOperationDeferLog(int cookie) { 1754 synchronized (mOperations) { 1755 return endOperationDeferLogLocked(cookie); 1756 } 1757 } 1758 endTransaction(boolean success)1759 public boolean endTransaction(boolean success) { 1760 synchronized (mOperations) { 1761 mTransaction.mResultLong = success ? 1 : 0; 1762 final long execTime = finishOperationLocked(mTransaction); 1763 final Operation operation = getOperationLocked(mTransaction.mCookie); 1764 if (operation != null) { 1765 operation.copyFrom(mTransaction); 1766 } 1767 mTransaction.setEmpty(); 1768 return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES 1769 && SQLiteDebug.shouldLogSlowQuery(execTime); 1770 } 1771 } 1772 logOperation(int cookie, String detail)1773 public void logOperation(int cookie, String detail) { 1774 synchronized (mOperations) { 1775 logOperationLocked(cookie, detail); 1776 } 1777 } 1778 setResult(long longResult)1779 public void setResult(long longResult) { 1780 mResultLong = longResult; 1781 } 1782 setResult(String stringResult)1783 public void setResult(String stringResult) { 1784 mResultString = stringResult; 1785 } 1786 endOperationDeferLogLocked(int cookie)1787 private boolean endOperationDeferLogLocked(int cookie) { 1788 final Operation operation = getOperationLocked(cookie); 1789 if (operation != null) { 1790 if (operation.mTraced) { 1791 Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(), 1792 operation.mCookie); 1793 } 1794 final long execTime = finishOperationLocked(operation); 1795 mPool.onStatementExecuted(execTime); 1796 return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery( 1797 execTime); 1798 } 1799 return false; 1800 } 1801 logOperationLocked(int cookie, String detail)1802 private void logOperationLocked(int cookie, String detail) { 1803 final Operation operation = getOperationLocked(cookie); 1804 operation.mResultLong = mResultLong; 1805 operation.mResultString = mResultString; 1806 StringBuilder msg = new StringBuilder(); 1807 operation.describe(msg, true); 1808 if (detail != null) { 1809 msg.append(", ").append(detail); 1810 } 1811 Log.d(TAG, msg.toString()); 1812 } 1813 newOperationCookieLocked(int index)1814 private int newOperationCookieLocked(int index) { 1815 final int generation = mGeneration++; 1816 return generation << COOKIE_GENERATION_SHIFT | index; 1817 } 1818 1819 /** Close out the operation and return the elapsed time. */ finishOperationLocked(Operation operation)1820 private long finishOperationLocked(Operation operation) { 1821 operation.mEndTime = SystemClock.uptimeMillis(); 1822 operation.mFinished = true; 1823 final long elapsed = operation.mEndTime - operation.mStartTime; 1824 if (elapsed > LONG_OPERATION_THRESHOLD_MS) { 1825 mLongOperations.getNextSlot().copyFrom(operation); 1826 mTotalLongOperations++; 1827 if (mLongLimiter.tryAcquire()) { 1828 Log.i(TAG, "Long db operation: " + mConfiguration.label); 1829 } 1830 } 1831 return elapsed; 1832 } 1833 getOperationLocked(int cookie)1834 private Operation getOperationLocked(int cookie) { 1835 final int index = cookie & COOKIE_INDEX_MASK; 1836 final Operation operation = mOperations[index]; 1837 return (operation != null && operation.mCookie == cookie) ? operation : null; 1838 } 1839 describeCurrentOperation()1840 public String describeCurrentOperation() { 1841 synchronized (mOperations) { 1842 final Operation operation = mOperations[mIndex]; 1843 if (operation != null && !operation.mFinished) { 1844 StringBuilder msg = new StringBuilder(); 1845 operation.describe(msg, false); 1846 return msg.toString(); 1847 } 1848 return null; 1849 } 1850 } 1851 1852 /** 1853 * Dump an Operation if it is not in the recent operations list. Return 1 if the 1854 * operation was dumped and 0 if not. 1855 */ dumpIfNotRecentLocked(Printer pw, Operation op, int counter)1856 private int dumpIfNotRecentLocked(Printer pw, Operation op, int counter) { 1857 if (op == null || op.isEmpty() || getOperationLocked(op.mCookie) != null) { 1858 return 0; 1859 } 1860 pw.println(op.describe(counter)); 1861 return 1; 1862 } 1863 dumpRecentLocked(Printer printer)1864 private void dumpRecentLocked(Printer printer) { 1865 synchronized (mOperations) { 1866 printer.println(" Most recently executed operations:"); 1867 int index = mIndex; 1868 if (index == 0) { 1869 printer.println(" <none>"); 1870 return; 1871 } 1872 1873 // Operations are dumped in order of most recent first. 1874 int counter = 0; 1875 int n = 0; 1876 Operation operation = mOperations[index]; 1877 do { 1878 printer.println(operation.describe(counter)); 1879 1880 if (index > 0) { 1881 index -= 1; 1882 } else { 1883 index = MAX_RECENT_OPERATIONS - 1; 1884 } 1885 n++; 1886 counter++; 1887 operation = mOperations[index]; 1888 } while (operation != null && n < MAX_RECENT_OPERATIONS); 1889 counter += dumpIfNotRecentLocked(printer, mTransaction, counter); 1890 } 1891 } 1892 dumpLongLocked(Printer printer)1893 private void dumpLongLocked(Printer printer) { 1894 printer.println(" Operations exceeding " + LONG_OPERATION_THRESHOLD_MS + "ms:"); 1895 if (mLongOperations.isEmpty()) { 1896 printer.println(" <none>"); 1897 return; 1898 } 1899 Operation[] longOps = mLongOperations.toArray(); 1900 for (int i = 0; i < longOps.length; i++) { 1901 if (longOps[i] != null) { 1902 printer.println(longOps[i].describe(i)); 1903 } 1904 } 1905 } 1906 getTotalLongOperations()1907 public long getTotalLongOperations() { 1908 return mTotalLongOperations; 1909 } 1910 dump(Printer printer)1911 public void dump(Printer printer) { 1912 synchronized (mOperations) { 1913 dumpRecentLocked(printer); 1914 dumpLongLocked(printer); 1915 } 1916 } 1917 } 1918 1919 private final class Operation { 1920 // Trim all SQL statements to 256 characters inside the trace marker. 1921 // This limit gives plenty of context while leaving space for other 1922 // entries in the trace buffer (and ensures atrace doesn't truncate the 1923 // marker for us, potentially losing metadata in the process). 1924 private static final int MAX_TRACE_METHOD_NAME_LEN = 256; 1925 1926 // The reserved start time that indicates the Operation is empty. 1927 private static final long EMPTY_OPERATION = -1; 1928 1929 // The formatter for the timestamp. 1930 private static final DateTimeFormatter sDateTime = 1931 DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS", Locale.US); 1932 1933 public long mStartWallTime; // in System.currentTimeMillis() 1934 public long mStartTime; // in SystemClock.uptimeMillis(); 1935 public long mEndTime; // in SystemClock.uptimeMillis(); 1936 public String mKind; 1937 public String mSql; 1938 public ArrayList<Object> mBindArgs; 1939 public boolean mFinished; 1940 public Exception mException; 1941 public int mCookie; 1942 public long mResultLong; // MIN_VALUE means "value not set". 1943 public String mResultString; 1944 public boolean mTraced; 1945 1946 /** Reset the object to begin a new operation. */ start()1947 void start() { 1948 mStartWallTime = System.currentTimeMillis(); 1949 mStartTime = SystemClock.uptimeMillis(); 1950 mEndTime = Long.MIN_VALUE; 1951 mKind = null; 1952 mSql = null; 1953 if (mBindArgs != null) mBindArgs.clear(); 1954 mFinished = false; 1955 mException = null; 1956 mCookie = -1; 1957 mResultLong = Long.MIN_VALUE; 1958 mResultString = null; 1959 mTraced = false; 1960 } 1961 1962 /** 1963 * Initialize from the source object. This is meant to clone the object for use in a 1964 * transaction operation. To that end, the local bind args are set to null. 1965 */ copyFrom(Operation r)1966 void copyFrom(Operation r) { 1967 mStartWallTime = r.mStartWallTime; 1968 mStartTime = r.mStartTime; 1969 mEndTime = r.mEndTime; 1970 mKind = r.mKind; 1971 mSql = r.mSql; 1972 mBindArgs = null; 1973 mFinished = r.mFinished; 1974 mException = r.mException; 1975 mCookie = r.mCookie; 1976 mResultLong = r.mResultLong; 1977 mResultString = r.mResultString; 1978 mTraced = r.mTraced; 1979 } 1980 1981 /** Mark the operation empty. */ setEmpty()1982 void setEmpty() { 1983 mStartWallTime = EMPTY_OPERATION; 1984 } 1985 1986 /** Return true if the operation is empty. */ isEmpty()1987 boolean isEmpty() { 1988 return mStartWallTime == EMPTY_OPERATION; 1989 } 1990 describe(StringBuilder msg, boolean allowDetailedLog)1991 public void describe(StringBuilder msg, boolean allowDetailedLog) { 1992 msg.append(mKind); 1993 if (mFinished) { 1994 msg.append(" took ").append(mEndTime - mStartTime).append("ms"); 1995 } else { 1996 msg.append(" started ").append(SystemClock.uptimeMillis() - mStartTime) 1997 .append("ms ago"); 1998 } 1999 msg.append(" - ").append(getStatus()); 2000 if (mSql != null) { 2001 msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\""); 2002 } 2003 final boolean dumpDetails = allowDetailedLog && NoPreloadHolder.DEBUG_LOG_DETAILED 2004 && mBindArgs != null && mBindArgs.size() != 0; 2005 if (dumpDetails) { 2006 msg.append(", bindArgs=["); 2007 final int count = mBindArgs.size(); 2008 for (int i = 0; i < count; i++) { 2009 final Object arg = mBindArgs.get(i); 2010 if (i != 0) { 2011 msg.append(", "); 2012 } 2013 if (arg == null) { 2014 msg.append("null"); 2015 } else if (arg instanceof byte[]) { 2016 msg.append("<byte[]>"); 2017 } else if (arg instanceof String) { 2018 msg.append("\"").append((String)arg).append("\""); 2019 } else { 2020 msg.append(arg); 2021 } 2022 } 2023 msg.append("]"); 2024 } 2025 msg.append(", path=").append(mPool.getPath()); 2026 if (mException != null) { 2027 msg.append(", exception=\"").append(mException.getMessage()).append("\""); 2028 } 2029 if (mResultLong != Long.MIN_VALUE) { 2030 msg.append(", result=").append(mResultLong); 2031 } 2032 if (mResultString != null) { 2033 msg.append(", result=\"").append(mResultString).append("\""); 2034 } 2035 } 2036 2037 /** 2038 * Convert a wall-clock time in milliseconds to logcat format. 2039 */ timeString(long millis)2040 private String timeString(long millis) { 2041 return sDateTime.withZone(ZoneId.systemDefault()).format(Instant.ofEpochMilli(millis)); 2042 } 2043 describe(int n)2044 public String describe(int n) { 2045 final StringBuilder msg = new StringBuilder(); 2046 final String start = timeString(mStartWallTime); 2047 msg.append(" ").append(n).append(": [").append(start).append("] "); 2048 describe(msg, false); // Never dump bindargs in a bugreport 2049 return msg.toString(); 2050 } 2051 getStatus()2052 private String getStatus() { 2053 if (!mFinished) { 2054 return "running"; 2055 } 2056 return mException != null ? "failed" : "succeeded"; 2057 } 2058 getTraceMethodName()2059 private String getTraceMethodName() { 2060 String methodName = mKind + " " + mSql; 2061 if (methodName.length() > MAX_TRACE_METHOD_NAME_LEN) 2062 return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN); 2063 return methodName; 2064 } 2065 } 2066 2067 /** 2068 * Return the ROWID of the last row to be inserted under this connection. Returns 0 if there 2069 * has never been an insert on this connection. 2070 * @return The ROWID of the last row to be inserted under this connection. 2071 * @hide 2072 */ getLastInsertRowId()2073 long getLastInsertRowId() { 2074 try { 2075 return nativeLastInsertRowId(mConnectionPtr); 2076 } finally { 2077 Reference.reachabilityFence(this); 2078 } 2079 } 2080 2081 /** 2082 * Return the number of database changes on the current connection made by the last SQL 2083 * statement 2084 * @hide 2085 */ getLastChangedRowCount()2086 long getLastChangedRowCount() { 2087 try { 2088 return nativeChanges(mConnectionPtr); 2089 } finally { 2090 Reference.reachabilityFence(this); 2091 } 2092 } 2093 2094 /** 2095 * Return the total number of database changes made on the current connection. 2096 * @hide 2097 */ getTotalChangedRowCount()2098 long getTotalChangedRowCount() { 2099 try { 2100 return nativeTotalChanges(mConnectionPtr); 2101 } finally { 2102 Reference.reachabilityFence(this); 2103 } 2104 } 2105 } 2106