blob: 9c639a5a986795c4ce1a6cd62c9202594482fb2a [file] [log] [blame]
Jeff Browne5360fb2011-10-31 17:48:13 -07001/*
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
17package android.database.sqlite;
18
Jeff Browne5360fb2011-10-31 17:48:13 -070019import android.database.Cursor;
20import android.database.CursorWindow;
21import android.database.DatabaseUtils;
22import android.database.sqlite.SQLiteDebug.DbStats;
Jeff Browna7771df2012-05-07 20:06:46 -070023import android.os.CancellationSignal;
24import android.os.OperationCanceledException;
Jeff Browne5360fb2011-10-31 17:48:13 -070025import android.os.ParcelFileDescriptor;
Makoto Onuki34436702016-04-07 09:07:04 -070026import android.os.SystemClock;
Greg Hackmanne12350f2014-12-01 14:31:21 -080027import android.os.Trace;
Jeff Browne5360fb2011-10-31 17:48:13 -070028import android.util.Log;
29import android.util.LruCache;
30import android.util.Printer;
31
Fyodor Kupolovd3b0c7e2017-06-20 11:51:55 -070032import dalvik.system.BlockGuard;
33import dalvik.system.CloseGuard;
34
Makoto Onuki0939c5a2018-08-24 13:38:40 -070035import java.io.File;
Jeff Browne5360fb2011-10-31 17:48:13 -070036import java.text.SimpleDateFormat;
37import java.util.ArrayList;
Elliott Hughesc00df6d2013-05-06 10:53:28 -070038import java.util.Date;
Jeff Browne5360fb2011-10-31 17:48:13 -070039import java.util.Map;
Fyodor Kupolovd3b0c7e2017-06-20 11:51:55 -070040
Jeff Browne5360fb2011-10-31 17:48:13 -070041
42/**
43 * Represents a SQLite database connection.
44 * Each connection wraps an instance of a native <code>sqlite3</code> object.
45 * <p>
46 * When database connection pooling is enabled, there can be multiple active
47 * connections to the same database. Otherwise there is typically only one
48 * connection per database.
49 * </p><p>
50 * When the SQLite WAL feature is enabled, multiple readers and one writer
51 * can concurrently access the database. Without WAL, readers and writers
52 * are mutually exclusive.
53 * </p>
54 *
55 * <h2>Ownership and concurrency guarantees</h2>
56 * <p>
57 * Connection objects are not thread-safe. They are acquired as needed to
58 * perform a database operation and are then returned to the pool. At any
59 * given time, a connection is either owned and used by a {@link SQLiteSession}
60 * object or the {@link SQLiteConnectionPool}. Those classes are
61 * responsible for serializing operations to guard against concurrent
62 * use of a connection.
63 * </p><p>
64 * The guarantee of having a single owner allows this class to be implemented
65 * without locks and greatly simplifies resource management.
66 * </p>
67 *
68 * <h2>Encapsulation guarantees</h2>
69 * <p>
70 * The connection object object owns *all* of the SQLite related native
71 * objects that are associated with the connection. What's more, there are
72 * no other objects in the system that are capable of obtaining handles to
73 * those native objects. Consequently, when the connection is closed, we do
74 * not have to worry about what other components might have references to
75 * its associated SQLite state -- there are none.
76 * </p><p>
77 * Encapsulation is what ensures that the connection object's
78 * lifecycle does not become a tortured mess of finalizers and reference
79 * queues.
80 * </p>
81 *
Jeff Browna9be4152012-01-18 15:29:57 -080082 * <h2>Reentrance</h2>
83 * <p>
84 * This class must tolerate reentrant execution of SQLite operations because
85 * triggers may call custom SQLite functions that perform additional queries.
86 * </p>
87 *
Jeff Browne5360fb2011-10-31 17:48:13 -070088 * @hide
89 */
Jeff Brown4c1241d2012-02-02 17:05:00 -080090public final class SQLiteConnection implements CancellationSignal.OnCancelListener {
Jeff Browne5360fb2011-10-31 17:48:13 -070091 private static final String TAG = "SQLiteConnection";
Jeff Brown2a293b62012-01-19 14:02:22 -080092 private static final boolean DEBUG = false;
Jeff Browne5360fb2011-10-31 17:48:13 -070093
94 private static final String[] EMPTY_STRING_ARRAY = new String[0];
95 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
96
Jeff Browne5360fb2011-10-31 17:48:13 -070097 private final CloseGuard mCloseGuard = CloseGuard.get();
98
99 private final SQLiteConnectionPool mPool;
100 private final SQLiteDatabaseConfiguration mConfiguration;
101 private final int mConnectionId;
102 private final boolean mIsPrimaryConnection;
Jeff Brown1d9f7422012-03-15 14:32:32 -0700103 private final boolean mIsReadOnlyConnection;
Jeff Browne5360fb2011-10-31 17:48:13 -0700104 private final PreparedStatementCache mPreparedStatementCache;
105 private PreparedStatement mPreparedStatementPool;
106
107 // The recent operations log.
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -0700108 private final OperationLog mRecentOperations;
Jeff Browne5360fb2011-10-31 17:48:13 -0700109
110 // The native SQLiteConnection pointer. (FOR INTERNAL USE ONLY)
Ashok Bhat738702d2014-01-02 13:42:56 +0000111 private long mConnectionPtr;
Jeff Browne5360fb2011-10-31 17:48:13 -0700112
113 private boolean mOnlyAllowReadOnlyOperations;
114
Jeff Brown4c1241d2012-02-02 17:05:00 -0800115 // The number of times attachCancellationSignal has been called.
Jeff Brown1d9f7422012-03-15 14:32:32 -0700116 // Because SQLite statement execution can be reentrant, we keep track of how many
Jeff Brown4c1241d2012-02-02 17:05:00 -0800117 // times we have attempted to attach a cancellation signal to the connection so that
Jeff Brown75ea64f2012-01-25 19:37:13 -0800118 // we can ensure that we detach the signal at the right time.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800119 private int mCancellationSignalAttachCount;
Jeff Brown75ea64f2012-01-25 19:37:13 -0800120
Ashok Bhat738702d2014-01-02 13:42:56 +0000121 private static native long nativeOpen(String path, int openFlags, String label,
Fyodor Kupolovd3b0c7e2017-06-20 11:51:55 -0700122 boolean enableTrace, boolean enableProfile, int lookasideSlotSize,
123 int lookasideSlotCount);
Ashok Bhat738702d2014-01-02 13:42:56 +0000124 private static native void nativeClose(long connectionPtr);
125 private static native void nativeRegisterCustomFunction(long connectionPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700126 SQLiteCustomFunction function);
Ashok Bhat738702d2014-01-02 13:42:56 +0000127 private static native void nativeRegisterLocalizedCollators(long connectionPtr, String locale);
128 private static native long nativePrepareStatement(long connectionPtr, String sql);
129 private static native void nativeFinalizeStatement(long connectionPtr, long statementPtr);
130 private static native int nativeGetParameterCount(long connectionPtr, long statementPtr);
131 private static native boolean nativeIsReadOnly(long connectionPtr, long statementPtr);
132 private static native int nativeGetColumnCount(long connectionPtr, long statementPtr);
133 private static native String nativeGetColumnName(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700134 int index);
Ashok Bhat738702d2014-01-02 13:42:56 +0000135 private static native void nativeBindNull(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700136 int index);
Ashok Bhat738702d2014-01-02 13:42:56 +0000137 private static native void nativeBindLong(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700138 int index, long value);
Ashok Bhat738702d2014-01-02 13:42:56 +0000139 private static native void nativeBindDouble(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700140 int index, double value);
Ashok Bhat738702d2014-01-02 13:42:56 +0000141 private static native void nativeBindString(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700142 int index, String value);
Ashok Bhat738702d2014-01-02 13:42:56 +0000143 private static native void nativeBindBlob(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700144 int index, byte[] value);
145 private static native void nativeResetStatementAndClearBindings(
Ashok Bhat738702d2014-01-02 13:42:56 +0000146 long connectionPtr, long statementPtr);
147 private static native void nativeExecute(long connectionPtr, long statementPtr);
148 private static native long nativeExecuteForLong(long connectionPtr, long statementPtr);
149 private static native String nativeExecuteForString(long connectionPtr, long statementPtr);
Jeff Browne5360fb2011-10-31 17:48:13 -0700150 private static native int nativeExecuteForBlobFileDescriptor(
Ashok Bhat738702d2014-01-02 13:42:56 +0000151 long connectionPtr, long statementPtr);
152 private static native int nativeExecuteForChangedRowCount(long connectionPtr, long statementPtr);
Jeff Browne5360fb2011-10-31 17:48:13 -0700153 private static native long nativeExecuteForLastInsertedRowId(
Ashok Bhat738702d2014-01-02 13:42:56 +0000154 long connectionPtr, long statementPtr);
Jeff Browne5360fb2011-10-31 17:48:13 -0700155 private static native long nativeExecuteForCursorWindow(
Ashok Bhat738702d2014-01-02 13:42:56 +0000156 long connectionPtr, long statementPtr, long windowPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700157 int startPos, int requiredPos, boolean countAllRows);
Ashok Bhat738702d2014-01-02 13:42:56 +0000158 private static native int nativeGetDbLookaside(long connectionPtr);
159 private static native void nativeCancel(long connectionPtr);
160 private static native void nativeResetCancel(long connectionPtr, boolean cancelable);
Jeff Browne5360fb2011-10-31 17:48:13 -0700161
162 private SQLiteConnection(SQLiteConnectionPool pool,
163 SQLiteDatabaseConfiguration configuration,
164 int connectionId, boolean primaryConnection) {
165 mPool = pool;
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -0700166 mRecentOperations = new OperationLog(mPool);
Jeff Browne5360fb2011-10-31 17:48:13 -0700167 mConfiguration = new SQLiteDatabaseConfiguration(configuration);
168 mConnectionId = connectionId;
169 mIsPrimaryConnection = primaryConnection;
Jeff Brown1d9f7422012-03-15 14:32:32 -0700170 mIsReadOnlyConnection = (configuration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
Jeff Browne5360fb2011-10-31 17:48:13 -0700171 mPreparedStatementCache = new PreparedStatementCache(
172 mConfiguration.maxSqlCacheSize);
173 mCloseGuard.open("close");
174 }
175
176 @Override
177 protected void finalize() throws Throwable {
178 try {
179 if (mPool != null && mConnectionPtr != 0) {
180 mPool.onConnectionLeaked();
181 }
182
183 dispose(true);
184 } finally {
185 super.finalize();
186 }
187 }
188
189 // Called by SQLiteConnectionPool only.
190 static SQLiteConnection open(SQLiteConnectionPool pool,
191 SQLiteDatabaseConfiguration configuration,
192 int connectionId, boolean primaryConnection) {
193 SQLiteConnection connection = new SQLiteConnection(pool, configuration,
194 connectionId, primaryConnection);
195 try {
196 connection.open();
197 return connection;
198 } catch (SQLiteException ex) {
199 connection.dispose(false);
200 throw ex;
201 }
202 }
203
204 // Called by SQLiteConnectionPool only.
205 // Closes the database closes and releases all of its associated resources.
206 // Do not call methods on the connection after it is closed. It will probably crash.
207 void close() {
208 dispose(false);
209 }
210
211 private void open() {
Jeff Browne5360fb2011-10-31 17:48:13 -0700212 mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
213 mConfiguration.label,
Fyodor Kupolovd3b0c7e2017-06-20 11:51:55 -0700214 SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME,
215 mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount);
Jeff Brown5936ff02012-02-29 21:03:20 -0800216 setPageSize();
Jeff Brown96496ad2012-03-23 14:38:06 -0700217 setForeignKeyModeFromConfiguration();
Jeff Brownd67c8c62012-03-22 14:15:01 -0700218 setWalModeFromConfiguration();
Jeff Brown8dc3cc22012-03-02 10:33:52 -0800219 setJournalSizeLimit();
220 setAutoCheckpointInterval();
Jeff Browne5360fb2011-10-31 17:48:13 -0700221 setLocaleFromConfiguration();
Niklas Brunlid27a65242012-09-25 12:55:34 +0200222
223 // Register custom functions.
224 final int functionCount = mConfiguration.customFunctions.size();
225 for (int i = 0; i < functionCount; i++) {
226 SQLiteCustomFunction function = mConfiguration.customFunctions.get(i);
227 nativeRegisterCustomFunction(mConnectionPtr, function);
228 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700229 }
230
231 private void dispose(boolean finalized) {
232 if (mCloseGuard != null) {
233 if (finalized) {
234 mCloseGuard.warnIfOpen();
235 }
236 mCloseGuard.close();
237 }
238
239 if (mConnectionPtr != 0) {
Jeff Browna9be4152012-01-18 15:29:57 -0800240 final int cookie = mRecentOperations.beginOperation("close", null, null);
Jeff Browne5360fb2011-10-31 17:48:13 -0700241 try {
242 mPreparedStatementCache.evictAll();
243 nativeClose(mConnectionPtr);
244 mConnectionPtr = 0;
245 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800246 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700247 }
248 }
249 }
250
Jeff Brown5936ff02012-02-29 21:03:20 -0800251 private void setPageSize() {
Jeff Brown1d9f7422012-03-15 14:32:32 -0700252 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
253 final long newValue = SQLiteGlobal.getDefaultPageSize();
254 long value = executeForLong("PRAGMA page_size", null, null);
255 if (value != newValue) {
256 execute("PRAGMA page_size=" + newValue, null, null);
257 }
Jeff Brown5936ff02012-02-29 21:03:20 -0800258 }
259 }
260
261 private void setAutoCheckpointInterval() {
Jeff Brown1d9f7422012-03-15 14:32:32 -0700262 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
263 final long newValue = SQLiteGlobal.getWALAutoCheckpoint();
264 long value = executeForLong("PRAGMA wal_autocheckpoint", null, null);
265 if (value != newValue) {
266 executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null);
267 }
Jeff Brown5936ff02012-02-29 21:03:20 -0800268 }
269 }
270
271 private void setJournalSizeLimit() {
Jeff Brown1d9f7422012-03-15 14:32:32 -0700272 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
273 final long newValue = SQLiteGlobal.getJournalSizeLimit();
274 long value = executeForLong("PRAGMA journal_size_limit", null, null);
275 if (value != newValue) {
276 executeForLong("PRAGMA journal_size_limit=" + newValue, null, null);
277 }
Jeff Brown5936ff02012-02-29 21:03:20 -0800278 }
279 }
280
Jeff Brown96496ad2012-03-23 14:38:06 -0700281 private void setForeignKeyModeFromConfiguration() {
282 if (!mIsReadOnlyConnection) {
283 final long newValue = mConfiguration.foreignKeyConstraintsEnabled ? 1 : 0;
284 long value = executeForLong("PRAGMA foreign_keys", null, null);
285 if (value != newValue) {
286 execute("PRAGMA foreign_keys=" + newValue, null, null);
287 }
288 }
289 }
290
Jeff Brownd67c8c62012-03-22 14:15:01 -0700291 private void setWalModeFromConfiguration() {
Jeff Brown1d9f7422012-03-15 14:32:32 -0700292 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
Fyodor Kupolov13a4b372017-11-07 18:45:35 -0800293 final boolean walEnabled =
Fyodor Kupolov5bd43ad2017-10-25 16:09:35 -0700294 (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
Fyodor Kupolov13a4b372017-11-07 18:45:35 -0800295 // Use compatibility WAL unless an app explicitly set journal/synchronous mode
Fyodor Kupolov692573b2018-03-06 12:34:36 -0800296 // or DISABLE_COMPATIBILITY_WAL flag is set
Fyodor Kupolov681ec312018-03-20 18:48:22 -0700297 final boolean useCompatibilityWal = mConfiguration.useCompatibilityWal();
Fyodor Kupolov13a4b372017-11-07 18:45:35 -0800298 if (walEnabled || useCompatibilityWal) {
Jeff Brownd67c8c62012-03-22 14:15:01 -0700299 setJournalMode("WAL");
Fyodor Kupolov8ba20892018-06-01 12:11:42 -0700300 if (mConfiguration.syncMode != null) {
301 setSyncMode(mConfiguration.syncMode);
302 } else if (useCompatibilityWal && SQLiteCompatibilityWalFlags.areFlagsSet()) {
Fyodor Kupolovee90c032017-12-12 11:52:57 -0800303 setSyncMode(SQLiteCompatibilityWalFlags.getWALSyncMode());
304 } else {
305 setSyncMode(SQLiteGlobal.getWALSyncMode());
306 }
Makoto Onuki0939c5a2018-08-24 13:38:40 -0700307 maybeTruncateWalFile();
Jeff Brownd67c8c62012-03-22 14:15:01 -0700308 } else {
Fyodor Kupolov13a4b372017-11-07 18:45:35 -0800309 setJournalMode(mConfiguration.journalMode == null
310 ? SQLiteGlobal.getDefaultJournalMode() : mConfiguration.journalMode);
311 setSyncMode(mConfiguration.syncMode == null
312 ? SQLiteGlobal.getDefaultSyncMode() : mConfiguration.syncMode);
Jeff Brown1d9f7422012-03-15 14:32:32 -0700313 }
Jeff Brown8dc3cc22012-03-02 10:33:52 -0800314 }
315 }
316
Makoto Onuki0939c5a2018-08-24 13:38:40 -0700317 /**
318 * If the WAL file exists and larger than a threshold, truncate it by executing
319 * PRAGMA wal_checkpoint.
320 */
321 private void maybeTruncateWalFile() {
322 final long threshold = SQLiteGlobal.getWALTruncateSize();
323 if (DEBUG) {
324 Log.d(TAG, "Truncate threshold=" + threshold);
325 }
326 if (threshold == 0) {
327 return;
328 }
329
330 final File walFile = new File(mConfiguration.path + "-wal");
331 if (!walFile.isFile()) {
332 return;
333 }
334 final long size = walFile.length();
335 if (size < threshold) {
336 if (DEBUG) {
337 Log.d(TAG, walFile.getAbsolutePath() + " " + size + " bytes: No need to truncate");
338 }
339 return;
340 }
341
342 Log.i(TAG, walFile.getAbsolutePath() + " " + size + " bytes: Bigger than "
343 + threshold + "; truncating");
344 try {
345 executeForString("PRAGMA wal_checkpoint(TRUNCATE)", null, null);
346 } catch (SQLiteException e) {
347 Log.w(TAG, "Failed to truncate the -wal file", e);
348 }
349 }
350
Jeff Brownd67c8c62012-03-22 14:15:01 -0700351 private void setSyncMode(String newValue) {
352 String value = executeForString("PRAGMA synchronous", null, null);
353 if (!canonicalizeSyncMode(value).equalsIgnoreCase(
354 canonicalizeSyncMode(newValue))) {
355 execute("PRAGMA synchronous=" + newValue, null, null);
356 }
357 }
358
359 private static String canonicalizeSyncMode(String value) {
Fyodor Kupolov13a4b372017-11-07 18:45:35 -0800360 switch (value) {
361 case "0": return "OFF";
362 case "1": return "NORMAL";
363 case "2": return "FULL";
Jeff Brownd67c8c62012-03-22 14:15:01 -0700364 }
365 return value;
366 }
367
368 private void setJournalMode(String newValue) {
369 String value = executeForString("PRAGMA journal_mode", null, null);
370 if (!value.equalsIgnoreCase(newValue)) {
371 try {
372 String result = executeForString("PRAGMA journal_mode=" + newValue, null, null);
373 if (result.equalsIgnoreCase(newValue)) {
374 return;
Jeff Brown1d9f7422012-03-15 14:32:32 -0700375 }
Jeff Brownd67c8c62012-03-22 14:15:01 -0700376 // PRAGMA journal_mode silently fails and returns the original journal
377 // mode in some cases if the journal mode could not be changed.
378 } catch (SQLiteDatabaseLockedException ex) {
379 // This error (SQLITE_BUSY) occurs if one connection has the database
380 // open in WAL mode and another tries to change it to non-WAL.
Jeff Brown5936ff02012-02-29 21:03:20 -0800381 }
Jeff Brownd67c8c62012-03-22 14:15:01 -0700382 // Because we always disable WAL mode when a database is first opened
383 // (even if we intend to re-enable it), we can encounter problems if
384 // there is another open connection to the database somewhere.
385 // This can happen for a variety of reasons such as an application opening
386 // the same database in multiple processes at the same time or if there is a
387 // crashing content provider service that the ActivityManager has
388 // removed from its registry but whose process hasn't quite died yet
389 // by the time it is restarted in a new process.
390 //
391 // If we don't change the journal mode, nothing really bad happens.
392 // In the worst case, an application that enables WAL might not actually
393 // get it, although it can still use connection pooling.
394 Log.w(TAG, "Could not change the database journal mode of '"
395 + mConfiguration.label + "' from '" + value + "' to '" + newValue
396 + "' because the database is locked. This usually means that "
397 + "there are other open connections to the database which prevents "
398 + "the database from enabling or disabling write-ahead logging mode. "
399 + "Proceeding without changing the journal mode.");
Jeff Brown5936ff02012-02-29 21:03:20 -0800400 }
401 }
402
Jeff Browne5360fb2011-10-31 17:48:13 -0700403 private void setLocaleFromConfiguration() {
Jeff Brown1d9f7422012-03-15 14:32:32 -0700404 if ((mConfiguration.openFlags & SQLiteDatabase.NO_LOCALIZED_COLLATORS) != 0) {
405 return;
406 }
407
408 // Register the localized collators.
409 final String newLocale = mConfiguration.locale.toString();
410 nativeRegisterLocalizedCollators(mConnectionPtr, newLocale);
411
412 // If the database is read-only, we cannot modify the android metadata table
413 // or existing indexes.
414 if (mIsReadOnlyConnection) {
415 return;
416 }
417
418 try {
419 // Ensure the android metadata table exists.
420 execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null);
421
422 // Check whether the locale was actually changed.
423 final String oldLocale = executeForString("SELECT locale FROM android_metadata "
424 + "UNION SELECT NULL ORDER BY locale DESC LIMIT 1", null, null);
425 if (oldLocale != null && oldLocale.equals(newLocale)) {
426 return;
427 }
428
429 // Go ahead and update the indexes using the new locale.
430 execute("BEGIN", null, null);
431 boolean success = false;
432 try {
433 execute("DELETE FROM android_metadata", null, null);
434 execute("INSERT INTO android_metadata (locale) VALUES(?)",
435 new Object[] { newLocale }, null);
436 execute("REINDEX LOCALIZED", null, null);
437 success = true;
438 } finally {
439 execute(success ? "COMMIT" : "ROLLBACK", null, null);
440 }
441 } catch (RuntimeException ex) {
442 throw new SQLiteException("Failed to change locale for db '" + mConfiguration.label
443 + "' to '" + newLocale + "'.", ex);
444 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700445 }
446
447 // Called by SQLiteConnectionPool only.
448 void reconfigure(SQLiteDatabaseConfiguration configuration) {
Jeff Brown76070d1692012-04-19 11:30:33 -0700449 mOnlyAllowReadOnlyOperations = false;
450
Jeff Browne5360fb2011-10-31 17:48:13 -0700451 // Register custom functions.
452 final int functionCount = configuration.customFunctions.size();
453 for (int i = 0; i < functionCount; i++) {
454 SQLiteCustomFunction function = configuration.customFunctions.get(i);
455 if (!mConfiguration.customFunctions.contains(function)) {
456 nativeRegisterCustomFunction(mConnectionPtr, function);
457 }
458 }
459
Jeff Brown5936ff02012-02-29 21:03:20 -0800460 // Remember what changed.
Jeff Brown96496ad2012-03-23 14:38:06 -0700461 boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
462 != mConfiguration.foreignKeyConstraintsEnabled;
Jeff Brown47847f32012-03-22 19:13:11 -0700463 boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
Fyodor Kupolov692573b2018-03-06 12:34:36 -0800464 & (SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING
465 | SQLiteDatabase.DISABLE_COMPATIBILITY_WAL)) != 0;
Jeff Browne5360fb2011-10-31 17:48:13 -0700466 boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
467
468 // Update configuration parameters.
469 mConfiguration.updateParametersFrom(configuration);
470
471 // Update prepared statement cache size.
472 mPreparedStatementCache.resize(configuration.maxSqlCacheSize);
473
Jeff Brown96496ad2012-03-23 14:38:06 -0700474 // Update foreign key mode.
475 if (foreignKeyModeChanged) {
476 setForeignKeyModeFromConfiguration();
477 }
478
Jeff Brownd67c8c62012-03-22 14:15:01 -0700479 // Update WAL.
480 if (walModeChanged) {
481 setWalModeFromConfiguration();
Jeff Brown5936ff02012-02-29 21:03:20 -0800482 }
483
Jeff Browne5360fb2011-10-31 17:48:13 -0700484 // Update locale.
485 if (localeChanged) {
486 setLocaleFromConfiguration();
487 }
488 }
489
490 // Called by SQLiteConnectionPool only.
491 // When set to true, executing write operations will throw SQLiteException.
492 // Preparing statements that might write is ok, just don't execute them.
493 void setOnlyAllowReadOnlyOperations(boolean readOnly) {
494 mOnlyAllowReadOnlyOperations = readOnly;
495 }
496
497 // Called by SQLiteConnectionPool only.
498 // Returns true if the prepared statement cache contains the specified SQL.
499 boolean isPreparedStatementInCache(String sql) {
500 return mPreparedStatementCache.get(sql) != null;
501 }
502
503 /**
504 * Gets the unique id of this connection.
505 * @return The connection id.
506 */
507 public int getConnectionId() {
508 return mConnectionId;
509 }
510
511 /**
512 * Returns true if this is the primary database connection.
513 * @return True if this is the primary database connection.
514 */
515 public boolean isPrimaryConnection() {
516 return mIsPrimaryConnection;
517 }
518
519 /**
520 * Prepares a statement for execution but does not bind its parameters or execute it.
521 * <p>
522 * This method can be used to check for syntax errors during compilation
523 * prior to execution of the statement. If the {@code outStatementInfo} argument
524 * is not null, the provided {@link SQLiteStatementInfo} object is populated
525 * with information about the statement.
526 * </p><p>
527 * A prepared statement makes no reference to the arguments that may eventually
528 * be bound to it, consequently it it possible to cache certain prepared statements
529 * such as SELECT or INSERT/UPDATE statements. If the statement is cacheable,
530 * then it will be stored in the cache for later.
531 * </p><p>
532 * To take advantage of this behavior as an optimization, the connection pool
533 * provides a method to acquire a connection that already has a given SQL statement
534 * in its prepared statement cache so that it is ready for execution.
535 * </p>
536 *
537 * @param sql The SQL statement to prepare.
538 * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate
539 * with information about the statement, or null if none.
540 *
541 * @throws SQLiteException if an error occurs, such as a syntax error.
542 */
543 public void prepare(String sql, SQLiteStatementInfo outStatementInfo) {
544 if (sql == null) {
545 throw new IllegalArgumentException("sql must not be null.");
546 }
547
Jeff Browna9be4152012-01-18 15:29:57 -0800548 final int cookie = mRecentOperations.beginOperation("prepare", sql, null);
Jeff Browne5360fb2011-10-31 17:48:13 -0700549 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800550 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700551 try {
552 if (outStatementInfo != null) {
553 outStatementInfo.numParameters = statement.mNumParameters;
554 outStatementInfo.readOnly = statement.mReadOnly;
555
556 final int columnCount = nativeGetColumnCount(
557 mConnectionPtr, statement.mStatementPtr);
558 if (columnCount == 0) {
559 outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
560 } else {
561 outStatementInfo.columnNames = new String[columnCount];
562 for (int i = 0; i < columnCount; i++) {
563 outStatementInfo.columnNames[i] = nativeGetColumnName(
564 mConnectionPtr, statement.mStatementPtr, i);
565 }
566 }
567 }
568 } finally {
569 releasePreparedStatement(statement);
570 }
571 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800572 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700573 throw ex;
574 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800575 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700576 }
577 }
578
579 /**
580 * Executes a statement that does not return a result.
581 *
582 * @param sql The SQL statement to execute.
583 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800584 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700585 *
586 * @throws SQLiteException if an error occurs, such as a syntax error
587 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800588 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700589 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800590 public void execute(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800591 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700592 if (sql == null) {
593 throw new IllegalArgumentException("sql must not be null.");
594 }
595
Jeff Browna9be4152012-01-18 15:29:57 -0800596 final int cookie = mRecentOperations.beginOperation("execute", sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700597 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800598 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700599 try {
600 throwIfStatementForbidden(statement);
601 bindArguments(statement, bindArgs);
602 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800603 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800604 try {
605 nativeExecute(mConnectionPtr, statement.mStatementPtr);
606 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800607 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800608 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700609 } finally {
610 releasePreparedStatement(statement);
611 }
612 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800613 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700614 throw ex;
615 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800616 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700617 }
618 }
619
620 /**
621 * Executes a statement that returns a single <code>long</code> result.
622 *
623 * @param sql The SQL statement to execute.
624 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800625 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700626 * @return The value of the first column in the first row of the result set
627 * as a <code>long</code>, or zero if none.
628 *
629 * @throws SQLiteException if an error occurs, such as a syntax error
630 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800631 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700632 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800633 public long executeForLong(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800634 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700635 if (sql == null) {
636 throw new IllegalArgumentException("sql must not be null.");
637 }
638
Jeff Browna9be4152012-01-18 15:29:57 -0800639 final int cookie = mRecentOperations.beginOperation("executeForLong", sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700640 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800641 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700642 try {
643 throwIfStatementForbidden(statement);
644 bindArguments(statement, bindArgs);
645 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800646 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800647 try {
648 return nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr);
649 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800650 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800651 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700652 } finally {
653 releasePreparedStatement(statement);
654 }
655 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800656 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700657 throw ex;
658 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800659 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700660 }
661 }
662
663 /**
664 * Executes a statement that returns a single {@link String} result.
665 *
666 * @param sql The SQL statement to execute.
667 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800668 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700669 * @return The value of the first column in the first row of the result set
670 * as a <code>String</code>, or null if none.
671 *
672 * @throws SQLiteException if an error occurs, such as a syntax error
673 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800674 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700675 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800676 public String executeForString(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800677 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700678 if (sql == null) {
679 throw new IllegalArgumentException("sql must not be null.");
680 }
681
Jeff Browna9be4152012-01-18 15:29:57 -0800682 final int cookie = mRecentOperations.beginOperation("executeForString", sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700683 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800684 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700685 try {
686 throwIfStatementForbidden(statement);
687 bindArguments(statement, bindArgs);
688 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800689 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800690 try {
691 return nativeExecuteForString(mConnectionPtr, statement.mStatementPtr);
692 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800693 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800694 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700695 } finally {
696 releasePreparedStatement(statement);
697 }
698 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800699 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700700 throw ex;
701 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800702 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700703 }
704 }
705
706 /**
707 * Executes a statement that returns a single BLOB result as a
708 * file descriptor to a shared memory region.
709 *
710 * @param sql The SQL statement to execute.
711 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800712 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700713 * @return The file descriptor for a shared memory region that contains
714 * the value of the first column in the first row of the result set as a BLOB,
715 * or null if none.
716 *
717 * @throws SQLiteException if an error occurs, such as a syntax error
718 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800719 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700720 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800721 public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800722 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700723 if (sql == null) {
724 throw new IllegalArgumentException("sql must not be null.");
725 }
726
Jeff Browna9be4152012-01-18 15:29:57 -0800727 final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor",
728 sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700729 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800730 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700731 try {
732 throwIfStatementForbidden(statement);
733 bindArguments(statement, bindArgs);
734 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800735 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800736 try {
737 int fd = nativeExecuteForBlobFileDescriptor(
738 mConnectionPtr, statement.mStatementPtr);
739 return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null;
740 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800741 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800742 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700743 } finally {
744 releasePreparedStatement(statement);
745 }
746 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800747 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700748 throw ex;
749 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800750 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700751 }
752 }
753
754 /**
755 * Executes a statement that returns a count of the number of rows
756 * that were changed. Use for UPDATE or DELETE SQL statements.
757 *
758 * @param sql The SQL statement to execute.
759 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800760 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700761 * @return The number of rows that were changed.
762 *
763 * @throws SQLiteException if an error occurs, such as a syntax error
764 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800765 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700766 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800767 public int executeForChangedRowCount(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800768 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700769 if (sql == null) {
770 throw new IllegalArgumentException("sql must not be null.");
771 }
772
Makoto Onuki72eebb62012-04-16 13:46:15 -0700773 int changedRows = 0;
Jeff Browna9be4152012-01-18 15:29:57 -0800774 final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount",
775 sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700776 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800777 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700778 try {
779 throwIfStatementForbidden(statement);
780 bindArguments(statement, bindArgs);
781 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800782 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800783 try {
Makoto Onuki72eebb62012-04-16 13:46:15 -0700784 changedRows = nativeExecuteForChangedRowCount(
Jeff Brown75ea64f2012-01-25 19:37:13 -0800785 mConnectionPtr, statement.mStatementPtr);
Makoto Onuki72eebb62012-04-16 13:46:15 -0700786 return changedRows;
Jeff Brown75ea64f2012-01-25 19:37:13 -0800787 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800788 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800789 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700790 } finally {
791 releasePreparedStatement(statement);
792 }
793 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800794 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700795 throw ex;
796 } finally {
Makoto Onuki72eebb62012-04-16 13:46:15 -0700797 if (mRecentOperations.endOperationDeferLog(cookie)) {
798 mRecentOperations.logOperation(cookie, "changedRows=" + changedRows);
799 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700800 }
801 }
802
803 /**
804 * Executes a statement that returns the row id of the last row inserted
805 * by the statement. Use for INSERT SQL statements.
806 *
807 * @param sql The SQL statement to execute.
808 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800809 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700810 * @return The row id of the last row that was inserted, or 0 if none.
811 *
812 * @throws SQLiteException if an error occurs, such as a syntax error
813 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800814 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700815 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800816 public long executeForLastInsertedRowId(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800817 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700818 if (sql == null) {
819 throw new IllegalArgumentException("sql must not be null.");
820 }
821
Jeff Browna9be4152012-01-18 15:29:57 -0800822 final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId",
823 sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700824 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800825 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700826 try {
827 throwIfStatementForbidden(statement);
828 bindArguments(statement, bindArgs);
829 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800830 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800831 try {
832 return nativeExecuteForLastInsertedRowId(
833 mConnectionPtr, statement.mStatementPtr);
834 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800835 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800836 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700837 } finally {
838 releasePreparedStatement(statement);
839 }
840 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800841 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700842 throw ex;
843 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800844 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700845 }
846 }
847
848 /**
849 * Executes a statement and populates the specified {@link CursorWindow}
850 * with a range of results. Returns the number of rows that were counted
851 * during query execution.
852 *
853 * @param sql The SQL statement to execute.
854 * @param bindArgs The arguments to bind, or null if none.
855 * @param window The cursor window to clear and fill.
856 * @param startPos The start position for filling the window.
857 * @param requiredPos The position of a row that MUST be in the window.
858 * If it won't fit, then the query should discard part of what it filled
859 * so that it does. Must be greater than or equal to <code>startPos</code>.
860 * @param countAllRows True to count all rows that the query would return
861 * regagless of whether they fit in the window.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800862 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700863 * @return The number of rows that were counted during query execution. Might
864 * not be all rows in the result set unless <code>countAllRows</code> is true.
865 *
866 * @throws SQLiteException if an error occurs, such as a syntax error
867 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800868 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700869 */
870 public int executeForCursorWindow(String sql, Object[] bindArgs,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800871 CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800872 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700873 if (sql == null) {
874 throw new IllegalArgumentException("sql must not be null.");
875 }
876 if (window == null) {
877 throw new IllegalArgumentException("window must not be null.");
878 }
879
Jeff Brown03bd3022012-03-06 13:48:56 -0800880 window.acquireReference();
Jeff Browne5360fb2011-10-31 17:48:13 -0700881 try {
Jeff Brown03bd3022012-03-06 13:48:56 -0800882 int actualPos = -1;
883 int countedRows = -1;
884 int filledRows = -1;
885 final int cookie = mRecentOperations.beginOperation("executeForCursorWindow",
886 sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700887 try {
Jeff Brown03bd3022012-03-06 13:48:56 -0800888 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800889 try {
Jeff Brown03bd3022012-03-06 13:48:56 -0800890 throwIfStatementForbidden(statement);
891 bindArguments(statement, bindArgs);
892 applyBlockGuardPolicy(statement);
893 attachCancellationSignal(cancellationSignal);
894 try {
895 final long result = nativeExecuteForCursorWindow(
896 mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
897 startPos, requiredPos, countAllRows);
898 actualPos = (int)(result >> 32);
899 countedRows = (int)result;
900 filledRows = window.getNumRows();
901 window.setStartPosition(actualPos);
902 return countedRows;
903 } finally {
904 detachCancellationSignal(cancellationSignal);
905 }
Jeff Brown75ea64f2012-01-25 19:37:13 -0800906 } finally {
Jeff Brown03bd3022012-03-06 13:48:56 -0800907 releasePreparedStatement(statement);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800908 }
Jeff Brown03bd3022012-03-06 13:48:56 -0800909 } catch (RuntimeException ex) {
910 mRecentOperations.failOperation(cookie, ex);
911 throw ex;
Jeff Browne5360fb2011-10-31 17:48:13 -0700912 } finally {
Jeff Brown03bd3022012-03-06 13:48:56 -0800913 if (mRecentOperations.endOperationDeferLog(cookie)) {
914 mRecentOperations.logOperation(cookie, "window='" + window
915 + "', startPos=" + startPos
916 + ", actualPos=" + actualPos
917 + ", filledRows=" + filledRows
918 + ", countedRows=" + countedRows);
919 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700920 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700921 } finally {
Jeff Brown03bd3022012-03-06 13:48:56 -0800922 window.releaseReference();
Jeff Browne5360fb2011-10-31 17:48:13 -0700923 }
924 }
925
926 private PreparedStatement acquirePreparedStatement(String sql) {
927 PreparedStatement statement = mPreparedStatementCache.get(sql);
Jeff Browna9be4152012-01-18 15:29:57 -0800928 boolean skipCache = false;
Jeff Browne5360fb2011-10-31 17:48:13 -0700929 if (statement != null) {
Jeff Browna9be4152012-01-18 15:29:57 -0800930 if (!statement.mInUse) {
931 return statement;
932 }
933 // The statement is already in the cache but is in use (this statement appears
934 // to be not only re-entrant but recursive!). So prepare a new copy of the
935 // statement but do not cache it.
936 skipCache = true;
Jeff Browne5360fb2011-10-31 17:48:13 -0700937 }
938
Ashok Bhat738702d2014-01-02 13:42:56 +0000939 final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700940 try {
941 final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
942 final int type = DatabaseUtils.getSqlStatementType(sql);
943 final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
944 statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly);
Jeff Browna9be4152012-01-18 15:29:57 -0800945 if (!skipCache && isCacheable(type)) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700946 mPreparedStatementCache.put(sql, statement);
947 statement.mInCache = true;
948 }
949 } catch (RuntimeException ex) {
950 // Finalize the statement if an exception occurred and we did not add
951 // it to the cache. If it is already in the cache, then leave it there.
952 if (statement == null || !statement.mInCache) {
953 nativeFinalizeStatement(mConnectionPtr, statementPtr);
954 }
955 throw ex;
956 }
Jeff Browna9be4152012-01-18 15:29:57 -0800957 statement.mInUse = true;
Jeff Browne5360fb2011-10-31 17:48:13 -0700958 return statement;
959 }
960
961 private void releasePreparedStatement(PreparedStatement statement) {
Jeff Browna9be4152012-01-18 15:29:57 -0800962 statement.mInUse = false;
Jeff Browne5360fb2011-10-31 17:48:13 -0700963 if (statement.mInCache) {
964 try {
965 nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr);
966 } catch (SQLiteException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800967 // The statement could not be reset due to an error. Remove it from the cache.
968 // When remove() is called, the cache will invoke its entryRemoved() callback,
969 // which will in turn call finalizePreparedStatement() to finalize and
970 // recycle the statement.
Jeff Brown2a293b62012-01-19 14:02:22 -0800971 if (DEBUG) {
972 Log.d(TAG, "Could not reset prepared statement due to an exception. "
Jeff Browne5360fb2011-10-31 17:48:13 -0700973 + "Removing it from the cache. SQL: "
974 + trimSqlForDisplay(statement.mSql), ex);
975 }
Jeff Brown2a293b62012-01-19 14:02:22 -0800976
Jeff Browne5360fb2011-10-31 17:48:13 -0700977 mPreparedStatementCache.remove(statement.mSql);
978 }
979 } else {
Jeff Browna9be4152012-01-18 15:29:57 -0800980 finalizePreparedStatement(statement);
Jeff Browne5360fb2011-10-31 17:48:13 -0700981 }
982 }
983
Jeff Browna9be4152012-01-18 15:29:57 -0800984 private void finalizePreparedStatement(PreparedStatement statement) {
985 nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr);
986 recyclePreparedStatement(statement);
987 }
988
Jeff Brown4c1241d2012-02-02 17:05:00 -0800989 private void attachCancellationSignal(CancellationSignal cancellationSignal) {
990 if (cancellationSignal != null) {
991 cancellationSignal.throwIfCanceled();
Jeff Brown75ea64f2012-01-25 19:37:13 -0800992
Jeff Brown4c1241d2012-02-02 17:05:00 -0800993 mCancellationSignalAttachCount += 1;
994 if (mCancellationSignalAttachCount == 1) {
995 // Reset cancellation flag before executing the statement.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800996 nativeResetCancel(mConnectionPtr, true /*cancelable*/);
997
998 // After this point, onCancel() may be called concurrently.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800999 cancellationSignal.setOnCancelListener(this);
Jeff Brown75ea64f2012-01-25 19:37:13 -08001000 }
1001 }
1002 }
1003
Jeff Brown4c1241d2012-02-02 17:05:00 -08001004 private void detachCancellationSignal(CancellationSignal cancellationSignal) {
1005 if (cancellationSignal != null) {
1006 assert mCancellationSignalAttachCount > 0;
Jeff Brown75ea64f2012-01-25 19:37:13 -08001007
Jeff Brown4c1241d2012-02-02 17:05:00 -08001008 mCancellationSignalAttachCount -= 1;
1009 if (mCancellationSignalAttachCount == 0) {
Jeff Brown75ea64f2012-01-25 19:37:13 -08001010 // After this point, onCancel() cannot be called concurrently.
Jeff Brown4c1241d2012-02-02 17:05:00 -08001011 cancellationSignal.setOnCancelListener(null);
Jeff Brown75ea64f2012-01-25 19:37:13 -08001012
Jeff Brown4c1241d2012-02-02 17:05:00 -08001013 // Reset cancellation flag after executing the statement.
Jeff Brown75ea64f2012-01-25 19:37:13 -08001014 nativeResetCancel(mConnectionPtr, false /*cancelable*/);
1015 }
1016 }
1017 }
1018
Jeff Brown4c1241d2012-02-02 17:05:00 -08001019 // CancellationSignal.OnCancelListener callback.
Jeff Brown75ea64f2012-01-25 19:37:13 -08001020 // This method may be called on a different thread than the executing statement.
Jeff Brown4c1241d2012-02-02 17:05:00 -08001021 // However, it will only be called between calls to attachCancellationSignal and
1022 // detachCancellationSignal, while a statement is executing. We can safely assume
Jeff Brown75ea64f2012-01-25 19:37:13 -08001023 // that the SQLite connection is still alive.
1024 @Override
1025 public void onCancel() {
1026 nativeCancel(mConnectionPtr);
1027 }
1028
Jeff Browne5360fb2011-10-31 17:48:13 -07001029 private void bindArguments(PreparedStatement statement, Object[] bindArgs) {
1030 final int count = bindArgs != null ? bindArgs.length : 0;
1031 if (count != statement.mNumParameters) {
1032 throw new SQLiteBindOrColumnIndexOutOfRangeException(
1033 "Expected " + statement.mNumParameters + " bind arguments but "
Sylvain Becuwe942085b2012-10-28 01:48:37 +02001034 + count + " were provided.");
Jeff Browne5360fb2011-10-31 17:48:13 -07001035 }
1036 if (count == 0) {
1037 return;
1038 }
1039
Ashok Bhat738702d2014-01-02 13:42:56 +00001040 final long statementPtr = statement.mStatementPtr;
Jeff Browne5360fb2011-10-31 17:48:13 -07001041 for (int i = 0; i < count; i++) {
1042 final Object arg = bindArgs[i];
1043 switch (DatabaseUtils.getTypeOfObject(arg)) {
1044 case Cursor.FIELD_TYPE_NULL:
1045 nativeBindNull(mConnectionPtr, statementPtr, i + 1);
1046 break;
1047 case Cursor.FIELD_TYPE_INTEGER:
1048 nativeBindLong(mConnectionPtr, statementPtr, i + 1,
1049 ((Number)arg).longValue());
1050 break;
1051 case Cursor.FIELD_TYPE_FLOAT:
1052 nativeBindDouble(mConnectionPtr, statementPtr, i + 1,
1053 ((Number)arg).doubleValue());
1054 break;
1055 case Cursor.FIELD_TYPE_BLOB:
1056 nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg);
1057 break;
1058 case Cursor.FIELD_TYPE_STRING:
1059 default:
1060 if (arg instanceof Boolean) {
1061 // Provide compatibility with legacy applications which may pass
1062 // Boolean values in bind args.
1063 nativeBindLong(mConnectionPtr, statementPtr, i + 1,
1064 ((Boolean)arg).booleanValue() ? 1 : 0);
1065 } else {
1066 nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString());
1067 }
1068 break;
1069 }
1070 }
1071 }
1072
1073 private void throwIfStatementForbidden(PreparedStatement statement) {
1074 if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) {
1075 throw new SQLiteException("Cannot execute this statement because it "
1076 + "might modify the database but the connection is read-only.");
1077 }
1078 }
1079
1080 private static boolean isCacheable(int statementType) {
1081 if (statementType == DatabaseUtils.STATEMENT_UPDATE
1082 || statementType == DatabaseUtils.STATEMENT_SELECT) {
1083 return true;
1084 }
1085 return false;
1086 }
1087
1088 private void applyBlockGuardPolicy(PreparedStatement statement) {
1089 if (!mConfiguration.isInMemoryDb()) {
1090 if (statement.mReadOnly) {
1091 BlockGuard.getThreadPolicy().onReadFromDisk();
1092 } else {
1093 BlockGuard.getThreadPolicy().onWriteToDisk();
1094 }
1095 }
1096 }
1097
1098 /**
1099 * Dumps debugging information about this connection.
1100 *
1101 * @param printer The printer to receive the dump, not null.
Jeff Browna9be4152012-01-18 15:29:57 -08001102 * @param verbose True to dump more verbose information.
Jeff Browne5360fb2011-10-31 17:48:13 -07001103 */
Jeff Browna9be4152012-01-18 15:29:57 -08001104 public void dump(Printer printer, boolean verbose) {
1105 dumpUnsafe(printer, verbose);
Jeff Browne5360fb2011-10-31 17:48:13 -07001106 }
1107
1108 /**
1109 * Dumps debugging information about this connection, in the case where the
1110 * caller might not actually own the connection.
1111 *
1112 * This function is written so that it may be called by a thread that does not
1113 * own the connection. We need to be very careful because the connection state is
1114 * not synchronized.
1115 *
1116 * At worst, the method may return stale or slightly wrong data, however
1117 * it should not crash. This is ok as it is only used for diagnostic purposes.
1118 *
1119 * @param printer The printer to receive the dump, not null.
Jeff Browna9be4152012-01-18 15:29:57 -08001120 * @param verbose True to dump more verbose information.
Jeff Browne5360fb2011-10-31 17:48:13 -07001121 */
Jeff Browna9be4152012-01-18 15:29:57 -08001122 void dumpUnsafe(Printer printer, boolean verbose) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001123 printer.println("Connection #" + mConnectionId + ":");
Jeff Browna9be4152012-01-18 15:29:57 -08001124 if (verbose) {
Ashok Bhat738702d2014-01-02 13:42:56 +00001125 printer.println(" connectionPtr: 0x" + Long.toHexString(mConnectionPtr));
Jeff Browna9be4152012-01-18 15:29:57 -08001126 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001127 printer.println(" isPrimaryConnection: " + mIsPrimaryConnection);
Jeff Browne5360fb2011-10-31 17:48:13 -07001128 printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
1129
Jeff Browncefeb292013-05-01 15:28:37 -07001130 mRecentOperations.dump(printer, verbose);
Jeff Browna9be4152012-01-18 15:29:57 -08001131
1132 if (verbose) {
1133 mPreparedStatementCache.dump(printer);
1134 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001135 }
1136
1137 /**
1138 * Describes the currently executing operation, in the case where the
1139 * caller might not actually own the connection.
1140 *
1141 * This function is written so that it may be called by a thread that does not
1142 * own the connection. We need to be very careful because the connection state is
1143 * not synchronized.
1144 *
1145 * At worst, the method may return stale or slightly wrong data, however
1146 * it should not crash. This is ok as it is only used for diagnostic purposes.
1147 *
1148 * @return A description of the current operation including how long it has been running,
1149 * or null if none.
1150 */
1151 String describeCurrentOperationUnsafe() {
1152 return mRecentOperations.describeCurrentOperation();
1153 }
1154
1155 /**
1156 * Collects statistics about database connection memory usage.
1157 *
1158 * @param dbStatsList The list to populate.
1159 */
1160 void collectDbStats(ArrayList<DbStats> dbStatsList) {
1161 // Get information about the main database.
1162 int lookaside = nativeGetDbLookaside(mConnectionPtr);
1163 long pageCount = 0;
1164 long pageSize = 0;
1165 try {
Jeff Brown75ea64f2012-01-25 19:37:13 -08001166 pageCount = executeForLong("PRAGMA page_count;", null, null);
1167 pageSize = executeForLong("PRAGMA page_size;", null, null);
Jeff Browne5360fb2011-10-31 17:48:13 -07001168 } catch (SQLiteException ex) {
1169 // Ignore.
1170 }
1171 dbStatsList.add(getMainDbStatsUnsafe(lookaside, pageCount, pageSize));
1172
1173 // Get information about attached databases.
1174 // We ignore the first row in the database list because it corresponds to
1175 // the main database which we have already described.
1176 CursorWindow window = new CursorWindow("collectDbStats");
1177 try {
Jeff Brown75ea64f2012-01-25 19:37:13 -08001178 executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null);
Jeff Browne5360fb2011-10-31 17:48:13 -07001179 for (int i = 1; i < window.getNumRows(); i++) {
1180 String name = window.getString(i, 1);
1181 String path = window.getString(i, 2);
1182 pageCount = 0;
1183 pageSize = 0;
1184 try {
Jeff Brown75ea64f2012-01-25 19:37:13 -08001185 pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null);
1186 pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null);
Jeff Browne5360fb2011-10-31 17:48:13 -07001187 } catch (SQLiteException ex) {
1188 // Ignore.
1189 }
1190 String label = " (attached) " + name;
1191 if (!path.isEmpty()) {
1192 label += ": " + path;
1193 }
1194 dbStatsList.add(new DbStats(label, pageCount, pageSize, 0, 0, 0, 0));
1195 }
1196 } catch (SQLiteException ex) {
1197 // Ignore.
1198 } finally {
1199 window.close();
1200 }
1201 }
1202
1203 /**
1204 * Collects statistics about database connection memory usage, in the case where the
1205 * caller might not actually own the connection.
1206 *
1207 * @return The statistics object, never null.
1208 */
1209 void collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList) {
1210 dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0));
1211 }
1212
1213 private DbStats getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize) {
1214 // The prepared statement cache is thread-safe so we can access its statistics
1215 // even if we do not own the database connection.
1216 String label = mConfiguration.path;
1217 if (!mIsPrimaryConnection) {
1218 label += " (" + mConnectionId + ")";
1219 }
1220 return new DbStats(label, pageCount, pageSize, lookaside,
1221 mPreparedStatementCache.hitCount(),
1222 mPreparedStatementCache.missCount(),
1223 mPreparedStatementCache.size());
1224 }
1225
1226 @Override
1227 public String toString() {
1228 return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")";
1229 }
1230
Ashok Bhat738702d2014-01-02 13:42:56 +00001231 private PreparedStatement obtainPreparedStatement(String sql, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -07001232 int numParameters, int type, boolean readOnly) {
1233 PreparedStatement statement = mPreparedStatementPool;
1234 if (statement != null) {
1235 mPreparedStatementPool = statement.mPoolNext;
1236 statement.mPoolNext = null;
1237 statement.mInCache = false;
1238 } else {
1239 statement = new PreparedStatement();
1240 }
1241 statement.mSql = sql;
1242 statement.mStatementPtr = statementPtr;
1243 statement.mNumParameters = numParameters;
1244 statement.mType = type;
1245 statement.mReadOnly = readOnly;
1246 return statement;
1247 }
1248
1249 private void recyclePreparedStatement(PreparedStatement statement) {
1250 statement.mSql = null;
1251 statement.mPoolNext = mPreparedStatementPool;
1252 mPreparedStatementPool = statement;
1253 }
1254
1255 private static String trimSqlForDisplay(String sql) {
Andreas Gampe4f47b402015-04-20 15:29:04 -07001256 // Note: Creating and caching a regular expression is expensive at preload-time
1257 // and stops compile-time initialization. This pattern is only used when
1258 // dumping the connection, which is a rare (mainly error) case. So:
1259 // DO NOT CACHE.
1260 return sql.replaceAll("[\\s]*\\n+[\\s]*", " ");
Jeff Browne5360fb2011-10-31 17:48:13 -07001261 }
1262
1263 /**
1264 * Holder type for a prepared statement.
1265 *
1266 * Although this object holds a pointer to a native statement object, it
1267 * does not have a finalizer. This is deliberate. The {@link SQLiteConnection}
1268 * owns the statement object and will take care of freeing it when needed.
1269 * In particular, closing the connection requires a guarantee of deterministic
1270 * resource disposal because all native statement objects must be freed before
1271 * the native database object can be closed. So no finalizers here.
1272 */
1273 private static final class PreparedStatement {
1274 // Next item in pool.
1275 public PreparedStatement mPoolNext;
1276
1277 // The SQL from which the statement was prepared.
1278 public String mSql;
1279
1280 // The native sqlite3_stmt object pointer.
1281 // Lifetime is managed explicitly by the connection.
Ashok Bhat738702d2014-01-02 13:42:56 +00001282 public long mStatementPtr;
Jeff Browne5360fb2011-10-31 17:48:13 -07001283
1284 // The number of parameters that the prepared statement has.
1285 public int mNumParameters;
1286
1287 // The statement type.
1288 public int mType;
1289
1290 // True if the statement is read-only.
1291 public boolean mReadOnly;
1292
1293 // True if the statement is in the cache.
1294 public boolean mInCache;
Jeff Browna9be4152012-01-18 15:29:57 -08001295
1296 // True if the statement is in use (currently executing).
1297 // We need this flag because due to the use of custom functions in triggers, it's
1298 // possible for SQLite calls to be re-entrant. Consequently we need to prevent
1299 // in use statements from being finalized until they are no longer in use.
1300 public boolean mInUse;
Jeff Browne5360fb2011-10-31 17:48:13 -07001301 }
1302
1303 private final class PreparedStatementCache
1304 extends LruCache<String, PreparedStatement> {
1305 public PreparedStatementCache(int size) {
1306 super(size);
1307 }
1308
1309 @Override
1310 protected void entryRemoved(boolean evicted, String key,
1311 PreparedStatement oldValue, PreparedStatement newValue) {
1312 oldValue.mInCache = false;
Jeff Browna9be4152012-01-18 15:29:57 -08001313 if (!oldValue.mInUse) {
1314 finalizePreparedStatement(oldValue);
1315 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001316 }
1317
1318 public void dump(Printer printer) {
1319 printer.println(" Prepared statement cache:");
1320 Map<String, PreparedStatement> cache = snapshot();
1321 if (!cache.isEmpty()) {
1322 int i = 0;
1323 for (Map.Entry<String, PreparedStatement> entry : cache.entrySet()) {
1324 PreparedStatement statement = entry.getValue();
1325 if (statement.mInCache) { // might be false due to a race with entryRemoved
1326 String sql = entry.getKey();
1327 printer.println(" " + i + ": statementPtr=0x"
Ashok Bhat738702d2014-01-02 13:42:56 +00001328 + Long.toHexString(statement.mStatementPtr)
Jeff Browne5360fb2011-10-31 17:48:13 -07001329 + ", numParameters=" + statement.mNumParameters
1330 + ", type=" + statement.mType
1331 + ", readOnly=" + statement.mReadOnly
1332 + ", sql=\"" + trimSqlForDisplay(sql) + "\"");
1333 }
1334 i += 1;
1335 }
1336 } else {
1337 printer.println(" <none>");
1338 }
1339 }
1340 }
1341
1342 private static final class OperationLog {
Jeff Brown2a293b62012-01-19 14:02:22 -08001343 private static final int MAX_RECENT_OPERATIONS = 20;
Jeff Browna9be4152012-01-18 15:29:57 -08001344 private static final int COOKIE_GENERATION_SHIFT = 8;
1345 private static final int COOKIE_INDEX_MASK = 0xff;
Jeff Browne5360fb2011-10-31 17:48:13 -07001346
1347 private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
1348 private int mIndex;
Jeff Browna9be4152012-01-18 15:29:57 -08001349 private int mGeneration;
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001350 private final SQLiteConnectionPool mPool;
1351
1352 OperationLog(SQLiteConnectionPool pool) {
1353 mPool = pool;
1354 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001355
Jeff Browna9be4152012-01-18 15:29:57 -08001356 public int beginOperation(String kind, String sql, Object[] bindArgs) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001357 synchronized (mOperations) {
1358 final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
1359 Operation operation = mOperations[index];
1360 if (operation == null) {
1361 operation = new Operation();
1362 mOperations[index] = operation;
1363 } else {
1364 operation.mFinished = false;
1365 operation.mException = null;
1366 if (operation.mBindArgs != null) {
1367 operation.mBindArgs.clear();
1368 }
1369 }
Makoto Onuki34436702016-04-07 09:07:04 -07001370 operation.mStartWallTime = System.currentTimeMillis();
1371 operation.mStartTime = SystemClock.uptimeMillis();
Jeff Browne5360fb2011-10-31 17:48:13 -07001372 operation.mKind = kind;
1373 operation.mSql = sql;
1374 if (bindArgs != null) {
1375 if (operation.mBindArgs == null) {
1376 operation.mBindArgs = new ArrayList<Object>();
1377 } else {
1378 operation.mBindArgs.clear();
1379 }
1380 for (int i = 0; i < bindArgs.length; i++) {
1381 final Object arg = bindArgs[i];
1382 if (arg != null && arg instanceof byte[]) {
1383 // Don't hold onto the real byte array longer than necessary.
1384 operation.mBindArgs.add(EMPTY_BYTE_ARRAY);
1385 } else {
1386 operation.mBindArgs.add(arg);
1387 }
1388 }
1389 }
Jeff Browna9be4152012-01-18 15:29:57 -08001390 operation.mCookie = newOperationCookieLocked(index);
Greg Hackmanne12350f2014-12-01 14:31:21 -08001391 if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
1392 Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
1393 operation.mCookie);
1394 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001395 mIndex = index;
Jeff Browna9be4152012-01-18 15:29:57 -08001396 return operation.mCookie;
Jeff Browne5360fb2011-10-31 17:48:13 -07001397 }
1398 }
1399
Jeff Browna9be4152012-01-18 15:29:57 -08001400 public void failOperation(int cookie, Exception ex) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001401 synchronized (mOperations) {
Jeff Browna9be4152012-01-18 15:29:57 -08001402 final Operation operation = getOperationLocked(cookie);
1403 if (operation != null) {
1404 operation.mException = ex;
Jeff Browne5360fb2011-10-31 17:48:13 -07001405 }
1406 }
1407 }
1408
Jeff Browna9be4152012-01-18 15:29:57 -08001409 public void endOperation(int cookie) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001410 synchronized (mOperations) {
Jeff Browna9be4152012-01-18 15:29:57 -08001411 if (endOperationDeferLogLocked(cookie)) {
1412 logOperationLocked(cookie, null);
1413 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001414 }
1415 }
1416
Jeff Browna9be4152012-01-18 15:29:57 -08001417 public boolean endOperationDeferLog(int cookie) {
1418 synchronized (mOperations) {
1419 return endOperationDeferLogLocked(cookie);
1420 }
1421 }
1422
1423 public void logOperation(int cookie, String detail) {
1424 synchronized (mOperations) {
1425 logOperationLocked(cookie, detail);
1426 }
1427 }
1428
1429 private boolean endOperationDeferLogLocked(int cookie) {
1430 final Operation operation = getOperationLocked(cookie);
1431 if (operation != null) {
Greg Hackmanne12350f2014-12-01 14:31:21 -08001432 if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
1433 Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
1434 operation.mCookie);
1435 }
Makoto Onuki34436702016-04-07 09:07:04 -07001436 operation.mEndTime = SystemClock.uptimeMillis();
Jeff Browna9be4152012-01-18 15:29:57 -08001437 operation.mFinished = true;
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001438 final long execTime = operation.mEndTime - operation.mStartTime;
1439 mPool.onStatementExecuted(execTime);
Jeff Browna9be4152012-01-18 15:29:57 -08001440 return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001441 execTime);
Jeff Browna9be4152012-01-18 15:29:57 -08001442 }
1443 return false;
1444 }
1445
1446 private void logOperationLocked(int cookie, String detail) {
1447 final Operation operation = getOperationLocked(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -07001448 StringBuilder msg = new StringBuilder();
Jeff Browncefeb292013-05-01 15:28:37 -07001449 operation.describe(msg, false);
Jeff Browne5360fb2011-10-31 17:48:13 -07001450 if (detail != null) {
1451 msg.append(", ").append(detail);
1452 }
1453 Log.d(TAG, msg.toString());
1454 }
1455
Jeff Browna9be4152012-01-18 15:29:57 -08001456 private int newOperationCookieLocked(int index) {
1457 final int generation = mGeneration++;
1458 return generation << COOKIE_GENERATION_SHIFT | index;
1459 }
1460
1461 private Operation getOperationLocked(int cookie) {
1462 final int index = cookie & COOKIE_INDEX_MASK;
1463 final Operation operation = mOperations[index];
1464 return operation.mCookie == cookie ? operation : null;
1465 }
1466
Jeff Browne5360fb2011-10-31 17:48:13 -07001467 public String describeCurrentOperation() {
1468 synchronized (mOperations) {
1469 final Operation operation = mOperations[mIndex];
1470 if (operation != null && !operation.mFinished) {
1471 StringBuilder msg = new StringBuilder();
Jeff Browncefeb292013-05-01 15:28:37 -07001472 operation.describe(msg, false);
Jeff Browne5360fb2011-10-31 17:48:13 -07001473 return msg.toString();
1474 }
1475 return null;
1476 }
1477 }
1478
Jeff Browncefeb292013-05-01 15:28:37 -07001479 public void dump(Printer printer, boolean verbose) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001480 synchronized (mOperations) {
1481 printer.println(" Most recently executed operations:");
1482 int index = mIndex;
1483 Operation operation = mOperations[index];
1484 if (operation != null) {
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001485 // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created,
1486 // and is relatively expensive to create during preloading. This method is only
1487 // used when dumping a connection, which is a rare (mainly error) case.
1488 SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Jeff Browne5360fb2011-10-31 17:48:13 -07001489 int n = 0;
1490 do {
1491 StringBuilder msg = new StringBuilder();
1492 msg.append(" ").append(n).append(": [");
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001493 String formattedStartTime = opDF.format(new Date(operation.mStartWallTime));
1494 msg.append(formattedStartTime);
Jeff Browne5360fb2011-10-31 17:48:13 -07001495 msg.append("] ");
Jeff Browncefeb292013-05-01 15:28:37 -07001496 operation.describe(msg, verbose);
Jeff Browne5360fb2011-10-31 17:48:13 -07001497 printer.println(msg.toString());
1498
1499 if (index > 0) {
1500 index -= 1;
1501 } else {
1502 index = MAX_RECENT_OPERATIONS - 1;
1503 }
1504 n += 1;
1505 operation = mOperations[index];
1506 } while (operation != null && n < MAX_RECENT_OPERATIONS);
1507 } else {
1508 printer.println(" <none>");
1509 }
1510 }
1511 }
1512 }
1513
1514 private static final class Operation {
Greg Hackmanne12350f2014-12-01 14:31:21 -08001515 // Trim all SQL statements to 256 characters inside the trace marker.
1516 // This limit gives plenty of context while leaving space for other
1517 // entries in the trace buffer (and ensures atrace doesn't truncate the
1518 // marker for us, potentially losing metadata in the process).
1519 private static final int MAX_TRACE_METHOD_NAME_LEN = 256;
1520
Makoto Onuki34436702016-04-07 09:07:04 -07001521 public long mStartWallTime; // in System.currentTimeMillis()
1522 public long mStartTime; // in SystemClock.uptimeMillis();
1523 public long mEndTime; // in SystemClock.uptimeMillis();
Jeff Browne5360fb2011-10-31 17:48:13 -07001524 public String mKind;
1525 public String mSql;
1526 public ArrayList<Object> mBindArgs;
1527 public boolean mFinished;
1528 public Exception mException;
Jeff Browna9be4152012-01-18 15:29:57 -08001529 public int mCookie;
Jeff Browne5360fb2011-10-31 17:48:13 -07001530
Jeff Browncefeb292013-05-01 15:28:37 -07001531 public void describe(StringBuilder msg, boolean verbose) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001532 msg.append(mKind);
1533 if (mFinished) {
1534 msg.append(" took ").append(mEndTime - mStartTime).append("ms");
1535 } else {
Makoto Onuki34436702016-04-07 09:07:04 -07001536 msg.append(" started ").append(System.currentTimeMillis() - mStartWallTime)
Jeff Browne5360fb2011-10-31 17:48:13 -07001537 .append("ms ago");
1538 }
1539 msg.append(" - ").append(getStatus());
1540 if (mSql != null) {
1541 msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\"");
1542 }
Jeff Browncefeb292013-05-01 15:28:37 -07001543 if (verbose && mBindArgs != null && mBindArgs.size() != 0) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001544 msg.append(", bindArgs=[");
1545 final int count = mBindArgs.size();
1546 for (int i = 0; i < count; i++) {
1547 final Object arg = mBindArgs.get(i);
1548 if (i != 0) {
1549 msg.append(", ");
1550 }
1551 if (arg == null) {
1552 msg.append("null");
1553 } else if (arg instanceof byte[]) {
1554 msg.append("<byte[]>");
1555 } else if (arg instanceof String) {
1556 msg.append("\"").append((String)arg).append("\"");
1557 } else {
1558 msg.append(arg);
1559 }
1560 }
1561 msg.append("]");
1562 }
1563 if (mException != null) {
1564 msg.append(", exception=\"").append(mException.getMessage()).append("\"");
1565 }
1566 }
1567
1568 private String getStatus() {
1569 if (!mFinished) {
1570 return "running";
1571 }
1572 return mException != null ? "failed" : "succeeded";
1573 }
1574
Greg Hackmanne12350f2014-12-01 14:31:21 -08001575 private String getTraceMethodName() {
1576 String methodName = mKind + " " + mSql;
1577 if (methodName.length() > MAX_TRACE_METHOD_NAME_LEN)
1578 return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN);
1579 return methodName;
1580 }
1581
Jeff Browne5360fb2011-10-31 17:48:13 -07001582 }
1583}