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