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