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