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