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 nativeOpen(String path, int openFlags, String label, boolean enableTrace, boolean enableProfile)120 private static native int nativeOpen(String path, int openFlags, String label, 121 boolean enableTrace, boolean enableProfile); nativeClose(int connectionPtr)122 private static native void nativeClose(int connectionPtr); nativeRegisterCustomFunction(int connectionPtr, SQLiteCustomFunction function)123 private static native void nativeRegisterCustomFunction(int connectionPtr, 124 SQLiteCustomFunction function); nativeRegisterLocalizedCollators(int connectionPtr, String locale)125 private static native void nativeRegisterLocalizedCollators(int connectionPtr, String locale); nativePrepareStatement(int connectionPtr, String sql)126 private static native int nativePrepareStatement(int connectionPtr, String sql); nativeFinalizeStatement(int connectionPtr, int statementPtr)127 private static native void nativeFinalizeStatement(int connectionPtr, int statementPtr); nativeGetParameterCount(int connectionPtr, int statementPtr)128 private static native int nativeGetParameterCount(int connectionPtr, int statementPtr); nativeIsReadOnly(int connectionPtr, int statementPtr)129 private static native boolean nativeIsReadOnly(int connectionPtr, int statementPtr); nativeGetColumnCount(int connectionPtr, int statementPtr)130 private static native int nativeGetColumnCount(int connectionPtr, int statementPtr); nativeGetColumnName(int connectionPtr, int statementPtr, int index)131 private static native String nativeGetColumnName(int connectionPtr, int statementPtr, 132 int index); nativeBindNull(int connectionPtr, int statementPtr, int index)133 private static native void nativeBindNull(int connectionPtr, int statementPtr, 134 int index); nativeBindLong(int connectionPtr, int statementPtr, int index, long value)135 private static native void nativeBindLong(int connectionPtr, int statementPtr, 136 int index, long value); nativeBindDouble(int connectionPtr, int statementPtr, int index, double value)137 private static native void nativeBindDouble(int connectionPtr, int statementPtr, 138 int index, double value); nativeBindString(int connectionPtr, int statementPtr, int index, String value)139 private static native void nativeBindString(int connectionPtr, int statementPtr, 140 int index, String value); nativeBindBlob(int connectionPtr, int statementPtr, int index, byte[] value)141 private static native void nativeBindBlob(int connectionPtr, int statementPtr, 142 int index, byte[] value); nativeResetStatementAndClearBindings( int connectionPtr, int statementPtr)143 private static native void nativeResetStatementAndClearBindings( 144 int connectionPtr, int statementPtr); nativeExecute(int connectionPtr, int statementPtr)145 private static native void nativeExecute(int connectionPtr, int statementPtr); nativeExecuteForLong(int connectionPtr, int statementPtr)146 private static native long nativeExecuteForLong(int connectionPtr, int statementPtr); nativeExecuteForString(int connectionPtr, int statementPtr)147 private static native String nativeExecuteForString(int connectionPtr, int statementPtr); nativeExecuteForBlobFileDescriptor( int connectionPtr, int statementPtr)148 private static native int nativeExecuteForBlobFileDescriptor( 149 int connectionPtr, int statementPtr); nativeExecuteForChangedRowCount(int connectionPtr, int statementPtr)150 private static native int nativeExecuteForChangedRowCount(int connectionPtr, int statementPtr); nativeExecuteForLastInsertedRowId( int connectionPtr, int statementPtr)151 private static native long nativeExecuteForLastInsertedRowId( 152 int connectionPtr, int statementPtr); nativeExecuteForCursorWindow( int connectionPtr, int statementPtr, int windowPtr, int startPos, int requiredPos, boolean countAllRows)153 private static native long nativeExecuteForCursorWindow( 154 int connectionPtr, int statementPtr, int windowPtr, 155 int startPos, int requiredPos, boolean countAllRows); nativeGetDbLookaside(int connectionPtr)156 private static native int nativeGetDbLookaside(int connectionPtr); nativeCancel(int connectionPtr)157 private static native void nativeCancel(int connectionPtr); nativeResetCancel(int connectionPtr, boolean cancelable)158 private static native void nativeResetCancel(int connectionPtr, boolean cancelable); 159 SQLiteConnection(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration, int connectionId, boolean primaryConnection)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 finalize()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. open(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration, int connectionId, boolean primaryConnection)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. close()204 void close() { 205 dispose(false); 206 } 207 open()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 dispose(boolean finalized)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 setPageSize()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 setAutoCheckpointInterval()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 setJournalSizeLimit()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 setForeignKeyModeFromConfiguration()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 setWalModeFromConfiguration()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 setSyncMode(String newValue)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 canonicalizeSyncMode(String value)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 setJournalMode(String newValue)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 setLocaleFromConfiguration()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. reconfigure(SQLiteDatabaseConfiguration configuration)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. setOnlyAllowReadOnlyOperations(boolean readOnly)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. isPreparedStatementInCache(String 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 */ getConnectionId()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 */ isPrimaryConnection()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 */ prepare(String sql, SQLiteStatementInfo outStatementInfo)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 */ execute(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)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 */ executeForLong(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)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 */ executeForString(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)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 */ executeForBlobFileDescriptor(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)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 */ executeForChangedRowCount(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)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 */ executeForLastInsertedRowId(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)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 */ executeForCursorWindow(String sql, Object[] bindArgs, CursorWindow window, int startPos, int requiredPos, boolean countAllRows, CancellationSignal cancellationSignal)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 acquirePreparedStatement(String sql)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 releasePreparedStatement(PreparedStatement statement)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 finalizePreparedStatement(PreparedStatement statement)927 private void finalizePreparedStatement(PreparedStatement statement) { 928 nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr); 929 recyclePreparedStatement(statement); 930 } 931 attachCancellationSignal(CancellationSignal cancellationSignal)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 detachCancellationSignal(CancellationSignal cancellationSignal)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 onCancel()968 public void onCancel() { 969 nativeCancel(mConnectionPtr); 970 } 971 bindArguments(PreparedStatement statement, Object[] bindArgs)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 throwIfStatementForbidden(PreparedStatement statement)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 isCacheable(int statementType)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 applyBlockGuardPolicy(PreparedStatement statement)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 */ dump(Printer printer, boolean verbose)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 */ dumpUnsafe(Printer printer, boolean verbose)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 */ describeCurrentOperationUnsafe()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 */ collectDbStats(ArrayList<DbStats> dbStatsList)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 */ collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList)1152 void collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList) { 1153 dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0)); 1154 } 1155 getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize)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 toString()1170 public String toString() { 1171 return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")"; 1172 } 1173 obtainPreparedStatement(String sql, int statementPtr, int numParameters, int type, boolean readOnly)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 recyclePreparedStatement(PreparedStatement statement)1192 private void recyclePreparedStatement(PreparedStatement statement) { 1193 statement.mSql = null; 1194 statement.mPoolNext = mPreparedStatementPool; 1195 mPreparedStatementPool = statement; 1196 } 1197 trimSqlForDisplay(String sql)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> { PreparedStatementCache(int size)1244 public PreparedStatementCache(int size) { 1245 super(size); 1246 } 1247 1248 @Override entryRemoved(boolean evicted, String key, PreparedStatement oldValue, PreparedStatement newValue)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 dump(Printer printer)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 beginOperation(String kind, String sql, Object[] bindArgs)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 failOperation(int cookie, Exception ex)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 endOperation(int cookie)1338 public void endOperation(int cookie) { 1339 synchronized (mOperations) { 1340 if (endOperationDeferLogLocked(cookie)) { 1341 logOperationLocked(cookie, null); 1342 } 1343 } 1344 } 1345 endOperationDeferLog(int cookie)1346 public boolean endOperationDeferLog(int cookie) { 1347 synchronized (mOperations) { 1348 return endOperationDeferLogLocked(cookie); 1349 } 1350 } 1351 logOperation(int cookie, String detail)1352 public void logOperation(int cookie, String detail) { 1353 synchronized (mOperations) { 1354 logOperationLocked(cookie, detail); 1355 } 1356 } 1357 endOperationDeferLogLocked(int cookie)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 logOperationLocked(int cookie, String detail)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 newOperationCookieLocked(int index)1379 private int newOperationCookieLocked(int index) { 1380 final int generation = mGeneration++; 1381 return generation << COOKIE_GENERATION_SHIFT | index; 1382 } 1383 getOperationLocked(int cookie)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 describeCurrentOperation()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 dump(Printer printer)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 describe(StringBuilder msg)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 getStatus()1482 private String getStatus() { 1483 if (!mFinished) { 1484 return "running"; 1485 } 1486 return mException != null ? "failed" : "succeeded"; 1487 } 1488 getFormattedStartTime()1489 private String getFormattedStartTime() { 1490 return sDateFormat.format(new Date(mStartTime)); 1491 } 1492 } 1493 } 1494