• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.database.sqlite;
18 
19 import dalvik.system.BlockGuard;
20 import dalvik.system.CloseGuard;
21 
22 import android.database.Cursor;
23 import android.database.CursorWindow;
24 import android.database.DatabaseUtils;
25 import android.database.sqlite.SQLiteDebug.DbStats;
26 import android.os.CancellationSignal;
27 import android.os.OperationCanceledException;
28 import android.os.ParcelFileDescriptor;
29 import android.util.Log;
30 import android.util.LruCache;
31 import android.util.Printer;
32 
33 import java.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 static final Pattern TRIM_SQL_PATTERN = Pattern.compile("[\\s]*\\n+[\\s]*");
95 
96     private final CloseGuard mCloseGuard = CloseGuard.get();
97 
98     private final SQLiteConnectionPool mPool;
99     private final SQLiteDatabaseConfiguration mConfiguration;
100     private final int mConnectionId;
101     private final boolean mIsPrimaryConnection;
102     private final boolean mIsReadOnlyConnection;
103     private final PreparedStatementCache mPreparedStatementCache;
104     private PreparedStatement mPreparedStatementPool;
105 
106     // The recent operations log.
107     private final OperationLog mRecentOperations = new OperationLog();
108 
109     // The native SQLiteConnection pointer.  (FOR INTERNAL USE ONLY)
110     private 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         return TRIM_SQL_PATTERN.matcher(sql).replaceAll(" ");
1207     }
1208 
1209     /**
1210      * Holder type for a prepared statement.
1211      *
1212      * Although this object holds a pointer to a native statement object, it
1213      * does not have a finalizer.  This is deliberate.  The {@link SQLiteConnection}
1214      * owns the statement object and will take care of freeing it when needed.
1215      * In particular, closing the connection requires a guarantee of deterministic
1216      * resource disposal because all native statement objects must be freed before
1217      * the native database object can be closed.  So no finalizers here.
1218      */
1219     private static final class PreparedStatement {
1220         // Next item in pool.
1221         public PreparedStatement mPoolNext;
1222 
1223         // The SQL from which the statement was prepared.
1224         public String mSql;
1225 
1226         // The native sqlite3_stmt object pointer.
1227         // Lifetime is managed explicitly by the connection.
1228         public long mStatementPtr;
1229 
1230         // The number of parameters that the prepared statement has.
1231         public int mNumParameters;
1232 
1233         // The statement type.
1234         public int mType;
1235 
1236         // True if the statement is read-only.
1237         public boolean mReadOnly;
1238 
1239         // True if the statement is in the cache.
1240         public boolean mInCache;
1241 
1242         // True if the statement is in use (currently executing).
1243         // We need this flag because due to the use of custom functions in triggers, it's
1244         // possible for SQLite calls to be re-entrant.  Consequently we need to prevent
1245         // in use statements from being finalized until they are no longer in use.
1246         public boolean mInUse;
1247     }
1248 
1249     private final class PreparedStatementCache
1250             extends LruCache<String, PreparedStatement> {
PreparedStatementCache(int size)1251         public PreparedStatementCache(int size) {
1252             super(size);
1253         }
1254 
1255         @Override
entryRemoved(boolean evicted, String key, PreparedStatement oldValue, PreparedStatement newValue)1256         protected void entryRemoved(boolean evicted, String key,
1257                 PreparedStatement oldValue, PreparedStatement newValue) {
1258             oldValue.mInCache = false;
1259             if (!oldValue.mInUse) {
1260                 finalizePreparedStatement(oldValue);
1261             }
1262         }
1263 
dump(Printer printer)1264         public void dump(Printer printer) {
1265             printer.println("  Prepared statement cache:");
1266             Map<String, PreparedStatement> cache = snapshot();
1267             if (!cache.isEmpty()) {
1268                 int i = 0;
1269                 for (Map.Entry<String, PreparedStatement> entry : cache.entrySet()) {
1270                     PreparedStatement statement = entry.getValue();
1271                     if (statement.mInCache) { // might be false due to a race with entryRemoved
1272                         String sql = entry.getKey();
1273                         printer.println("    " + i + ": statementPtr=0x"
1274                                 + Long.toHexString(statement.mStatementPtr)
1275                                 + ", numParameters=" + statement.mNumParameters
1276                                 + ", type=" + statement.mType
1277                                 + ", readOnly=" + statement.mReadOnly
1278                                 + ", sql=\"" + trimSqlForDisplay(sql) + "\"");
1279                     }
1280                     i += 1;
1281                 }
1282             } else {
1283                 printer.println("    <none>");
1284             }
1285         }
1286     }
1287 
1288     private static final class OperationLog {
1289         private static final int MAX_RECENT_OPERATIONS = 20;
1290         private static final int COOKIE_GENERATION_SHIFT = 8;
1291         private static final int COOKIE_INDEX_MASK = 0xff;
1292 
1293         private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
1294         private int mIndex;
1295         private int mGeneration;
1296 
beginOperation(String kind, String sql, Object[] bindArgs)1297         public int beginOperation(String kind, String sql, Object[] bindArgs) {
1298             synchronized (mOperations) {
1299                 final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
1300                 Operation operation = mOperations[index];
1301                 if (operation == null) {
1302                     operation = new Operation();
1303                     mOperations[index] = operation;
1304                 } else {
1305                     operation.mFinished = false;
1306                     operation.mException = null;
1307                     if (operation.mBindArgs != null) {
1308                         operation.mBindArgs.clear();
1309                     }
1310                 }
1311                 operation.mStartTime = System.currentTimeMillis();
1312                 operation.mKind = kind;
1313                 operation.mSql = sql;
1314                 if (bindArgs != null) {
1315                     if (operation.mBindArgs == null) {
1316                         operation.mBindArgs = new ArrayList<Object>();
1317                     } else {
1318                         operation.mBindArgs.clear();
1319                     }
1320                     for (int i = 0; i < bindArgs.length; i++) {
1321                         final Object arg = bindArgs[i];
1322                         if (arg != null && arg instanceof byte[]) {
1323                             // Don't hold onto the real byte array longer than necessary.
1324                             operation.mBindArgs.add(EMPTY_BYTE_ARRAY);
1325                         } else {
1326                             operation.mBindArgs.add(arg);
1327                         }
1328                     }
1329                 }
1330                 operation.mCookie = newOperationCookieLocked(index);
1331                 mIndex = index;
1332                 return operation.mCookie;
1333             }
1334         }
1335 
failOperation(int cookie, Exception ex)1336         public void failOperation(int cookie, Exception ex) {
1337             synchronized (mOperations) {
1338                 final Operation operation = getOperationLocked(cookie);
1339                 if (operation != null) {
1340                     operation.mException = ex;
1341                 }
1342             }
1343         }
1344 
endOperation(int cookie)1345         public void endOperation(int cookie) {
1346             synchronized (mOperations) {
1347                 if (endOperationDeferLogLocked(cookie)) {
1348                     logOperationLocked(cookie, null);
1349                 }
1350             }
1351         }
1352 
endOperationDeferLog(int cookie)1353         public boolean endOperationDeferLog(int cookie) {
1354             synchronized (mOperations) {
1355                 return endOperationDeferLogLocked(cookie);
1356             }
1357         }
1358 
logOperation(int cookie, String detail)1359         public void logOperation(int cookie, String detail) {
1360             synchronized (mOperations) {
1361                 logOperationLocked(cookie, detail);
1362             }
1363         }
1364 
endOperationDeferLogLocked(int cookie)1365         private boolean endOperationDeferLogLocked(int cookie) {
1366             final Operation operation = getOperationLocked(cookie);
1367             if (operation != null) {
1368                 operation.mEndTime = System.currentTimeMillis();
1369                 operation.mFinished = true;
1370                 return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
1371                                 operation.mEndTime - operation.mStartTime);
1372             }
1373             return false;
1374         }
1375 
logOperationLocked(int cookie, String detail)1376         private void logOperationLocked(int cookie, String detail) {
1377             final Operation operation = getOperationLocked(cookie);
1378             StringBuilder msg = new StringBuilder();
1379             operation.describe(msg, false);
1380             if (detail != null) {
1381                 msg.append(", ").append(detail);
1382             }
1383             Log.d(TAG, msg.toString());
1384         }
1385 
newOperationCookieLocked(int index)1386         private int newOperationCookieLocked(int index) {
1387             final int generation = mGeneration++;
1388             return generation << COOKIE_GENERATION_SHIFT | index;
1389         }
1390 
getOperationLocked(int cookie)1391         private Operation getOperationLocked(int cookie) {
1392             final int index = cookie & COOKIE_INDEX_MASK;
1393             final Operation operation = mOperations[index];
1394             return operation.mCookie == cookie ? operation : null;
1395         }
1396 
describeCurrentOperation()1397         public String describeCurrentOperation() {
1398             synchronized (mOperations) {
1399                 final Operation operation = mOperations[mIndex];
1400                 if (operation != null && !operation.mFinished) {
1401                     StringBuilder msg = new StringBuilder();
1402                     operation.describe(msg, false);
1403                     return msg.toString();
1404                 }
1405                 return null;
1406             }
1407         }
1408 
dump(Printer printer, boolean verbose)1409         public void dump(Printer printer, boolean verbose) {
1410             synchronized (mOperations) {
1411                 printer.println("  Most recently executed operations:");
1412                 int index = mIndex;
1413                 Operation operation = mOperations[index];
1414                 if (operation != null) {
1415                     int n = 0;
1416                     do {
1417                         StringBuilder msg = new StringBuilder();
1418                         msg.append("    ").append(n).append(": [");
1419                         msg.append(operation.getFormattedStartTime());
1420                         msg.append("] ");
1421                         operation.describe(msg, verbose);
1422                         printer.println(msg.toString());
1423 
1424                         if (index > 0) {
1425                             index -= 1;
1426                         } else {
1427                             index = MAX_RECENT_OPERATIONS - 1;
1428                         }
1429                         n += 1;
1430                         operation = mOperations[index];
1431                     } while (operation != null && n < MAX_RECENT_OPERATIONS);
1432                 } else {
1433                     printer.println("    <none>");
1434                 }
1435             }
1436         }
1437     }
1438 
1439     private static final class Operation {
1440         private static final SimpleDateFormat sDateFormat =
1441                 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
1442 
1443         public long mStartTime;
1444         public long mEndTime;
1445         public String mKind;
1446         public String mSql;
1447         public ArrayList<Object> mBindArgs;
1448         public boolean mFinished;
1449         public Exception mException;
1450         public int mCookie;
1451 
describe(StringBuilder msg, boolean verbose)1452         public void describe(StringBuilder msg, boolean verbose) {
1453             msg.append(mKind);
1454             if (mFinished) {
1455                 msg.append(" took ").append(mEndTime - mStartTime).append("ms");
1456             } else {
1457                 msg.append(" started ").append(System.currentTimeMillis() - mStartTime)
1458                         .append("ms ago");
1459             }
1460             msg.append(" - ").append(getStatus());
1461             if (mSql != null) {
1462                 msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\"");
1463             }
1464             if (verbose && mBindArgs != null && mBindArgs.size() != 0) {
1465                 msg.append(", bindArgs=[");
1466                 final int count = mBindArgs.size();
1467                 for (int i = 0; i < count; i++) {
1468                     final Object arg = mBindArgs.get(i);
1469                     if (i != 0) {
1470                         msg.append(", ");
1471                     }
1472                     if (arg == null) {
1473                         msg.append("null");
1474                     } else if (arg instanceof byte[]) {
1475                         msg.append("<byte[]>");
1476                     } else if (arg instanceof String) {
1477                         msg.append("\"").append((String)arg).append("\"");
1478                     } else {
1479                         msg.append(arg);
1480                     }
1481                 }
1482                 msg.append("]");
1483             }
1484             if (mException != null) {
1485                 msg.append(", exception=\"").append(mException.getMessage()).append("\"");
1486             }
1487         }
1488 
getStatus()1489         private String getStatus() {
1490             if (!mFinished) {
1491                 return "running";
1492             }
1493             return mException != null ? "failed" : "succeeded";
1494         }
1495 
getFormattedStartTime()1496         private String getFormattedStartTime() {
1497             return sDateFormat.format(new Date(mStartTime));
1498         }
1499     }
1500 }
1501