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