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