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