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