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