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