blob: 20505ca803dc770162f1dfe9938f4d66187d1672 [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;
Makoto Onukia761d2b2018-08-01 15:57:45 -070022import android.database.sqlite.SQLiteDebug.Consts;
Jeff Browne5360fb2011-10-31 17:48:13 -070023import android.database.sqlite.SQLiteDebug.DbStats;
Jeff Browna7771df2012-05-07 20:06:46 -070024import android.os.CancellationSignal;
25import android.os.OperationCanceledException;
Jeff Browne5360fb2011-10-31 17:48:13 -070026import android.os.ParcelFileDescriptor;
Makoto Onuki34436702016-04-07 09:07:04 -070027import android.os.SystemClock;
Greg Hackmanne12350f2014-12-01 14:31:21 -080028import android.os.Trace;
Jeff Browne5360fb2011-10-31 17:48:13 -070029import android.util.Log;
30import android.util.LruCache;
31import android.util.Printer;
32
Fyodor Kupolovd3b0c7e2017-06-20 11:51:55 -070033import dalvik.system.BlockGuard;
34import dalvik.system.CloseGuard;
35
Makoto Onuki96e06002018-08-24 13:38:40 -070036import java.io.File;
Makoto Onukiee93ad22018-10-18 16:24:13 -070037import java.io.IOException;
Jeff Browne5360fb2011-10-31 17:48:13 -070038import java.text.SimpleDateFormat;
39import java.util.ArrayList;
Elliott Hughesc00df6d2013-05-06 10:53:28 -070040import java.util.Date;
Jeff Browne5360fb2011-10-31 17:48:13 -070041import java.util.Map;
Fyodor Kupolovd3b0c7e2017-06-20 11:51:55 -070042
Jeff Browne5360fb2011-10-31 17:48:13 -070043/**
44 * Represents a SQLite database connection.
45 * Each connection wraps an instance of a native <code>sqlite3</code> object.
46 * <p>
47 * When database connection pooling is enabled, there can be multiple active
48 * connections to the same database. Otherwise there is typically only one
49 * connection per database.
50 * </p><p>
51 * When the SQLite WAL feature is enabled, multiple readers and one writer
52 * can concurrently access the database. Without WAL, readers and writers
53 * are mutually exclusive.
54 * </p>
55 *
56 * <h2>Ownership and concurrency guarantees</h2>
57 * <p>
58 * Connection objects are not thread-safe. They are acquired as needed to
59 * perform a database operation and are then returned to the pool. At any
60 * given time, a connection is either owned and used by a {@link SQLiteSession}
61 * object or the {@link SQLiteConnectionPool}. Those classes are
62 * responsible for serializing operations to guard against concurrent
63 * use of a connection.
64 * </p><p>
65 * The guarantee of having a single owner allows this class to be implemented
66 * without locks and greatly simplifies resource management.
67 * </p>
68 *
69 * <h2>Encapsulation guarantees</h2>
70 * <p>
71 * The connection object object owns *all* of the SQLite related native
72 * objects that are associated with the connection. What's more, there are
73 * no other objects in the system that are capable of obtaining handles to
74 * those native objects. Consequently, when the connection is closed, we do
75 * not have to worry about what other components might have references to
76 * its associated SQLite state -- there are none.
77 * </p><p>
78 * Encapsulation is what ensures that the connection object's
79 * lifecycle does not become a tortured mess of finalizers and reference
80 * queues.
81 * </p>
82 *
Jeff Browna9be4152012-01-18 15:29:57 -080083 * <h2>Reentrance</h2>
84 * <p>
85 * This class must tolerate reentrant execution of SQLite operations because
86 * triggers may call custom SQLite functions that perform additional queries.
87 * </p>
88 *
Jeff Browne5360fb2011-10-31 17:48:13 -070089 * @hide
90 */
Jeff Brown4c1241d2012-02-02 17:05:00 -080091public final class SQLiteConnection implements CancellationSignal.OnCancelListener {
Jeff Browne5360fb2011-10-31 17:48:13 -070092 private static final String TAG = "SQLiteConnection";
Jeff Brown2a293b62012-01-19 14:02:22 -080093 private static final boolean DEBUG = false;
Jeff Browne5360fb2011-10-31 17:48:13 -070094
95 private static final String[] EMPTY_STRING_ARRAY = new String[0];
96 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
97
Jeff Browne5360fb2011-10-31 17:48:13 -070098 private final CloseGuard mCloseGuard = CloseGuard.get();
99
100 private final SQLiteConnectionPool mPool;
101 private final SQLiteDatabaseConfiguration mConfiguration;
102 private final int mConnectionId;
103 private final boolean mIsPrimaryConnection;
Jeff Brown1d9f7422012-03-15 14:32:32 -0700104 private final boolean mIsReadOnlyConnection;
Jeff Browne5360fb2011-10-31 17:48:13 -0700105 private final PreparedStatementCache mPreparedStatementCache;
106 private PreparedStatement mPreparedStatementPool;
107
108 // The recent operations log.
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -0700109 private final OperationLog mRecentOperations;
Jeff Browne5360fb2011-10-31 17:48:13 -0700110
111 // The native SQLiteConnection pointer. (FOR INTERNAL USE ONLY)
Ashok Bhat738702d2014-01-02 13:42:56 +0000112 private long mConnectionPtr;
Jeff Browne5360fb2011-10-31 17:48:13 -0700113
114 private boolean mOnlyAllowReadOnlyOperations;
115
Jeff Brown4c1241d2012-02-02 17:05:00 -0800116 // The number of times attachCancellationSignal has been called.
Jeff Brown1d9f7422012-03-15 14:32:32 -0700117 // Because SQLite statement execution can be reentrant, we keep track of how many
Jeff Brown4c1241d2012-02-02 17:05:00 -0800118 // times we have attempted to attach a cancellation signal to the connection so that
Jeff Brown75ea64f2012-01-25 19:37:13 -0800119 // we can ensure that we detach the signal at the right time.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800120 private int mCancellationSignalAttachCount;
Jeff Brown75ea64f2012-01-25 19:37:13 -0800121
Ashok Bhat738702d2014-01-02 13:42:56 +0000122 private static native long nativeOpen(String path, int openFlags, String label,
Fyodor Kupolovd3b0c7e2017-06-20 11:51:55 -0700123 boolean enableTrace, boolean enableProfile, int lookasideSlotSize,
124 int lookasideSlotCount);
Ashok Bhat738702d2014-01-02 13:42:56 +0000125 private static native void nativeClose(long connectionPtr);
126 private static native void nativeRegisterCustomFunction(long connectionPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700127 SQLiteCustomFunction function);
Ashok Bhat738702d2014-01-02 13:42:56 +0000128 private static native void nativeRegisterLocalizedCollators(long connectionPtr, String locale);
129 private static native long nativePrepareStatement(long connectionPtr, String sql);
130 private static native void nativeFinalizeStatement(long connectionPtr, long statementPtr);
131 private static native int nativeGetParameterCount(long connectionPtr, long statementPtr);
132 private static native boolean nativeIsReadOnly(long connectionPtr, long statementPtr);
133 private static native int nativeGetColumnCount(long connectionPtr, long statementPtr);
134 private static native String nativeGetColumnName(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700135 int index);
Ashok Bhat738702d2014-01-02 13:42:56 +0000136 private static native void nativeBindNull(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700137 int index);
Ashok Bhat738702d2014-01-02 13:42:56 +0000138 private static native void nativeBindLong(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700139 int index, long value);
Ashok Bhat738702d2014-01-02 13:42:56 +0000140 private static native void nativeBindDouble(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700141 int index, double value);
Ashok Bhat738702d2014-01-02 13:42:56 +0000142 private static native void nativeBindString(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700143 int index, String value);
Ashok Bhat738702d2014-01-02 13:42:56 +0000144 private static native void nativeBindBlob(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700145 int index, byte[] value);
146 private static native void nativeResetStatementAndClearBindings(
Ashok Bhat738702d2014-01-02 13:42:56 +0000147 long connectionPtr, long statementPtr);
148 private static native void nativeExecute(long connectionPtr, long statementPtr);
149 private static native long nativeExecuteForLong(long connectionPtr, long statementPtr);
150 private static native String nativeExecuteForString(long connectionPtr, long statementPtr);
Jeff Browne5360fb2011-10-31 17:48:13 -0700151 private static native int nativeExecuteForBlobFileDescriptor(
Ashok Bhat738702d2014-01-02 13:42:56 +0000152 long connectionPtr, long statementPtr);
153 private static native int nativeExecuteForChangedRowCount(long connectionPtr, long statementPtr);
Jeff Browne5360fb2011-10-31 17:48:13 -0700154 private static native long nativeExecuteForLastInsertedRowId(
Ashok Bhat738702d2014-01-02 13:42:56 +0000155 long connectionPtr, long statementPtr);
Jeff Browne5360fb2011-10-31 17:48:13 -0700156 private static native long nativeExecuteForCursorWindow(
Ashok Bhat738702d2014-01-02 13:42:56 +0000157 long connectionPtr, long statementPtr, long windowPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700158 int startPos, int requiredPos, boolean countAllRows);
Ashok Bhat738702d2014-01-02 13:42:56 +0000159 private static native int nativeGetDbLookaside(long connectionPtr);
160 private static native void nativeCancel(long connectionPtr);
161 private static native void nativeResetCancel(long connectionPtr, boolean cancelable);
Jeff Browne5360fb2011-10-31 17:48:13 -0700162
163 private SQLiteConnection(SQLiteConnectionPool pool,
164 SQLiteDatabaseConfiguration configuration,
165 int connectionId, boolean primaryConnection) {
166 mPool = pool;
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -0700167 mRecentOperations = new OperationLog(mPool);
Jeff Browne5360fb2011-10-31 17:48:13 -0700168 mConfiguration = new SQLiteDatabaseConfiguration(configuration);
169 mConnectionId = connectionId;
170 mIsPrimaryConnection = primaryConnection;
Jeff Brown1d9f7422012-03-15 14:32:32 -0700171 mIsReadOnlyConnection = (configuration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
Jeff Browne5360fb2011-10-31 17:48:13 -0700172 mPreparedStatementCache = new PreparedStatementCache(
173 mConfiguration.maxSqlCacheSize);
174 mCloseGuard.open("close");
175 }
176
177 @Override
178 protected void finalize() throws Throwable {
179 try {
180 if (mPool != null && mConnectionPtr != 0) {
181 mPool.onConnectionLeaked();
182 }
183
184 dispose(true);
185 } finally {
186 super.finalize();
187 }
188 }
189
190 // Called by SQLiteConnectionPool only.
191 static SQLiteConnection open(SQLiteConnectionPool pool,
192 SQLiteDatabaseConfiguration configuration,
193 int connectionId, boolean primaryConnection) {
194 SQLiteConnection connection = new SQLiteConnection(pool, configuration,
195 connectionId, primaryConnection);
196 try {
197 connection.open();
198 return connection;
199 } catch (SQLiteException ex) {
200 connection.dispose(false);
201 throw ex;
202 }
203 }
204
205 // Called by SQLiteConnectionPool only.
206 // Closes the database closes and releases all of its associated resources.
207 // Do not call methods on the connection after it is closed. It will probably crash.
208 void close() {
209 dispose(false);
210 }
211
212 private void open() {
Makoto Onuki66813282018-08-14 10:48:21 -0700213 final int cookie = mRecentOperations.beginOperation("open", null, null);
214 try {
215 mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
216 mConfiguration.label,
217 SQLiteDebug.Consts.DEBUG_SQL_STATEMENTS, SQLiteDebug.Consts.DEBUG_SQL_TIME,
218 mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount);
219 } finally {
220 mRecentOperations.endOperation(cookie);
221 }
Jeff Brown5936ff02012-02-29 21:03:20 -0800222 setPageSize();
Jeff Brown96496ad2012-03-23 14:38:06 -0700223 setForeignKeyModeFromConfiguration();
Jeff Brownd67c8c62012-03-22 14:15:01 -0700224 setWalModeFromConfiguration();
Jeff Brown8dc3cc22012-03-02 10:33:52 -0800225 setJournalSizeLimit();
226 setAutoCheckpointInterval();
Jeff Browne5360fb2011-10-31 17:48:13 -0700227 setLocaleFromConfiguration();
Niklas Brunlid27a65242012-09-25 12:55:34 +0200228
229 // Register custom functions.
230 final int functionCount = mConfiguration.customFunctions.size();
231 for (int i = 0; i < functionCount; i++) {
232 SQLiteCustomFunction function = mConfiguration.customFunctions.get(i);
233 nativeRegisterCustomFunction(mConnectionPtr, function);
234 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700235 }
236
237 private void dispose(boolean finalized) {
238 if (mCloseGuard != null) {
239 if (finalized) {
240 mCloseGuard.warnIfOpen();
241 }
242 mCloseGuard.close();
243 }
244
245 if (mConnectionPtr != 0) {
Jeff Browna9be4152012-01-18 15:29:57 -0800246 final int cookie = mRecentOperations.beginOperation("close", null, null);
Jeff Browne5360fb2011-10-31 17:48:13 -0700247 try {
248 mPreparedStatementCache.evictAll();
249 nativeClose(mConnectionPtr);
250 mConnectionPtr = 0;
251 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800252 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700253 }
254 }
255 }
256
Jeff Brown5936ff02012-02-29 21:03:20 -0800257 private void setPageSize() {
Jeff Brown1d9f7422012-03-15 14:32:32 -0700258 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
259 final long newValue = SQLiteGlobal.getDefaultPageSize();
260 long value = executeForLong("PRAGMA page_size", null, null);
261 if (value != newValue) {
262 execute("PRAGMA page_size=" + newValue, null, null);
263 }
Jeff Brown5936ff02012-02-29 21:03:20 -0800264 }
265 }
266
267 private void setAutoCheckpointInterval() {
Jeff Brown1d9f7422012-03-15 14:32:32 -0700268 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
269 final long newValue = SQLiteGlobal.getWALAutoCheckpoint();
270 long value = executeForLong("PRAGMA wal_autocheckpoint", null, null);
271 if (value != newValue) {
272 executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null);
273 }
Jeff Brown5936ff02012-02-29 21:03:20 -0800274 }
275 }
276
277 private void setJournalSizeLimit() {
Jeff Brown1d9f7422012-03-15 14:32:32 -0700278 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
279 final long newValue = SQLiteGlobal.getJournalSizeLimit();
280 long value = executeForLong("PRAGMA journal_size_limit", null, null);
281 if (value != newValue) {
282 executeForLong("PRAGMA journal_size_limit=" + newValue, null, null);
283 }
Jeff Brown5936ff02012-02-29 21:03:20 -0800284 }
285 }
286
Jeff Brown96496ad2012-03-23 14:38:06 -0700287 private void setForeignKeyModeFromConfiguration() {
288 if (!mIsReadOnlyConnection) {
289 final long newValue = mConfiguration.foreignKeyConstraintsEnabled ? 1 : 0;
290 long value = executeForLong("PRAGMA foreign_keys", null, null);
291 if (value != newValue) {
292 execute("PRAGMA foreign_keys=" + newValue, null, null);
293 }
294 }
295 }
296
Jeff Brownd67c8c62012-03-22 14:15:01 -0700297 private void setWalModeFromConfiguration() {
Jeff Brown1d9f7422012-03-15 14:32:32 -0700298 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
Fyodor Kupolov13a4b372017-11-07 18:45:35 -0800299 final boolean walEnabled =
Fyodor Kupolov5bd43ad2017-10-25 16:09:35 -0700300 (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
Fyodor Kupolov13a4b372017-11-07 18:45:35 -0800301 // Use compatibility WAL unless an app explicitly set journal/synchronous mode
Fyodor Kupolov692573b2018-03-06 12:34:36 -0800302 // or DISABLE_COMPATIBILITY_WAL flag is set
Fyodor Kupolov681ec312018-03-20 18:48:22 -0700303 final boolean useCompatibilityWal = mConfiguration.useCompatibilityWal();
Fyodor Kupolov13a4b372017-11-07 18:45:35 -0800304 if (walEnabled || useCompatibilityWal) {
Jeff Brownd67c8c62012-03-22 14:15:01 -0700305 setJournalMode("WAL");
Fyodor Kupolov8ba20892018-06-01 12:11:42 -0700306 if (mConfiguration.syncMode != null) {
307 setSyncMode(mConfiguration.syncMode);
308 } else if (useCompatibilityWal && SQLiteCompatibilityWalFlags.areFlagsSet()) {
Fyodor Kupolovee90c032017-12-12 11:52:57 -0800309 setSyncMode(SQLiteCompatibilityWalFlags.getWALSyncMode());
310 } else {
311 setSyncMode(SQLiteGlobal.getWALSyncMode());
312 }
Makoto Onuki96e06002018-08-24 13:38:40 -0700313 maybeTruncateWalFile();
Jeff Brownd67c8c62012-03-22 14:15:01 -0700314 } else {
Fyodor Kupolov13a4b372017-11-07 18:45:35 -0800315 setJournalMode(mConfiguration.journalMode == null
316 ? SQLiteGlobal.getDefaultJournalMode() : mConfiguration.journalMode);
317 setSyncMode(mConfiguration.syncMode == null
318 ? SQLiteGlobal.getDefaultSyncMode() : mConfiguration.syncMode);
Jeff Brown1d9f7422012-03-15 14:32:32 -0700319 }
Jeff Brown8dc3cc22012-03-02 10:33:52 -0800320 }
321 }
322
Makoto Onuki96e06002018-08-24 13:38:40 -0700323 /**
324 * If the WAL file exists and larger than a threshold, truncate it by executing
325 * PRAGMA wal_checkpoint.
326 */
327 private void maybeTruncateWalFile() {
328 final long threshold = SQLiteGlobal.getWALTruncateSize();
329 if (DEBUG) {
330 Log.d(TAG, "Truncate threshold=" + threshold);
331 }
332 if (threshold == 0) {
333 return;
334 }
335
336 final File walFile = new File(mConfiguration.path + "-wal");
337 if (!walFile.isFile()) {
338 return;
339 }
340 final long size = walFile.length();
341 if (size < threshold) {
342 if (DEBUG) {
343 Log.d(TAG, walFile.getAbsolutePath() + " " + size + " bytes: No need to truncate");
344 }
345 return;
346 }
347
348 Log.i(TAG, walFile.getAbsolutePath() + " " + size + " bytes: Bigger than "
349 + threshold + "; truncating");
350 try {
351 executeForString("PRAGMA wal_checkpoint(TRUNCATE)", null, null);
352 } catch (SQLiteException e) {
353 Log.w(TAG, "Failed to truncate the -wal file", e);
354 }
355 }
356
Jeff Brownd67c8c62012-03-22 14:15:01 -0700357 private void setSyncMode(String newValue) {
358 String value = executeForString("PRAGMA synchronous", null, null);
359 if (!canonicalizeSyncMode(value).equalsIgnoreCase(
360 canonicalizeSyncMode(newValue))) {
361 execute("PRAGMA synchronous=" + newValue, null, null);
362 }
363 }
364
365 private static String canonicalizeSyncMode(String value) {
Fyodor Kupolov13a4b372017-11-07 18:45:35 -0800366 switch (value) {
367 case "0": return "OFF";
368 case "1": return "NORMAL";
369 case "2": return "FULL";
Jeff Brownd67c8c62012-03-22 14:15:01 -0700370 }
371 return value;
372 }
373
374 private void setJournalMode(String newValue) {
375 String value = executeForString("PRAGMA journal_mode", null, null);
376 if (!value.equalsIgnoreCase(newValue)) {
377 try {
378 String result = executeForString("PRAGMA journal_mode=" + newValue, null, null);
379 if (result.equalsIgnoreCase(newValue)) {
380 return;
Jeff Brown1d9f7422012-03-15 14:32:32 -0700381 }
Jeff Brownd67c8c62012-03-22 14:15:01 -0700382 // PRAGMA journal_mode silently fails and returns the original journal
383 // mode in some cases if the journal mode could not be changed.
384 } catch (SQLiteDatabaseLockedException ex) {
385 // This error (SQLITE_BUSY) occurs if one connection has the database
386 // open in WAL mode and another tries to change it to non-WAL.
Jeff Brown5936ff02012-02-29 21:03:20 -0800387 }
Jeff Brownd67c8c62012-03-22 14:15:01 -0700388 // Because we always disable WAL mode when a database is first opened
389 // (even if we intend to re-enable it), we can encounter problems if
390 // there is another open connection to the database somewhere.
391 // This can happen for a variety of reasons such as an application opening
392 // the same database in multiple processes at the same time or if there is a
393 // crashing content provider service that the ActivityManager has
394 // removed from its registry but whose process hasn't quite died yet
395 // by the time it is restarted in a new process.
396 //
397 // If we don't change the journal mode, nothing really bad happens.
398 // In the worst case, an application that enables WAL might not actually
399 // get it, although it can still use connection pooling.
400 Log.w(TAG, "Could not change the database journal mode of '"
401 + mConfiguration.label + "' from '" + value + "' to '" + newValue
402 + "' because the database is locked. This usually means that "
403 + "there are other open connections to the database which prevents "
404 + "the database from enabling or disabling write-ahead logging mode. "
405 + "Proceeding without changing the journal mode.");
Jeff Brown5936ff02012-02-29 21:03:20 -0800406 }
407 }
408
Jeff Browne5360fb2011-10-31 17:48:13 -0700409 private void setLocaleFromConfiguration() {
Jeff Brown1d9f7422012-03-15 14:32:32 -0700410 if ((mConfiguration.openFlags & SQLiteDatabase.NO_LOCALIZED_COLLATORS) != 0) {
411 return;
412 }
413
414 // Register the localized collators.
415 final String newLocale = mConfiguration.locale.toString();
416 nativeRegisterLocalizedCollators(mConnectionPtr, newLocale);
417
Makoto Onukiee93ad22018-10-18 16:24:13 -0700418 if (!mConfiguration.isInMemoryDb()) {
419 checkDatabaseWiped();
420 }
421
Jeff Brown1d9f7422012-03-15 14:32:32 -0700422 // If the database is read-only, we cannot modify the android metadata table
423 // or existing indexes.
424 if (mIsReadOnlyConnection) {
425 return;
426 }
427
428 try {
429 // Ensure the android metadata table exists.
430 execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null);
431
432 // Check whether the locale was actually changed.
433 final String oldLocale = executeForString("SELECT locale FROM android_metadata "
434 + "UNION SELECT NULL ORDER BY locale DESC LIMIT 1", null, null);
435 if (oldLocale != null && oldLocale.equals(newLocale)) {
436 return;
437 }
438
439 // Go ahead and update the indexes using the new locale.
440 execute("BEGIN", null, null);
441 boolean success = false;
442 try {
443 execute("DELETE FROM android_metadata", null, null);
444 execute("INSERT INTO android_metadata (locale) VALUES(?)",
445 new Object[] { newLocale }, null);
446 execute("REINDEX LOCALIZED", null, null);
447 success = true;
448 } finally {
449 execute(success ? "COMMIT" : "ROLLBACK", null, null);
450 }
451 } catch (RuntimeException ex) {
452 throw new SQLiteException("Failed to change locale for db '" + mConfiguration.label
453 + "' to '" + newLocale + "'.", ex);
454 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700455 }
456
Makoto Onukiee93ad22018-10-18 16:24:13 -0700457 private void checkDatabaseWiped() {
458 if (!SQLiteGlobal.checkDbWipe()) {
459 return;
460 }
461 try {
462 final File checkFile = new File(mConfiguration.path
463 + SQLiteGlobal.WIPE_CHECK_FILE_SUFFIX);
464
465 final boolean hasMetadataTable = executeForLong(
466 "SELECT count(*) FROM sqlite_master"
467 + " WHERE type='table' AND name='android_metadata'", null, null) > 0;
468 final boolean hasCheckFile = checkFile.exists();
469
470 if (!mIsReadOnlyConnection && !hasCheckFile) {
471 // Create the check file, unless it's a readonly connection,
472 // in which case we can't create the metadata table anyway.
473 checkFile.createNewFile();
474 }
475
476 if (!hasMetadataTable && hasCheckFile) {
477 // Bad. The DB is gone unexpectedly.
478 SQLiteDatabase.wipeDetected(mConfiguration.path, "unknown");
479 }
480
481 } catch (RuntimeException | IOException ex) {
482 SQLiteDatabase.wtfAsSystemServer(TAG,
483 "Unexpected exception while checking for wipe", ex);
484 }
485 }
486
Jeff Browne5360fb2011-10-31 17:48:13 -0700487 // Called by SQLiteConnectionPool only.
488 void reconfigure(SQLiteDatabaseConfiguration configuration) {
Jeff Brown76070d1692012-04-19 11:30:33 -0700489 mOnlyAllowReadOnlyOperations = false;
490
Jeff Browne5360fb2011-10-31 17:48:13 -0700491 // Register custom functions.
492 final int functionCount = configuration.customFunctions.size();
493 for (int i = 0; i < functionCount; i++) {
494 SQLiteCustomFunction function = configuration.customFunctions.get(i);
495 if (!mConfiguration.customFunctions.contains(function)) {
496 nativeRegisterCustomFunction(mConnectionPtr, function);
497 }
498 }
499
Jeff Brown5936ff02012-02-29 21:03:20 -0800500 // Remember what changed.
Jeff Brown96496ad2012-03-23 14:38:06 -0700501 boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
502 != mConfiguration.foreignKeyConstraintsEnabled;
Jeff Brown47847f32012-03-22 19:13:11 -0700503 boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
Fyodor Kupolov692573b2018-03-06 12:34:36 -0800504 & (SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING
505 | SQLiteDatabase.DISABLE_COMPATIBILITY_WAL)) != 0;
Jeff Browne5360fb2011-10-31 17:48:13 -0700506 boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
507
508 // Update configuration parameters.
509 mConfiguration.updateParametersFrom(configuration);
510
511 // Update prepared statement cache size.
512 mPreparedStatementCache.resize(configuration.maxSqlCacheSize);
513
Jeff Brown96496ad2012-03-23 14:38:06 -0700514 // Update foreign key mode.
515 if (foreignKeyModeChanged) {
516 setForeignKeyModeFromConfiguration();
517 }
518
Jeff Brownd67c8c62012-03-22 14:15:01 -0700519 // Update WAL.
520 if (walModeChanged) {
521 setWalModeFromConfiguration();
Jeff Brown5936ff02012-02-29 21:03:20 -0800522 }
523
Jeff Browne5360fb2011-10-31 17:48:13 -0700524 // Update locale.
525 if (localeChanged) {
526 setLocaleFromConfiguration();
527 }
528 }
529
530 // Called by SQLiteConnectionPool only.
531 // When set to true, executing write operations will throw SQLiteException.
532 // Preparing statements that might write is ok, just don't execute them.
533 void setOnlyAllowReadOnlyOperations(boolean readOnly) {
534 mOnlyAllowReadOnlyOperations = readOnly;
535 }
536
537 // Called by SQLiteConnectionPool only.
538 // Returns true if the prepared statement cache contains the specified SQL.
539 boolean isPreparedStatementInCache(String sql) {
540 return mPreparedStatementCache.get(sql) != null;
541 }
542
543 /**
544 * Gets the unique id of this connection.
545 * @return The connection id.
546 */
547 public int getConnectionId() {
548 return mConnectionId;
549 }
550
551 /**
552 * Returns true if this is the primary database connection.
553 * @return True if this is the primary database connection.
554 */
555 public boolean isPrimaryConnection() {
556 return mIsPrimaryConnection;
557 }
558
559 /**
560 * Prepares a statement for execution but does not bind its parameters or execute it.
561 * <p>
562 * This method can be used to check for syntax errors during compilation
563 * prior to execution of the statement. If the {@code outStatementInfo} argument
564 * is not null, the provided {@link SQLiteStatementInfo} object is populated
565 * with information about the statement.
566 * </p><p>
567 * A prepared statement makes no reference to the arguments that may eventually
568 * be bound to it, consequently it it possible to cache certain prepared statements
569 * such as SELECT or INSERT/UPDATE statements. If the statement is cacheable,
570 * then it will be stored in the cache for later.
571 * </p><p>
572 * To take advantage of this behavior as an optimization, the connection pool
573 * provides a method to acquire a connection that already has a given SQL statement
574 * in its prepared statement cache so that it is ready for execution.
575 * </p>
576 *
577 * @param sql The SQL statement to prepare.
578 * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate
579 * with information about the statement, or null if none.
580 *
581 * @throws SQLiteException if an error occurs, such as a syntax error.
582 */
583 public void prepare(String sql, SQLiteStatementInfo outStatementInfo) {
584 if (sql == null) {
585 throw new IllegalArgumentException("sql must not be null.");
586 }
587
Jeff Browna9be4152012-01-18 15:29:57 -0800588 final int cookie = mRecentOperations.beginOperation("prepare", sql, null);
Jeff Browne5360fb2011-10-31 17:48:13 -0700589 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800590 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700591 try {
592 if (outStatementInfo != null) {
593 outStatementInfo.numParameters = statement.mNumParameters;
594 outStatementInfo.readOnly = statement.mReadOnly;
595
596 final int columnCount = nativeGetColumnCount(
597 mConnectionPtr, statement.mStatementPtr);
598 if (columnCount == 0) {
599 outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
600 } else {
601 outStatementInfo.columnNames = new String[columnCount];
602 for (int i = 0; i < columnCount; i++) {
603 outStatementInfo.columnNames[i] = nativeGetColumnName(
604 mConnectionPtr, statement.mStatementPtr, i);
605 }
606 }
607 }
608 } finally {
609 releasePreparedStatement(statement);
610 }
611 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800612 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700613 throw ex;
614 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800615 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700616 }
617 }
618
619 /**
620 * Executes a statement that does not return a result.
621 *
622 * @param sql The SQL statement to execute.
623 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800624 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700625 *
626 * @throws SQLiteException if an error occurs, such as a syntax error
627 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800628 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700629 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800630 public void execute(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800631 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700632 if (sql == null) {
633 throw new IllegalArgumentException("sql must not be null.");
634 }
635
Jeff Browna9be4152012-01-18 15:29:57 -0800636 final int cookie = mRecentOperations.beginOperation("execute", sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700637 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800638 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700639 try {
640 throwIfStatementForbidden(statement);
641 bindArguments(statement, bindArgs);
642 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800643 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800644 try {
645 nativeExecute(mConnectionPtr, statement.mStatementPtr);
646 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800647 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800648 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700649 } finally {
650 releasePreparedStatement(statement);
651 }
652 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800653 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700654 throw ex;
655 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800656 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700657 }
658 }
659
660 /**
661 * Executes a statement that returns a single <code>long</code> result.
662 *
663 * @param sql The SQL statement to execute.
664 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800665 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700666 * @return The value of the first column in the first row of the result set
667 * as a <code>long</code>, or zero if none.
668 *
669 * @throws SQLiteException if an error occurs, such as a syntax error
670 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800671 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700672 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800673 public long executeForLong(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800674 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700675 if (sql == null) {
676 throw new IllegalArgumentException("sql must not be null.");
677 }
678
Jeff Browna9be4152012-01-18 15:29:57 -0800679 final int cookie = mRecentOperations.beginOperation("executeForLong", sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700680 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800681 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700682 try {
683 throwIfStatementForbidden(statement);
684 bindArguments(statement, bindArgs);
685 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800686 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800687 try {
Makoto Onuki6cb20332018-08-07 15:57:13 -0700688 long ret = nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr);
689 mRecentOperations.setResult(ret);
690 return ret;
Jeff Brown75ea64f2012-01-25 19:37:13 -0800691 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800692 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800693 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700694 } finally {
695 releasePreparedStatement(statement);
696 }
697 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800698 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700699 throw ex;
700 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800701 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700702 }
703 }
704
705 /**
706 * Executes a statement that returns a single {@link String} result.
707 *
708 * @param sql The SQL statement to execute.
709 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800710 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700711 * @return The value of the first column in the first row of the result set
712 * as a <code>String</code>, or null if none.
713 *
714 * @throws SQLiteException if an error occurs, such as a syntax error
715 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800716 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700717 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800718 public String executeForString(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800719 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700720 if (sql == null) {
721 throw new IllegalArgumentException("sql must not be null.");
722 }
723
Jeff Browna9be4152012-01-18 15:29:57 -0800724 final int cookie = mRecentOperations.beginOperation("executeForString", sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700725 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800726 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700727 try {
728 throwIfStatementForbidden(statement);
729 bindArguments(statement, bindArgs);
730 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800731 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800732 try {
Makoto Onuki6cb20332018-08-07 15:57:13 -0700733 String ret = nativeExecuteForString(mConnectionPtr, statement.mStatementPtr);
734 mRecentOperations.setResult(ret);
735 return ret;
Jeff Brown75ea64f2012-01-25 19:37:13 -0800736 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800737 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800738 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700739 } finally {
740 releasePreparedStatement(statement);
741 }
742 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800743 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700744 throw ex;
745 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800746 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700747 }
748 }
749
750 /**
751 * Executes a statement that returns a single BLOB result as a
752 * file descriptor to a shared memory region.
753 *
754 * @param sql The SQL statement to execute.
755 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800756 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700757 * @return The file descriptor for a shared memory region that contains
758 * the value of the first column in the first row of the result set as a BLOB,
759 * or null if none.
760 *
761 * @throws SQLiteException if an error occurs, such as a syntax error
762 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800763 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700764 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800765 public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800766 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700767 if (sql == null) {
768 throw new IllegalArgumentException("sql must not be null.");
769 }
770
Jeff Browna9be4152012-01-18 15:29:57 -0800771 final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor",
772 sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700773 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800774 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700775 try {
776 throwIfStatementForbidden(statement);
777 bindArguments(statement, bindArgs);
778 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800779 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800780 try {
781 int fd = nativeExecuteForBlobFileDescriptor(
782 mConnectionPtr, statement.mStatementPtr);
783 return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null;
784 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800785 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800786 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700787 } finally {
788 releasePreparedStatement(statement);
789 }
790 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800791 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700792 throw ex;
793 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800794 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700795 }
796 }
797
798 /**
799 * Executes a statement that returns a count of the number of rows
800 * that were changed. Use for UPDATE or DELETE SQL statements.
801 *
802 * @param sql The SQL statement to execute.
803 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800804 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700805 * @return The number of rows that were changed.
806 *
807 * @throws SQLiteException if an error occurs, such as a syntax error
808 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800809 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700810 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800811 public int executeForChangedRowCount(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800812 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700813 if (sql == null) {
814 throw new IllegalArgumentException("sql must not be null.");
815 }
816
Makoto Onuki72eebb62012-04-16 13:46:15 -0700817 int changedRows = 0;
Jeff Browna9be4152012-01-18 15:29:57 -0800818 final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount",
819 sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700820 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800821 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700822 try {
823 throwIfStatementForbidden(statement);
824 bindArguments(statement, bindArgs);
825 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800826 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800827 try {
Makoto Onuki72eebb62012-04-16 13:46:15 -0700828 changedRows = nativeExecuteForChangedRowCount(
Jeff Brown75ea64f2012-01-25 19:37:13 -0800829 mConnectionPtr, statement.mStatementPtr);
Makoto Onuki72eebb62012-04-16 13:46:15 -0700830 return changedRows;
Jeff Brown75ea64f2012-01-25 19:37:13 -0800831 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800832 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800833 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700834 } finally {
835 releasePreparedStatement(statement);
836 }
837 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800838 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700839 throw ex;
840 } finally {
Makoto Onuki72eebb62012-04-16 13:46:15 -0700841 if (mRecentOperations.endOperationDeferLog(cookie)) {
842 mRecentOperations.logOperation(cookie, "changedRows=" + changedRows);
843 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700844 }
845 }
846
847 /**
848 * Executes a statement that returns the row id of the last row inserted
849 * by the statement. Use for INSERT SQL statements.
850 *
851 * @param sql The SQL statement to execute.
852 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800853 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700854 * @return The row id of the last row that was inserted, or 0 if none.
855 *
856 * @throws SQLiteException if an error occurs, such as a syntax error
857 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800858 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700859 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800860 public long executeForLastInsertedRowId(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800861 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700862 if (sql == null) {
863 throw new IllegalArgumentException("sql must not be null.");
864 }
865
Jeff Browna9be4152012-01-18 15:29:57 -0800866 final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId",
867 sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700868 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800869 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700870 try {
871 throwIfStatementForbidden(statement);
872 bindArguments(statement, bindArgs);
873 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800874 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800875 try {
876 return nativeExecuteForLastInsertedRowId(
877 mConnectionPtr, statement.mStatementPtr);
878 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800879 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800880 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700881 } finally {
882 releasePreparedStatement(statement);
883 }
884 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800885 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700886 throw ex;
887 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800888 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700889 }
890 }
891
892 /**
893 * Executes a statement and populates the specified {@link CursorWindow}
894 * with a range of results. Returns the number of rows that were counted
895 * during query execution.
896 *
897 * @param sql The SQL statement to execute.
898 * @param bindArgs The arguments to bind, or null if none.
899 * @param window The cursor window to clear and fill.
900 * @param startPos The start position for filling the window.
901 * @param requiredPos The position of a row that MUST be in the window.
902 * If it won't fit, then the query should discard part of what it filled
903 * so that it does. Must be greater than or equal to <code>startPos</code>.
904 * @param countAllRows True to count all rows that the query would return
905 * regagless of whether they fit in the window.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800906 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700907 * @return The number of rows that were counted during query execution. Might
908 * not be all rows in the result set unless <code>countAllRows</code> is true.
909 *
910 * @throws SQLiteException if an error occurs, such as a syntax error
911 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800912 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700913 */
914 public int executeForCursorWindow(String sql, Object[] bindArgs,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800915 CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800916 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700917 if (sql == null) {
918 throw new IllegalArgumentException("sql must not be null.");
919 }
920 if (window == null) {
921 throw new IllegalArgumentException("window must not be null.");
922 }
923
Jeff Brown03bd3022012-03-06 13:48:56 -0800924 window.acquireReference();
Jeff Browne5360fb2011-10-31 17:48:13 -0700925 try {
Jeff Brown03bd3022012-03-06 13:48:56 -0800926 int actualPos = -1;
927 int countedRows = -1;
928 int filledRows = -1;
929 final int cookie = mRecentOperations.beginOperation("executeForCursorWindow",
930 sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700931 try {
Jeff Brown03bd3022012-03-06 13:48:56 -0800932 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800933 try {
Jeff Brown03bd3022012-03-06 13:48:56 -0800934 throwIfStatementForbidden(statement);
935 bindArguments(statement, bindArgs);
936 applyBlockGuardPolicy(statement);
937 attachCancellationSignal(cancellationSignal);
938 try {
939 final long result = nativeExecuteForCursorWindow(
940 mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
941 startPos, requiredPos, countAllRows);
942 actualPos = (int)(result >> 32);
943 countedRows = (int)result;
944 filledRows = window.getNumRows();
945 window.setStartPosition(actualPos);
946 return countedRows;
947 } finally {
948 detachCancellationSignal(cancellationSignal);
949 }
Jeff Brown75ea64f2012-01-25 19:37:13 -0800950 } finally {
Jeff Brown03bd3022012-03-06 13:48:56 -0800951 releasePreparedStatement(statement);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800952 }
Jeff Brown03bd3022012-03-06 13:48:56 -0800953 } catch (RuntimeException ex) {
954 mRecentOperations.failOperation(cookie, ex);
955 throw ex;
Jeff Browne5360fb2011-10-31 17:48:13 -0700956 } finally {
Jeff Brown03bd3022012-03-06 13:48:56 -0800957 if (mRecentOperations.endOperationDeferLog(cookie)) {
958 mRecentOperations.logOperation(cookie, "window='" + window
959 + "', startPos=" + startPos
960 + ", actualPos=" + actualPos
961 + ", filledRows=" + filledRows
962 + ", countedRows=" + countedRows);
963 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700964 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700965 } finally {
Jeff Brown03bd3022012-03-06 13:48:56 -0800966 window.releaseReference();
Jeff Browne5360fb2011-10-31 17:48:13 -0700967 }
968 }
969
970 private PreparedStatement acquirePreparedStatement(String sql) {
971 PreparedStatement statement = mPreparedStatementCache.get(sql);
Jeff Browna9be4152012-01-18 15:29:57 -0800972 boolean skipCache = false;
Jeff Browne5360fb2011-10-31 17:48:13 -0700973 if (statement != null) {
Jeff Browna9be4152012-01-18 15:29:57 -0800974 if (!statement.mInUse) {
975 return statement;
976 }
977 // The statement is already in the cache but is in use (this statement appears
978 // to be not only re-entrant but recursive!). So prepare a new copy of the
979 // statement but do not cache it.
980 skipCache = true;
Jeff Browne5360fb2011-10-31 17:48:13 -0700981 }
982
Ashok Bhat738702d2014-01-02 13:42:56 +0000983 final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700984 try {
985 final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
986 final int type = DatabaseUtils.getSqlStatementType(sql);
987 final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
988 statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly);
Jeff Browna9be4152012-01-18 15:29:57 -0800989 if (!skipCache && isCacheable(type)) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700990 mPreparedStatementCache.put(sql, statement);
991 statement.mInCache = true;
992 }
993 } catch (RuntimeException ex) {
994 // Finalize the statement if an exception occurred and we did not add
995 // it to the cache. If it is already in the cache, then leave it there.
996 if (statement == null || !statement.mInCache) {
997 nativeFinalizeStatement(mConnectionPtr, statementPtr);
998 }
999 throw ex;
1000 }
Jeff Browna9be4152012-01-18 15:29:57 -08001001 statement.mInUse = true;
Jeff Browne5360fb2011-10-31 17:48:13 -07001002 return statement;
1003 }
1004
1005 private void releasePreparedStatement(PreparedStatement statement) {
Jeff Browna9be4152012-01-18 15:29:57 -08001006 statement.mInUse = false;
Jeff Browne5360fb2011-10-31 17:48:13 -07001007 if (statement.mInCache) {
1008 try {
1009 nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr);
1010 } catch (SQLiteException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -08001011 // The statement could not be reset due to an error. Remove it from the cache.
1012 // When remove() is called, the cache will invoke its entryRemoved() callback,
1013 // which will in turn call finalizePreparedStatement() to finalize and
1014 // recycle the statement.
Jeff Brown2a293b62012-01-19 14:02:22 -08001015 if (DEBUG) {
1016 Log.d(TAG, "Could not reset prepared statement due to an exception. "
Jeff Browne5360fb2011-10-31 17:48:13 -07001017 + "Removing it from the cache. SQL: "
1018 + trimSqlForDisplay(statement.mSql), ex);
1019 }
Jeff Brown2a293b62012-01-19 14:02:22 -08001020
Jeff Browne5360fb2011-10-31 17:48:13 -07001021 mPreparedStatementCache.remove(statement.mSql);
1022 }
1023 } else {
Jeff Browna9be4152012-01-18 15:29:57 -08001024 finalizePreparedStatement(statement);
Jeff Browne5360fb2011-10-31 17:48:13 -07001025 }
1026 }
1027
Jeff Browna9be4152012-01-18 15:29:57 -08001028 private void finalizePreparedStatement(PreparedStatement statement) {
1029 nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr);
1030 recyclePreparedStatement(statement);
1031 }
1032
Jeff Brown4c1241d2012-02-02 17:05:00 -08001033 private void attachCancellationSignal(CancellationSignal cancellationSignal) {
1034 if (cancellationSignal != null) {
1035 cancellationSignal.throwIfCanceled();
Jeff Brown75ea64f2012-01-25 19:37:13 -08001036
Jeff Brown4c1241d2012-02-02 17:05:00 -08001037 mCancellationSignalAttachCount += 1;
1038 if (mCancellationSignalAttachCount == 1) {
1039 // Reset cancellation flag before executing the statement.
Jeff Brown75ea64f2012-01-25 19:37:13 -08001040 nativeResetCancel(mConnectionPtr, true /*cancelable*/);
1041
1042 // After this point, onCancel() may be called concurrently.
Jeff Brown4c1241d2012-02-02 17:05:00 -08001043 cancellationSignal.setOnCancelListener(this);
Jeff Brown75ea64f2012-01-25 19:37:13 -08001044 }
1045 }
1046 }
1047
Jeff Brown4c1241d2012-02-02 17:05:00 -08001048 private void detachCancellationSignal(CancellationSignal cancellationSignal) {
1049 if (cancellationSignal != null) {
1050 assert mCancellationSignalAttachCount > 0;
Jeff Brown75ea64f2012-01-25 19:37:13 -08001051
Jeff Brown4c1241d2012-02-02 17:05:00 -08001052 mCancellationSignalAttachCount -= 1;
1053 if (mCancellationSignalAttachCount == 0) {
Jeff Brown75ea64f2012-01-25 19:37:13 -08001054 // After this point, onCancel() cannot be called concurrently.
Jeff Brown4c1241d2012-02-02 17:05:00 -08001055 cancellationSignal.setOnCancelListener(null);
Jeff Brown75ea64f2012-01-25 19:37:13 -08001056
Jeff Brown4c1241d2012-02-02 17:05:00 -08001057 // Reset cancellation flag after executing the statement.
Jeff Brown75ea64f2012-01-25 19:37:13 -08001058 nativeResetCancel(mConnectionPtr, false /*cancelable*/);
1059 }
1060 }
1061 }
1062
Jeff Brown4c1241d2012-02-02 17:05:00 -08001063 // CancellationSignal.OnCancelListener callback.
Jeff Brown75ea64f2012-01-25 19:37:13 -08001064 // This method may be called on a different thread than the executing statement.
Jeff Brown4c1241d2012-02-02 17:05:00 -08001065 // However, it will only be called between calls to attachCancellationSignal and
1066 // detachCancellationSignal, while a statement is executing. We can safely assume
Jeff Brown75ea64f2012-01-25 19:37:13 -08001067 // that the SQLite connection is still alive.
1068 @Override
1069 public void onCancel() {
1070 nativeCancel(mConnectionPtr);
1071 }
1072
Jeff Browne5360fb2011-10-31 17:48:13 -07001073 private void bindArguments(PreparedStatement statement, Object[] bindArgs) {
1074 final int count = bindArgs != null ? bindArgs.length : 0;
1075 if (count != statement.mNumParameters) {
1076 throw new SQLiteBindOrColumnIndexOutOfRangeException(
1077 "Expected " + statement.mNumParameters + " bind arguments but "
Sylvain Becuwe942085b2012-10-28 01:48:37 +02001078 + count + " were provided.");
Jeff Browne5360fb2011-10-31 17:48:13 -07001079 }
1080 if (count == 0) {
1081 return;
1082 }
1083
Ashok Bhat738702d2014-01-02 13:42:56 +00001084 final long statementPtr = statement.mStatementPtr;
Jeff Browne5360fb2011-10-31 17:48:13 -07001085 for (int i = 0; i < count; i++) {
1086 final Object arg = bindArgs[i];
1087 switch (DatabaseUtils.getTypeOfObject(arg)) {
1088 case Cursor.FIELD_TYPE_NULL:
1089 nativeBindNull(mConnectionPtr, statementPtr, i + 1);
1090 break;
1091 case Cursor.FIELD_TYPE_INTEGER:
1092 nativeBindLong(mConnectionPtr, statementPtr, i + 1,
1093 ((Number)arg).longValue());
1094 break;
1095 case Cursor.FIELD_TYPE_FLOAT:
1096 nativeBindDouble(mConnectionPtr, statementPtr, i + 1,
1097 ((Number)arg).doubleValue());
1098 break;
1099 case Cursor.FIELD_TYPE_BLOB:
1100 nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg);
1101 break;
1102 case Cursor.FIELD_TYPE_STRING:
1103 default:
1104 if (arg instanceof Boolean) {
1105 // Provide compatibility with legacy applications which may pass
1106 // Boolean values in bind args.
1107 nativeBindLong(mConnectionPtr, statementPtr, i + 1,
1108 ((Boolean)arg).booleanValue() ? 1 : 0);
1109 } else {
1110 nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString());
1111 }
1112 break;
1113 }
1114 }
1115 }
1116
1117 private void throwIfStatementForbidden(PreparedStatement statement) {
1118 if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) {
1119 throw new SQLiteException("Cannot execute this statement because it "
1120 + "might modify the database but the connection is read-only.");
1121 }
1122 }
1123
1124 private static boolean isCacheable(int statementType) {
1125 if (statementType == DatabaseUtils.STATEMENT_UPDATE
1126 || statementType == DatabaseUtils.STATEMENT_SELECT) {
1127 return true;
1128 }
1129 return false;
1130 }
1131
1132 private void applyBlockGuardPolicy(PreparedStatement statement) {
1133 if (!mConfiguration.isInMemoryDb()) {
1134 if (statement.mReadOnly) {
1135 BlockGuard.getThreadPolicy().onReadFromDisk();
1136 } else {
1137 BlockGuard.getThreadPolicy().onWriteToDisk();
1138 }
1139 }
1140 }
1141
1142 /**
1143 * Dumps debugging information about this connection.
1144 *
1145 * @param printer The printer to receive the dump, not null.
Jeff Browna9be4152012-01-18 15:29:57 -08001146 * @param verbose True to dump more verbose information.
Jeff Browne5360fb2011-10-31 17:48:13 -07001147 */
Jeff Browna9be4152012-01-18 15:29:57 -08001148 public void dump(Printer printer, boolean verbose) {
1149 dumpUnsafe(printer, verbose);
Jeff Browne5360fb2011-10-31 17:48:13 -07001150 }
1151
1152 /**
1153 * Dumps debugging information about this connection, in the case where the
1154 * caller might not actually own the connection.
1155 *
1156 * This function is written so that it may be called by a thread that does not
1157 * own the connection. We need to be very careful because the connection state is
1158 * not synchronized.
1159 *
1160 * At worst, the method may return stale or slightly wrong data, however
1161 * it should not crash. This is ok as it is only used for diagnostic purposes.
1162 *
1163 * @param printer The printer to receive the dump, not null.
Jeff Browna9be4152012-01-18 15:29:57 -08001164 * @param verbose True to dump more verbose information.
Jeff Browne5360fb2011-10-31 17:48:13 -07001165 */
Jeff Browna9be4152012-01-18 15:29:57 -08001166 void dumpUnsafe(Printer printer, boolean verbose) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001167 printer.println("Connection #" + mConnectionId + ":");
Jeff Browna9be4152012-01-18 15:29:57 -08001168 if (verbose) {
Ashok Bhat738702d2014-01-02 13:42:56 +00001169 printer.println(" connectionPtr: 0x" + Long.toHexString(mConnectionPtr));
Jeff Browna9be4152012-01-18 15:29:57 -08001170 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001171 printer.println(" isPrimaryConnection: " + mIsPrimaryConnection);
Jeff Browne5360fb2011-10-31 17:48:13 -07001172 printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
1173
Makoto Onukia761d2b2018-08-01 15:57:45 -07001174 mRecentOperations.dump(printer);
Jeff Browna9be4152012-01-18 15:29:57 -08001175
1176 if (verbose) {
1177 mPreparedStatementCache.dump(printer);
1178 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001179 }
1180
1181 /**
1182 * Describes the currently executing operation, in the case where the
1183 * caller might not actually own the connection.
1184 *
1185 * This function is written so that it may be called by a thread that does not
1186 * own the connection. We need to be very careful because the connection state is
1187 * not synchronized.
1188 *
1189 * At worst, the method may return stale or slightly wrong data, however
1190 * it should not crash. This is ok as it is only used for diagnostic purposes.
1191 *
1192 * @return A description of the current operation including how long it has been running,
1193 * or null if none.
1194 */
1195 String describeCurrentOperationUnsafe() {
1196 return mRecentOperations.describeCurrentOperation();
1197 }
1198
1199 /**
1200 * Collects statistics about database connection memory usage.
1201 *
1202 * @param dbStatsList The list to populate.
1203 */
1204 void collectDbStats(ArrayList<DbStats> dbStatsList) {
1205 // Get information about the main database.
1206 int lookaside = nativeGetDbLookaside(mConnectionPtr);
1207 long pageCount = 0;
1208 long pageSize = 0;
1209 try {
Jeff Brown75ea64f2012-01-25 19:37:13 -08001210 pageCount = executeForLong("PRAGMA page_count;", null, null);
1211 pageSize = executeForLong("PRAGMA page_size;", null, null);
Jeff Browne5360fb2011-10-31 17:48:13 -07001212 } catch (SQLiteException ex) {
1213 // Ignore.
1214 }
1215 dbStatsList.add(getMainDbStatsUnsafe(lookaside, pageCount, pageSize));
1216
1217 // Get information about attached databases.
1218 // We ignore the first row in the database list because it corresponds to
1219 // the main database which we have already described.
1220 CursorWindow window = new CursorWindow("collectDbStats");
1221 try {
Jeff Brown75ea64f2012-01-25 19:37:13 -08001222 executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null);
Jeff Browne5360fb2011-10-31 17:48:13 -07001223 for (int i = 1; i < window.getNumRows(); i++) {
1224 String name = window.getString(i, 1);
1225 String path = window.getString(i, 2);
1226 pageCount = 0;
1227 pageSize = 0;
1228 try {
Jeff Brown75ea64f2012-01-25 19:37:13 -08001229 pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null);
1230 pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null);
Jeff Browne5360fb2011-10-31 17:48:13 -07001231 } catch (SQLiteException ex) {
1232 // Ignore.
1233 }
1234 String label = " (attached) " + name;
1235 if (!path.isEmpty()) {
1236 label += ": " + path;
1237 }
1238 dbStatsList.add(new DbStats(label, pageCount, pageSize, 0, 0, 0, 0));
1239 }
1240 } catch (SQLiteException ex) {
1241 // Ignore.
1242 } finally {
1243 window.close();
1244 }
1245 }
1246
1247 /**
1248 * Collects statistics about database connection memory usage, in the case where the
1249 * caller might not actually own the connection.
1250 *
1251 * @return The statistics object, never null.
1252 */
1253 void collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList) {
1254 dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0));
1255 }
1256
1257 private DbStats getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize) {
1258 // The prepared statement cache is thread-safe so we can access its statistics
1259 // even if we do not own the database connection.
1260 String label = mConfiguration.path;
1261 if (!mIsPrimaryConnection) {
1262 label += " (" + mConnectionId + ")";
1263 }
1264 return new DbStats(label, pageCount, pageSize, lookaside,
1265 mPreparedStatementCache.hitCount(),
1266 mPreparedStatementCache.missCount(),
1267 mPreparedStatementCache.size());
1268 }
1269
1270 @Override
1271 public String toString() {
1272 return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")";
1273 }
1274
Ashok Bhat738702d2014-01-02 13:42:56 +00001275 private PreparedStatement obtainPreparedStatement(String sql, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -07001276 int numParameters, int type, boolean readOnly) {
1277 PreparedStatement statement = mPreparedStatementPool;
1278 if (statement != null) {
1279 mPreparedStatementPool = statement.mPoolNext;
1280 statement.mPoolNext = null;
1281 statement.mInCache = false;
1282 } else {
1283 statement = new PreparedStatement();
1284 }
1285 statement.mSql = sql;
1286 statement.mStatementPtr = statementPtr;
1287 statement.mNumParameters = numParameters;
1288 statement.mType = type;
1289 statement.mReadOnly = readOnly;
1290 return statement;
1291 }
1292
1293 private void recyclePreparedStatement(PreparedStatement statement) {
1294 statement.mSql = null;
1295 statement.mPoolNext = mPreparedStatementPool;
1296 mPreparedStatementPool = statement;
1297 }
1298
1299 private static String trimSqlForDisplay(String sql) {
Andreas Gampe4f47b402015-04-20 15:29:04 -07001300 // Note: Creating and caching a regular expression is expensive at preload-time
1301 // and stops compile-time initialization. This pattern is only used when
1302 // dumping the connection, which is a rare (mainly error) case. So:
1303 // DO NOT CACHE.
1304 return sql.replaceAll("[\\s]*\\n+[\\s]*", " ");
Jeff Browne5360fb2011-10-31 17:48:13 -07001305 }
1306
1307 /**
1308 * Holder type for a prepared statement.
1309 *
1310 * Although this object holds a pointer to a native statement object, it
1311 * does not have a finalizer. This is deliberate. The {@link SQLiteConnection}
1312 * owns the statement object and will take care of freeing it when needed.
1313 * In particular, closing the connection requires a guarantee of deterministic
1314 * resource disposal because all native statement objects must be freed before
1315 * the native database object can be closed. So no finalizers here.
1316 */
1317 private static final class PreparedStatement {
1318 // Next item in pool.
1319 public PreparedStatement mPoolNext;
1320
1321 // The SQL from which the statement was prepared.
1322 public String mSql;
1323
1324 // The native sqlite3_stmt object pointer.
1325 // Lifetime is managed explicitly by the connection.
Ashok Bhat738702d2014-01-02 13:42:56 +00001326 public long mStatementPtr;
Jeff Browne5360fb2011-10-31 17:48:13 -07001327
1328 // The number of parameters that the prepared statement has.
1329 public int mNumParameters;
1330
1331 // The statement type.
1332 public int mType;
1333
1334 // True if the statement is read-only.
1335 public boolean mReadOnly;
1336
1337 // True if the statement is in the cache.
1338 public boolean mInCache;
Jeff Browna9be4152012-01-18 15:29:57 -08001339
1340 // True if the statement is in use (currently executing).
1341 // We need this flag because due to the use of custom functions in triggers, it's
1342 // possible for SQLite calls to be re-entrant. Consequently we need to prevent
1343 // in use statements from being finalized until they are no longer in use.
1344 public boolean mInUse;
Jeff Browne5360fb2011-10-31 17:48:13 -07001345 }
1346
1347 private final class PreparedStatementCache
1348 extends LruCache<String, PreparedStatement> {
1349 public PreparedStatementCache(int size) {
1350 super(size);
1351 }
1352
1353 @Override
1354 protected void entryRemoved(boolean evicted, String key,
1355 PreparedStatement oldValue, PreparedStatement newValue) {
1356 oldValue.mInCache = false;
Jeff Browna9be4152012-01-18 15:29:57 -08001357 if (!oldValue.mInUse) {
1358 finalizePreparedStatement(oldValue);
1359 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001360 }
1361
1362 public void dump(Printer printer) {
1363 printer.println(" Prepared statement cache:");
1364 Map<String, PreparedStatement> cache = snapshot();
1365 if (!cache.isEmpty()) {
1366 int i = 0;
1367 for (Map.Entry<String, PreparedStatement> entry : cache.entrySet()) {
1368 PreparedStatement statement = entry.getValue();
1369 if (statement.mInCache) { // might be false due to a race with entryRemoved
1370 String sql = entry.getKey();
1371 printer.println(" " + i + ": statementPtr=0x"
Ashok Bhat738702d2014-01-02 13:42:56 +00001372 + Long.toHexString(statement.mStatementPtr)
Jeff Browne5360fb2011-10-31 17:48:13 -07001373 + ", numParameters=" + statement.mNumParameters
1374 + ", type=" + statement.mType
1375 + ", readOnly=" + statement.mReadOnly
1376 + ", sql=\"" + trimSqlForDisplay(sql) + "\"");
1377 }
1378 i += 1;
1379 }
1380 } else {
1381 printer.println(" <none>");
1382 }
1383 }
1384 }
1385
1386 private static final class OperationLog {
Jeff Brown2a293b62012-01-19 14:02:22 -08001387 private static final int MAX_RECENT_OPERATIONS = 20;
Jeff Browna9be4152012-01-18 15:29:57 -08001388 private static final int COOKIE_GENERATION_SHIFT = 8;
1389 private static final int COOKIE_INDEX_MASK = 0xff;
Jeff Browne5360fb2011-10-31 17:48:13 -07001390
1391 private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
1392 private int mIndex;
Jeff Browna9be4152012-01-18 15:29:57 -08001393 private int mGeneration;
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001394 private final SQLiteConnectionPool mPool;
Makoto Onuki6cb20332018-08-07 15:57:13 -07001395 private long mResultLong = Long.MIN_VALUE;
1396 private String mResultString;
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001397
1398 OperationLog(SQLiteConnectionPool pool) {
1399 mPool = pool;
1400 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001401
Jeff Browna9be4152012-01-18 15:29:57 -08001402 public int beginOperation(String kind, String sql, Object[] bindArgs) {
Makoto Onuki6cb20332018-08-07 15:57:13 -07001403 mResultLong = Long.MIN_VALUE;
1404 mResultString = null;
1405
Jeff Browne5360fb2011-10-31 17:48:13 -07001406 synchronized (mOperations) {
1407 final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
1408 Operation operation = mOperations[index];
1409 if (operation == null) {
1410 operation = new Operation();
1411 mOperations[index] = operation;
1412 } else {
1413 operation.mFinished = false;
1414 operation.mException = null;
1415 if (operation.mBindArgs != null) {
1416 operation.mBindArgs.clear();
1417 }
1418 }
Makoto Onuki34436702016-04-07 09:07:04 -07001419 operation.mStartWallTime = System.currentTimeMillis();
1420 operation.mStartTime = SystemClock.uptimeMillis();
Jeff Browne5360fb2011-10-31 17:48:13 -07001421 operation.mKind = kind;
1422 operation.mSql = sql;
Makoto Onuki6cb20332018-08-07 15:57:13 -07001423 operation.mPath = mPool.getPath();
1424 operation.mResultLong = Long.MIN_VALUE;
1425 operation.mResultString = null;
Jeff Browne5360fb2011-10-31 17:48:13 -07001426 if (bindArgs != null) {
1427 if (operation.mBindArgs == null) {
1428 operation.mBindArgs = new ArrayList<Object>();
1429 } else {
1430 operation.mBindArgs.clear();
1431 }
1432 for (int i = 0; i < bindArgs.length; i++) {
1433 final Object arg = bindArgs[i];
1434 if (arg != null && arg instanceof byte[]) {
1435 // Don't hold onto the real byte array longer than necessary.
1436 operation.mBindArgs.add(EMPTY_BYTE_ARRAY);
1437 } else {
1438 operation.mBindArgs.add(arg);
1439 }
1440 }
1441 }
Jeff Browna9be4152012-01-18 15:29:57 -08001442 operation.mCookie = newOperationCookieLocked(index);
Greg Hackmanne12350f2014-12-01 14:31:21 -08001443 if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
1444 Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
1445 operation.mCookie);
1446 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001447 mIndex = index;
Jeff Browna9be4152012-01-18 15:29:57 -08001448 return operation.mCookie;
Jeff Browne5360fb2011-10-31 17:48:13 -07001449 }
1450 }
1451
Jeff Browna9be4152012-01-18 15:29:57 -08001452 public void failOperation(int cookie, Exception ex) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001453 synchronized (mOperations) {
Jeff Browna9be4152012-01-18 15:29:57 -08001454 final Operation operation = getOperationLocked(cookie);
1455 if (operation != null) {
1456 operation.mException = ex;
Jeff Browne5360fb2011-10-31 17:48:13 -07001457 }
1458 }
1459 }
1460
Jeff Browna9be4152012-01-18 15:29:57 -08001461 public void endOperation(int cookie) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001462 synchronized (mOperations) {
Jeff Browna9be4152012-01-18 15:29:57 -08001463 if (endOperationDeferLogLocked(cookie)) {
1464 logOperationLocked(cookie, null);
1465 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001466 }
1467 }
1468
Jeff Browna9be4152012-01-18 15:29:57 -08001469 public boolean endOperationDeferLog(int cookie) {
1470 synchronized (mOperations) {
1471 return endOperationDeferLogLocked(cookie);
1472 }
1473 }
1474
1475 public void logOperation(int cookie, String detail) {
1476 synchronized (mOperations) {
1477 logOperationLocked(cookie, detail);
1478 }
1479 }
1480
Makoto Onuki6cb20332018-08-07 15:57:13 -07001481 public void setResult(long longResult) {
1482 mResultLong = longResult;
1483 }
1484
1485 public void setResult(String stringResult) {
1486 mResultString = stringResult;
1487 }
1488
Jeff Browna9be4152012-01-18 15:29:57 -08001489 private boolean endOperationDeferLogLocked(int cookie) {
1490 final Operation operation = getOperationLocked(cookie);
1491 if (operation != null) {
Greg Hackmanne12350f2014-12-01 14:31:21 -08001492 if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
1493 Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
1494 operation.mCookie);
1495 }
Makoto Onuki34436702016-04-07 09:07:04 -07001496 operation.mEndTime = SystemClock.uptimeMillis();
Jeff Browna9be4152012-01-18 15:29:57 -08001497 operation.mFinished = true;
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001498 final long execTime = operation.mEndTime - operation.mStartTime;
1499 mPool.onStatementExecuted(execTime);
Makoto Onukia761d2b2018-08-01 15:57:45 -07001500 return SQLiteDebug.Consts.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001501 execTime);
Jeff Browna9be4152012-01-18 15:29:57 -08001502 }
1503 return false;
1504 }
1505
1506 private void logOperationLocked(int cookie, String detail) {
1507 final Operation operation = getOperationLocked(cookie);
Makoto Onuki6cb20332018-08-07 15:57:13 -07001508 operation.mResultLong = mResultLong;
1509 operation.mResultString = mResultString;
Jeff Browne5360fb2011-10-31 17:48:13 -07001510 StringBuilder msg = new StringBuilder();
Makoto Onukia761d2b2018-08-01 15:57:45 -07001511 operation.describe(msg, true);
Jeff Browne5360fb2011-10-31 17:48:13 -07001512 if (detail != null) {
1513 msg.append(", ").append(detail);
1514 }
1515 Log.d(TAG, msg.toString());
1516 }
1517
Jeff Browna9be4152012-01-18 15:29:57 -08001518 private int newOperationCookieLocked(int index) {
1519 final int generation = mGeneration++;
1520 return generation << COOKIE_GENERATION_SHIFT | index;
1521 }
1522
1523 private Operation getOperationLocked(int cookie) {
1524 final int index = cookie & COOKIE_INDEX_MASK;
1525 final Operation operation = mOperations[index];
1526 return operation.mCookie == cookie ? operation : null;
1527 }
1528
Jeff Browne5360fb2011-10-31 17:48:13 -07001529 public String describeCurrentOperation() {
1530 synchronized (mOperations) {
1531 final Operation operation = mOperations[mIndex];
1532 if (operation != null && !operation.mFinished) {
1533 StringBuilder msg = new StringBuilder();
Jeff Browncefeb292013-05-01 15:28:37 -07001534 operation.describe(msg, false);
Jeff Browne5360fb2011-10-31 17:48:13 -07001535 return msg.toString();
1536 }
1537 return null;
1538 }
1539 }
1540
Makoto Onukia761d2b2018-08-01 15:57:45 -07001541 public void dump(Printer printer) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001542 synchronized (mOperations) {
1543 printer.println(" Most recently executed operations:");
1544 int index = mIndex;
1545 Operation operation = mOperations[index];
1546 if (operation != null) {
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001547 // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created,
1548 // and is relatively expensive to create during preloading. This method is only
1549 // used when dumping a connection, which is a rare (mainly error) case.
1550 SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Jeff Browne5360fb2011-10-31 17:48:13 -07001551 int n = 0;
1552 do {
1553 StringBuilder msg = new StringBuilder();
1554 msg.append(" ").append(n).append(": [");
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001555 String formattedStartTime = opDF.format(new Date(operation.mStartWallTime));
1556 msg.append(formattedStartTime);
Jeff Browne5360fb2011-10-31 17:48:13 -07001557 msg.append("] ");
Makoto Onukia761d2b2018-08-01 15:57:45 -07001558 operation.describe(msg, false); // Never dump bingargs in a bugreport
Jeff Browne5360fb2011-10-31 17:48:13 -07001559 printer.println(msg.toString());
1560
1561 if (index > 0) {
1562 index -= 1;
1563 } else {
1564 index = MAX_RECENT_OPERATIONS - 1;
1565 }
1566 n += 1;
1567 operation = mOperations[index];
1568 } while (operation != null && n < MAX_RECENT_OPERATIONS);
1569 } else {
1570 printer.println(" <none>");
1571 }
1572 }
1573 }
1574 }
1575
1576 private static final class Operation {
Greg Hackmanne12350f2014-12-01 14:31:21 -08001577 // Trim all SQL statements to 256 characters inside the trace marker.
1578 // This limit gives plenty of context while leaving space for other
1579 // entries in the trace buffer (and ensures atrace doesn't truncate the
1580 // marker for us, potentially losing metadata in the process).
1581 private static final int MAX_TRACE_METHOD_NAME_LEN = 256;
1582
Makoto Onuki34436702016-04-07 09:07:04 -07001583 public long mStartWallTime; // in System.currentTimeMillis()
1584 public long mStartTime; // in SystemClock.uptimeMillis();
1585 public long mEndTime; // in SystemClock.uptimeMillis();
Jeff Browne5360fb2011-10-31 17:48:13 -07001586 public String mKind;
1587 public String mSql;
1588 public ArrayList<Object> mBindArgs;
1589 public boolean mFinished;
1590 public Exception mException;
Jeff Browna9be4152012-01-18 15:29:57 -08001591 public int mCookie;
Makoto Onuki6cb20332018-08-07 15:57:13 -07001592 public String mPath;
1593 public long mResultLong; // MIN_VALUE means "value not set".
1594 public String mResultString;
Jeff Browne5360fb2011-10-31 17:48:13 -07001595
Makoto Onuki6cb20332018-08-07 15:57:13 -07001596 public void describe(StringBuilder msg, boolean allowDetailedLog) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001597 msg.append(mKind);
1598 if (mFinished) {
1599 msg.append(" took ").append(mEndTime - mStartTime).append("ms");
1600 } else {
Makoto Onuki34436702016-04-07 09:07:04 -07001601 msg.append(" started ").append(System.currentTimeMillis() - mStartWallTime)
Jeff Browne5360fb2011-10-31 17:48:13 -07001602 .append("ms ago");
1603 }
1604 msg.append(" - ").append(getStatus());
1605 if (mSql != null) {
1606 msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\"");
1607 }
Makoto Onuki6cb20332018-08-07 15:57:13 -07001608 final boolean dumpDetails = allowDetailedLog && Consts.DEBUG_LOG_DETAILED
1609 && mBindArgs != null && mBindArgs.size() != 0;
1610 if (dumpDetails) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001611 msg.append(", bindArgs=[");
1612 final int count = mBindArgs.size();
1613 for (int i = 0; i < count; i++) {
1614 final Object arg = mBindArgs.get(i);
1615 if (i != 0) {
1616 msg.append(", ");
1617 }
1618 if (arg == null) {
1619 msg.append("null");
1620 } else if (arg instanceof byte[]) {
1621 msg.append("<byte[]>");
1622 } else if (arg instanceof String) {
1623 msg.append("\"").append((String)arg).append("\"");
1624 } else {
1625 msg.append(arg);
1626 }
1627 }
1628 msg.append("]");
1629 }
Makoto Onuki6cb20332018-08-07 15:57:13 -07001630 msg.append(", path=").append(mPath);
Jeff Browne5360fb2011-10-31 17:48:13 -07001631 if (mException != null) {
1632 msg.append(", exception=\"").append(mException.getMessage()).append("\"");
1633 }
Makoto Onuki6cb20332018-08-07 15:57:13 -07001634 if (mResultLong != Long.MIN_VALUE) {
1635 msg.append(", result=").append(mResultLong);
1636 }
1637 if (mResultString != null) {
1638 msg.append(", result=\"").append(mResultString).append("\"");
1639 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001640 }
1641
1642 private String getStatus() {
1643 if (!mFinished) {
1644 return "running";
1645 }
1646 return mException != null ? "failed" : "succeeded";
1647 }
1648
Greg Hackmanne12350f2014-12-01 14:31:21 -08001649 private String getTraceMethodName() {
1650 String methodName = mKind + " " + mSql;
1651 if (methodName.length() > MAX_TRACE_METHOD_NAME_LEN)
1652 return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN);
1653 return methodName;
1654 }
1655
Jeff Browne5360fb2011-10-31 17:48:13 -07001656 }
1657}