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