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