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