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