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