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