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