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