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 PreparedStatement statement = mPreparedStatementCache.get(sql); 1057 boolean skipCache = false; 1058 if (statement != null) { 1059 if (!statement.mInUse) { 1060 return statement; 1061 } 1062 // The statement is already in the cache but is in use (this statement appears 1063 // to be not only re-entrant but recursive!). So prepare a new copy of the 1064 // statement but do not cache it. 1065 skipCache = true; 1066 } 1067 1068 final long statementPtr = nativePrepareStatement(mConnectionPtr, sql); 1069 try { 1070 final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr); 1071 final int type = DatabaseUtils.getSqlStatementType(sql); 1072 final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr); 1073 statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly); 1074 if (!skipCache && isCacheable(type)) { 1075 mPreparedStatementCache.put(sql, statement); 1076 statement.mInCache = true; 1077 } 1078 } catch (RuntimeException ex) { 1079 // Finalize the statement if an exception occurred and we did not add 1080 // it to the cache. If it is already in the cache, then leave it there. 1081 if (statement == null || !statement.mInCache) { 1082 nativeFinalizeStatement(mConnectionPtr, statementPtr); 1083 } 1084 throw ex; 1085 } 1086 statement.mInUse = true; 1087 return statement; 1088 } 1089 releasePreparedStatement(PreparedStatement statement)1090 private void releasePreparedStatement(PreparedStatement statement) { 1091 statement.mInUse = false; 1092 if (statement.mInCache) { 1093 try { 1094 nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr); 1095 } catch (SQLiteException ex) { 1096 // The statement could not be reset due to an error. Remove it from the cache. 1097 // When remove() is called, the cache will invoke its entryRemoved() callback, 1098 // which will in turn call finalizePreparedStatement() to finalize and 1099 // recycle the statement. 1100 if (DEBUG) { 1101 Log.d(TAG, "Could not reset prepared statement due to an exception. " 1102 + "Removing it from the cache. SQL: " 1103 + trimSqlForDisplay(statement.mSql), ex); 1104 } 1105 1106 mPreparedStatementCache.remove(statement.mSql); 1107 } 1108 } else { 1109 finalizePreparedStatement(statement); 1110 } 1111 } 1112 finalizePreparedStatement(PreparedStatement statement)1113 private void finalizePreparedStatement(PreparedStatement statement) { 1114 nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr); 1115 recyclePreparedStatement(statement); 1116 } 1117 attachCancellationSignal(CancellationSignal cancellationSignal)1118 private void attachCancellationSignal(CancellationSignal cancellationSignal) { 1119 if (cancellationSignal != null) { 1120 cancellationSignal.throwIfCanceled(); 1121 1122 mCancellationSignalAttachCount += 1; 1123 if (mCancellationSignalAttachCount == 1) { 1124 // Reset cancellation flag before executing the statement. 1125 nativeResetCancel(mConnectionPtr, true /*cancelable*/); 1126 1127 // After this point, onCancel() may be called concurrently. 1128 cancellationSignal.setOnCancelListener(this); 1129 } 1130 } 1131 } 1132 detachCancellationSignal(CancellationSignal cancellationSignal)1133 private void detachCancellationSignal(CancellationSignal cancellationSignal) { 1134 if (cancellationSignal != null) { 1135 assert mCancellationSignalAttachCount > 0; 1136 1137 mCancellationSignalAttachCount -= 1; 1138 if (mCancellationSignalAttachCount == 0) { 1139 // After this point, onCancel() cannot be called concurrently. 1140 cancellationSignal.setOnCancelListener(null); 1141 1142 // Reset cancellation flag after executing the statement. 1143 nativeResetCancel(mConnectionPtr, false /*cancelable*/); 1144 } 1145 } 1146 } 1147 1148 // CancellationSignal.OnCancelListener callback. 1149 // This method may be called on a different thread than the executing statement. 1150 // However, it will only be called between calls to attachCancellationSignal and 1151 // detachCancellationSignal, while a statement is executing. We can safely assume 1152 // that the SQLite connection is still alive. 1153 @Override onCancel()1154 public void onCancel() { 1155 nativeCancel(mConnectionPtr); 1156 } 1157 bindArguments(PreparedStatement statement, Object[] bindArgs)1158 private void bindArguments(PreparedStatement statement, Object[] bindArgs) { 1159 final int count = bindArgs != null ? bindArgs.length : 0; 1160 if (count != statement.mNumParameters) { 1161 throw new SQLiteBindOrColumnIndexOutOfRangeException( 1162 "Expected " + statement.mNumParameters + " bind arguments but " 1163 + count + " were provided."); 1164 } 1165 if (count == 0) { 1166 return; 1167 } 1168 1169 final long statementPtr = statement.mStatementPtr; 1170 for (int i = 0; i < count; i++) { 1171 final Object arg = bindArgs[i]; 1172 switch (DatabaseUtils.getTypeOfObject(arg)) { 1173 case Cursor.FIELD_TYPE_NULL: 1174 nativeBindNull(mConnectionPtr, statementPtr, i + 1); 1175 break; 1176 case Cursor.FIELD_TYPE_INTEGER: 1177 nativeBindLong(mConnectionPtr, statementPtr, i + 1, 1178 ((Number)arg).longValue()); 1179 break; 1180 case Cursor.FIELD_TYPE_FLOAT: 1181 nativeBindDouble(mConnectionPtr, statementPtr, i + 1, 1182 ((Number)arg).doubleValue()); 1183 break; 1184 case Cursor.FIELD_TYPE_BLOB: 1185 nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg); 1186 break; 1187 case Cursor.FIELD_TYPE_STRING: 1188 default: 1189 if (arg instanceof Boolean) { 1190 // Provide compatibility with legacy applications which may pass 1191 // Boolean values in bind args. 1192 nativeBindLong(mConnectionPtr, statementPtr, i + 1, 1193 ((Boolean)arg).booleanValue() ? 1 : 0); 1194 } else { 1195 nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString()); 1196 } 1197 break; 1198 } 1199 } 1200 } 1201 throwIfStatementForbidden(PreparedStatement statement)1202 private void throwIfStatementForbidden(PreparedStatement statement) { 1203 if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) { 1204 throw new SQLiteException("Cannot execute this statement because it " 1205 + "might modify the database but the connection is read-only."); 1206 } 1207 } 1208 isCacheable(int statementType)1209 private static boolean isCacheable(int statementType) { 1210 if (statementType == DatabaseUtils.STATEMENT_UPDATE 1211 || statementType == DatabaseUtils.STATEMENT_SELECT) { 1212 return true; 1213 } 1214 return false; 1215 } 1216 applyBlockGuardPolicy(PreparedStatement statement)1217 private void applyBlockGuardPolicy(PreparedStatement statement) { 1218 if (!mConfiguration.isInMemoryDb()) { 1219 if (statement.mReadOnly) { 1220 BlockGuard.getThreadPolicy().onReadFromDisk(); 1221 } else { 1222 BlockGuard.getThreadPolicy().onWriteToDisk(); 1223 } 1224 } 1225 } 1226 1227 /** 1228 * Dumps debugging information about this connection. 1229 * 1230 * @param printer The printer to receive the dump, not null. 1231 * @param verbose True to dump more verbose information. 1232 */ dump(Printer printer, boolean verbose)1233 public void dump(Printer printer, boolean verbose) { 1234 dumpUnsafe(printer, verbose); 1235 } 1236 1237 /** 1238 * Dumps debugging information about this connection, in the case where the 1239 * caller might not actually own the connection. 1240 * 1241 * This function is written so that it may be called by a thread that does not 1242 * own the connection. We need to be very careful because the connection state is 1243 * not synchronized. 1244 * 1245 * At worst, the method may return stale or slightly wrong data, however 1246 * it should not crash. This is ok as it is only used for diagnostic purposes. 1247 * 1248 * @param printer The printer to receive the dump, not null. 1249 * @param verbose True to dump more verbose information. 1250 */ dumpUnsafe(Printer printer, boolean verbose)1251 void dumpUnsafe(Printer printer, boolean verbose) { 1252 printer.println("Connection #" + mConnectionId + ":"); 1253 if (verbose) { 1254 printer.println(" connectionPtr: 0x" + Long.toHexString(mConnectionPtr)); 1255 } 1256 printer.println(" isPrimaryConnection: " + mIsPrimaryConnection); 1257 printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations); 1258 1259 mRecentOperations.dump(printer); 1260 1261 if (verbose) { 1262 mPreparedStatementCache.dump(printer); 1263 } 1264 } 1265 1266 /** 1267 * Describes the currently executing operation, in the case where the 1268 * caller might not actually own the connection. 1269 * 1270 * This function is written so that it may be called by a thread that does not 1271 * own the connection. We need to be very careful because the connection state is 1272 * not synchronized. 1273 * 1274 * At worst, the method may return stale or slightly wrong data, however 1275 * it should not crash. This is ok as it is only used for diagnostic purposes. 1276 * 1277 * @return A description of the current operation including how long it has been running, 1278 * or null if none. 1279 */ describeCurrentOperationUnsafe()1280 String describeCurrentOperationUnsafe() { 1281 return mRecentOperations.describeCurrentOperation(); 1282 } 1283 1284 /** 1285 * Collects statistics about database connection memory usage. 1286 * 1287 * @param dbStatsList The list to populate. 1288 */ collectDbStats(ArrayList<DbStats> dbStatsList)1289 void collectDbStats(ArrayList<DbStats> dbStatsList) { 1290 // Get information about the main database. 1291 int lookaside = nativeGetDbLookaside(mConnectionPtr); 1292 long pageCount = 0; 1293 long pageSize = 0; 1294 try { 1295 pageCount = executeForLong("PRAGMA page_count;", null, null); 1296 pageSize = executeForLong("PRAGMA page_size;", null, null); 1297 } catch (SQLiteException ex) { 1298 // Ignore. 1299 } 1300 dbStatsList.add(getMainDbStatsUnsafe(lookaside, pageCount, pageSize)); 1301 1302 // Get information about attached databases. 1303 // We ignore the first row in the database list because it corresponds to 1304 // the main database which we have already described. 1305 CursorWindow window = new CursorWindow("collectDbStats"); 1306 try { 1307 executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null); 1308 for (int i = 1; i < window.getNumRows(); i++) { 1309 String name = window.getString(i, 1); 1310 String path = window.getString(i, 2); 1311 pageCount = 0; 1312 pageSize = 0; 1313 try { 1314 pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null); 1315 pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null); 1316 } catch (SQLiteException ex) { 1317 // Ignore. 1318 } 1319 StringBuilder label = new StringBuilder(" (attached) ").append(name); 1320 if (!path.isEmpty()) { 1321 label.append(": ").append(path); 1322 } 1323 dbStatsList.add(new DbStats(label.toString(), pageCount, pageSize, 0, 0, 0, 0)); 1324 } 1325 } catch (SQLiteException ex) { 1326 // Ignore. 1327 } finally { 1328 window.close(); 1329 } 1330 } 1331 1332 /** 1333 * Collects statistics about database connection memory usage, in the case where the 1334 * caller might not actually own the connection. 1335 * 1336 * @return The statistics object, never null. 1337 */ collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList)1338 void collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList) { 1339 dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0)); 1340 } 1341 getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize)1342 private DbStats getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize) { 1343 // The prepared statement cache is thread-safe so we can access its statistics 1344 // even if we do not own the database connection. 1345 String label; 1346 if (mIsPrimaryConnection) { 1347 label = mConfiguration.path; 1348 } else { 1349 label = mConfiguration.path + " (" + mConnectionId + ")"; 1350 } 1351 return new DbStats(label, pageCount, pageSize, lookaside, 1352 mPreparedStatementCache.hitCount(), 1353 mPreparedStatementCache.missCount(), 1354 mPreparedStatementCache.size()); 1355 } 1356 1357 @Override toString()1358 public String toString() { 1359 return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")"; 1360 } 1361 obtainPreparedStatement(String sql, long statementPtr, int numParameters, int type, boolean readOnly)1362 private PreparedStatement obtainPreparedStatement(String sql, long statementPtr, 1363 int numParameters, int type, boolean readOnly) { 1364 PreparedStatement statement = mPreparedStatementPool; 1365 if (statement != null) { 1366 mPreparedStatementPool = statement.mPoolNext; 1367 statement.mPoolNext = null; 1368 statement.mInCache = false; 1369 } else { 1370 statement = new PreparedStatement(); 1371 } 1372 statement.mSql = sql; 1373 statement.mStatementPtr = statementPtr; 1374 statement.mNumParameters = numParameters; 1375 statement.mType = type; 1376 statement.mReadOnly = readOnly; 1377 return statement; 1378 } 1379 recyclePreparedStatement(PreparedStatement statement)1380 private void recyclePreparedStatement(PreparedStatement statement) { 1381 statement.mSql = null; 1382 statement.mPoolNext = mPreparedStatementPool; 1383 mPreparedStatementPool = statement; 1384 } 1385 trimSqlForDisplay(String sql)1386 private static String trimSqlForDisplay(String sql) { 1387 // Note: Creating and caching a regular expression is expensive at preload-time 1388 // and stops compile-time initialization. This pattern is only used when 1389 // dumping the connection, which is a rare (mainly error) case. So: 1390 // DO NOT CACHE. 1391 return sql.replaceAll("[\\s]*\\n+[\\s]*", " "); 1392 } 1393 1394 /** 1395 * Holder type for a prepared statement. 1396 * 1397 * Although this object holds a pointer to a native statement object, it 1398 * does not have a finalizer. This is deliberate. The {@link SQLiteConnection} 1399 * owns the statement object and will take care of freeing it when needed. 1400 * In particular, closing the connection requires a guarantee of deterministic 1401 * resource disposal because all native statement objects must be freed before 1402 * the native database object can be closed. So no finalizers here. 1403 */ 1404 private static final class PreparedStatement { 1405 // Next item in pool. 1406 public PreparedStatement mPoolNext; 1407 1408 // The SQL from which the statement was prepared. 1409 public String mSql; 1410 1411 // The native sqlite3_stmt object pointer. 1412 // Lifetime is managed explicitly by the connection. 1413 public long mStatementPtr; 1414 1415 // The number of parameters that the prepared statement has. 1416 public int mNumParameters; 1417 1418 // The statement type. 1419 public int mType; 1420 1421 // True if the statement is read-only. 1422 public boolean mReadOnly; 1423 1424 // True if the statement is in the cache. 1425 public boolean mInCache; 1426 1427 // True if the statement is in use (currently executing). 1428 // We need this flag because due to the use of custom functions in triggers, it's 1429 // possible for SQLite calls to be re-entrant. Consequently we need to prevent 1430 // in use statements from being finalized until they are no longer in use. 1431 public boolean mInUse; 1432 } 1433 1434 private final class PreparedStatementCache 1435 extends LruCache<String, PreparedStatement> { PreparedStatementCache(int size)1436 public PreparedStatementCache(int size) { 1437 super(size); 1438 } 1439 1440 @Override entryRemoved(boolean evicted, String key, PreparedStatement oldValue, PreparedStatement newValue)1441 protected void entryRemoved(boolean evicted, String key, 1442 PreparedStatement oldValue, PreparedStatement newValue) { 1443 oldValue.mInCache = false; 1444 if (!oldValue.mInUse) { 1445 finalizePreparedStatement(oldValue); 1446 } 1447 } 1448 dump(Printer printer)1449 public void dump(Printer printer) { 1450 printer.println(" Prepared statement cache:"); 1451 Map<String, PreparedStatement> cache = snapshot(); 1452 if (!cache.isEmpty()) { 1453 int i = 0; 1454 for (Map.Entry<String, PreparedStatement> entry : cache.entrySet()) { 1455 PreparedStatement statement = entry.getValue(); 1456 if (statement.mInCache) { // might be false due to a race with entryRemoved 1457 String sql = entry.getKey(); 1458 printer.println(" " + i + ": statementPtr=0x" 1459 + Long.toHexString(statement.mStatementPtr) 1460 + ", numParameters=" + statement.mNumParameters 1461 + ", type=" + statement.mType 1462 + ", readOnly=" + statement.mReadOnly 1463 + ", sql=\"" + trimSqlForDisplay(sql) + "\""); 1464 } 1465 i += 1; 1466 } 1467 } else { 1468 printer.println(" <none>"); 1469 } 1470 } 1471 } 1472 1473 private static final class OperationLog { 1474 private static final int MAX_RECENT_OPERATIONS = 20; 1475 private static final int COOKIE_GENERATION_SHIFT = 8; 1476 private static final int COOKIE_INDEX_MASK = 0xff; 1477 1478 private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS]; 1479 private int mIndex; 1480 private int mGeneration; 1481 private final SQLiteConnectionPool mPool; 1482 private long mResultLong = Long.MIN_VALUE; 1483 private String mResultString; 1484 OperationLog(SQLiteConnectionPool pool)1485 OperationLog(SQLiteConnectionPool pool) { 1486 mPool = pool; 1487 } 1488 beginOperation(String kind, String sql, Object[] bindArgs)1489 public int beginOperation(String kind, String sql, Object[] bindArgs) { 1490 mResultLong = Long.MIN_VALUE; 1491 mResultString = null; 1492 1493 synchronized (mOperations) { 1494 final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS; 1495 Operation operation = mOperations[index]; 1496 if (operation == null) { 1497 operation = new Operation(); 1498 mOperations[index] = operation; 1499 } else { 1500 operation.mFinished = false; 1501 operation.mException = null; 1502 if (operation.mBindArgs != null) { 1503 operation.mBindArgs.clear(); 1504 } 1505 } 1506 operation.mStartWallTime = System.currentTimeMillis(); 1507 operation.mStartTime = SystemClock.uptimeMillis(); 1508 operation.mKind = kind; 1509 operation.mSql = sql; 1510 operation.mPath = mPool.getPath(); 1511 operation.mResultLong = Long.MIN_VALUE; 1512 operation.mResultString = null; 1513 if (bindArgs != null) { 1514 if (operation.mBindArgs == null) { 1515 operation.mBindArgs = new ArrayList<Object>(); 1516 } else { 1517 operation.mBindArgs.clear(); 1518 } 1519 for (int i = 0; i < bindArgs.length; i++) { 1520 final Object arg = bindArgs[i]; 1521 if (arg != null && arg instanceof byte[]) { 1522 // Don't hold onto the real byte array longer than necessary. 1523 operation.mBindArgs.add(EMPTY_BYTE_ARRAY); 1524 } else { 1525 operation.mBindArgs.add(arg); 1526 } 1527 } 1528 } 1529 operation.mCookie = newOperationCookieLocked(index); 1530 if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) { 1531 Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(), 1532 operation.mCookie); 1533 } 1534 mIndex = index; 1535 return operation.mCookie; 1536 } 1537 } 1538 failOperation(int cookie, Exception ex)1539 public void failOperation(int cookie, Exception ex) { 1540 synchronized (mOperations) { 1541 final Operation operation = getOperationLocked(cookie); 1542 if (operation != null) { 1543 operation.mException = ex; 1544 } 1545 } 1546 } 1547 endOperation(int cookie)1548 public void endOperation(int cookie) { 1549 synchronized (mOperations) { 1550 if (endOperationDeferLogLocked(cookie)) { 1551 logOperationLocked(cookie, null); 1552 } 1553 } 1554 } 1555 endOperationDeferLog(int cookie)1556 public boolean endOperationDeferLog(int cookie) { 1557 synchronized (mOperations) { 1558 return endOperationDeferLogLocked(cookie); 1559 } 1560 } 1561 logOperation(int cookie, String detail)1562 public void logOperation(int cookie, String detail) { 1563 synchronized (mOperations) { 1564 logOperationLocked(cookie, detail); 1565 } 1566 } 1567 setResult(long longResult)1568 public void setResult(long longResult) { 1569 mResultLong = longResult; 1570 } 1571 setResult(String stringResult)1572 public void setResult(String stringResult) { 1573 mResultString = stringResult; 1574 } 1575 endOperationDeferLogLocked(int cookie)1576 private boolean endOperationDeferLogLocked(int cookie) { 1577 final Operation operation = getOperationLocked(cookie); 1578 if (operation != null) { 1579 if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) { 1580 Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(), 1581 operation.mCookie); 1582 } 1583 operation.mEndTime = SystemClock.uptimeMillis(); 1584 operation.mFinished = true; 1585 final long execTime = operation.mEndTime - operation.mStartTime; 1586 mPool.onStatementExecuted(execTime); 1587 return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery( 1588 execTime); 1589 } 1590 return false; 1591 } 1592 logOperationLocked(int cookie, String detail)1593 private void logOperationLocked(int cookie, String detail) { 1594 final Operation operation = getOperationLocked(cookie); 1595 operation.mResultLong = mResultLong; 1596 operation.mResultString = mResultString; 1597 StringBuilder msg = new StringBuilder(); 1598 operation.describe(msg, true); 1599 if (detail != null) { 1600 msg.append(", ").append(detail); 1601 } 1602 Log.d(TAG, msg.toString()); 1603 } 1604 newOperationCookieLocked(int index)1605 private int newOperationCookieLocked(int index) { 1606 final int generation = mGeneration++; 1607 return generation << COOKIE_GENERATION_SHIFT | index; 1608 } 1609 getOperationLocked(int cookie)1610 private Operation getOperationLocked(int cookie) { 1611 final int index = cookie & COOKIE_INDEX_MASK; 1612 final Operation operation = mOperations[index]; 1613 return operation.mCookie == cookie ? operation : null; 1614 } 1615 describeCurrentOperation()1616 public String describeCurrentOperation() { 1617 synchronized (mOperations) { 1618 final Operation operation = mOperations[mIndex]; 1619 if (operation != null && !operation.mFinished) { 1620 StringBuilder msg = new StringBuilder(); 1621 operation.describe(msg, false); 1622 return msg.toString(); 1623 } 1624 return null; 1625 } 1626 } 1627 dump(Printer printer)1628 public void dump(Printer printer) { 1629 synchronized (mOperations) { 1630 printer.println(" Most recently executed operations:"); 1631 int index = mIndex; 1632 Operation operation = mOperations[index]; 1633 if (operation != null) { 1634 // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created, 1635 // and is relatively expensive to create during preloading. This method is only 1636 // used when dumping a connection, which is a rare (mainly error) case. 1637 SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 1638 int n = 0; 1639 do { 1640 StringBuilder msg = new StringBuilder(); 1641 msg.append(" ").append(n).append(": ["); 1642 String formattedStartTime = opDF.format(new Date(operation.mStartWallTime)); 1643 msg.append(formattedStartTime); 1644 msg.append("] "); 1645 operation.describe(msg, false); // Never dump bingargs in a bugreport 1646 printer.println(msg.toString()); 1647 1648 if (index > 0) { 1649 index -= 1; 1650 } else { 1651 index = MAX_RECENT_OPERATIONS - 1; 1652 } 1653 n += 1; 1654 operation = mOperations[index]; 1655 } while (operation != null && n < MAX_RECENT_OPERATIONS); 1656 } else { 1657 printer.println(" <none>"); 1658 } 1659 } 1660 } 1661 } 1662 1663 private static final class Operation { 1664 // Trim all SQL statements to 256 characters inside the trace marker. 1665 // This limit gives plenty of context while leaving space for other 1666 // entries in the trace buffer (and ensures atrace doesn't truncate the 1667 // marker for us, potentially losing metadata in the process). 1668 private static final int MAX_TRACE_METHOD_NAME_LEN = 256; 1669 1670 public long mStartWallTime; // in System.currentTimeMillis() 1671 public long mStartTime; // in SystemClock.uptimeMillis(); 1672 public long mEndTime; // in SystemClock.uptimeMillis(); 1673 public String mKind; 1674 public String mSql; 1675 public ArrayList<Object> mBindArgs; 1676 public boolean mFinished; 1677 public Exception mException; 1678 public int mCookie; 1679 public String mPath; 1680 public long mResultLong; // MIN_VALUE means "value not set". 1681 public String mResultString; 1682 describe(StringBuilder msg, boolean allowDetailedLog)1683 public void describe(StringBuilder msg, boolean allowDetailedLog) { 1684 msg.append(mKind); 1685 if (mFinished) { 1686 msg.append(" took ").append(mEndTime - mStartTime).append("ms"); 1687 } else { 1688 msg.append(" started ").append(System.currentTimeMillis() - mStartWallTime) 1689 .append("ms ago"); 1690 } 1691 msg.append(" - ").append(getStatus()); 1692 if (mSql != null) { 1693 msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\""); 1694 } 1695 final boolean dumpDetails = allowDetailedLog && NoPreloadHolder.DEBUG_LOG_DETAILED 1696 && mBindArgs != null && mBindArgs.size() != 0; 1697 if (dumpDetails) { 1698 msg.append(", bindArgs=["); 1699 final int count = mBindArgs.size(); 1700 for (int i = 0; i < count; i++) { 1701 final Object arg = mBindArgs.get(i); 1702 if (i != 0) { 1703 msg.append(", "); 1704 } 1705 if (arg == null) { 1706 msg.append("null"); 1707 } else if (arg instanceof byte[]) { 1708 msg.append("<byte[]>"); 1709 } else if (arg instanceof String) { 1710 msg.append("\"").append((String)arg).append("\""); 1711 } else { 1712 msg.append(arg); 1713 } 1714 } 1715 msg.append("]"); 1716 } 1717 msg.append(", path=").append(mPath); 1718 if (mException != null) { 1719 msg.append(", exception=\"").append(mException.getMessage()).append("\""); 1720 } 1721 if (mResultLong != Long.MIN_VALUE) { 1722 msg.append(", result=").append(mResultLong); 1723 } 1724 if (mResultString != null) { 1725 msg.append(", result=\"").append(mResultString).append("\""); 1726 } 1727 } 1728 getStatus()1729 private String getStatus() { 1730 if (!mFinished) { 1731 return "running"; 1732 } 1733 return mException != null ? "failed" : "succeeded"; 1734 } 1735 getTraceMethodName()1736 private String getTraceMethodName() { 1737 String methodName = mKind + " " + mSql; 1738 if (methodName.length() > MAX_TRACE_METHOD_NAME_LEN) 1739 return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN); 1740 return methodName; 1741 } 1742 1743 } 1744 } 1745