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