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