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