• 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.annotation.NonNull;
20 import com.android.internal.annotations.GuardedBy;
21 
22 import android.database.Cursor;
23 import android.database.CursorWindow;
24 import android.database.DatabaseUtils;
25 import android.database.sqlite.SQLiteDebug.DbStats;
26 import android.database.sqlite.SQLiteDebug.NoPreloadHolder;
27 import android.os.CancellationSignal;
28 import android.os.OperationCanceledException;
29 import android.os.ParcelFileDescriptor;
30 import android.os.SystemClock;
31 import android.os.Trace;
32 import android.text.TextUtils;
33 import android.util.Log;
34 import android.util.LruCache;
35 import android.util.Pair;
36 import android.util.Printer;
37 import com.android.internal.util.RingBuffer;
38 import dalvik.system.BlockGuard;
39 import dalvik.system.CloseGuard;
40 
41 import java.io.File;
42 import java.io.IOException;
43 import java.lang.ref.Reference;
44 import java.nio.file.FileSystems;
45 import java.nio.file.Files;
46 import java.nio.file.Path;
47 import java.time.Instant;
48 import java.time.ZoneId;
49 import java.time.format.DateTimeFormatter;
50 import java.util.ArrayList;
51 import java.util.Date;
52 import java.util.Locale;
53 import java.util.Map;
54 import java.util.function.BinaryOperator;
55 import java.util.function.UnaryOperator;
56 
57 /**
58  * Represents a SQLite database connection.
59  * Each connection wraps an instance of a native <code>sqlite3</code> object.
60  * <p>
61  * When database connection pooling is enabled, there can be multiple active
62  * connections to the same database.  Otherwise there is typically only one
63  * connection per database.
64  * </p><p>
65  * When the SQLite WAL feature is enabled, multiple readers and one writer
66  * can concurrently access the database.  Without WAL, readers and writers
67  * are mutually exclusive.
68  * </p>
69  *
70  * <h2>Ownership and concurrency guarantees</h2>
71  * <p>
72  * Connection objects are not thread-safe.  They are acquired as needed to
73  * perform a database operation and are then returned to the pool.  At any
74  * given time, a connection is either owned and used by a {@link SQLiteSession}
75  * object or the {@link SQLiteConnectionPool}.  Those classes are
76  * responsible for serializing operations to guard against concurrent
77  * use of a connection.
78  * </p><p>
79  * The guarantee of having a single owner allows this class to be implemented
80  * without locks and greatly simplifies resource management.
81  * </p>
82  *
83  * <h2>Encapsulation guarantees</h2>
84  * <p>
85  * The connection object object owns *all* of the SQLite related native
86  * objects that are associated with the connection.  What's more, there are
87  * no other objects in the system that are capable of obtaining handles to
88  * those native objects.  Consequently, when the connection is closed, we do
89  * not have to worry about what other components might have references to
90  * its associated SQLite state -- there are none.
91  * </p><p>
92  * Encapsulation is what ensures that the connection object's
93  * lifecycle does not become a tortured mess of finalizers and reference
94  * queues.
95  * </p>
96  *
97  * <h2>Reentrance</h2>
98  * <p>
99  * This class must tolerate reentrant execution of SQLite operations because
100  * triggers may call custom SQLite functions that perform additional queries.
101  * </p>
102  *
103  * @hide
104  */
105 public final class SQLiteConnection implements CancellationSignal.OnCancelListener {
106     private static final String TAG = "SQLiteConnection";
107     private static final boolean DEBUG = false;
108 
109     private static final String[] EMPTY_STRING_ARRAY = new String[0];
110     private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
111 
112     private final CloseGuard mCloseGuard = CloseGuard.get();
113 
114     private final SQLiteConnectionPool mPool;
115     private final SQLiteDatabaseConfiguration mConfiguration;
116     private final int mConnectionId;
117     private final boolean mIsPrimaryConnection;
118     private final boolean mIsReadOnlyConnection;
119     private PreparedStatement mPreparedStatementPool;
120 
121     private final PreparedStatementCache mPreparedStatementCache;
122 
123     // The recent operations log.
124     private final OperationLog mRecentOperations;
125 
126     // The native SQLiteConnection pointer.  (FOR INTERNAL USE ONLY)
127     private long mConnectionPtr;
128 
129     // Restrict this connection to read-only operations.
130     private boolean mOnlyAllowReadOnlyOperations;
131 
132     // The number of times attachCancellationSignal has been called.
133     // Because SQLite statement execution can be reentrant, we keep track of how many
134     // times we have attempted to attach a cancellation signal to the connection so that
135     // we can ensure that we detach the signal at the right time.
136     private int mCancellationSignalAttachCount;
137 
nativeOpen(String path, int openFlags, String label, boolean enableTrace, boolean enableProfile, int lookasideSlotSize, int lookasideSlotCount)138     private static native long nativeOpen(String path, int openFlags, String label,
139             boolean enableTrace, boolean enableProfile, int lookasideSlotSize,
140             int lookasideSlotCount);
nativeClose(long connectionPtr, boolean fast)141     private static native void nativeClose(long connectionPtr, boolean fast);
nativeRegisterCustomScalarFunction(long connectionPtr, String name, UnaryOperator<String> function)142     private static native void nativeRegisterCustomScalarFunction(long connectionPtr,
143             String name, UnaryOperator<String> function);
nativeRegisterCustomAggregateFunction(long connectionPtr, String name, BinaryOperator<String> function)144     private static native void nativeRegisterCustomAggregateFunction(long connectionPtr,
145             String name, BinaryOperator<String> function);
nativeRegisterLocalizedCollators(long connectionPtr, String locale)146     private static native void nativeRegisterLocalizedCollators(long connectionPtr, String locale);
nativePrepareStatement(long connectionPtr, String sql)147     private static native long nativePrepareStatement(long connectionPtr, String sql);
nativeFinalizeStatement(long connectionPtr, long statementPtr)148     private static native void nativeFinalizeStatement(long connectionPtr, long statementPtr);
nativeGetParameterCount(long connectionPtr, long statementPtr)149     private static native int nativeGetParameterCount(long connectionPtr, long statementPtr);
nativeIsReadOnly(long connectionPtr, long statementPtr)150     private static native boolean nativeIsReadOnly(long connectionPtr, long statementPtr);
nativeUpdatesTempOnly(long connectionPtr, long statementPtr)151     private static native boolean nativeUpdatesTempOnly(long connectionPtr, long statementPtr);
nativeGetColumnCount(long connectionPtr, long statementPtr)152     private static native int nativeGetColumnCount(long connectionPtr, long statementPtr);
nativeGetColumnName(long connectionPtr, long statementPtr, int index)153     private static native String nativeGetColumnName(long connectionPtr, long statementPtr,
154             int index);
nativeBindNull(long connectionPtr, long statementPtr, int index)155     private static native void nativeBindNull(long connectionPtr, long statementPtr,
156             int index);
nativeBindLong(long connectionPtr, long statementPtr, int index, long value)157     private static native void nativeBindLong(long connectionPtr, long statementPtr,
158             int index, long value);
nativeBindDouble(long connectionPtr, long statementPtr, int index, double value)159     private static native void nativeBindDouble(long connectionPtr, long statementPtr,
160             int index, double value);
nativeBindString(long connectionPtr, long statementPtr, int index, String value)161     private static native void nativeBindString(long connectionPtr, long statementPtr,
162             int index, String value);
nativeBindBlob(long connectionPtr, long statementPtr, int index, byte[] value)163     private static native void nativeBindBlob(long connectionPtr, long statementPtr,
164             int index, byte[] value);
nativeResetStatementAndClearBindings( long connectionPtr, long statementPtr)165     private static native void nativeResetStatementAndClearBindings(
166             long connectionPtr, long statementPtr);
nativeExecute(long connectionPtr, long statementPtr, boolean isPragmaStmt)167     private static native void nativeExecute(long connectionPtr, long statementPtr,
168             boolean isPragmaStmt);
nativeExecuteForLong(long connectionPtr, long statementPtr)169     private static native long nativeExecuteForLong(long connectionPtr, long statementPtr);
nativeExecuteForString(long connectionPtr, long statementPtr)170     private static native String nativeExecuteForString(long connectionPtr, long statementPtr);
nativeExecuteForBlobFileDescriptor( long connectionPtr, long statementPtr)171     private static native int nativeExecuteForBlobFileDescriptor(
172             long connectionPtr, long statementPtr);
nativeExecuteForChangedRowCount(long connectionPtr, long statementPtr)173     private static native int nativeExecuteForChangedRowCount(long connectionPtr, long statementPtr);
nativeExecuteForLastInsertedRowId( long connectionPtr, long statementPtr)174     private static native long nativeExecuteForLastInsertedRowId(
175             long connectionPtr, long statementPtr);
nativeExecuteForCursorWindow( long connectionPtr, long statementPtr, long windowPtr, int startPos, int requiredPos, boolean countAllRows)176     private static native long nativeExecuteForCursorWindow(
177             long connectionPtr, long statementPtr, long windowPtr,
178             int startPos, int requiredPos, boolean countAllRows);
nativeGetDbLookaside(long connectionPtr)179     private static native int nativeGetDbLookaside(long connectionPtr);
nativeCancel(long connectionPtr)180     private static native void nativeCancel(long connectionPtr);
nativeResetCancel(long connectionPtr, boolean cancelable)181     private static native void nativeResetCancel(long connectionPtr, boolean cancelable);
nativeLastInsertRowId(long connectionPtr)182     private static native int nativeLastInsertRowId(long connectionPtr);
nativeChanges(long connectionPtr)183     private static native long nativeChanges(long connectionPtr);
nativeTotalChanges(long connectionPtr)184     private static native long nativeTotalChanges(long connectionPtr);
185 
SQLiteConnection(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration, int connectionId, boolean primaryConnection)186     private SQLiteConnection(SQLiteConnectionPool pool,
187             SQLiteDatabaseConfiguration configuration,
188             int connectionId, boolean primaryConnection) {
189         mPool = pool;
190         mRecentOperations = new OperationLog();
191         mConfiguration = new SQLiteDatabaseConfiguration(configuration);
192         mConnectionId = connectionId;
193         mIsPrimaryConnection = primaryConnection;
194         mIsReadOnlyConnection = mConfiguration.isReadOnlyDatabase();
195         mPreparedStatementCache = new PreparedStatementCache(
196                 mConfiguration.maxSqlCacheSize);
197         mCloseGuard.open("SQLiteConnection.close");
198     }
199 
200     @Override
finalize()201     protected void finalize() throws Throwable {
202         try {
203             if (mPool != null && mConnectionPtr != 0) {
204                 mPool.onConnectionLeaked();
205             }
206 
207             dispose(true);
208         } finally {
209             super.finalize();
210         }
211     }
212 
213     // Called by SQLiteConnectionPool only.
open(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration, int connectionId, boolean primaryConnection)214     static SQLiteConnection open(SQLiteConnectionPool pool,
215             SQLiteDatabaseConfiguration configuration,
216             int connectionId, boolean primaryConnection) {
217         SQLiteConnection connection = new SQLiteConnection(pool, configuration,
218                 connectionId, primaryConnection);
219         try {
220             connection.open();
221             return connection;
222         } catch (SQLiteException ex) {
223             connection.dispose(false);
224             throw ex;
225         }
226     }
227 
228     // Called by SQLiteConnectionPool only.
229     // Closes the database closes and releases all of its associated resources.
230     // Do not call methods on the connection after it is closed.  It will probably crash.
close()231     void close() {
232         dispose(false);
233     }
234 
open()235     private void open() {
236         final String file = mConfiguration.path;
237         final int cookie = mRecentOperations.beginOperation("open", null, null);
238         try {
239             mConnectionPtr = nativeOpen(file, mConfiguration.openFlags,
240                     mConfiguration.label,
241                     NoPreloadHolder.DEBUG_SQL_STATEMENTS, NoPreloadHolder.DEBUG_SQL_TIME,
242                     mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount);
243         } catch (SQLiteCantOpenDatabaseException e) {
244             final StringBuilder message = new StringBuilder("Cannot open database ")
245                     .append("[").append(e.getMessage()).append("]")
246                     .append(" '").append(file).append("'")
247                     .append(" with flags 0x")
248                     .append(Integer.toHexString(mConfiguration.openFlags));
249 
250             try {
251                 // Try to diagnose for common reasons. If something fails in here, that's fine;
252                 // just swallow the exception.
253 
254                 final Path path = FileSystems.getDefault().getPath(file);
255                 final Path dir = path.getParent();
256                 if (dir == null) {
257                     message.append(": Directory not specified in the file path");
258                 } else if (!Files.isDirectory(dir)) {
259                     message.append(": Directory ").append(dir).append(" doesn't exist");
260                 } else if (!Files.exists(path)) {
261                     message.append(": File ").append(path).append(
262                             " doesn't exist");
263                     if ((mConfiguration.openFlags & SQLiteDatabase.CREATE_IF_NECESSARY) != 0) {
264                         message.append(
265                                 " and CREATE_IF_NECESSARY is set, check directory permissions");
266                     }
267                 } else if (!Files.isReadable(path)) {
268                     message.append(": File ").append(path).append(" is not readable");
269                 } else if (Files.isDirectory(path)) {
270                     message.append(": Path ").append(path).append(" is a directory");
271                 }
272             } catch (Throwable th) {
273                 // Ignore any exceptions generated whilst attempting to create extended diagnostic
274                 // messages.
275             }
276             throw new SQLiteCantOpenDatabaseException(message.toString(), e);
277         } finally {
278             mRecentOperations.endOperation(cookie);
279         }
280         setPageSize();
281         setForeignKeyModeFromConfiguration();
282         setJournalFromConfiguration();
283         setSyncModeFromConfiguration();
284         setJournalSizeLimit();
285         setAutoCheckpointInterval();
286         setLocaleFromConfiguration();
287         setCustomFunctionsFromConfiguration();
288         executePerConnectionSqlFromConfiguration(0);
289     }
290 
dispose(boolean finalized)291     private void dispose(boolean finalized) {
292         if (mCloseGuard != null) {
293             if (finalized) {
294                 mCloseGuard.warnIfOpen();
295             }
296             mCloseGuard.close();
297         }
298 
299         if (mConnectionPtr != 0) {
300             final int cookie = mRecentOperations.beginOperation("close", null, null);
301             try {
302                 mPreparedStatementCache.evictAll();
303                 nativeClose(mConnectionPtr, finalized && Flags.noCheckpointOnFinalize());
304                 mConnectionPtr = 0;
305             } finally {
306                 mRecentOperations.endOperation(cookie);
307             }
308         }
309     }
310 
311     /** Record the start of a transaction for logging and debugging. */
recordBeginTransaction(String mode)312     void recordBeginTransaction(String mode) {
313         mRecentOperations.beginTransaction(mode);
314     }
315 
316     /** Record the end of a transaction for logging and debugging. */
recordEndTransaction(boolean successful)317     void recordEndTransaction(boolean successful) {
318         mRecentOperations.endTransaction(successful);
319     }
320 
setPageSize()321     private void setPageSize() {
322         if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
323             final long newValue = SQLiteGlobal.getDefaultPageSize();
324             long value = executeForLong("PRAGMA page_size", null, null);
325             if (value != newValue) {
326                 execute("PRAGMA page_size=" + newValue, null, null);
327             }
328         }
329     }
330 
setAutoCheckpointInterval()331     private void setAutoCheckpointInterval() {
332         if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
333             final long newValue = SQLiteGlobal.getWALAutoCheckpoint();
334             long value = executeForLong("PRAGMA wal_autocheckpoint", null, null);
335             if (value != newValue) {
336                 executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null);
337             }
338         }
339     }
340 
setJournalSizeLimit()341     private void setJournalSizeLimit() {
342         if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
343             final long newValue = SQLiteGlobal.getJournalSizeLimit();
344             long value = executeForLong("PRAGMA journal_size_limit", null, null);
345             if (value != newValue) {
346                 executeForLong("PRAGMA journal_size_limit=" + newValue, null, null);
347             }
348         }
349     }
350 
setForeignKeyModeFromConfiguration()351     private void setForeignKeyModeFromConfiguration() {
352         if (!mIsReadOnlyConnection) {
353             final long newValue = mConfiguration.foreignKeyConstraintsEnabled ? 1 : 0;
354             long value = executeForLong("PRAGMA foreign_keys", null, null);
355             if (value != newValue) {
356                 execute("PRAGMA foreign_keys=" + newValue, null, null);
357             }
358         }
359     }
360 
setJournalFromConfiguration()361     private void setJournalFromConfiguration() {
362         if (!mIsReadOnlyConnection) {
363             setJournalMode(mConfiguration.resolveJournalMode());
364             maybeTruncateWalFile();
365         } else {
366             // No need to truncate for read only databases.
367             mConfiguration.shouldTruncateWalFile = false;
368         }
369     }
370 
setSyncModeFromConfiguration()371     private void setSyncModeFromConfiguration() {
372         if (!mIsReadOnlyConnection) {
373             setSyncMode(mConfiguration.resolveSyncMode());
374         }
375     }
376 
377     /**
378      * If the WAL file exists and larger than a threshold, truncate it by executing
379      * PRAGMA wal_checkpoint.
380      */
maybeTruncateWalFile()381     private void maybeTruncateWalFile() {
382         if (!mConfiguration.shouldTruncateWalFile) {
383             return;
384         }
385 
386         final long threshold = SQLiteGlobal.getWALTruncateSize();
387         if (DEBUG) {
388             Log.d(TAG, "Truncate threshold=" + threshold);
389         }
390         if (threshold == 0) {
391             return;
392         }
393 
394         final File walFile = new File(mConfiguration.path + "-wal");
395         if (!walFile.isFile()) {
396             return;
397         }
398         final long size = walFile.length();
399         if (size < threshold) {
400             if (DEBUG) {
401                 Log.d(TAG, walFile.getAbsolutePath() + " " + size + " bytes: No need to truncate");
402             }
403             return;
404         }
405 
406         try {
407             executeForString("PRAGMA wal_checkpoint(TRUNCATE)", null, null);
408             mConfiguration.shouldTruncateWalFile = false;
409         } catch (SQLiteException e) {
410             Log.w(TAG, "Failed to truncate the -wal file", e);
411         }
412     }
413 
setSyncMode(@QLiteDatabase.SyncMode String newValue)414     private void setSyncMode(@SQLiteDatabase.SyncMode String newValue) {
415         if (TextUtils.isEmpty(newValue)) {
416             // No change to the sync mode is intended
417             return;
418         }
419         String value = executeForString("PRAGMA synchronous", null, null);
420         if (!canonicalizeSyncMode(value).equalsIgnoreCase(
421                 canonicalizeSyncMode(newValue))) {
422             execute("PRAGMA synchronous=" + newValue, null, null);
423         }
424     }
425 
canonicalizeSyncMode(String value)426     private static @SQLiteDatabase.SyncMode String canonicalizeSyncMode(String value) {
427         switch (value) {
428             case "0": return SQLiteDatabase.SYNC_MODE_OFF;
429             case "1": return SQLiteDatabase.SYNC_MODE_NORMAL;
430             case "2": return SQLiteDatabase.SYNC_MODE_FULL;
431             case "3": return SQLiteDatabase.SYNC_MODE_EXTRA;
432         }
433         return value;
434     }
435 
setJournalMode(@QLiteDatabase.JournalMode String newValue)436     private void setJournalMode(@SQLiteDatabase.JournalMode String newValue) {
437         if (TextUtils.isEmpty(newValue)) {
438             // No change to the journal mode is intended
439             return;
440         }
441         String value = executeForString("PRAGMA journal_mode", null, null);
442         if (!value.equalsIgnoreCase(newValue)) {
443             try {
444                 String result = executeForString("PRAGMA journal_mode=" + newValue, null, null);
445                 if (result.equalsIgnoreCase(newValue)) {
446                     return;
447                 }
448                 // PRAGMA journal_mode silently fails and returns the original journal
449                 // mode in some cases if the journal mode could not be changed.
450             } catch (SQLiteDatabaseLockedException ex) {
451                 // This error (SQLITE_BUSY) occurs if one connection has the database
452                 // open in WAL mode and another tries to change it to non-WAL.
453             }
454             // Because we always disable WAL mode when a database is first opened
455             // (even if we intend to re-enable it), we can encounter problems if
456             // there is another open connection to the database somewhere.
457             // This can happen for a variety of reasons such as an application opening
458             // the same database in multiple processes at the same time or if there is a
459             // crashing content provider service that the ActivityManager has
460             // removed from its registry but whose process hasn't quite died yet
461             // by the time it is restarted in a new process.
462             //
463             // If we don't change the journal mode, nothing really bad happens.
464             // In the worst case, an application that enables WAL might not actually
465             // get it, although it can still use connection pooling.
466             Log.w(TAG, "Could not change the database journal mode of '"
467                     + mConfiguration.label + "' from '" + value + "' to '" + newValue
468                     + "' because the database is locked.  This usually means that "
469                     + "there are other open connections to the database which prevents "
470                     + "the database from enabling or disabling write-ahead logging mode.  "
471                     + "Proceeding without changing the journal mode.");
472         }
473     }
474 
setLocaleFromConfiguration()475     private void setLocaleFromConfiguration() {
476         if ((mConfiguration.openFlags & SQLiteDatabase.NO_LOCALIZED_COLLATORS) != 0) {
477             return;
478         }
479 
480         // Register the localized collators.
481         final String newLocale = mConfiguration.locale.toString();
482         nativeRegisterLocalizedCollators(mConnectionPtr, newLocale);
483 
484         if (!mConfiguration.isInMemoryDb()) {
485             checkDatabaseWiped();
486         }
487 
488         // If the database is read-only, we cannot modify the android metadata table
489         // or existing indexes.
490         if (mIsReadOnlyConnection) {
491             return;
492         }
493 
494         try {
495             // Ensure the android metadata table exists.
496             execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null);
497 
498             // Check whether the locale was actually changed.
499             final String oldLocale = executeForString("SELECT locale FROM android_metadata "
500                     + "UNION SELECT NULL ORDER BY locale DESC LIMIT 1", null, null);
501             if (oldLocale != null && oldLocale.equals(newLocale)) {
502                 return;
503             }
504 
505             // Go ahead and update the indexes using the new locale.
506             execute("BEGIN", null, null);
507             boolean success = false;
508             try {
509                 execute("DELETE FROM android_metadata", null, null);
510                 execute("INSERT INTO android_metadata (locale) VALUES(?)",
511                         new Object[] { newLocale }, null);
512                 execute("REINDEX LOCALIZED", null, null);
513                 success = true;
514             } finally {
515                 execute(success ? "COMMIT" : "ROLLBACK", null, null);
516             }
517         } catch (SQLiteException ex) {
518             throw ex;
519         } catch (RuntimeException ex) {
520             throw new SQLiteException("Failed to change locale for db '" + mConfiguration.label
521                     + "' to '" + newLocale + "'.", ex);
522         }
523     }
524 
setCustomFunctionsFromConfiguration()525     private void setCustomFunctionsFromConfiguration() {
526         for (int i = 0; i < mConfiguration.customScalarFunctions.size(); i++) {
527             nativeRegisterCustomScalarFunction(mConnectionPtr,
528                     mConfiguration.customScalarFunctions.keyAt(i),
529                     mConfiguration.customScalarFunctions.valueAt(i));
530         }
531         for (int i = 0; i < mConfiguration.customAggregateFunctions.size(); i++) {
532             nativeRegisterCustomAggregateFunction(mConnectionPtr,
533                     mConfiguration.customAggregateFunctions.keyAt(i),
534                     mConfiguration.customAggregateFunctions.valueAt(i));
535         }
536     }
537 
executePerConnectionSqlFromConfiguration(int startIndex)538     private void executePerConnectionSqlFromConfiguration(int startIndex) {
539         for (int i = startIndex; i < mConfiguration.perConnectionSql.size(); i++) {
540             final Pair<String, Object[]> statement = mConfiguration.perConnectionSql.get(i);
541             final int type = DatabaseUtils.getSqlStatementType(statement.first);
542             switch (type) {
543                 case DatabaseUtils.STATEMENT_SELECT:
544                     executeForString(statement.first, statement.second, null);
545                     break;
546                 case DatabaseUtils.STATEMENT_PRAGMA:
547                     execute(statement.first, statement.second, null);
548                     break;
549                 default:
550                     throw new IllegalArgumentException(
551                             "Unsupported configuration statement: " + statement);
552             }
553         }
554     }
555 
checkDatabaseWiped()556     private void checkDatabaseWiped() {
557         if (!SQLiteGlobal.checkDbWipe()) {
558             return;
559         }
560         try {
561             final File checkFile = new File(mConfiguration.path
562                     + SQLiteGlobal.WIPE_CHECK_FILE_SUFFIX);
563 
564             final boolean hasMetadataTable = executeForLong(
565                     "SELECT count(*) FROM sqlite_master"
566                             + " WHERE type='table' AND name='android_metadata'", null, null) > 0;
567             final boolean hasCheckFile = checkFile.exists();
568 
569             if (!mIsReadOnlyConnection && !hasCheckFile) {
570                 // Create the check file, unless it's a readonly connection,
571                 // in which case we can't create the metadata table anyway.
572                 checkFile.createNewFile();
573             }
574 
575             if (!hasMetadataTable && hasCheckFile) {
576                 // Bad. The DB is gone unexpectedly.
577                 SQLiteDatabase.wipeDetected(mConfiguration.path, "unknown");
578             }
579 
580         } catch (RuntimeException | IOException ex) {
581             SQLiteDatabase.wtfAsSystemServer(TAG,
582                     "Unexpected exception while checking for wipe", ex);
583         }
584     }
585 
586     // Called by SQLiteConnectionPool only.
reconfigure(SQLiteDatabaseConfiguration configuration)587     void reconfigure(SQLiteDatabaseConfiguration configuration) {
588         mOnlyAllowReadOnlyOperations = false;
589 
590         // Remember what changed.
591         boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
592                 != mConfiguration.foreignKeyConstraintsEnabled;
593         boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
594         boolean customScalarFunctionsChanged = !configuration.customScalarFunctions
595                 .equals(mConfiguration.customScalarFunctions);
596         boolean customAggregateFunctionsChanged = !configuration.customAggregateFunctions
597                 .equals(mConfiguration.customAggregateFunctions);
598         final int oldSize = mConfiguration.perConnectionSql.size();
599         final int newSize = configuration.perConnectionSql.size();
600         boolean perConnectionSqlChanged = newSize > oldSize;
601         boolean journalModeChanged = !configuration.resolveJournalMode().equalsIgnoreCase(
602                 mConfiguration.resolveJournalMode());
603         boolean syncModeChanged =
604                 !configuration.resolveSyncMode().equalsIgnoreCase(mConfiguration.resolveSyncMode());
605 
606         // Update configuration parameters.
607         mConfiguration.updateParametersFrom(configuration);
608 
609         // Update prepared statement cache size.
610         mPreparedStatementCache.resize(configuration.maxSqlCacheSize);
611 
612         if (foreignKeyModeChanged) {
613             setForeignKeyModeFromConfiguration();
614         }
615 
616         if (journalModeChanged) {
617             setJournalFromConfiguration();
618         }
619 
620         if (syncModeChanged) {
621             setSyncModeFromConfiguration();
622         }
623 
624         if (localeChanged) {
625             setLocaleFromConfiguration();
626         }
627         if (customScalarFunctionsChanged || customAggregateFunctionsChanged) {
628             setCustomFunctionsFromConfiguration();
629         }
630         if (perConnectionSqlChanged) {
631             executePerConnectionSqlFromConfiguration(oldSize);
632         }
633     }
634 
635     // Called by SQLiteConnectionPool only.
636     // When set to true, executing write operations will throw SQLiteException.
637     // Preparing statements that might write is ok, just don't execute them.
setOnlyAllowReadOnlyOperations(boolean readOnly)638     void setOnlyAllowReadOnlyOperations(boolean readOnly) {
639         mOnlyAllowReadOnlyOperations = readOnly;
640     }
641 
642     // Called by SQLiteConnectionPool only to decide if this connection has the desired statement
643     // already prepared.  Returns true if the prepared statement cache contains the specified SQL.
644     // The statement may be stale, but that will be a rare occurrence and affects performance only
645     // a tiny bit, and only when database schema changes.
isPreparedStatementInCache(String sql)646     boolean isPreparedStatementInCache(String sql) {
647         return mPreparedStatementCache.get(sql) != null;
648     }
649 
650     /**
651      * Gets the unique id of this connection.
652      * @return The connection id.
653      */
getConnectionId()654     public int getConnectionId() {
655         return mConnectionId;
656     }
657 
658     /**
659      * Returns true if this is the primary database connection.
660      * @return True if this is the primary database connection.
661      */
isPrimaryConnection()662     public boolean isPrimaryConnection() {
663         return mIsPrimaryConnection;
664     }
665 
666     /**
667      * Prepares a statement for execution but does not bind its parameters or execute it.
668      * <p>
669      * This method can be used to check for syntax errors during compilation
670      * prior to execution of the statement.  If the {@code outStatementInfo} argument
671      * is not null, the provided {@link SQLiteStatementInfo} object is populated
672      * with information about the statement.
673      * </p><p>
674      * A prepared statement makes no reference to the arguments that may eventually
675      * be bound to it, consequently it it possible to cache certain prepared statements
676      * such as SELECT or INSERT/UPDATE statements.  If the statement is cacheable,
677      * then it will be stored in the cache for later.
678      * </p><p>
679      * To take advantage of this behavior as an optimization, the connection pool
680      * provides a method to acquire a connection that already has a given SQL statement
681      * in its prepared statement cache so that it is ready for execution.
682      * </p>
683      *
684      * @param sql The SQL statement to prepare.
685      * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate
686      * with information about the statement, or null if none.
687      *
688      * @throws SQLiteException if an error occurs, such as a syntax error.
689      */
prepare(String sql, SQLiteStatementInfo outStatementInfo)690     public void prepare(String sql, SQLiteStatementInfo outStatementInfo) {
691         if (sql == null) {
692             throw new IllegalArgumentException("sql must not be null.");
693         }
694 
695         final int cookie = mRecentOperations.beginOperation("prepare", sql, null);
696         try {
697             final PreparedStatement statement = acquirePreparedStatement(sql);
698             try {
699                 if (outStatementInfo != null) {
700                     outStatementInfo.numParameters = statement.mNumParameters;
701                     outStatementInfo.readOnly = statement.mReadOnly;
702 
703                     final int columnCount = nativeGetColumnCount(
704                             mConnectionPtr, statement.mStatementPtr);
705                     if (columnCount == 0) {
706                         outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
707                     } else {
708                         outStatementInfo.columnNames = new String[columnCount];
709                         for (int i = 0; i < columnCount; i++) {
710                             outStatementInfo.columnNames[i] = nativeGetColumnName(
711                                     mConnectionPtr, statement.mStatementPtr, i);
712                         }
713                     }
714                 }
715             } finally {
716                 releasePreparedStatement(statement);
717             }
718         } catch (RuntimeException ex) {
719             mRecentOperations.failOperation(cookie, ex);
720             throw ex;
721         } finally {
722             mRecentOperations.endOperation(cookie);
723         }
724     }
725 
726     /**
727      * Executes a statement that does not return a result.
728      *
729      * @param sql The SQL statement to execute.
730      * @param bindArgs The arguments to bind, or null if none.
731      * @param cancellationSignal A signal to cancel the operation in progress, or null 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      */
execute(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)737     public void execute(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("execute", sql, bindArgs);
744         try {
745             final boolean isPragmaStmt =
746                 DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_PRAGMA;
747             final PreparedStatement statement = acquirePreparedStatement(sql);
748             try {
749                 throwIfStatementForbidden(statement);
750                 bindArguments(statement, bindArgs);
751                 applyBlockGuardPolicy(statement);
752                 attachCancellationSignal(cancellationSignal);
753                 try {
754                     nativeExecute(mConnectionPtr, statement.mStatementPtr, isPragmaStmt);
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 <code>long</code> 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>long</code>, or zero 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      */
executeForLong(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)782     public long executeForLong(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("executeForLong", 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                     long ret = nativeExecuteForLong(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 {@link String} result.
816      *
817      * @param sql The SQL statement to execute.
818      * @param bindArgs The arguments to bind, or null if none.
819      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
820      * @return The value of the first column in the first row of the result set
821      * as a <code>String</code>, 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      */
executeForString(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)827     public String executeForString(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("executeForString", sql, bindArgs);
834         try {
835             final PreparedStatement statement = acquirePreparedStatement(sql);
836             try {
837                 throwIfStatementForbidden(statement);
838                 bindArguments(statement, bindArgs);
839                 applyBlockGuardPolicy(statement);
840                 attachCancellationSignal(cancellationSignal);
841                 try {
842                     String ret = nativeExecuteForString(mConnectionPtr, statement.mStatementPtr);
843                     mRecentOperations.setResult(ret);
844                     return ret;
845                 } finally {
846                     detachCancellationSignal(cancellationSignal);
847                 }
848             } finally {
849                 releasePreparedStatement(statement);
850             }
851         } catch (RuntimeException ex) {
852             mRecentOperations.failOperation(cookie, ex);
853             throw ex;
854         } finally {
855             mRecentOperations.endOperation(cookie);
856         }
857     }
858 
859     /**
860      * Executes a statement that returns a single BLOB result as a
861      * file descriptor to a shared memory region.
862      *
863      * @param sql The SQL statement to execute.
864      * @param bindArgs The arguments to bind, or null if none.
865      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
866      * @return The file descriptor for a shared memory region that contains
867      * the value of the first column in the first row of the result set as a BLOB,
868      * or null if none.
869      *
870      * @throws SQLiteException if an error occurs, such as a syntax error
871      * or invalid number of bind arguments.
872      * @throws OperationCanceledException if the operation was canceled.
873      */
executeForBlobFileDescriptor(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)874     public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
875             CancellationSignal cancellationSignal) {
876         if (sql == null) {
877             throw new IllegalArgumentException("sql must not be null.");
878         }
879 
880         final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor",
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                     int fd = nativeExecuteForBlobFileDescriptor(
891                             mConnectionPtr, statement.mStatementPtr);
892                     return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null;
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             mRecentOperations.endOperation(cookie);
904         }
905     }
906 
907     /**
908      * Executes a statement that returns a count of the number of rows
909      * that were changed.  Use for UPDATE or DELETE SQL statements.
910      *
911      * @param sql The SQL statement to execute.
912      * @param bindArgs The arguments to bind, or null if none.
913      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
914      * @return The number of rows that were changed.
915      *
916      * @throws SQLiteException if an error occurs, such as a syntax error
917      * or invalid number of bind arguments.
918      * @throws OperationCanceledException if the operation was canceled.
919      */
executeForChangedRowCount(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)920     public int executeForChangedRowCount(String sql, Object[] bindArgs,
921             CancellationSignal cancellationSignal) {
922         if (sql == null) {
923             throw new IllegalArgumentException("sql must not be null.");
924         }
925 
926         int changedRows = 0;
927         final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount",
928                 sql, bindArgs);
929         try {
930             final PreparedStatement statement = acquirePreparedStatement(sql);
931             try {
932                 throwIfStatementForbidden(statement);
933                 bindArguments(statement, bindArgs);
934                 applyBlockGuardPolicy(statement);
935                 attachCancellationSignal(cancellationSignal);
936                 try {
937                     changedRows = nativeExecuteForChangedRowCount(
938                             mConnectionPtr, statement.mStatementPtr);
939                     return changedRows;
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             if (mRecentOperations.endOperationDeferLog(cookie)) {
951                 mRecentOperations.logOperation(cookie, "changedRows=" + changedRows);
952             }
953         }
954     }
955 
956     /**
957      * Executes a statement that returns the row id of the last row inserted
958      * by the statement.  Use for INSERT SQL statements.
959      *
960      * @param sql The SQL statement to execute.
961      * @param bindArgs The arguments to bind, or null if none.
962      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
963      * @return The row id of the last row that was inserted, or 0 if none.
964      *
965      * @throws SQLiteException if an error occurs, such as a syntax error
966      * or invalid number of bind arguments.
967      * @throws OperationCanceledException if the operation was canceled.
968      */
executeForLastInsertedRowId(String sql, Object[] bindArgs, CancellationSignal cancellationSignal)969     public long executeForLastInsertedRowId(String sql, Object[] bindArgs,
970             CancellationSignal cancellationSignal) {
971         if (sql == null) {
972             throw new IllegalArgumentException("sql must not be null.");
973         }
974 
975         final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId",
976                 sql, bindArgs);
977         try {
978             final PreparedStatement statement = acquirePreparedStatement(sql);
979             try {
980                 throwIfStatementForbidden(statement);
981                 bindArguments(statement, bindArgs);
982                 applyBlockGuardPolicy(statement);
983                 attachCancellationSignal(cancellationSignal);
984                 try {
985                     return nativeExecuteForLastInsertedRowId(
986                             mConnectionPtr, statement.mStatementPtr);
987                 } finally {
988                     detachCancellationSignal(cancellationSignal);
989                 }
990             } finally {
991                 releasePreparedStatement(statement);
992             }
993         } catch (RuntimeException ex) {
994             mRecentOperations.failOperation(cookie, ex);
995             throw ex;
996         } finally {
997             mRecentOperations.endOperation(cookie);
998         }
999     }
1000 
1001     /**
1002      * Executes a statement and populates the specified {@link CursorWindow}
1003      * with a range of results.  Returns the number of rows that were counted
1004      * during query execution.
1005      *
1006      * @param sql The SQL statement to execute.
1007      * @param bindArgs The arguments to bind, or null if none.
1008      * @param window The cursor window to clear and fill.
1009      * @param startPos The start position for filling the window.
1010      * @param requiredPos The position of a row that MUST be in the window.
1011      * If it won't fit, then the query should discard part of what it filled
1012      * so that it does.  Must be greater than or equal to <code>startPos</code>.
1013      * @param countAllRows True to count all rows that the query would return
1014      * regagless of whether they fit in the window.
1015      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
1016      * @return The number of rows that were counted during query execution.  Might
1017      * not be all rows in the result set unless <code>countAllRows</code> is true.
1018      *
1019      * @throws SQLiteException if an error occurs, such as a syntax error
1020      * or invalid number of bind arguments.
1021      * @throws OperationCanceledException if the operation was canceled.
1022      */
executeForCursorWindow(String sql, Object[] bindArgs, CursorWindow window, int startPos, int requiredPos, boolean countAllRows, CancellationSignal cancellationSignal)1023     public int executeForCursorWindow(String sql, Object[] bindArgs,
1024             CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
1025             CancellationSignal cancellationSignal) {
1026         if (sql == null) {
1027             throw new IllegalArgumentException("sql must not be null.");
1028         }
1029         if (window == null) {
1030             throw new IllegalArgumentException("window must not be null.");
1031         }
1032 
1033         window.acquireReference();
1034         try {
1035             int actualPos = -1;
1036             int countedRows = -1;
1037             int filledRows = -1;
1038             final int cookie = mRecentOperations.beginOperation("executeForCursorWindow",
1039                     sql, bindArgs);
1040             try {
1041                 final PreparedStatement statement = acquirePreparedStatement(sql);
1042                 try {
1043                     throwIfStatementForbidden(statement);
1044                     bindArguments(statement, bindArgs);
1045                     applyBlockGuardPolicy(statement);
1046                     attachCancellationSignal(cancellationSignal);
1047                     try {
1048                         final long result = nativeExecuteForCursorWindow(
1049                                 mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
1050                                 startPos, requiredPos, countAllRows);
1051                         actualPos = (int)(result >> 32);
1052                         countedRows = (int)result;
1053                         filledRows = window.getNumRows();
1054                         window.setStartPosition(actualPos);
1055                         return countedRows;
1056                     } finally {
1057                         detachCancellationSignal(cancellationSignal);
1058                     }
1059                 } finally {
1060                     releasePreparedStatement(statement);
1061                 }
1062             } catch (RuntimeException ex) {
1063                 mRecentOperations.failOperation(cookie, ex);
1064                 throw ex;
1065             } finally {
1066                 if (mRecentOperations.endOperationDeferLog(cookie)) {
1067                     mRecentOperations.logOperation(cookie, "window='" + window
1068                             + "', startPos=" + startPos
1069                             + ", actualPos=" + actualPos
1070                             + ", filledRows=" + filledRows
1071                             + ", countedRows=" + countedRows);
1072                 }
1073             }
1074         } finally {
1075             window.releaseReference();
1076         }
1077     }
1078 
1079     /**
1080      * Return a {@link #PreparedStatement}, possibly from the cache.
1081      */
acquirePreparedStatementLI(String sql)1082     private PreparedStatement acquirePreparedStatementLI(String sql) {
1083         ++mPool.mTotalPrepareStatements;
1084         PreparedStatement statement = mPreparedStatementCache.getStatement(sql);
1085         long seqNum = mPreparedStatementCache.getLastSeqNum();
1086 
1087         boolean skipCache = false;
1088         if (statement != null) {
1089             if (!statement.mInUse) {
1090                 if (statement.mSeqNum == seqNum) {
1091                     // This is a valid statement.  Claim it and return it.
1092                     statement.mInUse = true;
1093                     return statement;
1094                 } else {
1095                     // This is a stale statement.  Remove it from the cache.  Treat this as if the
1096                     // statement was never found, which means we should not skip the cache.
1097                     mPreparedStatementCache.remove(sql);
1098                     statement = null;
1099                     // Leave skipCache == false.
1100                 }
1101             } else {
1102                 // The statement is already in the cache but is in use (this statement appears to
1103                 // be not only re-entrant but recursive!).  So prepare a new copy of the statement
1104                 // but do not cache it.
1105                 skipCache = true;
1106             }
1107         }
1108         ++mPool.mTotalPrepareStatementCacheMiss;
1109         final long statementPtr = mPreparedStatementCache.createStatement(sql);
1110         seqNum = mPreparedStatementCache.getLastSeqNum();
1111         try {
1112             final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
1113             final int type = DatabaseUtils.getSqlStatementTypeExtended(sql);
1114             boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
1115             statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly,
1116                     seqNum);
1117             if (!skipCache && isCacheable(type)) {
1118                 mPreparedStatementCache.put(sql, statement);
1119                 statement.mInCache = true;
1120             }
1121         } catch (RuntimeException ex) {
1122             // Finalize the statement if an exception occurred and we did not add
1123             // it to the cache.  If it is already in the cache, then leave it there.
1124             if (statement == null || !statement.mInCache) {
1125                 nativeFinalizeStatement(mConnectionPtr, statementPtr);
1126             }
1127             throw ex;
1128         }
1129         statement.mInUse = true;
1130         return statement;
1131     }
1132 
1133     /**
1134      * Return a {@link #PreparedStatement}, possibly from the cache.
1135      */
acquirePreparedStatement(String sql)1136     PreparedStatement acquirePreparedStatement(String sql) {
1137         return acquirePreparedStatementLI(sql);
1138     }
1139 
1140     /**
1141      * Release a {@link #PreparedStatement} that was originally supplied by this connection.
1142      */
releasePreparedStatementLI(PreparedStatement statement)1143     private void releasePreparedStatementLI(PreparedStatement statement) {
1144         statement.mInUse = false;
1145         if (statement.mInCache) {
1146             try {
1147                 nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr);
1148             } catch (SQLiteException ex) {
1149                 // The statement could not be reset due to an error.  Remove it from the cache.
1150                 // When remove() is called, the cache will invoke its entryRemoved() callback,
1151                 // which will in turn call finalizePreparedStatement() to finalize and
1152                 // recycle the statement.
1153                 if (DEBUG) {
1154                     Log.d(TAG, "Could not reset prepared statement due to an exception.  "
1155                             + "Removing it from the cache.  SQL: "
1156                             + trimSqlForDisplay(statement.mSql), ex);
1157                 }
1158 
1159                 mPreparedStatementCache.remove(statement.mSql);
1160             }
1161         } else {
1162             finalizePreparedStatement(statement);
1163         }
1164     }
1165 
1166     /**
1167      * Release a {@link #PreparedStatement} that was originally supplied by this connection.
1168      */
releasePreparedStatement(PreparedStatement statement)1169     void releasePreparedStatement(PreparedStatement statement) {
1170         releasePreparedStatementLI(statement);
1171     }
1172 
finalizePreparedStatement(PreparedStatement statement)1173     private void finalizePreparedStatement(PreparedStatement statement) {
1174         nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr);
1175         recyclePreparedStatement(statement);
1176     }
1177 
1178     /**
1179      * Return a prepared statement for use by {@link SQLiteRawStatement}.  This throws if the
1180      * prepared statement is incompatible with this connection.
1181      */
acquirePersistentStatement(@onNull String sql)1182     PreparedStatement acquirePersistentStatement(@NonNull String sql) {
1183         final int cookie = mRecentOperations.beginOperation("prepare", sql, null);
1184         try {
1185             final PreparedStatement statement = acquirePreparedStatement(sql);
1186             throwIfStatementForbidden(statement);
1187             return statement;
1188         } catch (RuntimeException e) {
1189             mRecentOperations.failOperation(cookie, e);
1190             throw e;
1191         } finally {
1192             mRecentOperations.endOperation(cookie);
1193         }
1194     }
1195 
attachCancellationSignal(CancellationSignal cancellationSignal)1196     private void attachCancellationSignal(CancellationSignal cancellationSignal) {
1197         if (cancellationSignal != null) {
1198             cancellationSignal.throwIfCanceled();
1199 
1200             mCancellationSignalAttachCount += 1;
1201             if (mCancellationSignalAttachCount == 1) {
1202                 // Reset cancellation flag before executing the statement.
1203                 nativeResetCancel(mConnectionPtr, true /*cancelable*/);
1204 
1205                 // After this point, onCancel() may be called concurrently.
1206                 cancellationSignal.setOnCancelListener(this);
1207             }
1208         }
1209     }
1210 
detachCancellationSignal(CancellationSignal cancellationSignal)1211     private void detachCancellationSignal(CancellationSignal cancellationSignal) {
1212         if (cancellationSignal != null) {
1213             assert mCancellationSignalAttachCount > 0;
1214 
1215             mCancellationSignalAttachCount -= 1;
1216             if (mCancellationSignalAttachCount == 0) {
1217                 // After this point, onCancel() cannot be called concurrently.
1218                 cancellationSignal.setOnCancelListener(null);
1219 
1220                 // Reset cancellation flag after executing the statement.
1221                 nativeResetCancel(mConnectionPtr, false /*cancelable*/);
1222             }
1223         }
1224     }
1225 
1226     // CancellationSignal.OnCancelListener callback.
1227     // This method may be called on a different thread than the executing statement.
1228     // However, it will only be called between calls to attachCancellationSignal and
1229     // detachCancellationSignal, while a statement is executing.  We can safely assume
1230     // that the SQLite connection is still alive.
1231     @Override
onCancel()1232     public void onCancel() {
1233         nativeCancel(mConnectionPtr);
1234     }
1235 
bindArguments(PreparedStatement statement, Object[] bindArgs)1236     private void bindArguments(PreparedStatement statement, Object[] bindArgs) {
1237         final int count = bindArgs != null ? bindArgs.length : 0;
1238         if (count != statement.mNumParameters) {
1239             throw new SQLiteBindOrColumnIndexOutOfRangeException(
1240                     "Expected " + statement.mNumParameters + " bind arguments but "
1241                     + count + " were provided.");
1242         }
1243         if (count == 0) {
1244             return;
1245         }
1246 
1247         final long statementPtr = statement.mStatementPtr;
1248         for (int i = 0; i < count; i++) {
1249             final Object arg = bindArgs[i];
1250             switch (DatabaseUtils.getTypeOfObject(arg)) {
1251                 case Cursor.FIELD_TYPE_NULL:
1252                     nativeBindNull(mConnectionPtr, statementPtr, i + 1);
1253                     break;
1254                 case Cursor.FIELD_TYPE_INTEGER:
1255                     nativeBindLong(mConnectionPtr, statementPtr, i + 1,
1256                             ((Number)arg).longValue());
1257                     break;
1258                 case Cursor.FIELD_TYPE_FLOAT:
1259                     nativeBindDouble(mConnectionPtr, statementPtr, i + 1,
1260                             ((Number)arg).doubleValue());
1261                     break;
1262                 case Cursor.FIELD_TYPE_BLOB:
1263                     nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg);
1264                     break;
1265                 case Cursor.FIELD_TYPE_STRING:
1266                 default:
1267                     if (arg instanceof Boolean) {
1268                         // Provide compatibility with legacy applications which may pass
1269                         // Boolean values in bind args.
1270                         nativeBindLong(mConnectionPtr, statementPtr, i + 1,
1271                                 ((Boolean)arg).booleanValue() ? 1 : 0);
1272                     } else {
1273                         nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString());
1274                     }
1275                     break;
1276             }
1277         }
1278     }
1279 
1280     /**
1281      * Verify that the statement is read-only, if the connection only allows read-only
1282      * operations.  If the statement is not read-only, then check if the statement only modifies
1283      * temp tables, in which case it is treated the same as a read-only statement and is allowed.
1284      * @param statement The statement to check.
1285      * @throws SQLiteException if the statement could update the database inside a read-only
1286      * transaction.
1287      */
throwIfStatementForbidden(PreparedStatement statement)1288     void throwIfStatementForbidden(PreparedStatement statement) {
1289         if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) {
1290             statement.mReadOnly =
1291                   nativeUpdatesTempOnly(mConnectionPtr, statement.mStatementPtr);
1292             if (statement.mReadOnly) return;
1293 
1294             throw new SQLiteException("Cannot execute this statement because it "
1295                     + "might modify the database but the connection is read-only.");
1296         }
1297     }
1298 
isCacheable(int statementType)1299     private static boolean isCacheable(int statementType) {
1300         if (statementType == DatabaseUtils.STATEMENT_UPDATE
1301             || statementType == DatabaseUtils.STATEMENT_SELECT
1302             || statementType == DatabaseUtils.STATEMENT_WITH) {
1303             return true;
1304         }
1305         return false;
1306     }
1307 
applyBlockGuardPolicy(PreparedStatement statement)1308     private void applyBlockGuardPolicy(PreparedStatement statement) {
1309         if (!mConfiguration.isInMemoryDb()) {
1310             if (statement.mReadOnly) {
1311                 BlockGuard.getThreadPolicy().onReadFromDisk();
1312             } else {
1313                 BlockGuard.getThreadPolicy().onWriteToDisk();
1314             }
1315         }
1316     }
1317 
1318     /**
1319      * Dumps debugging information about this connection.
1320      *
1321      * @param printer The printer to receive the dump, not null.
1322      * @param verbose True to dump more verbose information.
1323      */
dump(Printer printer, boolean verbose)1324     public void dump(Printer printer, boolean verbose) {
1325         dumpUnsafe(printer, verbose);
1326     }
1327 
1328     /**
1329      * Dumps debugging information about this connection, in the case where the
1330      * caller might not actually own the connection.
1331      *
1332      * This function is written so that it may be called by a thread that does not
1333      * own the connection.  We need to be very careful because the connection state is
1334      * not synchronized.
1335      *
1336      * At worst, the method may return stale or slightly wrong data, however
1337      * it should not crash.  This is ok as it is only used for diagnostic purposes.
1338      *
1339      * @param printer The printer to receive the dump, not null.
1340      * @param verbose True to dump more verbose information.
1341      */
dumpUnsafe(Printer printer, boolean verbose)1342     void dumpUnsafe(Printer printer, boolean verbose) {
1343         printer.println("Connection #" + mConnectionId + ":");
1344         if (verbose) {
1345             printer.println("  connectionPtr: 0x" + Long.toHexString(mConnectionPtr));
1346         }
1347         printer.println("  isPrimaryConnection: " + mIsPrimaryConnection);
1348         printer.println("  onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
1349         printer.println("  totalLongOperations: " + mRecentOperations.getTotalLongOperations());
1350 
1351         mRecentOperations.dump(printer);
1352 
1353         if (verbose) {
1354             mPreparedStatementCache.dump(printer);
1355         }
1356     }
1357 
1358     /**
1359      * Describes the currently executing operation, in the case where the
1360      * caller might not actually own the connection.
1361      *
1362      * This function is written so that it may be called by a thread that does not
1363      * own the connection.  We need to be very careful because the connection state is
1364      * not synchronized.
1365      *
1366      * At worst, the method may return stale or slightly wrong data, however
1367      * it should not crash.  This is ok as it is only used for diagnostic purposes.
1368      *
1369      * @return A description of the current operation including how long it has been running,
1370      * or null if none.
1371      */
describeCurrentOperationUnsafe()1372     String describeCurrentOperationUnsafe() {
1373         return mRecentOperations.describeCurrentOperation();
1374     }
1375 
1376     /**
1377      * Collects statistics about database connection memory usage.
1378      *
1379      * @param dbStatsList The list to populate.
1380      */
collectDbStats(ArrayList<DbStats> dbStatsList)1381     void collectDbStats(ArrayList<DbStats> dbStatsList) {
1382         // Get information about the main database.
1383         int lookaside = nativeGetDbLookaside(mConnectionPtr);
1384         long pageCount = 0;
1385         long pageSize = 0;
1386         try {
1387             pageCount = executeForLong("PRAGMA page_count;", null, null);
1388             pageSize = executeForLong("PRAGMA page_size;", null, null);
1389         } catch (SQLiteException ex) {
1390             // Ignore.
1391         }
1392         dbStatsList.add(getMainDbStatsUnsafe(lookaside, pageCount, pageSize));
1393 
1394         // Get information about attached databases.
1395         // We ignore the first row in the database list because it corresponds to
1396         // the main database which we have already described.
1397         CursorWindow window = new CursorWindow("collectDbStats");
1398         try {
1399             executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null);
1400             for (int i = 1; i < window.getNumRows(); i++) {
1401                 String name = window.getString(i, 1);
1402                 String path = window.getString(i, 2);
1403                 pageCount = 0;
1404                 pageSize = 0;
1405                 try {
1406                     pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null);
1407                     pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null);
1408                 } catch (SQLiteException ex) {
1409                     // Ignore.
1410                 }
1411                 StringBuilder label = new StringBuilder("  (attached) ").append(name);
1412                 if (!path.isEmpty()) {
1413                     label.append(": ").append(path);
1414                 }
1415                 dbStatsList.add(
1416                         new DbStats(label.toString(), pageCount, pageSize, 0, 0, 0, 0, false));
1417             }
1418         } catch (SQLiteException ex) {
1419             // Ignore.
1420         } finally {
1421             window.close();
1422         }
1423     }
1424 
1425     /**
1426      * Collects statistics about database connection memory usage, in the case where the
1427      * caller might not actually own the connection.
1428      *
1429      * @return The statistics object, never null.
1430      */
collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList)1431     void collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList) {
1432         dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0));
1433     }
1434 
getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize)1435     private DbStats getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize) {
1436         // The prepared statement cache is thread-safe so we can access its statistics
1437         // even if we do not own the database connection.
1438         String label;
1439         if (mIsPrimaryConnection) {
1440             label = mConfiguration.path;
1441         } else {
1442             label = mConfiguration.path + " (" + mConnectionId + ")";
1443         }
1444         return new DbStats(label, pageCount, pageSize, lookaside,
1445                 mPreparedStatementCache.hitCount(), mPreparedStatementCache.missCount(),
1446                 mPreparedStatementCache.size(), false);
1447     }
1448 
1449     @Override
toString()1450     public String toString() {
1451         return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")";
1452     }
1453 
obtainPreparedStatement(String sql, long statementPtr, int numParameters, int type, boolean readOnly, long seqNum)1454     private PreparedStatement obtainPreparedStatement(String sql, long statementPtr,
1455             int numParameters, int type, boolean readOnly, long seqNum) {
1456         PreparedStatement statement = mPreparedStatementPool;
1457         if (statement != null) {
1458             mPreparedStatementPool = statement.mPoolNext;
1459             statement.mPoolNext = null;
1460             statement.mInCache = false;
1461         } else {
1462             statement = new PreparedStatement();
1463         }
1464         statement.mSql = sql;
1465         statement.mStatementPtr = statementPtr;
1466         statement.mNumParameters = numParameters;
1467         statement.mType = type;
1468         statement.mReadOnly = readOnly;
1469         statement.mSeqNum = seqNum;
1470         return statement;
1471     }
1472 
recyclePreparedStatement(PreparedStatement statement)1473     private void recyclePreparedStatement(PreparedStatement statement) {
1474         statement.mSql = null;
1475         statement.mPoolNext = mPreparedStatementPool;
1476         mPreparedStatementPool = statement;
1477     }
1478 
trimSqlForDisplay(String sql)1479     private static String trimSqlForDisplay(String sql) {
1480         // Note: Creating and caching a regular expression is expensive at preload-time
1481         //       and stops compile-time initialization. This pattern is only used when
1482         //       dumping the connection, which is a rare (mainly error) case. So:
1483         //       DO NOT CACHE.
1484         return sql.replaceAll("[\\s]*\\n+[\\s]*", " ");
1485     }
1486 
1487     // Update the database sequence number.  This number is stored in the prepared statement
1488     // cache.
setDatabaseSeqNum(long n)1489     void setDatabaseSeqNum(long n) {
1490         mPreparedStatementCache.setDatabaseSeqNum(n);
1491     }
1492 
1493     /**
1494      * Holder type for a prepared statement.
1495      *
1496      * Although this object holds a pointer to a native statement object, it
1497      * does not have a finalizer.  This is deliberate.  The {@link SQLiteConnection}
1498      * owns the statement object and will take care of freeing it when needed.
1499      * In particular, closing the connection requires a guarantee of deterministic
1500      * resource disposal because all native statement objects must be freed before
1501      * the native database object can be closed.  So no finalizers here.
1502      *
1503      * The class is package-visible so that {@link SQLiteRawStatement} can use it.
1504      */
1505     static final class PreparedStatement {
1506         // Next item in pool.
1507         public PreparedStatement mPoolNext;
1508 
1509         // The SQL from which the statement was prepared.
1510         public String mSql;
1511 
1512         // The native sqlite3_stmt object pointer.
1513         // Lifetime is managed explicitly by the connection.
1514         public long mStatementPtr;
1515 
1516         // The number of parameters that the prepared statement has.
1517         public int mNumParameters;
1518 
1519         // The statement type.
1520         public int mType;
1521 
1522         // True if the statement is read-only.
1523         public boolean mReadOnly;
1524 
1525         // True if the statement is in the cache.
1526         public boolean mInCache;
1527 
1528         // The database schema ID at the time this statement was created.  The ID is left zero for
1529         // statements that are not cached.  This value is meaningful only if mInCache is true.
1530         public long mSeqNum;
1531 
1532         // True if the statement is in use (currently executing).
1533         // We need this flag because due to the use of custom functions in triggers, it's
1534         // possible for SQLite calls to be re-entrant.  Consequently we need to prevent
1535         // in use statements from being finalized until they are no longer in use.
1536         public boolean mInUse;
1537     }
1538 
1539     private final class PreparedStatementCache extends LruCache<String, PreparedStatement> {
1540         // The database sequence number.  This changes every time the database schema changes.
1541         private long mDatabaseSeqNum = 0;
1542 
1543         // The database sequence number from the last getStatement() or createStatement()
1544         // call. The proper use of this variable depends on the caller being single threaded.
1545         private long mLastSeqNum = 0;
1546 
PreparedStatementCache(int size)1547         public PreparedStatementCache(int size) {
1548             super(size);
1549         }
1550 
setDatabaseSeqNum(long n)1551         public synchronized void setDatabaseSeqNum(long n) {
1552             mDatabaseSeqNum = n;
1553         }
1554 
1555         // Return the last database sequence number.
getLastSeqNum()1556         public long getLastSeqNum() {
1557             return mLastSeqNum;
1558         }
1559 
1560         // Return a statement from the cache.  Save the database sequence number for the caller.
getStatement(String sql)1561         public synchronized PreparedStatement getStatement(String sql) {
1562             mLastSeqNum = mDatabaseSeqNum;
1563             return get(sql);
1564         }
1565 
1566         // Return a new native prepared statement and save the database sequence number for the
1567         // caller.  This does not modify the cache in any way.  However, by being synchronized,
1568         // callers are guaranteed that the sequence number did not change across the native
1569         // preparation step.
createStatement(String sql)1570         public synchronized long createStatement(String sql) {
1571             mLastSeqNum = mDatabaseSeqNum;
1572             return nativePrepareStatement(mConnectionPtr, sql);
1573         }
1574 
1575         @Override
entryRemoved(boolean evicted, String key, PreparedStatement oldValue, PreparedStatement newValue)1576         protected void entryRemoved(boolean evicted, String key,
1577                 PreparedStatement oldValue, PreparedStatement newValue) {
1578             oldValue.mInCache = false;
1579             if (!oldValue.mInUse) {
1580                 finalizePreparedStatement(oldValue);
1581             }
1582         }
1583 
dump(Printer printer)1584         public void dump(Printer printer) {
1585             printer.println("  Prepared statement cache:");
1586             Map<String, PreparedStatement> cache = snapshot();
1587             if (!cache.isEmpty()) {
1588                 int i = 0;
1589                 for (Map.Entry<String, PreparedStatement> entry : cache.entrySet()) {
1590                     PreparedStatement statement = entry.getValue();
1591                     if (statement.mInCache) { // might be false due to a race with entryRemoved
1592                         String sql = entry.getKey();
1593                         printer.println("    " + i + ": statementPtr=0x"
1594                                 + Long.toHexString(statement.mStatementPtr)
1595                                 + ", numParameters=" + statement.mNumParameters
1596                                 + ", type=" + statement.mType
1597                                 + ", readOnly=" + statement.mReadOnly
1598                                 + ", sql=\"" + trimSqlForDisplay(sql) + "\"");
1599                     }
1600                     i += 1;
1601                 }
1602             } else {
1603                 printer.println("    <none>");
1604             }
1605         }
1606     }
1607 
1608     /**
1609      * This class implements a leaky bucket strategy to rate-limit operations.  A client
1610      * accumulates one credit every <n> milliseconds; a credit allows the client execute an
1611      * operation (which then deducts the credit).  Credits accumulate up to a maximum amount after
1612      * which they no longer accumulate.  The strategy allows a client to execute an operation
1613      * every <n> milliseconds, or to execute a burst, after a period of no operations.
1614      */
1615     private static class RateLimiter {
1616         // When the bucket was created, in ms.
1617         private final long mCreationUptimeMs;
1618         // The time required to accumulate a single credit.
1619         private final long mMsPerCredit;
1620         // The maximum number of credits the process can accumulate.
1621         private final int mMaxCredits;
1622         // Total credits consumed so far.
1623         private long mSpent = 0;
1624 
RateLimiter(long msPerCredit, int maxCredits)1625         RateLimiter(long msPerCredit, int maxCredits) {
1626             mMsPerCredit = msPerCredit;
1627             mMaxCredits = maxCredits;
1628             mCreationUptimeMs = SystemClock.uptimeMillis() - (mMsPerCredit * mMaxCredits);
1629         }
1630 
1631         /** Return true if there is a credit available (and consume that credit). */
tryAcquire()1632         boolean tryAcquire() {
1633             final long now = SystemClock.uptimeMillis();
1634             long credits = (now - mCreationUptimeMs) / mMsPerCredit;
1635 
1636             long available = credits - mSpent;
1637             if (available > mMaxCredits) {
1638                 mSpent += available - mMaxCredits;
1639                 available = credits - mSpent;
1640             }
1641             if (available > 0) {
1642                 mSpent++;
1643                 return true;
1644             } else {
1645                 return false;
1646             }
1647         }
1648     }
1649 
1650     private final class OperationLog {
1651         private static final int MAX_RECENT_OPERATIONS = 20;
1652         private static final int COOKIE_GENERATION_SHIFT = 8;
1653         private static final int COOKIE_INDEX_MASK = 0xff;
1654 
1655         // Operations over 2s are long.  Save the last ten.
1656         private static final long LONG_OPERATION_THRESHOLD_MS = 2_000;
1657         private static final int MAX_LONG_OPERATIONS = 10;
1658 
1659         private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
1660         private int mIndex = -1;
1661         private int mGeneration = 0;
1662         private final Operation mTransaction = new Operation();
1663         private long mResultLong = Long.MIN_VALUE;
1664         private String mResultString;
1665 
1666         private final RingBuffer<Operation> mLongOperations =
1667                 new RingBuffer<>(()->{return new Operation();},
1668                         (n) ->{return new Operation[n];},
1669                         MAX_LONG_OPERATIONS);
1670         private int mTotalLongOperations = 0;
1671 
1672         // Limit log messages to one every 5 minutes, except that a burst may be 10 messages long.
1673         private final RateLimiter mLongLimiter = new RateLimiter(300_000, 10);
1674 
beginOperation(String kind, String sql, Object[] bindArgs)1675         public int beginOperation(String kind, String sql, Object[] bindArgs) {
1676             mResultLong = Long.MIN_VALUE;
1677             mResultString = null;
1678 
1679             synchronized (mOperations) {
1680                 Operation operation = newOperationLocked();
1681                 operation.mKind = kind;
1682                 operation.mSql = sql;
1683                 if (bindArgs != null) {
1684                     if (operation.mBindArgs == null) {
1685                         operation.mBindArgs = new ArrayList<Object>();
1686                     }
1687                     for (int i = 0; i < bindArgs.length; i++) {
1688                         final Object arg = bindArgs[i];
1689                         if (arg != null && arg instanceof byte[]) {
1690                             // Don't hold onto the real byte array longer than necessary.
1691                             operation.mBindArgs.add(EMPTY_BYTE_ARRAY);
1692                         } else {
1693                             operation.mBindArgs.add(arg);
1694                         }
1695                     }
1696                 }
1697                 operation.mTraced = Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE);
1698                 if (operation.mTraced) {
1699                     Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
1700                             operation.mCookie);
1701                 }
1702                 return operation.mCookie;
1703             }
1704         }
1705 
beginTransaction(String kind)1706         public void beginTransaction(String kind) {
1707             synchronized (mOperations) {
1708                 Operation operation = newOperationLocked();
1709                 operation.mKind = kind;
1710                 mTransaction.copyFrom(operation);
1711 
1712                 if (operation.mTraced) {
1713                     Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
1714                             operation.mCookie);
1715                 }
1716             }
1717         }
1718 
1719         /**
1720          * Fetch a new operation from the ring buffer.  The operation is properly initialized.
1721          * This advances mIndex to point to the next element.
1722          */
newOperationLocked()1723         private Operation newOperationLocked() {
1724             final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
1725             Operation operation = mOperations[index];
1726             if (operation == null) {
1727                 mOperations[index] = new Operation();
1728                 operation = mOperations[index];
1729             }
1730             operation.start();
1731             operation.mCookie = newOperationCookieLocked(index);
1732             mIndex = index;
1733             return operation;
1734         }
1735 
failOperation(int cookie, Exception ex)1736         public void failOperation(int cookie, Exception ex) {
1737             synchronized (mOperations) {
1738                 final Operation operation = getOperationLocked(cookie);
1739                 if (operation != null) {
1740                     operation.mException = ex;
1741                 }
1742             }
1743         }
1744 
endOperation(int cookie)1745         public void endOperation(int cookie) {
1746             synchronized (mOperations) {
1747                 if (endOperationDeferLogLocked(cookie)) {
1748                     logOperationLocked(cookie, null);
1749                 }
1750             }
1751         }
1752 
endOperationDeferLog(int cookie)1753         public boolean endOperationDeferLog(int cookie) {
1754             synchronized (mOperations) {
1755                 return endOperationDeferLogLocked(cookie);
1756             }
1757         }
1758 
endTransaction(boolean success)1759         public boolean endTransaction(boolean success) {
1760             synchronized (mOperations) {
1761                 mTransaction.mResultLong = success ? 1 : 0;
1762                 final long execTime = finishOperationLocked(mTransaction);
1763                 final Operation operation = getOperationLocked(mTransaction.mCookie);
1764                 if (operation != null) {
1765                     operation.copyFrom(mTransaction);
1766                 }
1767                 mTransaction.setEmpty();
1768                 return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES
1769                         && SQLiteDebug.shouldLogSlowQuery(execTime);
1770             }
1771         }
1772 
logOperation(int cookie, String detail)1773         public void logOperation(int cookie, String detail) {
1774             synchronized (mOperations) {
1775                 logOperationLocked(cookie, detail);
1776             }
1777         }
1778 
setResult(long longResult)1779         public void setResult(long longResult) {
1780             mResultLong = longResult;
1781         }
1782 
setResult(String stringResult)1783         public void setResult(String stringResult) {
1784             mResultString = stringResult;
1785         }
1786 
endOperationDeferLogLocked(int cookie)1787         private boolean endOperationDeferLogLocked(int cookie) {
1788             final Operation operation = getOperationLocked(cookie);
1789             if (operation != null) {
1790                 if (operation.mTraced) {
1791                     Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
1792                             operation.mCookie);
1793                 }
1794                 final long execTime = finishOperationLocked(operation);
1795                 mPool.onStatementExecuted(execTime);
1796                 return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
1797                         execTime);
1798             }
1799             return false;
1800         }
1801 
logOperationLocked(int cookie, String detail)1802         private void logOperationLocked(int cookie, String detail) {
1803             final Operation operation = getOperationLocked(cookie);
1804             operation.mResultLong = mResultLong;
1805             operation.mResultString = mResultString;
1806             StringBuilder msg = new StringBuilder();
1807             operation.describe(msg, true);
1808             if (detail != null) {
1809                 msg.append(", ").append(detail);
1810             }
1811             Log.d(TAG, msg.toString());
1812         }
1813 
newOperationCookieLocked(int index)1814         private int newOperationCookieLocked(int index) {
1815             final int generation = mGeneration++;
1816             return generation << COOKIE_GENERATION_SHIFT | index;
1817         }
1818 
1819         /** Close out the operation and return the elapsed time. */
finishOperationLocked(Operation operation)1820         private long finishOperationLocked(Operation operation) {
1821             operation.mEndTime = SystemClock.uptimeMillis();
1822             operation.mFinished = true;
1823             final long elapsed = operation.mEndTime - operation.mStartTime;
1824             if (elapsed > LONG_OPERATION_THRESHOLD_MS) {
1825                 mLongOperations.getNextSlot().copyFrom(operation);
1826                 mTotalLongOperations++;
1827                 if (mLongLimiter.tryAcquire()) {
1828                     Log.i(TAG, "Long db operation: " + mConfiguration.label);
1829                 }
1830             }
1831             return elapsed;
1832         }
1833 
getOperationLocked(int cookie)1834         private Operation getOperationLocked(int cookie) {
1835             final int index = cookie & COOKIE_INDEX_MASK;
1836             final Operation operation = mOperations[index];
1837             return (operation != null && operation.mCookie == cookie) ? operation : null;
1838         }
1839 
describeCurrentOperation()1840         public String describeCurrentOperation() {
1841             synchronized (mOperations) {
1842                 final Operation operation = mOperations[mIndex];
1843                 if (operation != null && !operation.mFinished) {
1844                     StringBuilder msg = new StringBuilder();
1845                     operation.describe(msg, false);
1846                     return msg.toString();
1847                 }
1848                 return null;
1849             }
1850         }
1851 
1852         /**
1853          * Dump an Operation if it is not in the recent operations list.  Return 1 if the
1854          * operation was dumped and 0 if not.
1855          */
dumpIfNotRecentLocked(Printer pw, Operation op, int counter)1856         private int dumpIfNotRecentLocked(Printer pw, Operation op, int counter) {
1857             if (op == null || op.isEmpty() || getOperationLocked(op.mCookie) != null) {
1858                 return 0;
1859             }
1860             pw.println(op.describe(counter));
1861             return 1;
1862         }
1863 
dumpRecentLocked(Printer printer)1864         private void dumpRecentLocked(Printer printer) {
1865             synchronized (mOperations) {
1866                 printer.println("  Most recently executed operations:");
1867                 int index = mIndex;
1868                 if (index == 0) {
1869                     printer.println("    <none>");
1870                     return;
1871                 }
1872 
1873                 // Operations are dumped in order of most recent first.
1874                 int counter = 0;
1875                 int n = 0;
1876                 Operation operation = mOperations[index];
1877                 do {
1878                     printer.println(operation.describe(counter));
1879 
1880                     if (index > 0) {
1881                         index -= 1;
1882                     } else {
1883                         index = MAX_RECENT_OPERATIONS - 1;
1884                     }
1885                     n++;
1886                     counter++;
1887                     operation = mOperations[index];
1888                 } while (operation != null && n < MAX_RECENT_OPERATIONS);
1889                 counter += dumpIfNotRecentLocked(printer, mTransaction, counter);
1890             }
1891         }
1892 
dumpLongLocked(Printer printer)1893         private void dumpLongLocked(Printer printer) {
1894             printer.println("  Operations exceeding " + LONG_OPERATION_THRESHOLD_MS + "ms:");
1895             if (mLongOperations.isEmpty()) {
1896                 printer.println("    <none>");
1897                 return;
1898             }
1899             Operation[] longOps = mLongOperations.toArray();
1900             for (int i = 0; i < longOps.length; i++) {
1901                 if (longOps[i] != null) {
1902                     printer.println(longOps[i].describe(i));
1903                 }
1904             }
1905         }
1906 
getTotalLongOperations()1907         public long getTotalLongOperations() {
1908             return mTotalLongOperations;
1909         }
1910 
dump(Printer printer)1911         public void dump(Printer printer) {
1912             synchronized (mOperations) {
1913                 dumpRecentLocked(printer);
1914                 dumpLongLocked(printer);
1915             }
1916         }
1917     }
1918 
1919     private final class Operation {
1920         // Trim all SQL statements to 256 characters inside the trace marker.
1921         // This limit gives plenty of context while leaving space for other
1922         // entries in the trace buffer (and ensures atrace doesn't truncate the
1923         // marker for us, potentially losing metadata in the process).
1924         private static final int MAX_TRACE_METHOD_NAME_LEN = 256;
1925 
1926         // The reserved start time that indicates the Operation is empty.
1927         private static final long EMPTY_OPERATION = -1;
1928 
1929         // The formatter for the timestamp.
1930         private static final DateTimeFormatter sDateTime =
1931                 DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS", Locale.US);
1932 
1933         public long mStartWallTime; // in System.currentTimeMillis()
1934         public long mStartTime; // in SystemClock.uptimeMillis();
1935         public long mEndTime; // in SystemClock.uptimeMillis();
1936         public String mKind;
1937         public String mSql;
1938         public ArrayList<Object> mBindArgs;
1939         public boolean mFinished;
1940         public Exception mException;
1941         public int mCookie;
1942         public long mResultLong; // MIN_VALUE means "value not set".
1943         public String mResultString;
1944         public boolean mTraced;
1945 
1946         /** Reset the object to begin a new operation. */
start()1947         void start() {
1948             mStartWallTime = System.currentTimeMillis();
1949             mStartTime = SystemClock.uptimeMillis();
1950             mEndTime = Long.MIN_VALUE;
1951             mKind = null;
1952             mSql = null;
1953             if (mBindArgs != null) mBindArgs.clear();
1954             mFinished = false;
1955             mException = null;
1956             mCookie = -1;
1957             mResultLong = Long.MIN_VALUE;
1958             mResultString = null;
1959             mTraced = false;
1960         }
1961 
1962         /**
1963          * Initialize from the source object.  This is meant to clone the object for use in a
1964          * transaction operation.  To that end, the local bind args are set to null.
1965          */
copyFrom(Operation r)1966         void copyFrom(Operation r) {
1967             mStartWallTime = r.mStartWallTime;
1968             mStartTime = r.mStartTime;
1969             mEndTime = r.mEndTime;
1970             mKind = r.mKind;
1971             mSql = r.mSql;
1972             mBindArgs = null;
1973             mFinished = r.mFinished;
1974             mException = r.mException;
1975             mCookie = r.mCookie;
1976             mResultLong = r.mResultLong;
1977             mResultString = r.mResultString;
1978             mTraced = r.mTraced;
1979         }
1980 
1981         /** Mark the operation empty. */
setEmpty()1982         void setEmpty() {
1983             mStartWallTime = EMPTY_OPERATION;
1984         }
1985 
1986         /** Return true if the operation is empty. */
isEmpty()1987         boolean isEmpty() {
1988             return mStartWallTime == EMPTY_OPERATION;
1989         }
1990 
describe(StringBuilder msg, boolean allowDetailedLog)1991         public void describe(StringBuilder msg, boolean allowDetailedLog) {
1992             msg.append(mKind);
1993             if (mFinished) {
1994                 msg.append(" took ").append(mEndTime - mStartTime).append("ms");
1995             } else {
1996                 msg.append(" started ").append(SystemClock.uptimeMillis() - mStartTime)
1997                         .append("ms ago");
1998             }
1999             msg.append(" - ").append(getStatus());
2000             if (mSql != null) {
2001                 msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\"");
2002             }
2003             final boolean dumpDetails = allowDetailedLog && NoPreloadHolder.DEBUG_LOG_DETAILED
2004                     && mBindArgs != null && mBindArgs.size() != 0;
2005             if (dumpDetails) {
2006                 msg.append(", bindArgs=[");
2007                 final int count = mBindArgs.size();
2008                 for (int i = 0; i < count; i++) {
2009                     final Object arg = mBindArgs.get(i);
2010                     if (i != 0) {
2011                         msg.append(", ");
2012                     }
2013                     if (arg == null) {
2014                         msg.append("null");
2015                     } else if (arg instanceof byte[]) {
2016                         msg.append("<byte[]>");
2017                     } else if (arg instanceof String) {
2018                         msg.append("\"").append((String)arg).append("\"");
2019                     } else {
2020                         msg.append(arg);
2021                     }
2022                 }
2023                 msg.append("]");
2024             }
2025             msg.append(", path=").append(mPool.getPath());
2026             if (mException != null) {
2027                 msg.append(", exception=\"").append(mException.getMessage()).append("\"");
2028             }
2029             if (mResultLong != Long.MIN_VALUE) {
2030                 msg.append(", result=").append(mResultLong);
2031             }
2032             if (mResultString != null) {
2033                 msg.append(", result=\"").append(mResultString).append("\"");
2034             }
2035         }
2036 
2037         /**
2038          * Convert a wall-clock time in milliseconds to logcat format.
2039          */
timeString(long millis)2040         private String timeString(long millis) {
2041             return sDateTime.withZone(ZoneId.systemDefault()).format(Instant.ofEpochMilli(millis));
2042         }
2043 
describe(int n)2044         public String describe(int n) {
2045             final StringBuilder msg = new StringBuilder();
2046             final String start = timeString(mStartWallTime);
2047             msg.append("    ").append(n).append(": [").append(start).append("] ");
2048             describe(msg, false); // Never dump bindargs in a bugreport
2049             return msg.toString();
2050         }
2051 
getStatus()2052         private String getStatus() {
2053             if (!mFinished) {
2054                 return "running";
2055             }
2056             return mException != null ? "failed" : "succeeded";
2057         }
2058 
getTraceMethodName()2059         private String getTraceMethodName() {
2060             String methodName = mKind + " " + mSql;
2061             if (methodName.length() > MAX_TRACE_METHOD_NAME_LEN)
2062                 return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN);
2063             return methodName;
2064         }
2065     }
2066 
2067     /**
2068      * Return the ROWID of the last row to be inserted under this connection.  Returns 0 if there
2069      * has never been an insert on this connection.
2070      * @return The ROWID of the last row to be inserted under this connection.
2071      * @hide
2072      */
getLastInsertRowId()2073     long getLastInsertRowId() {
2074         try {
2075             return nativeLastInsertRowId(mConnectionPtr);
2076         } finally {
2077             Reference.reachabilityFence(this);
2078         }
2079     }
2080 
2081     /**
2082      * Return the number of database changes on the current connection made by the last SQL
2083      * statement
2084      * @hide
2085      */
getLastChangedRowCount()2086     long getLastChangedRowCount() {
2087         try {
2088             return nativeChanges(mConnectionPtr);
2089         } finally {
2090             Reference.reachabilityFence(this);
2091         }
2092     }
2093 
2094     /**
2095      * Return the total number of database changes made on the current connection.
2096      * @hide
2097      */
getTotalChangedRowCount()2098     long getTotalChangedRowCount() {
2099         try {
2100             return nativeTotalChanges(mConnectionPtr);
2101         } finally {
2102             Reference.reachabilityFence(this);
2103         }
2104     }
2105 }
2106