Roll android_tools support library to 25.1.0
[android_tools.git] / sdk / sources / android-23 / android / database / sqlite / SQLiteConnection.java
blob3cda39ae9aad7a43aaa33122298039c944f9023d
1 /*
2 * Copyright (C) 2011 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.database.sqlite;
19 import dalvik.system.BlockGuard;
20 import dalvik.system.CloseGuard;
22 import android.database.Cursor;
23 import android.database.CursorWindow;
24 import android.database.DatabaseUtils;
25 import android.database.sqlite.SQLiteDebug.DbStats;
26 import android.os.CancellationSignal;
27 import android.os.OperationCanceledException;
28 import android.os.ParcelFileDescriptor;
29 import android.util.Log;
30 import android.util.LruCache;
31 import android.util.Printer;
33 import java.text.SimpleDateFormat;
34 import java.util.ArrayList;
35 import java.util.Date;
36 import java.util.Map;
37 import java.util.regex.Pattern;
39 /**
40 * Represents a SQLite database connection.
41 * Each connection wraps an instance of a native <code>sqlite3</code> object.
42 * <p>
43 * When database connection pooling is enabled, there can be multiple active
44 * connections to the same database. Otherwise there is typically only one
45 * connection per database.
46 * </p><p>
47 * When the SQLite WAL feature is enabled, multiple readers and one writer
48 * can concurrently access the database. Without WAL, readers and writers
49 * are mutually exclusive.
50 * </p>
52 * <h2>Ownership and concurrency guarantees</h2>
53 * <p>
54 * Connection objects are not thread-safe. They are acquired as needed to
55 * perform a database operation and are then returned to the pool. At any
56 * given time, a connection is either owned and used by a {@link SQLiteSession}
57 * object or the {@link SQLiteConnectionPool}. Those classes are
58 * responsible for serializing operations to guard against concurrent
59 * use of a connection.
60 * </p><p>
61 * The guarantee of having a single owner allows this class to be implemented
62 * without locks and greatly simplifies resource management.
63 * </p>
65 * <h2>Encapsulation guarantees</h2>
66 * <p>
67 * The connection object object owns *all* of the SQLite related native
68 * objects that are associated with the connection. What's more, there are
69 * no other objects in the system that are capable of obtaining handles to
70 * those native objects. Consequently, when the connection is closed, we do
71 * not have to worry about what other components might have references to
72 * its associated SQLite state -- there are none.
73 * </p><p>
74 * Encapsulation is what ensures that the connection object's
75 * lifecycle does not become a tortured mess of finalizers and reference
76 * queues.
77 * </p>
79 * <h2>Reentrance</h2>
80 * <p>
81 * This class must tolerate reentrant execution of SQLite operations because
82 * triggers may call custom SQLite functions that perform additional queries.
83 * </p>
85 * @hide
87 public final class SQLiteConnection implements CancellationSignal.OnCancelListener {
88 private static final String TAG = "SQLiteConnection";
89 private static final boolean DEBUG = false;
91 private static final String[] EMPTY_STRING_ARRAY = new String[0];
92 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
94 private final CloseGuard mCloseGuard = CloseGuard.get();
96 private final SQLiteConnectionPool mPool;
97 private final SQLiteDatabaseConfiguration mConfiguration;
98 private final int mConnectionId;
99 private final boolean mIsPrimaryConnection;
100 private final boolean mIsReadOnlyConnection;
101 private final PreparedStatementCache mPreparedStatementCache;
102 private PreparedStatement mPreparedStatementPool;
104 // The recent operations log.
105 private final OperationLog mRecentOperations = new OperationLog();
107 // The native SQLiteConnection pointer. (FOR INTERNAL USE ONLY)
108 private long mConnectionPtr;
110 private boolean mOnlyAllowReadOnlyOperations;
112 // The number of times attachCancellationSignal has been called.
113 // Because SQLite statement execution can be reentrant, we keep track of how many
114 // times we have attempted to attach a cancellation signal to the connection so that
115 // we can ensure that we detach the signal at the right time.
116 private int mCancellationSignalAttachCount;
118 private static native long nativeOpen(String path, int openFlags, String label,
119 boolean enableTrace, boolean enableProfile);
120 private static native void nativeClose(long connectionPtr);
121 private static native void nativeRegisterCustomFunction(long connectionPtr,
122 SQLiteCustomFunction function);
123 private static native void nativeRegisterLocalizedCollators(long connectionPtr, String locale);
124 private static native long nativePrepareStatement(long connectionPtr, String sql);
125 private static native void nativeFinalizeStatement(long connectionPtr, long statementPtr);
126 private static native int nativeGetParameterCount(long connectionPtr, long statementPtr);
127 private static native boolean nativeIsReadOnly(long connectionPtr, long statementPtr);
128 private static native int nativeGetColumnCount(long connectionPtr, long statementPtr);
129 private static native String nativeGetColumnName(long connectionPtr, long statementPtr,
130 int index);
131 private static native void nativeBindNull(long connectionPtr, long statementPtr,
132 int index);
133 private static native void nativeBindLong(long connectionPtr, long statementPtr,
134 int index, long value);
135 private static native void nativeBindDouble(long connectionPtr, long statementPtr,
136 int index, double value);
137 private static native void nativeBindString(long connectionPtr, long statementPtr,
138 int index, String value);
139 private static native void nativeBindBlob(long connectionPtr, long statementPtr,
140 int index, byte[] value);
141 private static native void nativeResetStatementAndClearBindings(
142 long connectionPtr, long statementPtr);
143 private static native void nativeExecute(long connectionPtr, long statementPtr);
144 private static native long nativeExecuteForLong(long connectionPtr, long statementPtr);
145 private static native String nativeExecuteForString(long connectionPtr, long statementPtr);
146 private static native int nativeExecuteForBlobFileDescriptor(
147 long connectionPtr, long statementPtr);
148 private static native int nativeExecuteForChangedRowCount(long connectionPtr, long statementPtr);
149 private static native long nativeExecuteForLastInsertedRowId(
150 long connectionPtr, long statementPtr);
151 private static native long nativeExecuteForCursorWindow(
152 long connectionPtr, long statementPtr, long windowPtr,
153 int startPos, int requiredPos, boolean countAllRows);
154 private static native int nativeGetDbLookaside(long connectionPtr);
155 private static native void nativeCancel(long connectionPtr);
156 private static native void nativeResetCancel(long connectionPtr, boolean cancelable);
158 private SQLiteConnection(SQLiteConnectionPool pool,
159 SQLiteDatabaseConfiguration configuration,
160 int connectionId, boolean primaryConnection) {
161 mPool = pool;
162 mConfiguration = new SQLiteDatabaseConfiguration(configuration);
163 mConnectionId = connectionId;
164 mIsPrimaryConnection = primaryConnection;
165 mIsReadOnlyConnection = (configuration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
166 mPreparedStatementCache = new PreparedStatementCache(
167 mConfiguration.maxSqlCacheSize);
168 mCloseGuard.open("close");
171 @Override
172 protected void finalize() throws Throwable {
173 try {
174 if (mPool != null && mConnectionPtr != 0) {
175 mPool.onConnectionLeaked();
178 dispose(true);
179 } finally {
180 super.finalize();
184 // Called by SQLiteConnectionPool only.
185 static SQLiteConnection open(SQLiteConnectionPool pool,
186 SQLiteDatabaseConfiguration configuration,
187 int connectionId, boolean primaryConnection) {
188 SQLiteConnection connection = new SQLiteConnection(pool, configuration,
189 connectionId, primaryConnection);
190 try {
191 connection.open();
192 return connection;
193 } catch (SQLiteException ex) {
194 connection.dispose(false);
195 throw ex;
199 // Called by SQLiteConnectionPool only.
200 // Closes the database closes and releases all of its associated resources.
201 // Do not call methods on the connection after it is closed. It will probably crash.
202 void close() {
203 dispose(false);
206 private void open() {
207 mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
208 mConfiguration.label,
209 SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME);
211 setPageSize();
212 setForeignKeyModeFromConfiguration();
213 setWalModeFromConfiguration();
214 setJournalSizeLimit();
215 setAutoCheckpointInterval();
216 setLocaleFromConfiguration();
218 // Register custom functions.
219 final int functionCount = mConfiguration.customFunctions.size();
220 for (int i = 0; i < functionCount; i++) {
221 SQLiteCustomFunction function = mConfiguration.customFunctions.get(i);
222 nativeRegisterCustomFunction(mConnectionPtr, function);
226 private void dispose(boolean finalized) {
227 if (mCloseGuard != null) {
228 if (finalized) {
229 mCloseGuard.warnIfOpen();
231 mCloseGuard.close();
234 if (mConnectionPtr != 0) {
235 final int cookie = mRecentOperations.beginOperation("close", null, null);
236 try {
237 mPreparedStatementCache.evictAll();
238 nativeClose(mConnectionPtr);
239 mConnectionPtr = 0;
240 } finally {
241 mRecentOperations.endOperation(cookie);
246 private void setPageSize() {
247 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
248 final long newValue = SQLiteGlobal.getDefaultPageSize();
249 long value = executeForLong("PRAGMA page_size", null, null);
250 if (value != newValue) {
251 execute("PRAGMA page_size=" + newValue, null, null);
256 private void setAutoCheckpointInterval() {
257 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
258 final long newValue = SQLiteGlobal.getWALAutoCheckpoint();
259 long value = executeForLong("PRAGMA wal_autocheckpoint", null, null);
260 if (value != newValue) {
261 executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null);
266 private void setJournalSizeLimit() {
267 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
268 final long newValue = SQLiteGlobal.getJournalSizeLimit();
269 long value = executeForLong("PRAGMA journal_size_limit", null, null);
270 if (value != newValue) {
271 executeForLong("PRAGMA journal_size_limit=" + newValue, null, null);
276 private void setForeignKeyModeFromConfiguration() {
277 if (!mIsReadOnlyConnection) {
278 final long newValue = mConfiguration.foreignKeyConstraintsEnabled ? 1 : 0;
279 long value = executeForLong("PRAGMA foreign_keys", null, null);
280 if (value != newValue) {
281 execute("PRAGMA foreign_keys=" + newValue, null, null);
286 private void setWalModeFromConfiguration() {
287 if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
288 if ((mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
289 setJournalMode("WAL");
290 setSyncMode(SQLiteGlobal.getWALSyncMode());
291 } else {
292 setJournalMode(SQLiteGlobal.getDefaultJournalMode());
293 setSyncMode(SQLiteGlobal.getDefaultSyncMode());
298 private void setSyncMode(String newValue) {
299 String value = executeForString("PRAGMA synchronous", null, null);
300 if (!canonicalizeSyncMode(value).equalsIgnoreCase(
301 canonicalizeSyncMode(newValue))) {
302 execute("PRAGMA synchronous=" + newValue, null, null);
306 private static String canonicalizeSyncMode(String value) {
307 if (value.equals("0")) {
308 return "OFF";
309 } else if (value.equals("1")) {
310 return "NORMAL";
311 } else if (value.equals("2")) {
312 return "FULL";
314 return value;
317 private void setJournalMode(String newValue) {
318 String value = executeForString("PRAGMA journal_mode", null, null);
319 if (!value.equalsIgnoreCase(newValue)) {
320 try {
321 String result = executeForString("PRAGMA journal_mode=" + newValue, null, null);
322 if (result.equalsIgnoreCase(newValue)) {
323 return;
325 // PRAGMA journal_mode silently fails and returns the original journal
326 // mode in some cases if the journal mode could not be changed.
327 } catch (SQLiteDatabaseLockedException ex) {
328 // This error (SQLITE_BUSY) occurs if one connection has the database
329 // open in WAL mode and another tries to change it to non-WAL.
331 // Because we always disable WAL mode when a database is first opened
332 // (even if we intend to re-enable it), we can encounter problems if
333 // there is another open connection to the database somewhere.
334 // This can happen for a variety of reasons such as an application opening
335 // the same database in multiple processes at the same time or if there is a
336 // crashing content provider service that the ActivityManager has
337 // removed from its registry but whose process hasn't quite died yet
338 // by the time it is restarted in a new process.
340 // If we don't change the journal mode, nothing really bad happens.
341 // In the worst case, an application that enables WAL might not actually
342 // get it, although it can still use connection pooling.
343 Log.w(TAG, "Could not change the database journal mode of '"
344 + mConfiguration.label + "' from '" + value + "' to '" + newValue
345 + "' because the database is locked. This usually means that "
346 + "there are other open connections to the database which prevents "
347 + "the database from enabling or disabling write-ahead logging mode. "
348 + "Proceeding without changing the journal mode.");
352 private void setLocaleFromConfiguration() {
353 if ((mConfiguration.openFlags & SQLiteDatabase.NO_LOCALIZED_COLLATORS) != 0) {
354 return;
357 // Register the localized collators.
358 final String newLocale = mConfiguration.locale.toString();
359 nativeRegisterLocalizedCollators(mConnectionPtr, newLocale);
361 // If the database is read-only, we cannot modify the android metadata table
362 // or existing indexes.
363 if (mIsReadOnlyConnection) {
364 return;
367 try {
368 // Ensure the android metadata table exists.
369 execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null);
371 // Check whether the locale was actually changed.
372 final String oldLocale = executeForString("SELECT locale FROM android_metadata "
373 + "UNION SELECT NULL ORDER BY locale DESC LIMIT 1", null, null);
374 if (oldLocale != null && oldLocale.equals(newLocale)) {
375 return;
378 // Go ahead and update the indexes using the new locale.
379 execute("BEGIN", null, null);
380 boolean success = false;
381 try {
382 execute("DELETE FROM android_metadata", null, null);
383 execute("INSERT INTO android_metadata (locale) VALUES(?)",
384 new Object[] { newLocale }, null);
385 execute("REINDEX LOCALIZED", null, null);
386 success = true;
387 } finally {
388 execute(success ? "COMMIT" : "ROLLBACK", null, null);
390 } catch (RuntimeException ex) {
391 throw new SQLiteException("Failed to change locale for db '" + mConfiguration.label
392 + "' to '" + newLocale + "'.", ex);
396 // Called by SQLiteConnectionPool only.
397 void reconfigure(SQLiteDatabaseConfiguration configuration) {
398 mOnlyAllowReadOnlyOperations = false;
400 // Register custom functions.
401 final int functionCount = configuration.customFunctions.size();
402 for (int i = 0; i < functionCount; i++) {
403 SQLiteCustomFunction function = configuration.customFunctions.get(i);
404 if (!mConfiguration.customFunctions.contains(function)) {
405 nativeRegisterCustomFunction(mConnectionPtr, function);
409 // Remember what changed.
410 boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
411 != mConfiguration.foreignKeyConstraintsEnabled;
412 boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
413 & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
414 boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
416 // Update configuration parameters.
417 mConfiguration.updateParametersFrom(configuration);
419 // Update prepared statement cache size.
420 mPreparedStatementCache.resize(configuration.maxSqlCacheSize);
422 // Update foreign key mode.
423 if (foreignKeyModeChanged) {
424 setForeignKeyModeFromConfiguration();
427 // Update WAL.
428 if (walModeChanged) {
429 setWalModeFromConfiguration();
432 // Update locale.
433 if (localeChanged) {
434 setLocaleFromConfiguration();
438 // Called by SQLiteConnectionPool only.
439 // When set to true, executing write operations will throw SQLiteException.
440 // Preparing statements that might write is ok, just don't execute them.
441 void setOnlyAllowReadOnlyOperations(boolean readOnly) {
442 mOnlyAllowReadOnlyOperations = readOnly;
445 // Called by SQLiteConnectionPool only.
446 // Returns true if the prepared statement cache contains the specified SQL.
447 boolean isPreparedStatementInCache(String sql) {
448 return mPreparedStatementCache.get(sql) != null;
452 * Gets the unique id of this connection.
453 * @return The connection id.
455 public int getConnectionId() {
456 return mConnectionId;
460 * Returns true if this is the primary database connection.
461 * @return True if this is the primary database connection.
463 public boolean isPrimaryConnection() {
464 return mIsPrimaryConnection;
468 * Prepares a statement for execution but does not bind its parameters or execute it.
469 * <p>
470 * This method can be used to check for syntax errors during compilation
471 * prior to execution of the statement. If the {@code outStatementInfo} argument
472 * is not null, the provided {@link SQLiteStatementInfo} object is populated
473 * with information about the statement.
474 * </p><p>
475 * A prepared statement makes no reference to the arguments that may eventually
476 * be bound to it, consequently it it possible to cache certain prepared statements
477 * such as SELECT or INSERT/UPDATE statements. If the statement is cacheable,
478 * then it will be stored in the cache for later.
479 * </p><p>
480 * To take advantage of this behavior as an optimization, the connection pool
481 * provides a method to acquire a connection that already has a given SQL statement
482 * in its prepared statement cache so that it is ready for execution.
483 * </p>
485 * @param sql The SQL statement to prepare.
486 * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate
487 * with information about the statement, or null if none.
489 * @throws SQLiteException if an error occurs, such as a syntax error.
491 public void prepare(String sql, SQLiteStatementInfo outStatementInfo) {
492 if (sql == null) {
493 throw new IllegalArgumentException("sql must not be null.");
496 final int cookie = mRecentOperations.beginOperation("prepare", sql, null);
497 try {
498 final PreparedStatement statement = acquirePreparedStatement(sql);
499 try {
500 if (outStatementInfo != null) {
501 outStatementInfo.numParameters = statement.mNumParameters;
502 outStatementInfo.readOnly = statement.mReadOnly;
504 final int columnCount = nativeGetColumnCount(
505 mConnectionPtr, statement.mStatementPtr);
506 if (columnCount == 0) {
507 outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
508 } else {
509 outStatementInfo.columnNames = new String[columnCount];
510 for (int i = 0; i < columnCount; i++) {
511 outStatementInfo.columnNames[i] = nativeGetColumnName(
512 mConnectionPtr, statement.mStatementPtr, i);
516 } finally {
517 releasePreparedStatement(statement);
519 } catch (RuntimeException ex) {
520 mRecentOperations.failOperation(cookie, ex);
521 throw ex;
522 } finally {
523 mRecentOperations.endOperation(cookie);
528 * Executes a statement that does not return a result.
530 * @param sql The SQL statement to execute.
531 * @param bindArgs The arguments to bind, or null if none.
532 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
534 * @throws SQLiteException if an error occurs, such as a syntax error
535 * or invalid number of bind arguments.
536 * @throws OperationCanceledException if the operation was canceled.
538 public void execute(String sql, Object[] bindArgs,
539 CancellationSignal cancellationSignal) {
540 if (sql == null) {
541 throw new IllegalArgumentException("sql must not be null.");
544 final int cookie = mRecentOperations.beginOperation("execute", sql, bindArgs);
545 try {
546 final PreparedStatement statement = acquirePreparedStatement(sql);
547 try {
548 throwIfStatementForbidden(statement);
549 bindArguments(statement, bindArgs);
550 applyBlockGuardPolicy(statement);
551 attachCancellationSignal(cancellationSignal);
552 try {
553 nativeExecute(mConnectionPtr, statement.mStatementPtr);
554 } finally {
555 detachCancellationSignal(cancellationSignal);
557 } finally {
558 releasePreparedStatement(statement);
560 } catch (RuntimeException ex) {
561 mRecentOperations.failOperation(cookie, ex);
562 throw ex;
563 } finally {
564 mRecentOperations.endOperation(cookie);
569 * Executes a statement that returns a single <code>long</code> result.
571 * @param sql The SQL statement to execute.
572 * @param bindArgs The arguments to bind, or null if none.
573 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
574 * @return The value of the first column in the first row of the result set
575 * as a <code>long</code>, or zero if none.
577 * @throws SQLiteException if an error occurs, such as a syntax error
578 * or invalid number of bind arguments.
579 * @throws OperationCanceledException if the operation was canceled.
581 public long executeForLong(String sql, Object[] bindArgs,
582 CancellationSignal cancellationSignal) {
583 if (sql == null) {
584 throw new IllegalArgumentException("sql must not be null.");
587 final int cookie = mRecentOperations.beginOperation("executeForLong", sql, bindArgs);
588 try {
589 final PreparedStatement statement = acquirePreparedStatement(sql);
590 try {
591 throwIfStatementForbidden(statement);
592 bindArguments(statement, bindArgs);
593 applyBlockGuardPolicy(statement);
594 attachCancellationSignal(cancellationSignal);
595 try {
596 return nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr);
597 } finally {
598 detachCancellationSignal(cancellationSignal);
600 } finally {
601 releasePreparedStatement(statement);
603 } catch (RuntimeException ex) {
604 mRecentOperations.failOperation(cookie, ex);
605 throw ex;
606 } finally {
607 mRecentOperations.endOperation(cookie);
612 * Executes a statement that returns a single {@link String} result.
614 * @param sql The SQL statement to execute.
615 * @param bindArgs The arguments to bind, or null if none.
616 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
617 * @return The value of the first column in the first row of the result set
618 * as a <code>String</code>, or null if none.
620 * @throws SQLiteException if an error occurs, such as a syntax error
621 * or invalid number of bind arguments.
622 * @throws OperationCanceledException if the operation was canceled.
624 public String executeForString(String sql, Object[] bindArgs,
625 CancellationSignal cancellationSignal) {
626 if (sql == null) {
627 throw new IllegalArgumentException("sql must not be null.");
630 final int cookie = mRecentOperations.beginOperation("executeForString", sql, bindArgs);
631 try {
632 final PreparedStatement statement = acquirePreparedStatement(sql);
633 try {
634 throwIfStatementForbidden(statement);
635 bindArguments(statement, bindArgs);
636 applyBlockGuardPolicy(statement);
637 attachCancellationSignal(cancellationSignal);
638 try {
639 return nativeExecuteForString(mConnectionPtr, statement.mStatementPtr);
640 } finally {
641 detachCancellationSignal(cancellationSignal);
643 } finally {
644 releasePreparedStatement(statement);
646 } catch (RuntimeException ex) {
647 mRecentOperations.failOperation(cookie, ex);
648 throw ex;
649 } finally {
650 mRecentOperations.endOperation(cookie);
655 * Executes a statement that returns a single BLOB result as a
656 * file descriptor to a shared memory region.
658 * @param sql The SQL statement to execute.
659 * @param bindArgs The arguments to bind, or null if none.
660 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
661 * @return The file descriptor for a shared memory region that contains
662 * the value of the first column in the first row of the result set as a BLOB,
663 * or null if none.
665 * @throws SQLiteException if an error occurs, such as a syntax error
666 * or invalid number of bind arguments.
667 * @throws OperationCanceledException if the operation was canceled.
669 public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
670 CancellationSignal cancellationSignal) {
671 if (sql == null) {
672 throw new IllegalArgumentException("sql must not be null.");
675 final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor",
676 sql, bindArgs);
677 try {
678 final PreparedStatement statement = acquirePreparedStatement(sql);
679 try {
680 throwIfStatementForbidden(statement);
681 bindArguments(statement, bindArgs);
682 applyBlockGuardPolicy(statement);
683 attachCancellationSignal(cancellationSignal);
684 try {
685 int fd = nativeExecuteForBlobFileDescriptor(
686 mConnectionPtr, statement.mStatementPtr);
687 return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null;
688 } finally {
689 detachCancellationSignal(cancellationSignal);
691 } finally {
692 releasePreparedStatement(statement);
694 } catch (RuntimeException ex) {
695 mRecentOperations.failOperation(cookie, ex);
696 throw ex;
697 } finally {
698 mRecentOperations.endOperation(cookie);
703 * Executes a statement that returns a count of the number of rows
704 * that were changed. Use for UPDATE or DELETE SQL statements.
706 * @param sql The SQL statement to execute.
707 * @param bindArgs The arguments to bind, or null if none.
708 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
709 * @return The number of rows that were changed.
711 * @throws SQLiteException if an error occurs, such as a syntax error
712 * or invalid number of bind arguments.
713 * @throws OperationCanceledException if the operation was canceled.
715 public int executeForChangedRowCount(String sql, Object[] bindArgs,
716 CancellationSignal cancellationSignal) {
717 if (sql == null) {
718 throw new IllegalArgumentException("sql must not be null.");
721 int changedRows = 0;
722 final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount",
723 sql, bindArgs);
724 try {
725 final PreparedStatement statement = acquirePreparedStatement(sql);
726 try {
727 throwIfStatementForbidden(statement);
728 bindArguments(statement, bindArgs);
729 applyBlockGuardPolicy(statement);
730 attachCancellationSignal(cancellationSignal);
731 try {
732 changedRows = nativeExecuteForChangedRowCount(
733 mConnectionPtr, statement.mStatementPtr);
734 return changedRows;
735 } finally {
736 detachCancellationSignal(cancellationSignal);
738 } finally {
739 releasePreparedStatement(statement);
741 } catch (RuntimeException ex) {
742 mRecentOperations.failOperation(cookie, ex);
743 throw ex;
744 } finally {
745 if (mRecentOperations.endOperationDeferLog(cookie)) {
746 mRecentOperations.logOperation(cookie, "changedRows=" + changedRows);
752 * Executes a statement that returns the row id of the last row inserted
753 * by the statement. Use for INSERT SQL statements.
755 * @param sql The SQL statement to execute.
756 * @param bindArgs The arguments to bind, or null if none.
757 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
758 * @return The row id of the last row that was inserted, or 0 if none.
760 * @throws SQLiteException if an error occurs, such as a syntax error
761 * or invalid number of bind arguments.
762 * @throws OperationCanceledException if the operation was canceled.
764 public long executeForLastInsertedRowId(String sql, Object[] bindArgs,
765 CancellationSignal cancellationSignal) {
766 if (sql == null) {
767 throw new IllegalArgumentException("sql must not be null.");
770 final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId",
771 sql, bindArgs);
772 try {
773 final PreparedStatement statement = acquirePreparedStatement(sql);
774 try {
775 throwIfStatementForbidden(statement);
776 bindArguments(statement, bindArgs);
777 applyBlockGuardPolicy(statement);
778 attachCancellationSignal(cancellationSignal);
779 try {
780 return nativeExecuteForLastInsertedRowId(
781 mConnectionPtr, statement.mStatementPtr);
782 } finally {
783 detachCancellationSignal(cancellationSignal);
785 } finally {
786 releasePreparedStatement(statement);
788 } catch (RuntimeException ex) {
789 mRecentOperations.failOperation(cookie, ex);
790 throw ex;
791 } finally {
792 mRecentOperations.endOperation(cookie);
797 * Executes a statement and populates the specified {@link CursorWindow}
798 * with a range of results. Returns the number of rows that were counted
799 * during query execution.
801 * @param sql The SQL statement to execute.
802 * @param bindArgs The arguments to bind, or null if none.
803 * @param window The cursor window to clear and fill.
804 * @param startPos The start position for filling the window.
805 * @param requiredPos The position of a row that MUST be in the window.
806 * If it won't fit, then the query should discard part of what it filled
807 * so that it does. Must be greater than or equal to <code>startPos</code>.
808 * @param countAllRows True to count all rows that the query would return
809 * regagless of whether they fit in the window.
810 * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
811 * @return The number of rows that were counted during query execution. Might
812 * not be all rows in the result set unless <code>countAllRows</code> is true.
814 * @throws SQLiteException if an error occurs, such as a syntax error
815 * or invalid number of bind arguments.
816 * @throws OperationCanceledException if the operation was canceled.
818 public int executeForCursorWindow(String sql, Object[] bindArgs,
819 CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
820 CancellationSignal cancellationSignal) {
821 if (sql == null) {
822 throw new IllegalArgumentException("sql must not be null.");
824 if (window == null) {
825 throw new IllegalArgumentException("window must not be null.");
828 window.acquireReference();
829 try {
830 int actualPos = -1;
831 int countedRows = -1;
832 int filledRows = -1;
833 final int cookie = mRecentOperations.beginOperation("executeForCursorWindow",
834 sql, bindArgs);
835 try {
836 final PreparedStatement statement = acquirePreparedStatement(sql);
837 try {
838 throwIfStatementForbidden(statement);
839 bindArguments(statement, bindArgs);
840 applyBlockGuardPolicy(statement);
841 attachCancellationSignal(cancellationSignal);
842 try {
843 final long result = nativeExecuteForCursorWindow(
844 mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
845 startPos, requiredPos, countAllRows);
846 actualPos = (int)(result >> 32);
847 countedRows = (int)result;
848 filledRows = window.getNumRows();
849 window.setStartPosition(actualPos);
850 return countedRows;
851 } finally {
852 detachCancellationSignal(cancellationSignal);
854 } finally {
855 releasePreparedStatement(statement);
857 } catch (RuntimeException ex) {
858 mRecentOperations.failOperation(cookie, ex);
859 throw ex;
860 } finally {
861 if (mRecentOperations.endOperationDeferLog(cookie)) {
862 mRecentOperations.logOperation(cookie, "window='" + window
863 + "', startPos=" + startPos
864 + ", actualPos=" + actualPos
865 + ", filledRows=" + filledRows
866 + ", countedRows=" + countedRows);
869 } finally {
870 window.releaseReference();
874 private PreparedStatement acquirePreparedStatement(String sql) {
875 PreparedStatement statement = mPreparedStatementCache.get(sql);
876 boolean skipCache = false;
877 if (statement != null) {
878 if (!statement.mInUse) {
879 return statement;
881 // The statement is already in the cache but is in use (this statement appears
882 // to be not only re-entrant but recursive!). So prepare a new copy of the
883 // statement but do not cache it.
884 skipCache = true;
887 final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
888 try {
889 final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
890 final int type = DatabaseUtils.getSqlStatementType(sql);
891 final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
892 statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly);
893 if (!skipCache && isCacheable(type)) {
894 mPreparedStatementCache.put(sql, statement);
895 statement.mInCache = true;
897 } catch (RuntimeException ex) {
898 // Finalize the statement if an exception occurred and we did not add
899 // it to the cache. If it is already in the cache, then leave it there.
900 if (statement == null || !statement.mInCache) {
901 nativeFinalizeStatement(mConnectionPtr, statementPtr);
903 throw ex;
905 statement.mInUse = true;
906 return statement;
909 private void releasePreparedStatement(PreparedStatement statement) {
910 statement.mInUse = false;
911 if (statement.mInCache) {
912 try {
913 nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr);
914 } catch (SQLiteException ex) {
915 // The statement could not be reset due to an error. Remove it from the cache.
916 // When remove() is called, the cache will invoke its entryRemoved() callback,
917 // which will in turn call finalizePreparedStatement() to finalize and
918 // recycle the statement.
919 if (DEBUG) {
920 Log.d(TAG, "Could not reset prepared statement due to an exception. "
921 + "Removing it from the cache. SQL: "
922 + trimSqlForDisplay(statement.mSql), ex);
925 mPreparedStatementCache.remove(statement.mSql);
927 } else {
928 finalizePreparedStatement(statement);
932 private void finalizePreparedStatement(PreparedStatement statement) {
933 nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr);
934 recyclePreparedStatement(statement);
937 private void attachCancellationSignal(CancellationSignal cancellationSignal) {
938 if (cancellationSignal != null) {
939 cancellationSignal.throwIfCanceled();
941 mCancellationSignalAttachCount += 1;
942 if (mCancellationSignalAttachCount == 1) {
943 // Reset cancellation flag before executing the statement.
944 nativeResetCancel(mConnectionPtr, true /*cancelable*/);
946 // After this point, onCancel() may be called concurrently.
947 cancellationSignal.setOnCancelListener(this);
952 private void detachCancellationSignal(CancellationSignal cancellationSignal) {
953 if (cancellationSignal != null) {
954 assert mCancellationSignalAttachCount > 0;
956 mCancellationSignalAttachCount -= 1;
957 if (mCancellationSignalAttachCount == 0) {
958 // After this point, onCancel() cannot be called concurrently.
959 cancellationSignal.setOnCancelListener(null);
961 // Reset cancellation flag after executing the statement.
962 nativeResetCancel(mConnectionPtr, false /*cancelable*/);
967 // CancellationSignal.OnCancelListener callback.
968 // This method may be called on a different thread than the executing statement.
969 // However, it will only be called between calls to attachCancellationSignal and
970 // detachCancellationSignal, while a statement is executing. We can safely assume
971 // that the SQLite connection is still alive.
972 @Override
973 public void onCancel() {
974 nativeCancel(mConnectionPtr);
977 private void bindArguments(PreparedStatement statement, Object[] bindArgs) {
978 final int count = bindArgs != null ? bindArgs.length : 0;
979 if (count != statement.mNumParameters) {
980 throw new SQLiteBindOrColumnIndexOutOfRangeException(
981 "Expected " + statement.mNumParameters + " bind arguments but "
982 + count + " were provided.");
984 if (count == 0) {
985 return;
988 final long statementPtr = statement.mStatementPtr;
989 for (int i = 0; i < count; i++) {
990 final Object arg = bindArgs[i];
991 switch (DatabaseUtils.getTypeOfObject(arg)) {
992 case Cursor.FIELD_TYPE_NULL:
993 nativeBindNull(mConnectionPtr, statementPtr, i + 1);
994 break;
995 case Cursor.FIELD_TYPE_INTEGER:
996 nativeBindLong(mConnectionPtr, statementPtr, i + 1,
997 ((Number)arg).longValue());
998 break;
999 case Cursor.FIELD_TYPE_FLOAT:
1000 nativeBindDouble(mConnectionPtr, statementPtr, i + 1,
1001 ((Number)arg).doubleValue());
1002 break;
1003 case Cursor.FIELD_TYPE_BLOB:
1004 nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg);
1005 break;
1006 case Cursor.FIELD_TYPE_STRING:
1007 default:
1008 if (arg instanceof Boolean) {
1009 // Provide compatibility with legacy applications which may pass
1010 // Boolean values in bind args.
1011 nativeBindLong(mConnectionPtr, statementPtr, i + 1,
1012 ((Boolean)arg).booleanValue() ? 1 : 0);
1013 } else {
1014 nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString());
1016 break;
1021 private void throwIfStatementForbidden(PreparedStatement statement) {
1022 if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) {
1023 throw new SQLiteException("Cannot execute this statement because it "
1024 + "might modify the database but the connection is read-only.");
1028 private static boolean isCacheable(int statementType) {
1029 if (statementType == DatabaseUtils.STATEMENT_UPDATE
1030 || statementType == DatabaseUtils.STATEMENT_SELECT) {
1031 return true;
1033 return false;
1036 private void applyBlockGuardPolicy(PreparedStatement statement) {
1037 if (!mConfiguration.isInMemoryDb()) {
1038 if (statement.mReadOnly) {
1039 BlockGuard.getThreadPolicy().onReadFromDisk();
1040 } else {
1041 BlockGuard.getThreadPolicy().onWriteToDisk();
1047 * Dumps debugging information about this connection.
1049 * @param printer The printer to receive the dump, not null.
1050 * @param verbose True to dump more verbose information.
1052 public void dump(Printer printer, boolean verbose) {
1053 dumpUnsafe(printer, verbose);
1057 * Dumps debugging information about this connection, in the case where the
1058 * caller might not actually own the connection.
1060 * This function is written so that it may be called by a thread that does not
1061 * own the connection. We need to be very careful because the connection state is
1062 * not synchronized.
1064 * At worst, the method may return stale or slightly wrong data, however
1065 * it should not crash. This is ok as it is only used for diagnostic purposes.
1067 * @param printer The printer to receive the dump, not null.
1068 * @param verbose True to dump more verbose information.
1070 void dumpUnsafe(Printer printer, boolean verbose) {
1071 printer.println("Connection #" + mConnectionId + ":");
1072 if (verbose) {
1073 printer.println(" connectionPtr: 0x" + Long.toHexString(mConnectionPtr));
1075 printer.println(" isPrimaryConnection: " + mIsPrimaryConnection);
1076 printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
1078 mRecentOperations.dump(printer, verbose);
1080 if (verbose) {
1081 mPreparedStatementCache.dump(printer);
1086 * Describes the currently executing operation, in the case where the
1087 * caller might not actually own the connection.
1089 * This function is written so that it may be called by a thread that does not
1090 * own the connection. We need to be very careful because the connection state is
1091 * not synchronized.
1093 * At worst, the method may return stale or slightly wrong data, however
1094 * it should not crash. This is ok as it is only used for diagnostic purposes.
1096 * @return A description of the current operation including how long it has been running,
1097 * or null if none.
1099 String describeCurrentOperationUnsafe() {
1100 return mRecentOperations.describeCurrentOperation();
1104 * Collects statistics about database connection memory usage.
1106 * @param dbStatsList The list to populate.
1108 void collectDbStats(ArrayList<DbStats> dbStatsList) {
1109 // Get information about the main database.
1110 int lookaside = nativeGetDbLookaside(mConnectionPtr);
1111 long pageCount = 0;
1112 long pageSize = 0;
1113 try {
1114 pageCount = executeForLong("PRAGMA page_count;", null, null);
1115 pageSize = executeForLong("PRAGMA page_size;", null, null);
1116 } catch (SQLiteException ex) {
1117 // Ignore.
1119 dbStatsList.add(getMainDbStatsUnsafe(lookaside, pageCount, pageSize));
1121 // Get information about attached databases.
1122 // We ignore the first row in the database list because it corresponds to
1123 // the main database which we have already described.
1124 CursorWindow window = new CursorWindow("collectDbStats");
1125 try {
1126 executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null);
1127 for (int i = 1; i < window.getNumRows(); i++) {
1128 String name = window.getString(i, 1);
1129 String path = window.getString(i, 2);
1130 pageCount = 0;
1131 pageSize = 0;
1132 try {
1133 pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null);
1134 pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null);
1135 } catch (SQLiteException ex) {
1136 // Ignore.
1138 String label = " (attached) " + name;
1139 if (!path.isEmpty()) {
1140 label += ": " + path;
1142 dbStatsList.add(new DbStats(label, pageCount, pageSize, 0, 0, 0, 0));
1144 } catch (SQLiteException ex) {
1145 // Ignore.
1146 } finally {
1147 window.close();
1152 * Collects statistics about database connection memory usage, in the case where the
1153 * caller might not actually own the connection.
1155 * @return The statistics object, never null.
1157 void collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList) {
1158 dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0));
1161 private DbStats getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize) {
1162 // The prepared statement cache is thread-safe so we can access its statistics
1163 // even if we do not own the database connection.
1164 String label = mConfiguration.path;
1165 if (!mIsPrimaryConnection) {
1166 label += " (" + mConnectionId + ")";
1168 return new DbStats(label, pageCount, pageSize, lookaside,
1169 mPreparedStatementCache.hitCount(),
1170 mPreparedStatementCache.missCount(),
1171 mPreparedStatementCache.size());
1174 @Override
1175 public String toString() {
1176 return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")";
1179 private PreparedStatement obtainPreparedStatement(String sql, long statementPtr,
1180 int numParameters, int type, boolean readOnly) {
1181 PreparedStatement statement = mPreparedStatementPool;
1182 if (statement != null) {
1183 mPreparedStatementPool = statement.mPoolNext;
1184 statement.mPoolNext = null;
1185 statement.mInCache = false;
1186 } else {
1187 statement = new PreparedStatement();
1189 statement.mSql = sql;
1190 statement.mStatementPtr = statementPtr;
1191 statement.mNumParameters = numParameters;
1192 statement.mType = type;
1193 statement.mReadOnly = readOnly;
1194 return statement;
1197 private void recyclePreparedStatement(PreparedStatement statement) {
1198 statement.mSql = null;
1199 statement.mPoolNext = mPreparedStatementPool;
1200 mPreparedStatementPool = statement;
1203 private static String trimSqlForDisplay(String sql) {
1204 // Note: Creating and caching a regular expression is expensive at preload-time
1205 // and stops compile-time initialization. This pattern is only used when
1206 // dumping the connection, which is a rare (mainly error) case. So:
1207 // DO NOT CACHE.
1208 return sql.replaceAll("[\\s]*\\n+[\\s]*", " ");
1212 * Holder type for a prepared statement.
1214 * Although this object holds a pointer to a native statement object, it
1215 * does not have a finalizer. This is deliberate. The {@link SQLiteConnection}
1216 * owns the statement object and will take care of freeing it when needed.
1217 * In particular, closing the connection requires a guarantee of deterministic
1218 * resource disposal because all native statement objects must be freed before
1219 * the native database object can be closed. So no finalizers here.
1221 private static final class PreparedStatement {
1222 // Next item in pool.
1223 public PreparedStatement mPoolNext;
1225 // The SQL from which the statement was prepared.
1226 public String mSql;
1228 // The native sqlite3_stmt object pointer.
1229 // Lifetime is managed explicitly by the connection.
1230 public long mStatementPtr;
1232 // The number of parameters that the prepared statement has.
1233 public int mNumParameters;
1235 // The statement type.
1236 public int mType;
1238 // True if the statement is read-only.
1239 public boolean mReadOnly;
1241 // True if the statement is in the cache.
1242 public boolean mInCache;
1244 // True if the statement is in use (currently executing).
1245 // We need this flag because due to the use of custom functions in triggers, it's
1246 // possible for SQLite calls to be re-entrant. Consequently we need to prevent
1247 // in use statements from being finalized until they are no longer in use.
1248 public boolean mInUse;
1251 private final class PreparedStatementCache
1252 extends LruCache<String, PreparedStatement> {
1253 public PreparedStatementCache(int size) {
1254 super(size);
1257 @Override
1258 protected void entryRemoved(boolean evicted, String key,
1259 PreparedStatement oldValue, PreparedStatement newValue) {
1260 oldValue.mInCache = false;
1261 if (!oldValue.mInUse) {
1262 finalizePreparedStatement(oldValue);
1266 public void dump(Printer printer) {
1267 printer.println(" Prepared statement cache:");
1268 Map<String, PreparedStatement> cache = snapshot();
1269 if (!cache.isEmpty()) {
1270 int i = 0;
1271 for (Map.Entry<String, PreparedStatement> entry : cache.entrySet()) {
1272 PreparedStatement statement = entry.getValue();
1273 if (statement.mInCache) { // might be false due to a race with entryRemoved
1274 String sql = entry.getKey();
1275 printer.println(" " + i + ": statementPtr=0x"
1276 + Long.toHexString(statement.mStatementPtr)
1277 + ", numParameters=" + statement.mNumParameters
1278 + ", type=" + statement.mType
1279 + ", readOnly=" + statement.mReadOnly
1280 + ", sql=\"" + trimSqlForDisplay(sql) + "\"");
1282 i += 1;
1284 } else {
1285 printer.println(" <none>");
1290 private static final class OperationLog {
1291 private static final int MAX_RECENT_OPERATIONS = 20;
1292 private static final int COOKIE_GENERATION_SHIFT = 8;
1293 private static final int COOKIE_INDEX_MASK = 0xff;
1295 private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
1296 private int mIndex;
1297 private int mGeneration;
1299 public int beginOperation(String kind, String sql, Object[] bindArgs) {
1300 synchronized (mOperations) {
1301 final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
1302 Operation operation = mOperations[index];
1303 if (operation == null) {
1304 operation = new Operation();
1305 mOperations[index] = operation;
1306 } else {
1307 operation.mFinished = false;
1308 operation.mException = null;
1309 if (operation.mBindArgs != null) {
1310 operation.mBindArgs.clear();
1313 operation.mStartTime = System.currentTimeMillis();
1314 operation.mKind = kind;
1315 operation.mSql = sql;
1316 if (bindArgs != null) {
1317 if (operation.mBindArgs == null) {
1318 operation.mBindArgs = new ArrayList<Object>();
1319 } else {
1320 operation.mBindArgs.clear();
1322 for (int i = 0; i < bindArgs.length; i++) {
1323 final Object arg = bindArgs[i];
1324 if (arg != null && arg instanceof byte[]) {
1325 // Don't hold onto the real byte array longer than necessary.
1326 operation.mBindArgs.add(EMPTY_BYTE_ARRAY);
1327 } else {
1328 operation.mBindArgs.add(arg);
1332 operation.mCookie = newOperationCookieLocked(index);
1333 mIndex = index;
1334 return operation.mCookie;
1338 public void failOperation(int cookie, Exception ex) {
1339 synchronized (mOperations) {
1340 final Operation operation = getOperationLocked(cookie);
1341 if (operation != null) {
1342 operation.mException = ex;
1347 public void endOperation(int cookie) {
1348 synchronized (mOperations) {
1349 if (endOperationDeferLogLocked(cookie)) {
1350 logOperationLocked(cookie, null);
1355 public boolean endOperationDeferLog(int cookie) {
1356 synchronized (mOperations) {
1357 return endOperationDeferLogLocked(cookie);
1361 public void logOperation(int cookie, String detail) {
1362 synchronized (mOperations) {
1363 logOperationLocked(cookie, detail);
1367 private boolean endOperationDeferLogLocked(int cookie) {
1368 final Operation operation = getOperationLocked(cookie);
1369 if (operation != null) {
1370 operation.mEndTime = System.currentTimeMillis();
1371 operation.mFinished = true;
1372 return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
1373 operation.mEndTime - operation.mStartTime);
1375 return false;
1378 private void logOperationLocked(int cookie, String detail) {
1379 final Operation operation = getOperationLocked(cookie);
1380 StringBuilder msg = new StringBuilder();
1381 operation.describe(msg, false);
1382 if (detail != null) {
1383 msg.append(", ").append(detail);
1385 Log.d(TAG, msg.toString());
1388 private int newOperationCookieLocked(int index) {
1389 final int generation = mGeneration++;
1390 return generation << COOKIE_GENERATION_SHIFT | index;
1393 private Operation getOperationLocked(int cookie) {
1394 final int index = cookie & COOKIE_INDEX_MASK;
1395 final Operation operation = mOperations[index];
1396 return operation.mCookie == cookie ? operation : null;
1399 public String describeCurrentOperation() {
1400 synchronized (mOperations) {
1401 final Operation operation = mOperations[mIndex];
1402 if (operation != null && !operation.mFinished) {
1403 StringBuilder msg = new StringBuilder();
1404 operation.describe(msg, false);
1405 return msg.toString();
1407 return null;
1411 public void dump(Printer printer, boolean verbose) {
1412 synchronized (mOperations) {
1413 printer.println(" Most recently executed operations:");
1414 int index = mIndex;
1415 Operation operation = mOperations[index];
1416 if (operation != null) {
1417 int n = 0;
1418 do {
1419 StringBuilder msg = new StringBuilder();
1420 msg.append(" ").append(n).append(": [");
1421 msg.append(operation.getFormattedStartTime());
1422 msg.append("] ");
1423 operation.describe(msg, verbose);
1424 printer.println(msg.toString());
1426 if (index > 0) {
1427 index -= 1;
1428 } else {
1429 index = MAX_RECENT_OPERATIONS - 1;
1431 n += 1;
1432 operation = mOperations[index];
1433 } while (operation != null && n < MAX_RECENT_OPERATIONS);
1434 } else {
1435 printer.println(" <none>");
1441 private static final class Operation {
1442 public long mStartTime;
1443 public long mEndTime;
1444 public String mKind;
1445 public String mSql;
1446 public ArrayList<Object> mBindArgs;
1447 public boolean mFinished;
1448 public Exception mException;
1449 public int mCookie;
1451 public void describe(StringBuilder msg, boolean verbose) {
1452 msg.append(mKind);
1453 if (mFinished) {
1454 msg.append(" took ").append(mEndTime - mStartTime).append("ms");
1455 } else {
1456 msg.append(" started ").append(System.currentTimeMillis() - mStartTime)
1457 .append("ms ago");
1459 msg.append(" - ").append(getStatus());
1460 if (mSql != null) {
1461 msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\"");
1463 if (verbose && mBindArgs != null && mBindArgs.size() != 0) {
1464 msg.append(", bindArgs=[");
1465 final int count = mBindArgs.size();
1466 for (int i = 0; i < count; i++) {
1467 final Object arg = mBindArgs.get(i);
1468 if (i != 0) {
1469 msg.append(", ");
1471 if (arg == null) {
1472 msg.append("null");
1473 } else if (arg instanceof byte[]) {
1474 msg.append("<byte[]>");
1475 } else if (arg instanceof String) {
1476 msg.append("\"").append((String)arg).append("\"");
1477 } else {
1478 msg.append(arg);
1481 msg.append("]");
1483 if (mException != null) {
1484 msg.append(", exception=\"").append(mException.getMessage()).append("\"");
1488 private String getStatus() {
1489 if (!mFinished) {
1490 return "running";
1492 return mException != null ? "failed" : "succeeded";
1495 private String getFormattedStartTime() {
1496 // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created, and is
1497 // relatively expensive to create during preloading. This method is only used
1498 // when dumping a connection, which is a rare (mainly error) case. So:
1499 // DO NOT CACHE.
1500 return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(mStartTime));