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