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 120 private static native long nativeOpen(String path, int openFlags, String label, 121 boolean enableTrace, boolean enableProfile, int lookasideSlotSize, 122 int lookasideSlotCount); 123 private static native void nativeClose(long connectionPtr); 124 private static native void nativeRegisterCustomFunction(long connectionPtr, 125 SQLiteCustomFunction function); 126 private static native void nativeRegisterLocalizedCollators(long connectionPtr, String locale); 127 private static native long nativePrepareStatement(long connectionPtr, String sql); 128 private static native void nativeFinalizeStatement(long connectionPtr, long statementPtr); 129 private static native int nativeGetParameterCount(long connectionPtr, long statementPtr); 130 private static native boolean nativeIsReadOnly(long connectionPtr, long statementPtr); 131 private static native int nativeGetColumnCount(long connectionPtr, long statementPtr); 132 private static native String nativeGetColumnName(long connectionPtr, long statementPtr, 133 int index); 134 private static native void nativeBindNull(long connectionPtr, long statementPtr, 135 int index); 136 private static native void nativeBindLong(long connectionPtr, long statementPtr, 137 int index, long value); 138 private static native void nativeBindDouble(long connectionPtr, long statementPtr, 139 int index, double value); 140 private static native void nativeBindString(long connectionPtr, long statementPtr, 141 int index, String value); 142 private static native void nativeBindBlob(long connectionPtr, long statementPtr, 143 int index, byte[] value); 144 private static native void nativeResetStatementAndClearBindings( 145 long connectionPtr, long statementPtr); 146 private static native void nativeExecute(long connectionPtr, long statementPtr); 147 private static native long nativeExecuteForLong(long connectionPtr, long statementPtr); 148 private static native String nativeExecuteForString(long connectionPtr, long statementPtr); 149 private static native int nativeExecuteForBlobFileDescriptor( 150 long connectionPtr, long statementPtr); 151 private static native int nativeExecuteForChangedRowCount(long connectionPtr, long statementPtr); 152 private static native long nativeExecuteForLastInsertedRowId( 153 long connectionPtr, long statementPtr); 154 private static native long nativeExecuteForCursorWindow( 155 long connectionPtr, long statementPtr, long windowPtr, 156 int startPos, int requiredPos, boolean countAllRows); 157 private static native int nativeGetDbLookaside(long connectionPtr); 158 private static native void nativeCancel(long connectionPtr); 159 private static native void nativeResetCancel(long connectionPtr, boolean cancelable); 160 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 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. 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. 206 void close() { 207 dispose(false); 208 } 209 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 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 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 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 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 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 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 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 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 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 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. 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. 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. 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 948 private void finalizePreparedStatement(PreparedStatement statement) { 949 nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr); 950 recyclePreparedStatement(statement); 951 } 952 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 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 989 public void onCancel() { 990 nativeCancel(mConnectionPtr); 991 } 992 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 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 1173 void collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList) { 1174 dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0)); 1175 } 1176 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 1191 public String toString() { 1192 return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")"; 1193 } 1194 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 1213 private void recyclePreparedStatement(PreparedStatement statement) { 1214 statement.mSql = null; 1215 statement.mPoolNext = mPreparedStatementPool; 1216 mPreparedStatementPool = statement; 1217 } 1218 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> { 1269 public PreparedStatementCache(int size) { 1270 super(size); 1271 } 1272 1273 @Override 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 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 1316 OperationLog(SQLiteConnectionPool pool) { 1317 mPool = pool; 1318 } 1319 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 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 1373 public void endOperation(int cookie) { 1374 synchronized (mOperations) { 1375 if (endOperationDeferLogLocked(cookie)) { 1376 logOperationLocked(cookie, null); 1377 } 1378 } 1379 } 1380 1381 public boolean endOperationDeferLog(int cookie) { 1382 synchronized (mOperations) { 1383 return endOperationDeferLogLocked(cookie); 1384 } 1385 } 1386 1387 public void logOperation(int cookie, String detail) { 1388 synchronized (mOperations) { 1389 logOperationLocked(cookie, detail); 1390 } 1391 } 1392 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 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 1420 private int newOperationCookieLocked(int index) { 1421 final int generation = mGeneration++; 1422 return generation << COOKIE_GENERATION_SHIFT | index; 1423 } 1424 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 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 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 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 1532 private String getStatus() { 1533 if (!mFinished) { 1534 return "running"; 1535 } 1536 return mException != null ? "failed" : "succeeded"; 1537 } 1538 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