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
;
37 import java
.util
.regex
.Pattern
;
40 * Represents a SQLite database connection.
41 * Each connection wraps an instance of a native <code>sqlite3</code> object.
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.
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.
52 * <h2>Ownership and concurrency guarantees</h2>
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.
61 * The guarantee of having a single owner allows this class to be implemented
62 * without locks and greatly simplifies resource management.
65 * <h2>Encapsulation guarantees</h2>
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.
74 * Encapsulation is what ensures that the connection object's
75 * lifecycle does not become a tortured mess of finalizers and reference
81 * This class must tolerate reentrant execution of SQLite operations because
82 * triggers may call custom SQLite functions that perform additional queries.
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
,
131 private static native void nativeBindNull(long connectionPtr
, long statementPtr
,
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
) {
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");
172 protected void finalize() throws Throwable
{
174 if (mPool
!= null && mConnectionPtr
!= 0) {
175 mPool
.onConnectionLeaked();
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
);
193 } catch (SQLiteException ex
) {
194 connection
.dispose(false);
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.
206 private void open() {
207 mConnectionPtr
= nativeOpen(mConfiguration
.path
, mConfiguration
.openFlags
,
208 mConfiguration
.label
,
209 SQLiteDebug
.DEBUG_SQL_STATEMENTS
, SQLiteDebug
.DEBUG_SQL_TIME
);
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) {
229 mCloseGuard
.warnIfOpen();
234 if (mConnectionPtr
!= 0) {
235 final int cookie
= mRecentOperations
.beginOperation("close", null, null);
237 mPreparedStatementCache
.evictAll();
238 nativeClose(mConnectionPtr
);
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());
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")) {
309 } else if (value
.equals("1")) {
311 } else if (value
.equals("2")) {
317 private void setJournalMode(String newValue
) {
318 String value
= executeForString("PRAGMA journal_mode", null, null);
319 if (!value
.equalsIgnoreCase(newValue
)) {
321 String result
= executeForString("PRAGMA journal_mode=" + newValue
, null, null);
322 if (result
.equalsIgnoreCase(newValue
)) {
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) {
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
) {
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
)) {
378 // Go ahead and update the indexes using the new locale.
379 execute("BEGIN", null, null);
380 boolean success
= false;
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);
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();
428 if (walModeChanged
) {
429 setWalModeFromConfiguration();
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.
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.
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.
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.
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
) {
493 throw new IllegalArgumentException("sql must not be null.");
496 final int cookie
= mRecentOperations
.beginOperation("prepare", sql
, null);
498 final PreparedStatement statement
= acquirePreparedStatement(sql
);
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
;
509 outStatementInfo
.columnNames
= new String
[columnCount
];
510 for (int i
= 0; i
< columnCount
; i
++) {
511 outStatementInfo
.columnNames
[i
] = nativeGetColumnName(
512 mConnectionPtr
, statement
.mStatementPtr
, i
);
517 releasePreparedStatement(statement
);
519 } catch (RuntimeException ex
) {
520 mRecentOperations
.failOperation(cookie
, ex
);
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
) {
541 throw new IllegalArgumentException("sql must not be null.");
544 final int cookie
= mRecentOperations
.beginOperation("execute", sql
, bindArgs
);
546 final PreparedStatement statement
= acquirePreparedStatement(sql
);
548 throwIfStatementForbidden(statement
);
549 bindArguments(statement
, bindArgs
);
550 applyBlockGuardPolicy(statement
);
551 attachCancellationSignal(cancellationSignal
);
553 nativeExecute(mConnectionPtr
, statement
.mStatementPtr
);
555 detachCancellationSignal(cancellationSignal
);
558 releasePreparedStatement(statement
);
560 } catch (RuntimeException ex
) {
561 mRecentOperations
.failOperation(cookie
, ex
);
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
) {
584 throw new IllegalArgumentException("sql must not be null.");
587 final int cookie
= mRecentOperations
.beginOperation("executeForLong", sql
, bindArgs
);
589 final PreparedStatement statement
= acquirePreparedStatement(sql
);
591 throwIfStatementForbidden(statement
);
592 bindArguments(statement
, bindArgs
);
593 applyBlockGuardPolicy(statement
);
594 attachCancellationSignal(cancellationSignal
);
596 return nativeExecuteForLong(mConnectionPtr
, statement
.mStatementPtr
);
598 detachCancellationSignal(cancellationSignal
);
601 releasePreparedStatement(statement
);
603 } catch (RuntimeException ex
) {
604 mRecentOperations
.failOperation(cookie
, ex
);
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
) {
627 throw new IllegalArgumentException("sql must not be null.");
630 final int cookie
= mRecentOperations
.beginOperation("executeForString", sql
, bindArgs
);
632 final PreparedStatement statement
= acquirePreparedStatement(sql
);
634 throwIfStatementForbidden(statement
);
635 bindArguments(statement
, bindArgs
);
636 applyBlockGuardPolicy(statement
);
637 attachCancellationSignal(cancellationSignal
);
639 return nativeExecuteForString(mConnectionPtr
, statement
.mStatementPtr
);
641 detachCancellationSignal(cancellationSignal
);
644 releasePreparedStatement(statement
);
646 } catch (RuntimeException ex
) {
647 mRecentOperations
.failOperation(cookie
, ex
);
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,
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
) {
672 throw new IllegalArgumentException("sql must not be null.");
675 final int cookie
= mRecentOperations
.beginOperation("executeForBlobFileDescriptor",
678 final PreparedStatement statement
= acquirePreparedStatement(sql
);
680 throwIfStatementForbidden(statement
);
681 bindArguments(statement
, bindArgs
);
682 applyBlockGuardPolicy(statement
);
683 attachCancellationSignal(cancellationSignal
);
685 int fd
= nativeExecuteForBlobFileDescriptor(
686 mConnectionPtr
, statement
.mStatementPtr
);
687 return fd
>= 0 ? ParcelFileDescriptor
.adoptFd(fd
) : null;
689 detachCancellationSignal(cancellationSignal
);
692 releasePreparedStatement(statement
);
694 } catch (RuntimeException ex
) {
695 mRecentOperations
.failOperation(cookie
, ex
);
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
) {
718 throw new IllegalArgumentException("sql must not be null.");
722 final int cookie
= mRecentOperations
.beginOperation("executeForChangedRowCount",
725 final PreparedStatement statement
= acquirePreparedStatement(sql
);
727 throwIfStatementForbidden(statement
);
728 bindArguments(statement
, bindArgs
);
729 applyBlockGuardPolicy(statement
);
730 attachCancellationSignal(cancellationSignal
);
732 changedRows
= nativeExecuteForChangedRowCount(
733 mConnectionPtr
, statement
.mStatementPtr
);
736 detachCancellationSignal(cancellationSignal
);
739 releasePreparedStatement(statement
);
741 } catch (RuntimeException ex
) {
742 mRecentOperations
.failOperation(cookie
, ex
);
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
) {
767 throw new IllegalArgumentException("sql must not be null.");
770 final int cookie
= mRecentOperations
.beginOperation("executeForLastInsertedRowId",
773 final PreparedStatement statement
= acquirePreparedStatement(sql
);
775 throwIfStatementForbidden(statement
);
776 bindArguments(statement
, bindArgs
);
777 applyBlockGuardPolicy(statement
);
778 attachCancellationSignal(cancellationSignal
);
780 return nativeExecuteForLastInsertedRowId(
781 mConnectionPtr
, statement
.mStatementPtr
);
783 detachCancellationSignal(cancellationSignal
);
786 releasePreparedStatement(statement
);
788 } catch (RuntimeException ex
) {
789 mRecentOperations
.failOperation(cookie
, ex
);
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
) {
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();
831 int countedRows
= -1;
833 final int cookie
= mRecentOperations
.beginOperation("executeForCursorWindow",
836 final PreparedStatement statement
= acquirePreparedStatement(sql
);
838 throwIfStatementForbidden(statement
);
839 bindArguments(statement
, bindArgs
);
840 applyBlockGuardPolicy(statement
);
841 attachCancellationSignal(cancellationSignal
);
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
);
852 detachCancellationSignal(cancellationSignal
);
855 releasePreparedStatement(statement
);
857 } catch (RuntimeException ex
) {
858 mRecentOperations
.failOperation(cookie
, ex
);
861 if (mRecentOperations
.endOperationDeferLog(cookie
)) {
862 mRecentOperations
.logOperation(cookie
, "window='" + window
863 + "', startPos=" + startPos
864 + ", actualPos=" + actualPos
865 + ", filledRows=" + filledRows
866 + ", countedRows=" + countedRows
);
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
) {
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.
887 final long statementPtr
= nativePrepareStatement(mConnectionPtr
, sql
);
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
);
905 statement
.mInUse
= true;
909 private void releasePreparedStatement(PreparedStatement statement
) {
910 statement
.mInUse
= false;
911 if (statement
.mInCache
) {
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.
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
);
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.
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.");
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);
995 case Cursor
.FIELD_TYPE_INTEGER
:
996 nativeBindLong(mConnectionPtr
, statementPtr
, i
+ 1,
997 ((Number
)arg
).longValue());
999 case Cursor
.FIELD_TYPE_FLOAT
:
1000 nativeBindDouble(mConnectionPtr
, statementPtr
, i
+ 1,
1001 ((Number
)arg
).doubleValue());
1003 case Cursor
.FIELD_TYPE_BLOB
:
1004 nativeBindBlob(mConnectionPtr
, statementPtr
, i
+ 1, (byte[])arg
);
1006 case Cursor
.FIELD_TYPE_STRING
:
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);
1014 nativeBindString(mConnectionPtr
, statementPtr
, i
+ 1, arg
.toString());
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
) {
1036 private void applyBlockGuardPolicy(PreparedStatement statement
) {
1037 if (!mConfiguration
.isInMemoryDb()) {
1038 if (statement
.mReadOnly
) {
1039 BlockGuard
.getThreadPolicy().onReadFromDisk();
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
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
+ ":");
1073 printer
.println(" connectionPtr: 0x" + Long
.toHexString(mConnectionPtr
));
1075 printer
.println(" isPrimaryConnection: " + mIsPrimaryConnection
);
1076 printer
.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations
);
1078 mRecentOperations
.dump(printer
, 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
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,
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
);
1114 pageCount
= executeForLong("PRAGMA page_count;", null, null);
1115 pageSize
= executeForLong("PRAGMA page_size;", null, null);
1116 } catch (SQLiteException ex
) {
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");
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);
1133 pageCount
= executeForLong("PRAGMA " + name
+ ".page_count;", null, null);
1134 pageSize
= executeForLong("PRAGMA " + name
+ ".page_size;", null, null);
1135 } catch (SQLiteException ex
) {
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
) {
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());
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;
1187 statement
= new PreparedStatement();
1189 statement
.mSql
= sql
;
1190 statement
.mStatementPtr
= statementPtr
;
1191 statement
.mNumParameters
= numParameters
;
1192 statement
.mType
= type
;
1193 statement
.mReadOnly
= readOnly
;
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:
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.
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.
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
) {
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()) {
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
) + "\"");
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
];
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
;
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
>();
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
);
1328 operation
.mBindArgs
.add(arg
);
1332 operation
.mCookie
= newOperationCookieLocked(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
);
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();
1411 public void dump(Printer printer
, boolean verbose
) {
1412 synchronized (mOperations
) {
1413 printer
.println(" Most recently executed operations:");
1415 Operation operation
= mOperations
[index
];
1416 if (operation
!= null) {
1419 StringBuilder msg
= new StringBuilder();
1420 msg
.append(" ").append(n
).append(": [");
1421 msg
.append(operation
.getFormattedStartTime());
1423 operation
.describe(msg
, verbose
);
1424 printer
.println(msg
.toString());
1429 index
= MAX_RECENT_OPERATIONS
- 1;
1432 operation
= mOperations
[index
];
1433 } while (operation
!= null && n
< MAX_RECENT_OPERATIONS
);
1435 printer
.println(" <none>");
1441 private static final class Operation
{
1442 public long mStartTime
;
1443 public long mEndTime
;
1444 public String mKind
;
1446 public ArrayList
<Object
> mBindArgs
;
1447 public boolean mFinished
;
1448 public Exception mException
;
1451 public void describe(StringBuilder msg
, boolean verbose
) {
1454 msg
.append(" took ").append(mEndTime
- mStartTime
).append("ms");
1456 msg
.append(" started ").append(System
.currentTimeMillis() - mStartTime
)
1459 msg
.append(" - ").append(getStatus());
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
);
1473 } else if (arg
instanceof byte[]) {
1474 msg
.append("<byte[]>");
1475 } else if (arg
instanceof String
) {
1476 msg
.append("\"").append((String
)arg
).append("\"");
1483 if (mException
!= null) {
1484 msg
.append(", exception=\"").append(mException
.getMessage()).append("\"");
1488 private String
getStatus() {
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:
1500 return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(mStartTime
));