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