blob: 101fb821f4ce9fcac12a95a725c9c4c82b9ff2d5 [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
Jeff Browne5360fb2011-10-31 17:48:13 -070035import java.text.SimpleDateFormat;
36import java.util.ArrayList;
Elliott Hughesc00df6d2013-05-06 10:53:28 -070037import java.util.Date;
Jeff Browne5360fb2011-10-31 17:48:13 -070038import java.util.Map;
Fyodor Kupolovd3b0c7e2017-06-20 11:51:55 -070039
Jeff Browne5360fb2011-10-31 17:48:13 -070040
41/**
42 * Represents a SQLite database connection.
43 * Each connection wraps an instance of a native <code>sqlite3</code> object.
44 * <p>
45 * When database connection pooling is enabled, there can be multiple active
46 * connections to the same database. Otherwise there is typically only one
47 * connection per database.
48 * </p><p>
49 * When the SQLite WAL feature is enabled, multiple readers and one writer
50 * can concurrently access the database. Without WAL, readers and writers
51 * are mutually exclusive.
52 * </p>
53 *
54 * <h2>Ownership and concurrency guarantees</h2>
55 * <p>
56 * Connection objects are not thread-safe. They are acquired as needed to
57 * perform a database operation and are then returned to the pool. At any
58 * given time, a connection is either owned and used by a {@link SQLiteSession}
59 * object or the {@link SQLiteConnectionPool}. Those classes are
60 * responsible for serializing operations to guard against concurrent
61 * use of a connection.
62 * </p><p>
63 * The guarantee of having a single owner allows this class to be implemented
64 * without locks and greatly simplifies resource management.
65 * </p>
66 *
67 * <h2>Encapsulation guarantees</h2>
68 * <p>
69 * The connection object object owns *all* of the SQLite related native
70 * objects that are associated with the connection. What's more, there are
71 * no other objects in the system that are capable of obtaining handles to
72 * those native objects. Consequently, when the connection is closed, we do
73 * not have to worry about what other components might have references to
74 * its associated SQLite state -- there are none.
75 * </p><p>
76 * Encapsulation is what ensures that the connection object's
77 * lifecycle does not become a tortured mess of finalizers and reference
78 * queues.
79 * </p>
80 *
Jeff Browna9be4152012-01-18 15:29:57 -080081 * <h2>Reentrance</h2>
82 * <p>
83 * This class must tolerate reentrant execution of SQLite operations because
84 * triggers may call custom SQLite functions that perform additional queries.
85 * </p>
86 *
Jeff Browne5360fb2011-10-31 17:48:13 -070087 * @hide
88 */
Jeff Brown4c1241d2012-02-02 17:05:00 -080089public final class SQLiteConnection implements CancellationSignal.OnCancelListener {
Jeff Browne5360fb2011-10-31 17:48:13 -070090 private static final String TAG = "SQLiteConnection";
Jeff Brown2a293b62012-01-19 14:02:22 -080091 private static final boolean DEBUG = false;
Jeff Browne5360fb2011-10-31 17:48:13 -070092
93 private static final String[] EMPTY_STRING_ARRAY = new String[0];
94 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
95
Jeff Browne5360fb2011-10-31 17:48:13 -070096 private final CloseGuard mCloseGuard = CloseGuard.get();
97
98 private final SQLiteConnectionPool mPool;
99 private final SQLiteDatabaseConfiguration mConfiguration;
100 private final int mConnectionId;
101 private final boolean mIsPrimaryConnection;
Jeff Brown1d9f7422012-03-15 14:32:32 -0700102 private final boolean mIsReadOnlyConnection;
Jeff Browne5360fb2011-10-31 17:48:13 -0700103 private final PreparedStatementCache mPreparedStatementCache;
104 private PreparedStatement mPreparedStatementPool;
105
106 // The recent operations log.
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -0700107 private final OperationLog mRecentOperations;
Jeff Browne5360fb2011-10-31 17:48:13 -0700108
109 // The native SQLiteConnection pointer. (FOR INTERNAL USE ONLY)
Ashok Bhat738702d2014-01-02 13:42:56 +0000110 private long mConnectionPtr;
Jeff Browne5360fb2011-10-31 17:48:13 -0700111
112 private boolean mOnlyAllowReadOnlyOperations;
113
Jeff Brown4c1241d2012-02-02 17:05:00 -0800114 // The number of times attachCancellationSignal has been called.
Jeff Brown1d9f7422012-03-15 14:32:32 -0700115 // Because SQLite statement execution can be reentrant, we keep track of how many
Jeff Brown4c1241d2012-02-02 17:05:00 -0800116 // times we have attempted to attach a cancellation signal to the connection so that
Jeff Brown75ea64f2012-01-25 19:37:13 -0800117 // we can ensure that we detach the signal at the right time.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800118 private int mCancellationSignalAttachCount;
Jeff Brown75ea64f2012-01-25 19:37:13 -0800119
Ashok Bhat738702d2014-01-02 13:42:56 +0000120 private static native long nativeOpen(String path, int openFlags, String label,
Fyodor Kupolovd3b0c7e2017-06-20 11:51:55 -0700121 boolean enableTrace, boolean enableProfile, int lookasideSlotSize,
122 int lookasideSlotCount);
Ashok Bhat738702d2014-01-02 13:42:56 +0000123 private static native void nativeClose(long connectionPtr);
124 private static native void nativeRegisterCustomFunction(long connectionPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700125 SQLiteCustomFunction function);
Ashok Bhat738702d2014-01-02 13:42:56 +0000126 private static native void nativeRegisterLocalizedCollators(long connectionPtr, String locale);
127 private static native long nativePrepareStatement(long connectionPtr, String sql);
128 private static native void nativeFinalizeStatement(long connectionPtr, long statementPtr);
129 private static native int nativeGetParameterCount(long connectionPtr, long statementPtr);
130 private static native boolean nativeIsReadOnly(long connectionPtr, long statementPtr);
131 private static native int nativeGetColumnCount(long connectionPtr, long statementPtr);
132 private static native String nativeGetColumnName(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700133 int index);
Ashok Bhat738702d2014-01-02 13:42:56 +0000134 private static native void nativeBindNull(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 nativeBindLong(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700137 int index, long value);
Ashok Bhat738702d2014-01-02 13:42:56 +0000138 private static native void nativeBindDouble(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700139 int index, double value);
Ashok Bhat738702d2014-01-02 13:42:56 +0000140 private static native void nativeBindString(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700141 int index, String value);
Ashok Bhat738702d2014-01-02 13:42:56 +0000142 private static native void nativeBindBlob(long connectionPtr, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700143 int index, byte[] value);
144 private static native void nativeResetStatementAndClearBindings(
Ashok Bhat738702d2014-01-02 13:42:56 +0000145 long connectionPtr, long statementPtr);
146 private static native void nativeExecute(long connectionPtr, long statementPtr);
147 private static native long nativeExecuteForLong(long connectionPtr, long statementPtr);
148 private static native String nativeExecuteForString(long connectionPtr, long statementPtr);
Jeff Browne5360fb2011-10-31 17:48:13 -0700149 private static native int nativeExecuteForBlobFileDescriptor(
Ashok Bhat738702d2014-01-02 13:42:56 +0000150 long connectionPtr, long statementPtr);
151 private static native int nativeExecuteForChangedRowCount(long connectionPtr, long statementPtr);
Jeff Browne5360fb2011-10-31 17:48:13 -0700152 private static native long nativeExecuteForLastInsertedRowId(
Ashok Bhat738702d2014-01-02 13:42:56 +0000153 long connectionPtr, long statementPtr);
Jeff Browne5360fb2011-10-31 17:48:13 -0700154 private static native long nativeExecuteForCursorWindow(
Ashok Bhat738702d2014-01-02 13:42:56 +0000155 long connectionPtr, long statementPtr, long windowPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -0700156 int startPos, int requiredPos, boolean countAllRows);
Ashok Bhat738702d2014-01-02 13:42:56 +0000157 private static native int nativeGetDbLookaside(long connectionPtr);
158 private static native void nativeCancel(long connectionPtr);
159 private static native void nativeResetCancel(long connectionPtr, boolean cancelable);
Jeff Browne5360fb2011-10-31 17:48:13 -0700160
161 private SQLiteConnection(SQLiteConnectionPool pool,
162 SQLiteDatabaseConfiguration configuration,
163 int connectionId, boolean primaryConnection) {
164 mPool = pool;
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -0700165 mRecentOperations = new OperationLog(mPool);
Jeff Browne5360fb2011-10-31 17:48:13 -0700166 mConfiguration = new SQLiteDatabaseConfiguration(configuration);
167 mConnectionId = connectionId;
168 mIsPrimaryConnection = primaryConnection;
Jeff Brown1d9f7422012-03-15 14:32:32 -0700169 mIsReadOnlyConnection = (configuration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
Jeff Browne5360fb2011-10-31 17:48:13 -0700170 mPreparedStatementCache = new PreparedStatementCache(
171 mConfiguration.maxSqlCacheSize);
172 mCloseGuard.open("close");
173 }
174
175 @Override
176 protected void finalize() throws Throwable {
177 try {
178 if (mPool != null && mConnectionPtr != 0) {
179 mPool.onConnectionLeaked();
180 }
181
182 dispose(true);
183 } finally {
184 super.finalize();
185 }
186 }
187
188 // Called by SQLiteConnectionPool only.
189 static SQLiteConnection open(SQLiteConnectionPool pool,
190 SQLiteDatabaseConfiguration configuration,
191 int connectionId, boolean primaryConnection) {
192 SQLiteConnection connection = new SQLiteConnection(pool, configuration,
193 connectionId, primaryConnection);
194 try {
195 connection.open();
196 return connection;
197 } catch (SQLiteException ex) {
198 connection.dispose(false);
199 throw ex;
200 }
201 }
202
203 // Called by SQLiteConnectionPool only.
204 // Closes the database closes and releases all of its associated resources.
205 // Do not call methods on the connection after it is closed. It will probably crash.
206 void close() {
207 dispose(false);
208 }
209
210 private void open() {
Jeff Browne5360fb2011-10-31 17:48:13 -0700211 mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
212 mConfiguration.label,
Fyodor Kupolovd3b0c7e2017-06-20 11:51:55 -0700213 SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME,
214 mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount);
Jeff Brown5936ff02012-02-29 21:03:20 -0800215 setPageSize();
Jeff Brown96496ad2012-03-23 14:38:06 -0700216 setForeignKeyModeFromConfiguration();
Jeff Brownd67c8c62012-03-22 14:15:01 -0700217 setWalModeFromConfiguration();
Jeff Brown8dc3cc22012-03-02 10:33:52 -0800218 setJournalSizeLimit();
219 setAutoCheckpointInterval();
Jeff Browne5360fb2011-10-31 17:48:13 -0700220 setLocaleFromConfiguration();
Niklas Brunlid27a65242012-09-25 12:55:34 +0200221
222 // Register custom functions.
223 final int functionCount = mConfiguration.customFunctions.size();
224 for (int i = 0; i < functionCount; i++) {
225 SQLiteCustomFunction function = mConfiguration.customFunctions.get(i);
226 nativeRegisterCustomFunction(mConnectionPtr, function);
227 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700228 }
229
230 private void dispose(boolean finalized) {
231 if (mCloseGuard != null) {
232 if (finalized) {
233 mCloseGuard.warnIfOpen();
234 }
235 mCloseGuard.close();
236 }
237
238 if (mConnectionPtr != 0) {
Jeff Browna9be4152012-01-18 15:29:57 -0800239 final int cookie = mRecentOperations.beginOperation("close", null, null);
Jeff Browne5360fb2011-10-31 17:48:13 -0700240 try {
241 mPreparedStatementCache.evictAll();
242 nativeClose(mConnectionPtr);
243 mConnectionPtr = 0;
244 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800245 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700246 }
247 }
248 }
249
Jeff Brown5936ff02012-02-29 21:03:20 -0800250 private void setPageSize() {
Jeff Brown1d9f7422012-03-15 14:32:32 -0700251 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
252 final long newValue = SQLiteGlobal.getDefaultPageSize();
253 long value = executeForLong("PRAGMA page_size", null, null);
254 if (value != newValue) {
255 execute("PRAGMA page_size=" + newValue, null, null);
256 }
Jeff Brown5936ff02012-02-29 21:03:20 -0800257 }
258 }
259
260 private void setAutoCheckpointInterval() {
Jeff Brown1d9f7422012-03-15 14:32:32 -0700261 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
262 final long newValue = SQLiteGlobal.getWALAutoCheckpoint();
263 long value = executeForLong("PRAGMA wal_autocheckpoint", null, null);
264 if (value != newValue) {
265 executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null);
266 }
Jeff Brown5936ff02012-02-29 21:03:20 -0800267 }
268 }
269
270 private void setJournalSizeLimit() {
Jeff Brown1d9f7422012-03-15 14:32:32 -0700271 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
272 final long newValue = SQLiteGlobal.getJournalSizeLimit();
273 long value = executeForLong("PRAGMA journal_size_limit", null, null);
274 if (value != newValue) {
275 executeForLong("PRAGMA journal_size_limit=" + newValue, null, null);
276 }
Jeff Brown5936ff02012-02-29 21:03:20 -0800277 }
278 }
279
Jeff Brown96496ad2012-03-23 14:38:06 -0700280 private void setForeignKeyModeFromConfiguration() {
281 if (!mIsReadOnlyConnection) {
282 final long newValue = mConfiguration.foreignKeyConstraintsEnabled ? 1 : 0;
283 long value = executeForLong("PRAGMA foreign_keys", null, null);
284 if (value != newValue) {
285 execute("PRAGMA foreign_keys=" + newValue, null, null);
286 }
287 }
288 }
289
Jeff Brownd67c8c62012-03-22 14:15:01 -0700290 private void setWalModeFromConfiguration() {
Jeff Brown1d9f7422012-03-15 14:32:32 -0700291 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
Fyodor Kupolov13a4b372017-11-07 18:45:35 -0800292 final boolean walEnabled =
Fyodor Kupolov5bd43ad2017-10-25 16:09:35 -0700293 (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
Fyodor Kupolov13a4b372017-11-07 18:45:35 -0800294 // Use compatibility WAL unless an app explicitly set journal/synchronous mode
Fyodor Kupolov692573b2018-03-06 12:34:36 -0800295 // or DISABLE_COMPATIBILITY_WAL flag is set
Fyodor Kupolov681ec312018-03-20 18:48:22 -0700296 final boolean useCompatibilityWal = mConfiguration.useCompatibilityWal();
Fyodor Kupolov13a4b372017-11-07 18:45:35 -0800297 if (walEnabled || useCompatibilityWal) {
Jeff Brownd67c8c62012-03-22 14:15:01 -0700298 setJournalMode("WAL");
Fyodor Kupolov8ba20892018-06-01 12:11:42 -0700299 if (mConfiguration.syncMode != null) {
300 setSyncMode(mConfiguration.syncMode);
301 } else if (useCompatibilityWal && SQLiteCompatibilityWalFlags.areFlagsSet()) {
Fyodor Kupolovee90c032017-12-12 11:52:57 -0800302 setSyncMode(SQLiteCompatibilityWalFlags.getWALSyncMode());
303 } else {
304 setSyncMode(SQLiteGlobal.getWALSyncMode());
305 }
Jeff Brownd67c8c62012-03-22 14:15:01 -0700306 } else {
Fyodor Kupolov13a4b372017-11-07 18:45:35 -0800307 setJournalMode(mConfiguration.journalMode == null
308 ? SQLiteGlobal.getDefaultJournalMode() : mConfiguration.journalMode);
309 setSyncMode(mConfiguration.syncMode == null
310 ? SQLiteGlobal.getDefaultSyncMode() : mConfiguration.syncMode);
Jeff Brown1d9f7422012-03-15 14:32:32 -0700311 }
Jeff Brown8dc3cc22012-03-02 10:33:52 -0800312 }
313 }
314
Jeff Brownd67c8c62012-03-22 14:15:01 -0700315 private void setSyncMode(String newValue) {
316 String value = executeForString("PRAGMA synchronous", null, null);
317 if (!canonicalizeSyncMode(value).equalsIgnoreCase(
318 canonicalizeSyncMode(newValue))) {
319 execute("PRAGMA synchronous=" + newValue, null, null);
320 }
321 }
322
323 private static String canonicalizeSyncMode(String value) {
Fyodor Kupolov13a4b372017-11-07 18:45:35 -0800324 switch (value) {
325 case "0": return "OFF";
326 case "1": return "NORMAL";
327 case "2": return "FULL";
Jeff Brownd67c8c62012-03-22 14:15:01 -0700328 }
329 return value;
330 }
331
332 private void setJournalMode(String newValue) {
333 String value = executeForString("PRAGMA journal_mode", null, null);
334 if (!value.equalsIgnoreCase(newValue)) {
335 try {
336 String result = executeForString("PRAGMA journal_mode=" + newValue, null, null);
337 if (result.equalsIgnoreCase(newValue)) {
338 return;
Jeff Brown1d9f7422012-03-15 14:32:32 -0700339 }
Jeff Brownd67c8c62012-03-22 14:15:01 -0700340 // PRAGMA journal_mode silently fails and returns the original journal
341 // mode in some cases if the journal mode could not be changed.
342 } catch (SQLiteDatabaseLockedException ex) {
343 // This error (SQLITE_BUSY) occurs if one connection has the database
344 // open in WAL mode and another tries to change it to non-WAL.
Jeff Brown5936ff02012-02-29 21:03:20 -0800345 }
Jeff Brownd67c8c62012-03-22 14:15:01 -0700346 // Because we always disable WAL mode when a database is first opened
347 // (even if we intend to re-enable it), we can encounter problems if
348 // there is another open connection to the database somewhere.
349 // This can happen for a variety of reasons such as an application opening
350 // the same database in multiple processes at the same time or if there is a
351 // crashing content provider service that the ActivityManager has
352 // removed from its registry but whose process hasn't quite died yet
353 // by the time it is restarted in a new process.
354 //
355 // If we don't change the journal mode, nothing really bad happens.
356 // In the worst case, an application that enables WAL might not actually
357 // get it, although it can still use connection pooling.
358 Log.w(TAG, "Could not change the database journal mode of '"
359 + mConfiguration.label + "' from '" + value + "' to '" + newValue
360 + "' because the database is locked. This usually means that "
361 + "there are other open connections to the database which prevents "
362 + "the database from enabling or disabling write-ahead logging mode. "
363 + "Proceeding without changing the journal mode.");
Jeff Brown5936ff02012-02-29 21:03:20 -0800364 }
365 }
366
Jeff Browne5360fb2011-10-31 17:48:13 -0700367 private void setLocaleFromConfiguration() {
Jeff Brown1d9f7422012-03-15 14:32:32 -0700368 if ((mConfiguration.openFlags & SQLiteDatabase.NO_LOCALIZED_COLLATORS) != 0) {
369 return;
370 }
371
372 // Register the localized collators.
373 final String newLocale = mConfiguration.locale.toString();
374 nativeRegisterLocalizedCollators(mConnectionPtr, newLocale);
375
376 // If the database is read-only, we cannot modify the android metadata table
377 // or existing indexes.
378 if (mIsReadOnlyConnection) {
379 return;
380 }
381
382 try {
383 // Ensure the android metadata table exists.
384 execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null);
385
386 // Check whether the locale was actually changed.
387 final String oldLocale = executeForString("SELECT locale FROM android_metadata "
388 + "UNION SELECT NULL ORDER BY locale DESC LIMIT 1", null, null);
389 if (oldLocale != null && oldLocale.equals(newLocale)) {
390 return;
391 }
392
393 // Go ahead and update the indexes using the new locale.
394 execute("BEGIN", null, null);
395 boolean success = false;
396 try {
397 execute("DELETE FROM android_metadata", null, null);
398 execute("INSERT INTO android_metadata (locale) VALUES(?)",
399 new Object[] { newLocale }, null);
400 execute("REINDEX LOCALIZED", null, null);
401 success = true;
402 } finally {
403 execute(success ? "COMMIT" : "ROLLBACK", null, null);
404 }
405 } catch (RuntimeException ex) {
406 throw new SQLiteException("Failed to change locale for db '" + mConfiguration.label
407 + "' to '" + newLocale + "'.", ex);
408 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700409 }
410
411 // Called by SQLiteConnectionPool only.
412 void reconfigure(SQLiteDatabaseConfiguration configuration) {
Jeff Brown76070d1692012-04-19 11:30:33 -0700413 mOnlyAllowReadOnlyOperations = false;
414
Jeff Browne5360fb2011-10-31 17:48:13 -0700415 // Register custom functions.
416 final int functionCount = configuration.customFunctions.size();
417 for (int i = 0; i < functionCount; i++) {
418 SQLiteCustomFunction function = configuration.customFunctions.get(i);
419 if (!mConfiguration.customFunctions.contains(function)) {
420 nativeRegisterCustomFunction(mConnectionPtr, function);
421 }
422 }
423
Jeff Brown5936ff02012-02-29 21:03:20 -0800424 // Remember what changed.
Jeff Brown96496ad2012-03-23 14:38:06 -0700425 boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
426 != mConfiguration.foreignKeyConstraintsEnabled;
Jeff Brown47847f32012-03-22 19:13:11 -0700427 boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
Fyodor Kupolov692573b2018-03-06 12:34:36 -0800428 & (SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING
429 | SQLiteDatabase.DISABLE_COMPATIBILITY_WAL)) != 0;
Jeff Browne5360fb2011-10-31 17:48:13 -0700430 boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
431
432 // Update configuration parameters.
433 mConfiguration.updateParametersFrom(configuration);
434
435 // Update prepared statement cache size.
436 mPreparedStatementCache.resize(configuration.maxSqlCacheSize);
437
Jeff Brown96496ad2012-03-23 14:38:06 -0700438 // Update foreign key mode.
439 if (foreignKeyModeChanged) {
440 setForeignKeyModeFromConfiguration();
441 }
442
Jeff Brownd67c8c62012-03-22 14:15:01 -0700443 // Update WAL.
444 if (walModeChanged) {
445 setWalModeFromConfiguration();
Jeff Brown5936ff02012-02-29 21:03:20 -0800446 }
447
Jeff Browne5360fb2011-10-31 17:48:13 -0700448 // Update locale.
449 if (localeChanged) {
450 setLocaleFromConfiguration();
451 }
452 }
453
454 // Called by SQLiteConnectionPool only.
455 // When set to true, executing write operations will throw SQLiteException.
456 // Preparing statements that might write is ok, just don't execute them.
457 void setOnlyAllowReadOnlyOperations(boolean readOnly) {
458 mOnlyAllowReadOnlyOperations = readOnly;
459 }
460
461 // Called by SQLiteConnectionPool only.
462 // Returns true if the prepared statement cache contains the specified SQL.
463 boolean isPreparedStatementInCache(String sql) {
464 return mPreparedStatementCache.get(sql) != null;
465 }
466
467 /**
468 * Gets the unique id of this connection.
469 * @return The connection id.
470 */
471 public int getConnectionId() {
472 return mConnectionId;
473 }
474
475 /**
476 * Returns true if this is the primary database connection.
477 * @return True if this is the primary database connection.
478 */
479 public boolean isPrimaryConnection() {
480 return mIsPrimaryConnection;
481 }
482
483 /**
484 * Prepares a statement for execution but does not bind its parameters or execute it.
485 * <p>
486 * This method can be used to check for syntax errors during compilation
487 * prior to execution of the statement. If the {@code outStatementInfo} argument
488 * is not null, the provided {@link SQLiteStatementInfo} object is populated
489 * with information about the statement.
490 * </p><p>
491 * A prepared statement makes no reference to the arguments that may eventually
492 * be bound to it, consequently it it possible to cache certain prepared statements
493 * such as SELECT or INSERT/UPDATE statements. If the statement is cacheable,
494 * then it will be stored in the cache for later.
495 * </p><p>
496 * To take advantage of this behavior as an optimization, the connection pool
497 * provides a method to acquire a connection that already has a given SQL statement
498 * in its prepared statement cache so that it is ready for execution.
499 * </p>
500 *
501 * @param sql The SQL statement to prepare.
502 * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate
503 * with information about the statement, or null if none.
504 *
505 * @throws SQLiteException if an error occurs, such as a syntax error.
506 */
507 public void prepare(String sql, SQLiteStatementInfo outStatementInfo) {
508 if (sql == null) {
509 throw new IllegalArgumentException("sql must not be null.");
510 }
511
Jeff Browna9be4152012-01-18 15:29:57 -0800512 final int cookie = mRecentOperations.beginOperation("prepare", sql, null);
Jeff Browne5360fb2011-10-31 17:48:13 -0700513 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800514 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700515 try {
516 if (outStatementInfo != null) {
517 outStatementInfo.numParameters = statement.mNumParameters;
518 outStatementInfo.readOnly = statement.mReadOnly;
519
520 final int columnCount = nativeGetColumnCount(
521 mConnectionPtr, statement.mStatementPtr);
522 if (columnCount == 0) {
523 outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
524 } else {
525 outStatementInfo.columnNames = new String[columnCount];
526 for (int i = 0; i < columnCount; i++) {
527 outStatementInfo.columnNames[i] = nativeGetColumnName(
528 mConnectionPtr, statement.mStatementPtr, i);
529 }
530 }
531 }
532 } finally {
533 releasePreparedStatement(statement);
534 }
535 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800536 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700537 throw ex;
538 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800539 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700540 }
541 }
542
543 /**
544 * Executes a statement that does not return a result.
545 *
546 * @param sql The SQL statement to execute.
547 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800548 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700549 *
550 * @throws SQLiteException if an error occurs, such as a syntax error
551 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800552 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700553 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800554 public void execute(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800555 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700556 if (sql == null) {
557 throw new IllegalArgumentException("sql must not be null.");
558 }
559
Jeff Browna9be4152012-01-18 15:29:57 -0800560 final int cookie = mRecentOperations.beginOperation("execute", sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700561 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800562 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700563 try {
564 throwIfStatementForbidden(statement);
565 bindArguments(statement, bindArgs);
566 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800567 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800568 try {
569 nativeExecute(mConnectionPtr, statement.mStatementPtr);
570 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800571 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800572 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700573 } finally {
574 releasePreparedStatement(statement);
575 }
576 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800577 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700578 throw ex;
579 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800580 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700581 }
582 }
583
584 /**
585 * Executes a statement that returns a single <code>long</code> result.
586 *
587 * @param sql The SQL statement to execute.
588 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800589 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700590 * @return The value of the first column in the first row of the result set
591 * as a <code>long</code>, or zero if none.
592 *
593 * @throws SQLiteException if an error occurs, such as a syntax error
594 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800595 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700596 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800597 public long executeForLong(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800598 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700599 if (sql == null) {
600 throw new IllegalArgumentException("sql must not be null.");
601 }
602
Jeff Browna9be4152012-01-18 15:29:57 -0800603 final int cookie = mRecentOperations.beginOperation("executeForLong", sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700604 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800605 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700606 try {
607 throwIfStatementForbidden(statement);
608 bindArguments(statement, bindArgs);
609 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800610 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800611 try {
612 return nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr);
613 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800614 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800615 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700616 } finally {
617 releasePreparedStatement(statement);
618 }
619 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800620 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700621 throw ex;
622 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800623 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700624 }
625 }
626
627 /**
628 * Executes a statement that returns a single {@link String} result.
629 *
630 * @param sql The SQL statement to execute.
631 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800632 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700633 * @return The value of the first column in the first row of the result set
634 * as a <code>String</code>, or null if none.
635 *
636 * @throws SQLiteException if an error occurs, such as a syntax error
637 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800638 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700639 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800640 public String executeForString(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800641 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700642 if (sql == null) {
643 throw new IllegalArgumentException("sql must not be null.");
644 }
645
Jeff Browna9be4152012-01-18 15:29:57 -0800646 final int cookie = mRecentOperations.beginOperation("executeForString", sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700647 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800648 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700649 try {
650 throwIfStatementForbidden(statement);
651 bindArguments(statement, bindArgs);
652 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800653 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800654 try {
655 return nativeExecuteForString(mConnectionPtr, statement.mStatementPtr);
656 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800657 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800658 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700659 } finally {
660 releasePreparedStatement(statement);
661 }
662 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800663 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700664 throw ex;
665 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800666 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700667 }
668 }
669
670 /**
671 * Executes a statement that returns a single BLOB result as a
672 * file descriptor to a shared memory region.
673 *
674 * @param sql The SQL statement to execute.
675 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800676 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700677 * @return The file descriptor for a shared memory region that contains
678 * the value of the first column in the first row of the result set as a BLOB,
679 * or null if none.
680 *
681 * @throws SQLiteException if an error occurs, such as a syntax error
682 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800683 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700684 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800685 public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800686 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700687 if (sql == null) {
688 throw new IllegalArgumentException("sql must not be null.");
689 }
690
Jeff Browna9be4152012-01-18 15:29:57 -0800691 final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor",
692 sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700693 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800694 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700695 try {
696 throwIfStatementForbidden(statement);
697 bindArguments(statement, bindArgs);
698 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800699 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800700 try {
701 int fd = nativeExecuteForBlobFileDescriptor(
702 mConnectionPtr, statement.mStatementPtr);
703 return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null;
704 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800705 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800706 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700707 } finally {
708 releasePreparedStatement(statement);
709 }
710 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800711 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700712 throw ex;
713 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800714 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700715 }
716 }
717
718 /**
719 * Executes a statement that returns a count of the number of rows
720 * that were changed. Use for UPDATE or DELETE SQL statements.
721 *
722 * @param sql The SQL statement to execute.
723 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800724 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700725 * @return The number of rows that were changed.
726 *
727 * @throws SQLiteException if an error occurs, such as a syntax error
728 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800729 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700730 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800731 public int executeForChangedRowCount(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800732 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700733 if (sql == null) {
734 throw new IllegalArgumentException("sql must not be null.");
735 }
736
Makoto Onuki72eebb62012-04-16 13:46:15 -0700737 int changedRows = 0;
Jeff Browna9be4152012-01-18 15:29:57 -0800738 final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount",
739 sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700740 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800741 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700742 try {
743 throwIfStatementForbidden(statement);
744 bindArguments(statement, bindArgs);
745 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800746 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800747 try {
Makoto Onuki72eebb62012-04-16 13:46:15 -0700748 changedRows = nativeExecuteForChangedRowCount(
Jeff Brown75ea64f2012-01-25 19:37:13 -0800749 mConnectionPtr, statement.mStatementPtr);
Makoto Onuki72eebb62012-04-16 13:46:15 -0700750 return changedRows;
Jeff Brown75ea64f2012-01-25 19:37:13 -0800751 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800752 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800753 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700754 } finally {
755 releasePreparedStatement(statement);
756 }
757 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800758 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700759 throw ex;
760 } finally {
Makoto Onuki72eebb62012-04-16 13:46:15 -0700761 if (mRecentOperations.endOperationDeferLog(cookie)) {
762 mRecentOperations.logOperation(cookie, "changedRows=" + changedRows);
763 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700764 }
765 }
766
767 /**
768 * Executes a statement that returns the row id of the last row inserted
769 * by the statement. Use for INSERT SQL statements.
770 *
771 * @param sql The SQL statement to execute.
772 * @param bindArgs The arguments to bind, or null if none.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800773 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700774 * @return The row id of the last row that was inserted, or 0 if none.
775 *
776 * @throws SQLiteException if an error occurs, such as a syntax error
777 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800778 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700779 */
Jeff Brown75ea64f2012-01-25 19:37:13 -0800780 public long executeForLastInsertedRowId(String sql, Object[] bindArgs,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800781 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700782 if (sql == null) {
783 throw new IllegalArgumentException("sql must not be null.");
784 }
785
Jeff Browna9be4152012-01-18 15:29:57 -0800786 final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId",
787 sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700788 try {
Jeff Browna9be4152012-01-18 15:29:57 -0800789 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700790 try {
791 throwIfStatementForbidden(statement);
792 bindArguments(statement, bindArgs);
793 applyBlockGuardPolicy(statement);
Jeff Brown4c1241d2012-02-02 17:05:00 -0800794 attachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800795 try {
796 return nativeExecuteForLastInsertedRowId(
797 mConnectionPtr, statement.mStatementPtr);
798 } finally {
Jeff Brown4c1241d2012-02-02 17:05:00 -0800799 detachCancellationSignal(cancellationSignal);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800800 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700801 } finally {
802 releasePreparedStatement(statement);
803 }
804 } catch (RuntimeException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800805 mRecentOperations.failOperation(cookie, ex);
Jeff Browne5360fb2011-10-31 17:48:13 -0700806 throw ex;
807 } finally {
Jeff Browna9be4152012-01-18 15:29:57 -0800808 mRecentOperations.endOperation(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -0700809 }
810 }
811
812 /**
813 * Executes a statement and populates the specified {@link CursorWindow}
814 * with a range of results. Returns the number of rows that were counted
815 * during query execution.
816 *
817 * @param sql The SQL statement to execute.
818 * @param bindArgs The arguments to bind, or null if none.
819 * @param window The cursor window to clear and fill.
820 * @param startPos The start position for filling the window.
821 * @param requiredPos The position of a row that MUST be in the window.
822 * If it won't fit, then the query should discard part of what it filled
823 * so that it does. Must be greater than or equal to <code>startPos</code>.
824 * @param countAllRows True to count all rows that the query would return
825 * regagless of whether they fit in the window.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800826 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
Jeff Browne5360fb2011-10-31 17:48:13 -0700827 * @return The number of rows that were counted during query execution. Might
828 * not be all rows in the result set unless <code>countAllRows</code> is true.
829 *
830 * @throws SQLiteException if an error occurs, such as a syntax error
831 * or invalid number of bind arguments.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800832 * @throws OperationCanceledException if the operation was canceled.
Jeff Browne5360fb2011-10-31 17:48:13 -0700833 */
834 public int executeForCursorWindow(String sql, Object[] bindArgs,
Jeff Brown75ea64f2012-01-25 19:37:13 -0800835 CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
Jeff Brown4c1241d2012-02-02 17:05:00 -0800836 CancellationSignal cancellationSignal) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700837 if (sql == null) {
838 throw new IllegalArgumentException("sql must not be null.");
839 }
840 if (window == null) {
841 throw new IllegalArgumentException("window must not be null.");
842 }
843
Jeff Brown03bd3022012-03-06 13:48:56 -0800844 window.acquireReference();
Jeff Browne5360fb2011-10-31 17:48:13 -0700845 try {
Jeff Brown03bd3022012-03-06 13:48:56 -0800846 int actualPos = -1;
847 int countedRows = -1;
848 int filledRows = -1;
849 final int cookie = mRecentOperations.beginOperation("executeForCursorWindow",
850 sql, bindArgs);
Jeff Browne5360fb2011-10-31 17:48:13 -0700851 try {
Jeff Brown03bd3022012-03-06 13:48:56 -0800852 final PreparedStatement statement = acquirePreparedStatement(sql);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800853 try {
Jeff Brown03bd3022012-03-06 13:48:56 -0800854 throwIfStatementForbidden(statement);
855 bindArguments(statement, bindArgs);
856 applyBlockGuardPolicy(statement);
857 attachCancellationSignal(cancellationSignal);
858 try {
859 final long result = nativeExecuteForCursorWindow(
860 mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
861 startPos, requiredPos, countAllRows);
862 actualPos = (int)(result >> 32);
863 countedRows = (int)result;
864 filledRows = window.getNumRows();
865 window.setStartPosition(actualPos);
866 return countedRows;
867 } finally {
868 detachCancellationSignal(cancellationSignal);
869 }
Jeff Brown75ea64f2012-01-25 19:37:13 -0800870 } finally {
Jeff Brown03bd3022012-03-06 13:48:56 -0800871 releasePreparedStatement(statement);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800872 }
Jeff Brown03bd3022012-03-06 13:48:56 -0800873 } catch (RuntimeException ex) {
874 mRecentOperations.failOperation(cookie, ex);
875 throw ex;
Jeff Browne5360fb2011-10-31 17:48:13 -0700876 } finally {
Jeff Brown03bd3022012-03-06 13:48:56 -0800877 if (mRecentOperations.endOperationDeferLog(cookie)) {
878 mRecentOperations.logOperation(cookie, "window='" + window
879 + "', startPos=" + startPos
880 + ", actualPos=" + actualPos
881 + ", filledRows=" + filledRows
882 + ", countedRows=" + countedRows);
883 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700884 }
Jeff Browne5360fb2011-10-31 17:48:13 -0700885 } finally {
Jeff Brown03bd3022012-03-06 13:48:56 -0800886 window.releaseReference();
Jeff Browne5360fb2011-10-31 17:48:13 -0700887 }
888 }
889
890 private PreparedStatement acquirePreparedStatement(String sql) {
891 PreparedStatement statement = mPreparedStatementCache.get(sql);
Jeff Browna9be4152012-01-18 15:29:57 -0800892 boolean skipCache = false;
Jeff Browne5360fb2011-10-31 17:48:13 -0700893 if (statement != null) {
Jeff Browna9be4152012-01-18 15:29:57 -0800894 if (!statement.mInUse) {
895 return statement;
896 }
897 // The statement is already in the cache but is in use (this statement appears
898 // to be not only re-entrant but recursive!). So prepare a new copy of the
899 // statement but do not cache it.
900 skipCache = true;
Jeff Browne5360fb2011-10-31 17:48:13 -0700901 }
902
Ashok Bhat738702d2014-01-02 13:42:56 +0000903 final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
Jeff Browne5360fb2011-10-31 17:48:13 -0700904 try {
905 final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
906 final int type = DatabaseUtils.getSqlStatementType(sql);
907 final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
908 statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly);
Jeff Browna9be4152012-01-18 15:29:57 -0800909 if (!skipCache && isCacheable(type)) {
Jeff Browne5360fb2011-10-31 17:48:13 -0700910 mPreparedStatementCache.put(sql, statement);
911 statement.mInCache = true;
912 }
913 } catch (RuntimeException ex) {
914 // Finalize the statement if an exception occurred and we did not add
915 // it to the cache. If it is already in the cache, then leave it there.
916 if (statement == null || !statement.mInCache) {
917 nativeFinalizeStatement(mConnectionPtr, statementPtr);
918 }
919 throw ex;
920 }
Jeff Browna9be4152012-01-18 15:29:57 -0800921 statement.mInUse = true;
Jeff Browne5360fb2011-10-31 17:48:13 -0700922 return statement;
923 }
924
925 private void releasePreparedStatement(PreparedStatement statement) {
Jeff Browna9be4152012-01-18 15:29:57 -0800926 statement.mInUse = false;
Jeff Browne5360fb2011-10-31 17:48:13 -0700927 if (statement.mInCache) {
928 try {
929 nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr);
930 } catch (SQLiteException ex) {
Jeff Browna9be4152012-01-18 15:29:57 -0800931 // The statement could not be reset due to an error. Remove it from the cache.
932 // When remove() is called, the cache will invoke its entryRemoved() callback,
933 // which will in turn call finalizePreparedStatement() to finalize and
934 // recycle the statement.
Jeff Brown2a293b62012-01-19 14:02:22 -0800935 if (DEBUG) {
936 Log.d(TAG, "Could not reset prepared statement due to an exception. "
Jeff Browne5360fb2011-10-31 17:48:13 -0700937 + "Removing it from the cache. SQL: "
938 + trimSqlForDisplay(statement.mSql), ex);
939 }
Jeff Brown2a293b62012-01-19 14:02:22 -0800940
Jeff Browne5360fb2011-10-31 17:48:13 -0700941 mPreparedStatementCache.remove(statement.mSql);
942 }
943 } else {
Jeff Browna9be4152012-01-18 15:29:57 -0800944 finalizePreparedStatement(statement);
Jeff Browne5360fb2011-10-31 17:48:13 -0700945 }
946 }
947
Jeff Browna9be4152012-01-18 15:29:57 -0800948 private void finalizePreparedStatement(PreparedStatement statement) {
949 nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr);
950 recyclePreparedStatement(statement);
951 }
952
Jeff Brown4c1241d2012-02-02 17:05:00 -0800953 private void attachCancellationSignal(CancellationSignal cancellationSignal) {
954 if (cancellationSignal != null) {
955 cancellationSignal.throwIfCanceled();
Jeff Brown75ea64f2012-01-25 19:37:13 -0800956
Jeff Brown4c1241d2012-02-02 17:05:00 -0800957 mCancellationSignalAttachCount += 1;
958 if (mCancellationSignalAttachCount == 1) {
959 // Reset cancellation flag before executing the statement.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800960 nativeResetCancel(mConnectionPtr, true /*cancelable*/);
961
962 // After this point, onCancel() may be called concurrently.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800963 cancellationSignal.setOnCancelListener(this);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800964 }
965 }
966 }
967
Jeff Brown4c1241d2012-02-02 17:05:00 -0800968 private void detachCancellationSignal(CancellationSignal cancellationSignal) {
969 if (cancellationSignal != null) {
970 assert mCancellationSignalAttachCount > 0;
Jeff Brown75ea64f2012-01-25 19:37:13 -0800971
Jeff Brown4c1241d2012-02-02 17:05:00 -0800972 mCancellationSignalAttachCount -= 1;
973 if (mCancellationSignalAttachCount == 0) {
Jeff Brown75ea64f2012-01-25 19:37:13 -0800974 // After this point, onCancel() cannot be called concurrently.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800975 cancellationSignal.setOnCancelListener(null);
Jeff Brown75ea64f2012-01-25 19:37:13 -0800976
Jeff Brown4c1241d2012-02-02 17:05:00 -0800977 // Reset cancellation flag after executing the statement.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800978 nativeResetCancel(mConnectionPtr, false /*cancelable*/);
979 }
980 }
981 }
982
Jeff Brown4c1241d2012-02-02 17:05:00 -0800983 // CancellationSignal.OnCancelListener callback.
Jeff Brown75ea64f2012-01-25 19:37:13 -0800984 // This method may be called on a different thread than the executing statement.
Jeff Brown4c1241d2012-02-02 17:05:00 -0800985 // However, it will only be called between calls to attachCancellationSignal and
986 // detachCancellationSignal, while a statement is executing. We can safely assume
Jeff Brown75ea64f2012-01-25 19:37:13 -0800987 // that the SQLite connection is still alive.
988 @Override
989 public void onCancel() {
990 nativeCancel(mConnectionPtr);
991 }
992
Jeff Browne5360fb2011-10-31 17:48:13 -0700993 private void bindArguments(PreparedStatement statement, Object[] bindArgs) {
994 final int count = bindArgs != null ? bindArgs.length : 0;
995 if (count != statement.mNumParameters) {
996 throw new SQLiteBindOrColumnIndexOutOfRangeException(
997 "Expected " + statement.mNumParameters + " bind arguments but "
Sylvain Becuwe942085b2012-10-28 01:48:37 +0200998 + count + " were provided.");
Jeff Browne5360fb2011-10-31 17:48:13 -0700999 }
1000 if (count == 0) {
1001 return;
1002 }
1003
Ashok Bhat738702d2014-01-02 13:42:56 +00001004 final long statementPtr = statement.mStatementPtr;
Jeff Browne5360fb2011-10-31 17:48:13 -07001005 for (int i = 0; i < count; i++) {
1006 final Object arg = bindArgs[i];
1007 switch (DatabaseUtils.getTypeOfObject(arg)) {
1008 case Cursor.FIELD_TYPE_NULL:
1009 nativeBindNull(mConnectionPtr, statementPtr, i + 1);
1010 break;
1011 case Cursor.FIELD_TYPE_INTEGER:
1012 nativeBindLong(mConnectionPtr, statementPtr, i + 1,
1013 ((Number)arg).longValue());
1014 break;
1015 case Cursor.FIELD_TYPE_FLOAT:
1016 nativeBindDouble(mConnectionPtr, statementPtr, i + 1,
1017 ((Number)arg).doubleValue());
1018 break;
1019 case Cursor.FIELD_TYPE_BLOB:
1020 nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg);
1021 break;
1022 case Cursor.FIELD_TYPE_STRING:
1023 default:
1024 if (arg instanceof Boolean) {
1025 // Provide compatibility with legacy applications which may pass
1026 // Boolean values in bind args.
1027 nativeBindLong(mConnectionPtr, statementPtr, i + 1,
1028 ((Boolean)arg).booleanValue() ? 1 : 0);
1029 } else {
1030 nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString());
1031 }
1032 break;
1033 }
1034 }
1035 }
1036
1037 private void throwIfStatementForbidden(PreparedStatement statement) {
1038 if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) {
1039 throw new SQLiteException("Cannot execute this statement because it "
1040 + "might modify the database but the connection is read-only.");
1041 }
1042 }
1043
1044 private static boolean isCacheable(int statementType) {
1045 if (statementType == DatabaseUtils.STATEMENT_UPDATE
1046 || statementType == DatabaseUtils.STATEMENT_SELECT) {
1047 return true;
1048 }
1049 return false;
1050 }
1051
1052 private void applyBlockGuardPolicy(PreparedStatement statement) {
1053 if (!mConfiguration.isInMemoryDb()) {
1054 if (statement.mReadOnly) {
1055 BlockGuard.getThreadPolicy().onReadFromDisk();
1056 } else {
1057 BlockGuard.getThreadPolicy().onWriteToDisk();
1058 }
1059 }
1060 }
1061
1062 /**
1063 * Dumps debugging information about this connection.
1064 *
1065 * @param printer The printer to receive the dump, not null.
Jeff Browna9be4152012-01-18 15:29:57 -08001066 * @param verbose True to dump more verbose information.
Jeff Browne5360fb2011-10-31 17:48:13 -07001067 */
Jeff Browna9be4152012-01-18 15:29:57 -08001068 public void dump(Printer printer, boolean verbose) {
1069 dumpUnsafe(printer, verbose);
Jeff Browne5360fb2011-10-31 17:48:13 -07001070 }
1071
1072 /**
1073 * Dumps debugging information about this connection, in the case where the
1074 * caller might not actually own the connection.
1075 *
1076 * This function is written so that it may be called by a thread that does not
1077 * own the connection. We need to be very careful because the connection state is
1078 * not synchronized.
1079 *
1080 * At worst, the method may return stale or slightly wrong data, however
1081 * it should not crash. This is ok as it is only used for diagnostic purposes.
1082 *
1083 * @param printer The printer to receive the dump, not null.
Jeff Browna9be4152012-01-18 15:29:57 -08001084 * @param verbose True to dump more verbose information.
Jeff Browne5360fb2011-10-31 17:48:13 -07001085 */
Jeff Browna9be4152012-01-18 15:29:57 -08001086 void dumpUnsafe(Printer printer, boolean verbose) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001087 printer.println("Connection #" + mConnectionId + ":");
Jeff Browna9be4152012-01-18 15:29:57 -08001088 if (verbose) {
Ashok Bhat738702d2014-01-02 13:42:56 +00001089 printer.println(" connectionPtr: 0x" + Long.toHexString(mConnectionPtr));
Jeff Browna9be4152012-01-18 15:29:57 -08001090 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001091 printer.println(" isPrimaryConnection: " + mIsPrimaryConnection);
Jeff Browne5360fb2011-10-31 17:48:13 -07001092 printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
1093
Jeff Browncefeb292013-05-01 15:28:37 -07001094 mRecentOperations.dump(printer, verbose);
Jeff Browna9be4152012-01-18 15:29:57 -08001095
1096 if (verbose) {
1097 mPreparedStatementCache.dump(printer);
1098 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001099 }
1100
1101 /**
1102 * Describes the currently executing operation, in the case where the
1103 * caller might not actually own the connection.
1104 *
1105 * This function is written so that it may be called by a thread that does not
1106 * own the connection. We need to be very careful because the connection state is
1107 * not synchronized.
1108 *
1109 * At worst, the method may return stale or slightly wrong data, however
1110 * it should not crash. This is ok as it is only used for diagnostic purposes.
1111 *
1112 * @return A description of the current operation including how long it has been running,
1113 * or null if none.
1114 */
1115 String describeCurrentOperationUnsafe() {
1116 return mRecentOperations.describeCurrentOperation();
1117 }
1118
1119 /**
1120 * Collects statistics about database connection memory usage.
1121 *
1122 * @param dbStatsList The list to populate.
1123 */
1124 void collectDbStats(ArrayList<DbStats> dbStatsList) {
1125 // Get information about the main database.
1126 int lookaside = nativeGetDbLookaside(mConnectionPtr);
1127 long pageCount = 0;
1128 long pageSize = 0;
1129 try {
Jeff Brown75ea64f2012-01-25 19:37:13 -08001130 pageCount = executeForLong("PRAGMA page_count;", null, null);
1131 pageSize = executeForLong("PRAGMA page_size;", null, null);
Jeff Browne5360fb2011-10-31 17:48:13 -07001132 } catch (SQLiteException ex) {
1133 // Ignore.
1134 }
1135 dbStatsList.add(getMainDbStatsUnsafe(lookaside, pageCount, pageSize));
1136
1137 // Get information about attached databases.
1138 // We ignore the first row in the database list because it corresponds to
1139 // the main database which we have already described.
1140 CursorWindow window = new CursorWindow("collectDbStats");
1141 try {
Jeff Brown75ea64f2012-01-25 19:37:13 -08001142 executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null);
Jeff Browne5360fb2011-10-31 17:48:13 -07001143 for (int i = 1; i < window.getNumRows(); i++) {
1144 String name = window.getString(i, 1);
1145 String path = window.getString(i, 2);
1146 pageCount = 0;
1147 pageSize = 0;
1148 try {
Jeff Brown75ea64f2012-01-25 19:37:13 -08001149 pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null);
1150 pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null);
Jeff Browne5360fb2011-10-31 17:48:13 -07001151 } catch (SQLiteException ex) {
1152 // Ignore.
1153 }
1154 String label = " (attached) " + name;
1155 if (!path.isEmpty()) {
1156 label += ": " + path;
1157 }
1158 dbStatsList.add(new DbStats(label, pageCount, pageSize, 0, 0, 0, 0));
1159 }
1160 } catch (SQLiteException ex) {
1161 // Ignore.
1162 } finally {
1163 window.close();
1164 }
1165 }
1166
1167 /**
1168 * Collects statistics about database connection memory usage, in the case where the
1169 * caller might not actually own the connection.
1170 *
1171 * @return The statistics object, never null.
1172 */
1173 void collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList) {
1174 dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0));
1175 }
1176
1177 private DbStats getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize) {
1178 // The prepared statement cache is thread-safe so we can access its statistics
1179 // even if we do not own the database connection.
1180 String label = mConfiguration.path;
1181 if (!mIsPrimaryConnection) {
1182 label += " (" + mConnectionId + ")";
1183 }
1184 return new DbStats(label, pageCount, pageSize, lookaside,
1185 mPreparedStatementCache.hitCount(),
1186 mPreparedStatementCache.missCount(),
1187 mPreparedStatementCache.size());
1188 }
1189
1190 @Override
1191 public String toString() {
1192 return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")";
1193 }
1194
Ashok Bhat738702d2014-01-02 13:42:56 +00001195 private PreparedStatement obtainPreparedStatement(String sql, long statementPtr,
Jeff Browne5360fb2011-10-31 17:48:13 -07001196 int numParameters, int type, boolean readOnly) {
1197 PreparedStatement statement = mPreparedStatementPool;
1198 if (statement != null) {
1199 mPreparedStatementPool = statement.mPoolNext;
1200 statement.mPoolNext = null;
1201 statement.mInCache = false;
1202 } else {
1203 statement = new PreparedStatement();
1204 }
1205 statement.mSql = sql;
1206 statement.mStatementPtr = statementPtr;
1207 statement.mNumParameters = numParameters;
1208 statement.mType = type;
1209 statement.mReadOnly = readOnly;
1210 return statement;
1211 }
1212
1213 private void recyclePreparedStatement(PreparedStatement statement) {
1214 statement.mSql = null;
1215 statement.mPoolNext = mPreparedStatementPool;
1216 mPreparedStatementPool = statement;
1217 }
1218
1219 private static String trimSqlForDisplay(String sql) {
Andreas Gampe4f47b402015-04-20 15:29:04 -07001220 // Note: Creating and caching a regular expression is expensive at preload-time
1221 // and stops compile-time initialization. This pattern is only used when
1222 // dumping the connection, which is a rare (mainly error) case. So:
1223 // DO NOT CACHE.
1224 return sql.replaceAll("[\\s]*\\n+[\\s]*", " ");
Jeff Browne5360fb2011-10-31 17:48:13 -07001225 }
1226
1227 /**
1228 * Holder type for a prepared statement.
1229 *
1230 * Although this object holds a pointer to a native statement object, it
1231 * does not have a finalizer. This is deliberate. The {@link SQLiteConnection}
1232 * owns the statement object and will take care of freeing it when needed.
1233 * In particular, closing the connection requires a guarantee of deterministic
1234 * resource disposal because all native statement objects must be freed before
1235 * the native database object can be closed. So no finalizers here.
1236 */
1237 private static final class PreparedStatement {
1238 // Next item in pool.
1239 public PreparedStatement mPoolNext;
1240
1241 // The SQL from which the statement was prepared.
1242 public String mSql;
1243
1244 // The native sqlite3_stmt object pointer.
1245 // Lifetime is managed explicitly by the connection.
Ashok Bhat738702d2014-01-02 13:42:56 +00001246 public long mStatementPtr;
Jeff Browne5360fb2011-10-31 17:48:13 -07001247
1248 // The number of parameters that the prepared statement has.
1249 public int mNumParameters;
1250
1251 // The statement type.
1252 public int mType;
1253
1254 // True if the statement is read-only.
1255 public boolean mReadOnly;
1256
1257 // True if the statement is in the cache.
1258 public boolean mInCache;
Jeff Browna9be4152012-01-18 15:29:57 -08001259
1260 // True if the statement is in use (currently executing).
1261 // We need this flag because due to the use of custom functions in triggers, it's
1262 // possible for SQLite calls to be re-entrant. Consequently we need to prevent
1263 // in use statements from being finalized until they are no longer in use.
1264 public boolean mInUse;
Jeff Browne5360fb2011-10-31 17:48:13 -07001265 }
1266
1267 private final class PreparedStatementCache
1268 extends LruCache<String, PreparedStatement> {
1269 public PreparedStatementCache(int size) {
1270 super(size);
1271 }
1272
1273 @Override
1274 protected void entryRemoved(boolean evicted, String key,
1275 PreparedStatement oldValue, PreparedStatement newValue) {
1276 oldValue.mInCache = false;
Jeff Browna9be4152012-01-18 15:29:57 -08001277 if (!oldValue.mInUse) {
1278 finalizePreparedStatement(oldValue);
1279 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001280 }
1281
1282 public void dump(Printer printer) {
1283 printer.println(" Prepared statement cache:");
1284 Map<String, PreparedStatement> cache = snapshot();
1285 if (!cache.isEmpty()) {
1286 int i = 0;
1287 for (Map.Entry<String, PreparedStatement> entry : cache.entrySet()) {
1288 PreparedStatement statement = entry.getValue();
1289 if (statement.mInCache) { // might be false due to a race with entryRemoved
1290 String sql = entry.getKey();
1291 printer.println(" " + i + ": statementPtr=0x"
Ashok Bhat738702d2014-01-02 13:42:56 +00001292 + Long.toHexString(statement.mStatementPtr)
Jeff Browne5360fb2011-10-31 17:48:13 -07001293 + ", numParameters=" + statement.mNumParameters
1294 + ", type=" + statement.mType
1295 + ", readOnly=" + statement.mReadOnly
1296 + ", sql=\"" + trimSqlForDisplay(sql) + "\"");
1297 }
1298 i += 1;
1299 }
1300 } else {
1301 printer.println(" <none>");
1302 }
1303 }
1304 }
1305
1306 private static final class OperationLog {
Jeff Brown2a293b62012-01-19 14:02:22 -08001307 private static final int MAX_RECENT_OPERATIONS = 20;
Jeff Browna9be4152012-01-18 15:29:57 -08001308 private static final int COOKIE_GENERATION_SHIFT = 8;
1309 private static final int COOKIE_INDEX_MASK = 0xff;
Jeff Browne5360fb2011-10-31 17:48:13 -07001310
1311 private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
1312 private int mIndex;
Jeff Browna9be4152012-01-18 15:29:57 -08001313 private int mGeneration;
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001314 private final SQLiteConnectionPool mPool;
1315
1316 OperationLog(SQLiteConnectionPool pool) {
1317 mPool = pool;
1318 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001319
Jeff Browna9be4152012-01-18 15:29:57 -08001320 public int beginOperation(String kind, String sql, Object[] bindArgs) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001321 synchronized (mOperations) {
1322 final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
1323 Operation operation = mOperations[index];
1324 if (operation == null) {
1325 operation = new Operation();
1326 mOperations[index] = operation;
1327 } else {
1328 operation.mFinished = false;
1329 operation.mException = null;
1330 if (operation.mBindArgs != null) {
1331 operation.mBindArgs.clear();
1332 }
1333 }
Makoto Onuki34436702016-04-07 09:07:04 -07001334 operation.mStartWallTime = System.currentTimeMillis();
1335 operation.mStartTime = SystemClock.uptimeMillis();
Jeff Browne5360fb2011-10-31 17:48:13 -07001336 operation.mKind = kind;
1337 operation.mSql = sql;
1338 if (bindArgs != null) {
1339 if (operation.mBindArgs == null) {
1340 operation.mBindArgs = new ArrayList<Object>();
1341 } else {
1342 operation.mBindArgs.clear();
1343 }
1344 for (int i = 0; i < bindArgs.length; i++) {
1345 final Object arg = bindArgs[i];
1346 if (arg != null && arg instanceof byte[]) {
1347 // Don't hold onto the real byte array longer than necessary.
1348 operation.mBindArgs.add(EMPTY_BYTE_ARRAY);
1349 } else {
1350 operation.mBindArgs.add(arg);
1351 }
1352 }
1353 }
Jeff Browna9be4152012-01-18 15:29:57 -08001354 operation.mCookie = newOperationCookieLocked(index);
Greg Hackmanne12350f2014-12-01 14:31:21 -08001355 if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
1356 Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
1357 operation.mCookie);
1358 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001359 mIndex = index;
Jeff Browna9be4152012-01-18 15:29:57 -08001360 return operation.mCookie;
Jeff Browne5360fb2011-10-31 17:48:13 -07001361 }
1362 }
1363
Jeff Browna9be4152012-01-18 15:29:57 -08001364 public void failOperation(int cookie, Exception ex) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001365 synchronized (mOperations) {
Jeff Browna9be4152012-01-18 15:29:57 -08001366 final Operation operation = getOperationLocked(cookie);
1367 if (operation != null) {
1368 operation.mException = ex;
Jeff Browne5360fb2011-10-31 17:48:13 -07001369 }
1370 }
1371 }
1372
Jeff Browna9be4152012-01-18 15:29:57 -08001373 public void endOperation(int cookie) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001374 synchronized (mOperations) {
Jeff Browna9be4152012-01-18 15:29:57 -08001375 if (endOperationDeferLogLocked(cookie)) {
1376 logOperationLocked(cookie, null);
1377 }
Jeff Browne5360fb2011-10-31 17:48:13 -07001378 }
1379 }
1380
Jeff Browna9be4152012-01-18 15:29:57 -08001381 public boolean endOperationDeferLog(int cookie) {
1382 synchronized (mOperations) {
1383 return endOperationDeferLogLocked(cookie);
1384 }
1385 }
1386
1387 public void logOperation(int cookie, String detail) {
1388 synchronized (mOperations) {
1389 logOperationLocked(cookie, detail);
1390 }
1391 }
1392
1393 private boolean endOperationDeferLogLocked(int cookie) {
1394 final Operation operation = getOperationLocked(cookie);
1395 if (operation != null) {
Greg Hackmanne12350f2014-12-01 14:31:21 -08001396 if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
1397 Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
1398 operation.mCookie);
1399 }
Makoto Onuki34436702016-04-07 09:07:04 -07001400 operation.mEndTime = SystemClock.uptimeMillis();
Jeff Browna9be4152012-01-18 15:29:57 -08001401 operation.mFinished = true;
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001402 final long execTime = operation.mEndTime - operation.mStartTime;
1403 mPool.onStatementExecuted(execTime);
Jeff Browna9be4152012-01-18 15:29:57 -08001404 return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001405 execTime);
Jeff Browna9be4152012-01-18 15:29:57 -08001406 }
1407 return false;
1408 }
1409
1410 private void logOperationLocked(int cookie, String detail) {
1411 final Operation operation = getOperationLocked(cookie);
Jeff Browne5360fb2011-10-31 17:48:13 -07001412 StringBuilder msg = new StringBuilder();
Jeff Browncefeb292013-05-01 15:28:37 -07001413 operation.describe(msg, false);
Jeff Browne5360fb2011-10-31 17:48:13 -07001414 if (detail != null) {
1415 msg.append(", ").append(detail);
1416 }
1417 Log.d(TAG, msg.toString());
1418 }
1419
Jeff Browna9be4152012-01-18 15:29:57 -08001420 private int newOperationCookieLocked(int index) {
1421 final int generation = mGeneration++;
1422 return generation << COOKIE_GENERATION_SHIFT | index;
1423 }
1424
1425 private Operation getOperationLocked(int cookie) {
1426 final int index = cookie & COOKIE_INDEX_MASK;
1427 final Operation operation = mOperations[index];
1428 return operation.mCookie == cookie ? operation : null;
1429 }
1430
Jeff Browne5360fb2011-10-31 17:48:13 -07001431 public String describeCurrentOperation() {
1432 synchronized (mOperations) {
1433 final Operation operation = mOperations[mIndex];
1434 if (operation != null && !operation.mFinished) {
1435 StringBuilder msg = new StringBuilder();
Jeff Browncefeb292013-05-01 15:28:37 -07001436 operation.describe(msg, false);
Jeff Browne5360fb2011-10-31 17:48:13 -07001437 return msg.toString();
1438 }
1439 return null;
1440 }
1441 }
1442
Jeff Browncefeb292013-05-01 15:28:37 -07001443 public void dump(Printer printer, boolean verbose) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001444 synchronized (mOperations) {
1445 printer.println(" Most recently executed operations:");
1446 int index = mIndex;
1447 Operation operation = mOperations[index];
1448 if (operation != null) {
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001449 // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created,
1450 // and is relatively expensive to create during preloading. This method is only
1451 // used when dumping a connection, which is a rare (mainly error) case.
1452 SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Jeff Browne5360fb2011-10-31 17:48:13 -07001453 int n = 0;
1454 do {
1455 StringBuilder msg = new StringBuilder();
1456 msg.append(" ").append(n).append(": [");
Fyodor Kupolov76dd22f2017-09-18 14:33:16 -07001457 String formattedStartTime = opDF.format(new Date(operation.mStartWallTime));
1458 msg.append(formattedStartTime);
Jeff Browne5360fb2011-10-31 17:48:13 -07001459 msg.append("] ");
Jeff Browncefeb292013-05-01 15:28:37 -07001460 operation.describe(msg, verbose);
Jeff Browne5360fb2011-10-31 17:48:13 -07001461 printer.println(msg.toString());
1462
1463 if (index > 0) {
1464 index -= 1;
1465 } else {
1466 index = MAX_RECENT_OPERATIONS - 1;
1467 }
1468 n += 1;
1469 operation = mOperations[index];
1470 } while (operation != null && n < MAX_RECENT_OPERATIONS);
1471 } else {
1472 printer.println(" <none>");
1473 }
1474 }
1475 }
1476 }
1477
1478 private static final class Operation {
Greg Hackmanne12350f2014-12-01 14:31:21 -08001479 // Trim all SQL statements to 256 characters inside the trace marker.
1480 // This limit gives plenty of context while leaving space for other
1481 // entries in the trace buffer (and ensures atrace doesn't truncate the
1482 // marker for us, potentially losing metadata in the process).
1483 private static final int MAX_TRACE_METHOD_NAME_LEN = 256;
1484
Makoto Onuki34436702016-04-07 09:07:04 -07001485 public long mStartWallTime; // in System.currentTimeMillis()
1486 public long mStartTime; // in SystemClock.uptimeMillis();
1487 public long mEndTime; // in SystemClock.uptimeMillis();
Jeff Browne5360fb2011-10-31 17:48:13 -07001488 public String mKind;
1489 public String mSql;
1490 public ArrayList<Object> mBindArgs;
1491 public boolean mFinished;
1492 public Exception mException;
Jeff Browna9be4152012-01-18 15:29:57 -08001493 public int mCookie;
Jeff Browne5360fb2011-10-31 17:48:13 -07001494
Jeff Browncefeb292013-05-01 15:28:37 -07001495 public void describe(StringBuilder msg, boolean verbose) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001496 msg.append(mKind);
1497 if (mFinished) {
1498 msg.append(" took ").append(mEndTime - mStartTime).append("ms");
1499 } else {
Makoto Onuki34436702016-04-07 09:07:04 -07001500 msg.append(" started ").append(System.currentTimeMillis() - mStartWallTime)
Jeff Browne5360fb2011-10-31 17:48:13 -07001501 .append("ms ago");
1502 }
1503 msg.append(" - ").append(getStatus());
1504 if (mSql != null) {
1505 msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\"");
1506 }
Jeff Browncefeb292013-05-01 15:28:37 -07001507 if (verbose && mBindArgs != null && mBindArgs.size() != 0) {
Jeff Browne5360fb2011-10-31 17:48:13 -07001508 msg.append(", bindArgs=[");
1509 final int count = mBindArgs.size();
1510 for (int i = 0; i < count; i++) {
1511 final Object arg = mBindArgs.get(i);
1512 if (i != 0) {
1513 msg.append(", ");
1514 }
1515 if (arg == null) {
1516 msg.append("null");
1517 } else if (arg instanceof byte[]) {
1518 msg.append("<byte[]>");
1519 } else if (arg instanceof String) {
1520 msg.append("\"").append((String)arg).append("\"");
1521 } else {
1522 msg.append(arg);
1523 }
1524 }
1525 msg.append("]");
1526 }
1527 if (mException != null) {
1528 msg.append(", exception=\"").append(mException.getMessage()).append("\"");
1529 }
1530 }
1531
1532 private String getStatus() {
1533 if (!mFinished) {
1534 return "running";
1535 }
1536 return mException != null ? "failed" : "succeeded";
1537 }
1538
Greg Hackmanne12350f2014-12-01 14:31:21 -08001539 private String getTraceMethodName() {
1540 String methodName = mKind + " " + mSql;
1541 if (methodName.length() > MAX_TRACE_METHOD_NAME_LEN)
1542 return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN);
1543 return methodName;
1544 }
1545
Jeff Browne5360fb2011-10-31 17:48:13 -07001546 }
1547}