SQLiteConnection.java revision 681ec3128e3c3d7ea49bccff2e5615401ac92baf
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.database.sqlite;
18
19import android.database.Cursor;
20import android.database.CursorWindow;
21import android.database.DatabaseUtils;
22import android.database.sqlite.SQLiteDebug.DbStats;
23import android.os.CancellationSignal;
24import android.os.OperationCanceledException;
25import android.os.ParcelFileDescriptor;
26import android.os.SystemClock;
27import android.os.Trace;
28import android.util.Log;
29import android.util.LruCache;
30import android.util.Printer;
31
32import dalvik.system.BlockGuard;
33import dalvik.system.CloseGuard;
34
35import java.text.SimpleDateFormat;
36import java.util.ArrayList;
37import java.util.Date;
38import java.util.Map;
39
40
41/**
42 * Represents a SQLite database connection.
43 * Each connection wraps an instance of a native <code>sqlite3</code> object.
44 * <p>
45 * When database connection pooling is enabled, there can be multiple active
46 * connections to the same database.  Otherwise there is typically only one
47 * connection per database.
48 * </p><p>
49 * When the SQLite WAL feature is enabled, multiple readers and one writer
50 * can concurrently access the database.  Without WAL, readers and writers
51 * are mutually exclusive.
52 * </p>
53 *
54 * <h2>Ownership and concurrency guarantees</h2>
55 * <p>
56 * Connection objects are not thread-safe.  They are acquired as needed to
57 * perform a database operation and are then returned to the pool.  At any
58 * given time, a connection is either owned and used by a {@link SQLiteSession}
59 * object or the {@link SQLiteConnectionPool}.  Those classes are
60 * responsible for serializing operations to guard against concurrent
61 * use of a connection.
62 * </p><p>
63 * The guarantee of having a single owner allows this class to be implemented
64 * without locks and greatly simplifies resource management.
65 * </p>
66 *
67 * <h2>Encapsulation guarantees</h2>
68 * <p>
69 * The connection object object owns *all* of the SQLite related native
70 * objects that are associated with the connection.  What's more, there are
71 * no other objects in the system that are capable of obtaining handles to
72 * those native objects.  Consequently, when the connection is closed, we do
73 * not have to worry about what other components might have references to
74 * its associated SQLite state -- there are none.
75 * </p><p>
76 * Encapsulation is what ensures that the connection object's
77 * lifecycle does not become a tortured mess of finalizers and reference
78 * queues.
79 * </p>
80 *
81 * <h2>Reentrance</h2>
82 * <p>
83 * This class must tolerate reentrant execution of SQLite operations because
84 * triggers may call custom SQLite functions that perform additional queries.
85 * </p>
86 *
87 * @hide
88 */
89public final class SQLiteConnection implements CancellationSignal.OnCancelListener {
90    private static final String TAG = "SQLiteConnection";
91    private static final boolean DEBUG = false;
92
93    private static final String[] EMPTY_STRING_ARRAY = new String[0];
94    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
95
96    private final CloseGuard mCloseGuard = CloseGuard.get();
97
98    private final SQLiteConnectionPool mPool;
99    private final SQLiteDatabaseConfiguration mConfiguration;
100    private final int mConnectionId;
101    private final boolean mIsPrimaryConnection;
102    private final boolean mIsReadOnlyConnection;
103    private final PreparedStatementCache mPreparedStatementCache;
104    private PreparedStatement mPreparedStatementPool;
105
106    // The recent operations log.
107    private final OperationLog mRecentOperations;
108
109    // The native SQLiteConnection pointer.  (FOR INTERNAL USE ONLY)
110    private long mConnectionPtr;
111
112    private boolean mOnlyAllowReadOnlyOperations;
113
114    // The number of times attachCancellationSignal has been called.
115    // Because SQLite statement execution can be reentrant, we keep track of how many
116    // times we have attempted to attach a cancellation signal to the connection so that
117    // we can ensure that we detach the signal at the right time.
118    private int mCancellationSignalAttachCount;
119
120    private static native long nativeOpen(String path, int openFlags, String label,
121            boolean enableTrace, boolean enableProfile, int lookasideSlotSize,
122            int lookasideSlotCount);
123    private static native void nativeClose(long connectionPtr);
124    private static native void nativeRegisterCustomFunction(long connectionPtr,
125            SQLiteCustomFunction function);
126    private static native void nativeRegisterLocalizedCollators(long connectionPtr, String locale);
127    private static native long nativePrepareStatement(long connectionPtr, String sql);
128    private static native void nativeFinalizeStatement(long connectionPtr, long statementPtr);
129    private static native int nativeGetParameterCount(long connectionPtr, long statementPtr);
130    private static native boolean nativeIsReadOnly(long connectionPtr, long statementPtr);
131    private static native int nativeGetColumnCount(long connectionPtr, long statementPtr);
132    private static native String nativeGetColumnName(long connectionPtr, long statementPtr,
133            int index);
134    private static native void nativeBindNull(long connectionPtr, long statementPtr,
135            int index);
136    private static native void nativeBindLong(long connectionPtr, long statementPtr,
137            int index, long value);
138    private static native void nativeBindDouble(long connectionPtr, long statementPtr,
139            int index, double value);
140    private static native void nativeBindString(long connectionPtr, long statementPtr,
141            int index, String value);
142    private static native void nativeBindBlob(long connectionPtr, long statementPtr,
143            int index, byte[] value);
144    private static native void nativeResetStatementAndClearBindings(
145            long connectionPtr, long statementPtr);
146    private static native void nativeExecute(long connectionPtr, long statementPtr);
147    private static native long nativeExecuteForLong(long connectionPtr, long statementPtr);
148    private static native String nativeExecuteForString(long connectionPtr, long statementPtr);
149    private static native int nativeExecuteForBlobFileDescriptor(
150            long connectionPtr, long statementPtr);
151    private static native int nativeExecuteForChangedRowCount(long connectionPtr, long statementPtr);
152    private static native long nativeExecuteForLastInsertedRowId(
153            long connectionPtr, long statementPtr);
154    private static native long nativeExecuteForCursorWindow(
155            long connectionPtr, long statementPtr, long windowPtr,
156            int startPos, int requiredPos, boolean countAllRows);
157    private static native int nativeGetDbLookaside(long connectionPtr);
158    private static native void nativeCancel(long connectionPtr);
159    private static native void nativeResetCancel(long connectionPtr, boolean cancelable);
160
161    private SQLiteConnection(SQLiteConnectionPool pool,
162            SQLiteDatabaseConfiguration configuration,
163            int connectionId, boolean primaryConnection) {
164        mPool = pool;
165        mRecentOperations = new OperationLog(mPool);
166        mConfiguration = new SQLiteDatabaseConfiguration(configuration);
167        mConnectionId = connectionId;
168        mIsPrimaryConnection = primaryConnection;
169        mIsReadOnlyConnection = (configuration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
170        mPreparedStatementCache = new PreparedStatementCache(
171                mConfiguration.maxSqlCacheSize);
172        mCloseGuard.open("close");
173    }
174
175    @Override
176    protected void finalize() throws Throwable {
177        try {
178            if (mPool != null && mConnectionPtr != 0) {
179                mPool.onConnectionLeaked();
180            }
181
182            dispose(true);
183        } finally {
184            super.finalize();
185        }
186    }
187
188    // Called by SQLiteConnectionPool only.
189    static SQLiteConnection open(SQLiteConnectionPool pool,
190            SQLiteDatabaseConfiguration configuration,
191            int connectionId, boolean primaryConnection) {
192        SQLiteConnection connection = new SQLiteConnection(pool, configuration,
193                connectionId, primaryConnection);
194        try {
195            connection.open();
196            return connection;
197        } catch (SQLiteException ex) {
198            connection.dispose(false);
199            throw ex;
200        }
201    }
202
203    // Called by SQLiteConnectionPool only.
204    // Closes the database closes and releases all of its associated resources.
205    // Do not call methods on the connection after it is closed.  It will probably crash.
206    void close() {
207        dispose(false);
208    }
209
210    private void open() {
211        mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
212                mConfiguration.label,
213                SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME,
214                mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount);
215        setPageSize();
216        setForeignKeyModeFromConfiguration();
217        setWalModeFromConfiguration();
218        setJournalSizeLimit();
219        setAutoCheckpointInterval();
220        setLocaleFromConfiguration();
221
222        // Register custom functions.
223        final int functionCount = mConfiguration.customFunctions.size();
224        for (int i = 0; i < functionCount; i++) {
225            SQLiteCustomFunction function = mConfiguration.customFunctions.get(i);
226            nativeRegisterCustomFunction(mConnectionPtr, function);
227        }
228    }
229
230    private void dispose(boolean finalized) {
231        if (mCloseGuard != null) {
232            if (finalized) {
233                mCloseGuard.warnIfOpen();
234            }
235            mCloseGuard.close();
236        }
237
238        if (mConnectionPtr != 0) {
239            final int cookie = mRecentOperations.beginOperation("close", null, null);
240            try {
241                mPreparedStatementCache.evictAll();
242                nativeClose(mConnectionPtr);
243                mConnectionPtr = 0;
244            } finally {
245                mRecentOperations.endOperation(cookie);
246            }
247        }
248    }
249
250    private void setPageSize() {
251        if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
252            final long newValue = SQLiteGlobal.getDefaultPageSize();
253            long value = executeForLong("PRAGMA page_size", null, null);
254            if (value != newValue) {
255                execute("PRAGMA page_size=" + newValue, null, null);
256            }
257        }
258    }
259
260    private void setAutoCheckpointInterval() {
261        if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
262            final long newValue = SQLiteGlobal.getWALAutoCheckpoint();
263            long value = executeForLong("PRAGMA wal_autocheckpoint", null, null);
264            if (value != newValue) {
265                executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null);
266            }
267        }
268    }
269
270    private void setJournalSizeLimit() {
271        if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
272            final long newValue = SQLiteGlobal.getJournalSizeLimit();
273            long value = executeForLong("PRAGMA journal_size_limit", null, null);
274            if (value != newValue) {
275                executeForLong("PRAGMA journal_size_limit=" + newValue, null, null);
276            }
277        }
278    }
279
280    private void setForeignKeyModeFromConfiguration() {
281        if (!mIsReadOnlyConnection) {
282            final long newValue = mConfiguration.foreignKeyConstraintsEnabled ? 1 : 0;
283            long value = executeForLong("PRAGMA foreign_keys", null, null);
284            if (value != newValue) {
285                execute("PRAGMA foreign_keys=" + newValue, null, null);
286            }
287        }
288    }
289
290    private void setWalModeFromConfiguration() {
291        if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
292            final boolean walEnabled =
293                    (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
294            // Use compatibility WAL unless an app explicitly set journal/synchronous mode
295            // or DISABLE_COMPATIBILITY_WAL flag is set
296            final boolean useCompatibilityWal = mConfiguration.useCompatibilityWal();
297            if (walEnabled || useCompatibilityWal) {
298                setJournalMode("WAL");
299                if (useCompatibilityWal && SQLiteCompatibilityWalFlags.areFlagsSet()) {
300                    setSyncMode(SQLiteCompatibilityWalFlags.getWALSyncMode());
301                } else {
302                    setSyncMode(SQLiteGlobal.getWALSyncMode());
303                }
304            } else {
305                setJournalMode(mConfiguration.journalMode == null
306                        ? SQLiteGlobal.getDefaultJournalMode() : mConfiguration.journalMode);
307                setSyncMode(mConfiguration.syncMode == null
308                        ? SQLiteGlobal.getDefaultSyncMode() : mConfiguration.syncMode);
309            }
310        }
311    }
312
313    private void setSyncMode(String newValue) {
314        String value = executeForString("PRAGMA synchronous", null, null);
315        if (!canonicalizeSyncMode(value).equalsIgnoreCase(
316                canonicalizeSyncMode(newValue))) {
317            execute("PRAGMA synchronous=" + newValue, null, null);
318        }
319    }
320
321    private static String canonicalizeSyncMode(String value) {
322        switch (value) {
323            case "0": return "OFF";
324            case "1": return "NORMAL";
325            case "2": return "FULL";
326        }
327        return value;
328    }
329
330    private void setJournalMode(String newValue) {
331        String value = executeForString("PRAGMA journal_mode", null, null);
332        if (!value.equalsIgnoreCase(newValue)) {
333            try {
334                String result = executeForString("PRAGMA journal_mode=" + newValue, null, null);
335                if (result.equalsIgnoreCase(newValue)) {
336                    return;
337                }
338                // PRAGMA journal_mode silently fails and returns the original journal
339                // mode in some cases if the journal mode could not be changed.
340            } catch (SQLiteDatabaseLockedException ex) {
341                // This error (SQLITE_BUSY) occurs if one connection has the database
342                // open in WAL mode and another tries to change it to non-WAL.
343            }
344            // Because we always disable WAL mode when a database is first opened
345            // (even if we intend to re-enable it), we can encounter problems if
346            // there is another open connection to the database somewhere.
347            // This can happen for a variety of reasons such as an application opening
348            // the same database in multiple processes at the same time or if there is a
349            // crashing content provider service that the ActivityManager has
350            // removed from its registry but whose process hasn't quite died yet
351            // by the time it is restarted in a new process.
352            //
353            // If we don't change the journal mode, nothing really bad happens.
354            // In the worst case, an application that enables WAL might not actually
355            // get it, although it can still use connection pooling.
356            Log.w(TAG, "Could not change the database journal mode of '"
357                    + mConfiguration.label + "' from '" + value + "' to '" + newValue
358                    + "' because the database is locked.  This usually means that "
359                    + "there are other open connections to the database which prevents "
360                    + "the database from enabling or disabling write-ahead logging mode.  "
361                    + "Proceeding without changing the journal mode.");
362        }
363    }
364
365    private void setLocaleFromConfiguration() {
366        if ((mConfiguration.openFlags & SQLiteDatabase.NO_LOCALIZED_COLLATORS) != 0) {
367            return;
368        }
369
370        // Register the localized collators.
371        final String newLocale = mConfiguration.locale.toString();
372        nativeRegisterLocalizedCollators(mConnectionPtr, newLocale);
373
374        // If the database is read-only, we cannot modify the android metadata table
375        // or existing indexes.
376        if (mIsReadOnlyConnection) {
377            return;
378        }
379
380        try {
381            // Ensure the android metadata table exists.
382            execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null);
383
384            // Check whether the locale was actually changed.
385            final String oldLocale = executeForString("SELECT locale FROM android_metadata "
386                    + "UNION SELECT NULL ORDER BY locale DESC LIMIT 1", null, null);
387            if (oldLocale != null && oldLocale.equals(newLocale)) {
388                return;
389            }
390
391            // Go ahead and update the indexes using the new locale.
392            execute("BEGIN", null, null);
393            boolean success = false;
394            try {
395                execute("DELETE FROM android_metadata", null, null);
396                execute("INSERT INTO android_metadata (locale) VALUES(?)",
397                        new Object[] { newLocale }, null);
398                execute("REINDEX LOCALIZED", null, null);
399                success = true;
400            } finally {
401                execute(success ? "COMMIT" : "ROLLBACK", null, null);
402            }
403        } catch (RuntimeException ex) {
404            throw new SQLiteException("Failed to change locale for db '" + mConfiguration.label
405                    + "' to '" + newLocale + "'.", ex);
406        }
407    }
408
409    // Called by SQLiteConnectionPool only.
410    void reconfigure(SQLiteDatabaseConfiguration configuration) {
411        mOnlyAllowReadOnlyOperations = false;
412
413        // Register custom functions.
414        final int functionCount = configuration.customFunctions.size();
415        for (int i = 0; i < functionCount; i++) {
416            SQLiteCustomFunction function = configuration.customFunctions.get(i);
417            if (!mConfiguration.customFunctions.contains(function)) {
418                nativeRegisterCustomFunction(mConnectionPtr, function);
419            }
420        }
421
422        // Remember what changed.
423        boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
424                != mConfiguration.foreignKeyConstraintsEnabled;
425        boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
426                & (SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING
427                | SQLiteDatabase.DISABLE_COMPATIBILITY_WAL)) != 0;
428        boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
429
430        // Update configuration parameters.
431        mConfiguration.updateParametersFrom(configuration);
432
433        // Update prepared statement cache size.
434        mPreparedStatementCache.resize(configuration.maxSqlCacheSize);
435
436        // Update foreign key mode.
437        if (foreignKeyModeChanged) {
438            setForeignKeyModeFromConfiguration();
439        }
440
441        // Update WAL.
442        if (walModeChanged) {
443            setWalModeFromConfiguration();
444        }
445
446        // Update locale.
447        if (localeChanged) {
448            setLocaleFromConfiguration();
449        }
450    }
451
452    // Called by SQLiteConnectionPool only.
453    // When set to true, executing write operations will throw SQLiteException.
454    // Preparing statements that might write is ok, just don't execute them.
455    void setOnlyAllowReadOnlyOperations(boolean readOnly) {
456        mOnlyAllowReadOnlyOperations = readOnly;
457    }
458
459    // Called by SQLiteConnectionPool only.
460    // Returns true if the prepared statement cache contains the specified SQL.
461    boolean isPreparedStatementInCache(String sql) {
462        return mPreparedStatementCache.get(sql) != null;
463    }
464
465    /**
466     * Gets the unique id of this connection.
467     * @return The connection id.
468     */
469    public int getConnectionId() {
470        return mConnectionId;
471    }
472
473    /**
474     * Returns true if this is the primary database connection.
475     * @return True if this is the primary database connection.
476     */
477    public boolean isPrimaryConnection() {
478        return mIsPrimaryConnection;
479    }
480
481    /**
482     * Prepares a statement for execution but does not bind its parameters or execute it.
483     * <p>
484     * This method can be used to check for syntax errors during compilation
485     * prior to execution of the statement.  If the {@code outStatementInfo} argument
486     * is not null, the provided {@link SQLiteStatementInfo} object is populated
487     * with information about the statement.
488     * </p><p>
489     * A prepared statement makes no reference to the arguments that may eventually
490     * be bound to it, consequently it it possible to cache certain prepared statements
491     * such as SELECT or INSERT/UPDATE statements.  If the statement is cacheable,
492     * then it will be stored in the cache for later.
493     * </p><p>
494     * To take advantage of this behavior as an optimization, the connection pool
495     * provides a method to acquire a connection that already has a given SQL statement
496     * in its prepared statement cache so that it is ready for execution.
497     * </p>
498     *
499     * @param sql The SQL statement to prepare.
500     * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate
501     * with information about the statement, or null if none.
502     *
503     * @throws SQLiteException if an error occurs, such as a syntax error.
504     */
505    public void prepare(String sql, SQLiteStatementInfo outStatementInfo) {
506        if (sql == null) {
507            throw new IllegalArgumentException("sql must not be null.");
508        }
509
510        final int cookie = mRecentOperations.beginOperation("prepare", sql, null);
511        try {
512            final PreparedStatement statement = acquirePreparedStatement(sql);
513            try {
514                if (outStatementInfo != null) {
515                    outStatementInfo.numParameters = statement.mNumParameters;
516                    outStatementInfo.readOnly = statement.mReadOnly;
517
518                    final int columnCount = nativeGetColumnCount(
519                            mConnectionPtr, statement.mStatementPtr);
520                    if (columnCount == 0) {
521                        outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
522                    } else {
523                        outStatementInfo.columnNames = new String[columnCount];
524                        for (int i = 0; i < columnCount; i++) {
525                            outStatementInfo.columnNames[i] = nativeGetColumnName(
526                                    mConnectionPtr, statement.mStatementPtr, i);
527                        }
528                    }
529                }
530            } finally {
531                releasePreparedStatement(statement);
532            }
533        } catch (RuntimeException ex) {
534            mRecentOperations.failOperation(cookie, ex);
535            throw ex;
536        } finally {
537            mRecentOperations.endOperation(cookie);
538        }
539    }
540
541    /**
542     * Executes a statement that does not return a result.
543     *
544     * @param sql The SQL statement to execute.
545     * @param bindArgs The arguments to bind, or null if none.
546     * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
547     *
548     * @throws SQLiteException if an error occurs, such as a syntax error
549     * or invalid number of bind arguments.
550     * @throws OperationCanceledException if the operation was canceled.
551     */
552    public void execute(String sql, Object[] bindArgs,
553            CancellationSignal cancellationSignal) {
554        if (sql == null) {
555            throw new IllegalArgumentException("sql must not be null.");
556        }
557
558        final int cookie = mRecentOperations.beginOperation("execute", sql, bindArgs);
559        try {
560            final PreparedStatement statement = acquirePreparedStatement(sql);
561            try {
562                throwIfStatementForbidden(statement);
563                bindArguments(statement, bindArgs);
564                applyBlockGuardPolicy(statement);
565                attachCancellationSignal(cancellationSignal);
566                try {
567                    nativeExecute(mConnectionPtr, statement.mStatementPtr);
568                } finally {
569                    detachCancellationSignal(cancellationSignal);
570                }
571            } finally {
572                releasePreparedStatement(statement);
573            }
574        } catch (RuntimeException ex) {
575            mRecentOperations.failOperation(cookie, ex);
576            throw ex;
577        } finally {
578            mRecentOperations.endOperation(cookie);
579        }
580    }
581
582    /**
583     * Executes a statement that returns a single <code>long</code> result.
584     *
585     * @param sql The SQL statement to execute.
586     * @param bindArgs The arguments to bind, or null if none.
587     * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
588     * @return The value of the first column in the first row of the result set
589     * as a <code>long</code>, or zero if none.
590     *
591     * @throws SQLiteException if an error occurs, such as a syntax error
592     * or invalid number of bind arguments.
593     * @throws OperationCanceledException if the operation was canceled.
594     */
595    public long executeForLong(String sql, Object[] bindArgs,
596            CancellationSignal cancellationSignal) {
597        if (sql == null) {
598            throw new IllegalArgumentException("sql must not be null.");
599        }
600
601        final int cookie = mRecentOperations.beginOperation("executeForLong", sql, bindArgs);
602        try {
603            final PreparedStatement statement = acquirePreparedStatement(sql);
604            try {
605                throwIfStatementForbidden(statement);
606                bindArguments(statement, bindArgs);
607                applyBlockGuardPolicy(statement);
608                attachCancellationSignal(cancellationSignal);
609                try {
610                    return nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr);
611                } finally {
612                    detachCancellationSignal(cancellationSignal);
613                }
614            } finally {
615                releasePreparedStatement(statement);
616            }
617        } catch (RuntimeException ex) {
618            mRecentOperations.failOperation(cookie, ex);
619            throw ex;
620        } finally {
621            mRecentOperations.endOperation(cookie);
622        }
623    }
624
625    /**
626     * Executes a statement that returns a single {@link String} result.
627     *
628     * @param sql The SQL statement to execute.
629     * @param bindArgs The arguments to bind, or null if none.
630     * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
631     * @return The value of the first column in the first row of the result set
632     * as a <code>String</code>, or null if none.
633     *
634     * @throws SQLiteException if an error occurs, such as a syntax error
635     * or invalid number of bind arguments.
636     * @throws OperationCanceledException if the operation was canceled.
637     */
638    public String executeForString(String sql, Object[] bindArgs,
639            CancellationSignal cancellationSignal) {
640        if (sql == null) {
641            throw new IllegalArgumentException("sql must not be null.");
642        }
643
644        final int cookie = mRecentOperations.beginOperation("executeForString", sql, bindArgs);
645        try {
646            final PreparedStatement statement = acquirePreparedStatement(sql);
647            try {
648                throwIfStatementForbidden(statement);
649                bindArguments(statement, bindArgs);
650                applyBlockGuardPolicy(statement);
651                attachCancellationSignal(cancellationSignal);
652                try {
653                    return nativeExecuteForString(mConnectionPtr, statement.mStatementPtr);
654                } finally {
655                    detachCancellationSignal(cancellationSignal);
656                }
657            } finally {
658                releasePreparedStatement(statement);
659            }
660        } catch (RuntimeException ex) {
661            mRecentOperations.failOperation(cookie, ex);
662            throw ex;
663        } finally {
664            mRecentOperations.endOperation(cookie);
665        }
666    }
667
668    /**
669     * Executes a statement that returns a single BLOB result as a
670     * file descriptor to a shared memory region.
671     *
672     * @param sql The SQL statement to execute.
673     * @param bindArgs The arguments to bind, or null if none.
674     * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
675     * @return The file descriptor for a shared memory region that contains
676     * the value of the first column in the first row of the result set as a BLOB,
677     * or null if none.
678     *
679     * @throws SQLiteException if an error occurs, such as a syntax error
680     * or invalid number of bind arguments.
681     * @throws OperationCanceledException if the operation was canceled.
682     */
683    public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
684            CancellationSignal cancellationSignal) {
685        if (sql == null) {
686            throw new IllegalArgumentException("sql must not be null.");
687        }
688
689        final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor",
690                sql, bindArgs);
691        try {
692            final PreparedStatement statement = acquirePreparedStatement(sql);
693            try {
694                throwIfStatementForbidden(statement);
695                bindArguments(statement, bindArgs);
696                applyBlockGuardPolicy(statement);
697                attachCancellationSignal(cancellationSignal);
698                try {
699                    int fd = nativeExecuteForBlobFileDescriptor(
700                            mConnectionPtr, statement.mStatementPtr);
701                    return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null;
702                } finally {
703                    detachCancellationSignal(cancellationSignal);
704                }
705            } finally {
706                releasePreparedStatement(statement);
707            }
708        } catch (RuntimeException ex) {
709            mRecentOperations.failOperation(cookie, ex);
710            throw ex;
711        } finally {
712            mRecentOperations.endOperation(cookie);
713        }
714    }
715
716    /**
717     * Executes a statement that returns a count of the number of rows
718     * that were changed.  Use for UPDATE or DELETE SQL statements.
719     *
720     * @param sql The SQL statement to execute.
721     * @param bindArgs The arguments to bind, or null if none.
722     * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
723     * @return The number of rows that were changed.
724     *
725     * @throws SQLiteException if an error occurs, such as a syntax error
726     * or invalid number of bind arguments.
727     * @throws OperationCanceledException if the operation was canceled.
728     */
729    public int executeForChangedRowCount(String sql, Object[] bindArgs,
730            CancellationSignal cancellationSignal) {
731        if (sql == null) {
732            throw new IllegalArgumentException("sql must not be null.");
733        }
734
735        int changedRows = 0;
736        final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount",
737                sql, bindArgs);
738        try {
739            final PreparedStatement statement = acquirePreparedStatement(sql);
740            try {
741                throwIfStatementForbidden(statement);
742                bindArguments(statement, bindArgs);
743                applyBlockGuardPolicy(statement);
744                attachCancellationSignal(cancellationSignal);
745                try {
746                    changedRows = nativeExecuteForChangedRowCount(
747                            mConnectionPtr, statement.mStatementPtr);
748                    return changedRows;
749                } finally {
750                    detachCancellationSignal(cancellationSignal);
751                }
752            } finally {
753                releasePreparedStatement(statement);
754            }
755        } catch (RuntimeException ex) {
756            mRecentOperations.failOperation(cookie, ex);
757            throw ex;
758        } finally {
759            if (mRecentOperations.endOperationDeferLog(cookie)) {
760                mRecentOperations.logOperation(cookie, "changedRows=" + changedRows);
761            }
762        }
763    }
764
765    /**
766     * Executes a statement that returns the row id of the last row inserted
767     * by the statement.  Use for INSERT SQL statements.
768     *
769     * @param sql The SQL statement to execute.
770     * @param bindArgs The arguments to bind, or null if none.
771     * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
772     * @return The row id of the last row that was inserted, or 0 if none.
773     *
774     * @throws SQLiteException if an error occurs, such as a syntax error
775     * or invalid number of bind arguments.
776     * @throws OperationCanceledException if the operation was canceled.
777     */
778    public long executeForLastInsertedRowId(String sql, Object[] bindArgs,
779            CancellationSignal cancellationSignal) {
780        if (sql == null) {
781            throw new IllegalArgumentException("sql must not be null.");
782        }
783
784        final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId",
785                sql, bindArgs);
786        try {
787            final PreparedStatement statement = acquirePreparedStatement(sql);
788            try {
789                throwIfStatementForbidden(statement);
790                bindArguments(statement, bindArgs);
791                applyBlockGuardPolicy(statement);
792                attachCancellationSignal(cancellationSignal);
793                try {
794                    return nativeExecuteForLastInsertedRowId(
795                            mConnectionPtr, statement.mStatementPtr);
796                } finally {
797                    detachCancellationSignal(cancellationSignal);
798                }
799            } finally {
800                releasePreparedStatement(statement);
801            }
802        } catch (RuntimeException ex) {
803            mRecentOperations.failOperation(cookie, ex);
804            throw ex;
805        } finally {
806            mRecentOperations.endOperation(cookie);
807        }
808    }
809
810    /**
811     * Executes a statement and populates the specified {@link CursorWindow}
812     * with a range of results.  Returns the number of rows that were counted
813     * during query execution.
814     *
815     * @param sql The SQL statement to execute.
816     * @param bindArgs The arguments to bind, or null if none.
817     * @param window The cursor window to clear and fill.
818     * @param startPos The start position for filling the window.
819     * @param requiredPos The position of a row that MUST be in the window.
820     * If it won't fit, then the query should discard part of what it filled
821     * so that it does.  Must be greater than or equal to <code>startPos</code>.
822     * @param countAllRows True to count all rows that the query would return
823     * regagless of whether they fit in the window.
824     * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
825     * @return The number of rows that were counted during query execution.  Might
826     * not be all rows in the result set unless <code>countAllRows</code> is true.
827     *
828     * @throws SQLiteException if an error occurs, such as a syntax error
829     * or invalid number of bind arguments.
830     * @throws OperationCanceledException if the operation was canceled.
831     */
832    public int executeForCursorWindow(String sql, Object[] bindArgs,
833            CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
834            CancellationSignal cancellationSignal) {
835        if (sql == null) {
836            throw new IllegalArgumentException("sql must not be null.");
837        }
838        if (window == null) {
839            throw new IllegalArgumentException("window must not be null.");
840        }
841
842        window.acquireReference();
843        try {
844            int actualPos = -1;
845            int countedRows = -1;
846            int filledRows = -1;
847            final int cookie = mRecentOperations.beginOperation("executeForCursorWindow",
848                    sql, bindArgs);
849            try {
850                final PreparedStatement statement = acquirePreparedStatement(sql);
851                try {
852                    throwIfStatementForbidden(statement);
853                    bindArguments(statement, bindArgs);
854                    applyBlockGuardPolicy(statement);
855                    attachCancellationSignal(cancellationSignal);
856                    try {
857                        final long result = nativeExecuteForCursorWindow(
858                                mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
859                                startPos, requiredPos, countAllRows);
860                        actualPos = (int)(result >> 32);
861                        countedRows = (int)result;
862                        filledRows = window.getNumRows();
863                        window.setStartPosition(actualPos);
864                        return countedRows;
865                    } finally {
866                        detachCancellationSignal(cancellationSignal);
867                    }
868                } finally {
869                    releasePreparedStatement(statement);
870                }
871            } catch (RuntimeException ex) {
872                mRecentOperations.failOperation(cookie, ex);
873                throw ex;
874            } finally {
875                if (mRecentOperations.endOperationDeferLog(cookie)) {
876                    mRecentOperations.logOperation(cookie, "window='" + window
877                            + "', startPos=" + startPos
878                            + ", actualPos=" + actualPos
879                            + ", filledRows=" + filledRows
880                            + ", countedRows=" + countedRows);
881                }
882            }
883        } finally {
884            window.releaseReference();
885        }
886    }
887
888    private PreparedStatement acquirePreparedStatement(String sql) {
889        PreparedStatement statement = mPreparedStatementCache.get(sql);
890        boolean skipCache = false;
891        if (statement != null) {
892            if (!statement.mInUse) {
893                return statement;
894            }
895            // The statement is already in the cache but is in use (this statement appears
896            // to be not only re-entrant but recursive!).  So prepare a new copy of the
897            // statement but do not cache it.
898            skipCache = true;
899        }
900
901        final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
902        try {
903            final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
904            final int type = DatabaseUtils.getSqlStatementType(sql);
905            final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
906            statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly);
907            if (!skipCache && isCacheable(type)) {
908                mPreparedStatementCache.put(sql, statement);
909                statement.mInCache = true;
910            }
911        } catch (RuntimeException ex) {
912            // Finalize the statement if an exception occurred and we did not add
913            // it to the cache.  If it is already in the cache, then leave it there.
914            if (statement == null || !statement.mInCache) {
915                nativeFinalizeStatement(mConnectionPtr, statementPtr);
916            }
917            throw ex;
918        }
919        statement.mInUse = true;
920        return statement;
921    }
922
923    private void releasePreparedStatement(PreparedStatement statement) {
924        statement.mInUse = false;
925        if (statement.mInCache) {
926            try {
927                nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr);
928            } catch (SQLiteException ex) {
929                // The statement could not be reset due to an error.  Remove it from the cache.
930                // When remove() is called, the cache will invoke its entryRemoved() callback,
931                // which will in turn call finalizePreparedStatement() to finalize and
932                // recycle the statement.
933                if (DEBUG) {
934                    Log.d(TAG, "Could not reset prepared statement due to an exception.  "
935                            + "Removing it from the cache.  SQL: "
936                            + trimSqlForDisplay(statement.mSql), ex);
937                }
938
939                mPreparedStatementCache.remove(statement.mSql);
940            }
941        } else {
942            finalizePreparedStatement(statement);
943        }
944    }
945
946    private void finalizePreparedStatement(PreparedStatement statement) {
947        nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr);
948        recyclePreparedStatement(statement);
949    }
950
951    private void attachCancellationSignal(CancellationSignal cancellationSignal) {
952        if (cancellationSignal != null) {
953            cancellationSignal.throwIfCanceled();
954
955            mCancellationSignalAttachCount += 1;
956            if (mCancellationSignalAttachCount == 1) {
957                // Reset cancellation flag before executing the statement.
958                nativeResetCancel(mConnectionPtr, true /*cancelable*/);
959
960                // After this point, onCancel() may be called concurrently.
961                cancellationSignal.setOnCancelListener(this);
962            }
963        }
964    }
965
966    private void detachCancellationSignal(CancellationSignal cancellationSignal) {
967        if (cancellationSignal != null) {
968            assert mCancellationSignalAttachCount > 0;
969
970            mCancellationSignalAttachCount -= 1;
971            if (mCancellationSignalAttachCount == 0) {
972                // After this point, onCancel() cannot be called concurrently.
973                cancellationSignal.setOnCancelListener(null);
974
975                // Reset cancellation flag after executing the statement.
976                nativeResetCancel(mConnectionPtr, false /*cancelable*/);
977            }
978        }
979    }
980
981    // CancellationSignal.OnCancelListener callback.
982    // This method may be called on a different thread than the executing statement.
983    // However, it will only be called between calls to attachCancellationSignal and
984    // detachCancellationSignal, while a statement is executing.  We can safely assume
985    // that the SQLite connection is still alive.
986    @Override
987    public void onCancel() {
988        nativeCancel(mConnectionPtr);
989    }
990
991    private void bindArguments(PreparedStatement statement, Object[] bindArgs) {
992        final int count = bindArgs != null ? bindArgs.length : 0;
993        if (count != statement.mNumParameters) {
994            throw new SQLiteBindOrColumnIndexOutOfRangeException(
995                    "Expected " + statement.mNumParameters + " bind arguments but "
996                    + count + " were provided.");
997        }
998        if (count == 0) {
999            return;
1000        }
1001
1002        final long statementPtr = statement.mStatementPtr;
1003        for (int i = 0; i < count; i++) {
1004            final Object arg = bindArgs[i];
1005            switch (DatabaseUtils.getTypeOfObject(arg)) {
1006                case Cursor.FIELD_TYPE_NULL:
1007                    nativeBindNull(mConnectionPtr, statementPtr, i + 1);
1008                    break;
1009                case Cursor.FIELD_TYPE_INTEGER:
1010                    nativeBindLong(mConnectionPtr, statementPtr, i + 1,
1011                            ((Number)arg).longValue());
1012                    break;
1013                case Cursor.FIELD_TYPE_FLOAT:
1014                    nativeBindDouble(mConnectionPtr, statementPtr, i + 1,
1015                            ((Number)arg).doubleValue());
1016                    break;
1017                case Cursor.FIELD_TYPE_BLOB:
1018                    nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg);
1019                    break;
1020                case Cursor.FIELD_TYPE_STRING:
1021                default:
1022                    if (arg instanceof Boolean) {
1023                        // Provide compatibility with legacy applications which may pass
1024                        // Boolean values in bind args.
1025                        nativeBindLong(mConnectionPtr, statementPtr, i + 1,
1026                                ((Boolean)arg).booleanValue() ? 1 : 0);
1027                    } else {
1028                        nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString());
1029                    }
1030                    break;
1031            }
1032        }
1033    }
1034
1035    private void throwIfStatementForbidden(PreparedStatement statement) {
1036        if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) {
1037            throw new SQLiteException("Cannot execute this statement because it "
1038                    + "might modify the database but the connection is read-only.");
1039        }
1040    }
1041
1042    private static boolean isCacheable(int statementType) {
1043        if (statementType == DatabaseUtils.STATEMENT_UPDATE
1044                || statementType == DatabaseUtils.STATEMENT_SELECT) {
1045            return true;
1046        }
1047        return false;
1048    }
1049
1050    private void applyBlockGuardPolicy(PreparedStatement statement) {
1051        if (!mConfiguration.isInMemoryDb()) {
1052            if (statement.mReadOnly) {
1053                BlockGuard.getThreadPolicy().onReadFromDisk();
1054            } else {
1055                BlockGuard.getThreadPolicy().onWriteToDisk();
1056            }
1057        }
1058    }
1059
1060    /**
1061     * Dumps debugging information about this connection.
1062     *
1063     * @param printer The printer to receive the dump, not null.
1064     * @param verbose True to dump more verbose information.
1065     */
1066    public void dump(Printer printer, boolean verbose) {
1067        dumpUnsafe(printer, verbose);
1068    }
1069
1070    /**
1071     * Dumps debugging information about this connection, in the case where the
1072     * caller might not actually own the connection.
1073     *
1074     * This function is written so that it may be called by a thread that does not
1075     * own the connection.  We need to be very careful because the connection state is
1076     * not synchronized.
1077     *
1078     * At worst, the method may return stale or slightly wrong data, however
1079     * it should not crash.  This is ok as it is only used for diagnostic purposes.
1080     *
1081     * @param printer The printer to receive the dump, not null.
1082     * @param verbose True to dump more verbose information.
1083     */
1084    void dumpUnsafe(Printer printer, boolean verbose) {
1085        printer.println("Connection #" + mConnectionId + ":");
1086        if (verbose) {
1087            printer.println("  connectionPtr: 0x" + Long.toHexString(mConnectionPtr));
1088        }
1089        printer.println("  isPrimaryConnection: " + mIsPrimaryConnection);
1090        printer.println("  onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
1091
1092        mRecentOperations.dump(printer, verbose);
1093
1094        if (verbose) {
1095            mPreparedStatementCache.dump(printer);
1096        }
1097    }
1098
1099    /**
1100     * Describes the currently executing operation, in the case where the
1101     * caller might not actually own the connection.
1102     *
1103     * This function is written so that it may be called by a thread that does not
1104     * own the connection.  We need to be very careful because the connection state is
1105     * not synchronized.
1106     *
1107     * At worst, the method may return stale or slightly wrong data, however
1108     * it should not crash.  This is ok as it is only used for diagnostic purposes.
1109     *
1110     * @return A description of the current operation including how long it has been running,
1111     * or null if none.
1112     */
1113    String describeCurrentOperationUnsafe() {
1114        return mRecentOperations.describeCurrentOperation();
1115    }
1116
1117    /**
1118     * Collects statistics about database connection memory usage.
1119     *
1120     * @param dbStatsList The list to populate.
1121     */
1122    void collectDbStats(ArrayList<DbStats> dbStatsList) {
1123        // Get information about the main database.
1124        int lookaside = nativeGetDbLookaside(mConnectionPtr);
1125        long pageCount = 0;
1126        long pageSize = 0;
1127        try {
1128            pageCount = executeForLong("PRAGMA page_count;", null, null);
1129            pageSize = executeForLong("PRAGMA page_size;", null, null);
1130        } catch (SQLiteException ex) {
1131            // Ignore.
1132        }
1133        dbStatsList.add(getMainDbStatsUnsafe(lookaside, pageCount, pageSize));
1134
1135        // Get information about attached databases.
1136        // We ignore the first row in the database list because it corresponds to
1137        // the main database which we have already described.
1138        CursorWindow window = new CursorWindow("collectDbStats");
1139        try {
1140            executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null);
1141            for (int i = 1; i < window.getNumRows(); i++) {
1142                String name = window.getString(i, 1);
1143                String path = window.getString(i, 2);
1144                pageCount = 0;
1145                pageSize = 0;
1146                try {
1147                    pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null);
1148                    pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null);
1149                } catch (SQLiteException ex) {
1150                    // Ignore.
1151                }
1152                String label = "  (attached) " + name;
1153                if (!path.isEmpty()) {
1154                    label += ": " + path;
1155                }
1156                dbStatsList.add(new DbStats(label, pageCount, pageSize, 0, 0, 0, 0));
1157            }
1158        } catch (SQLiteException ex) {
1159            // Ignore.
1160        } finally {
1161            window.close();
1162        }
1163    }
1164
1165    /**
1166     * Collects statistics about database connection memory usage, in the case where the
1167     * caller might not actually own the connection.
1168     *
1169     * @return The statistics object, never null.
1170     */
1171    void collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList) {
1172        dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0));
1173    }
1174
1175    private DbStats getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize) {
1176        // The prepared statement cache is thread-safe so we can access its statistics
1177        // even if we do not own the database connection.
1178        String label = mConfiguration.path;
1179        if (!mIsPrimaryConnection) {
1180            label += " (" + mConnectionId + ")";
1181        }
1182        return new DbStats(label, pageCount, pageSize, lookaside,
1183                mPreparedStatementCache.hitCount(),
1184                mPreparedStatementCache.missCount(),
1185                mPreparedStatementCache.size());
1186    }
1187
1188    @Override
1189    public String toString() {
1190        return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")";
1191    }
1192
1193    private PreparedStatement obtainPreparedStatement(String sql, long statementPtr,
1194            int numParameters, int type, boolean readOnly) {
1195        PreparedStatement statement = mPreparedStatementPool;
1196        if (statement != null) {
1197            mPreparedStatementPool = statement.mPoolNext;
1198            statement.mPoolNext = null;
1199            statement.mInCache = false;
1200        } else {
1201            statement = new PreparedStatement();
1202        }
1203        statement.mSql = sql;
1204        statement.mStatementPtr = statementPtr;
1205        statement.mNumParameters = numParameters;
1206        statement.mType = type;
1207        statement.mReadOnly = readOnly;
1208        return statement;
1209    }
1210
1211    private void recyclePreparedStatement(PreparedStatement statement) {
1212        statement.mSql = null;
1213        statement.mPoolNext = mPreparedStatementPool;
1214        mPreparedStatementPool = statement;
1215    }
1216
1217    private static String trimSqlForDisplay(String sql) {
1218        // Note: Creating and caching a regular expression is expensive at preload-time
1219        //       and stops compile-time initialization. This pattern is only used when
1220        //       dumping the connection, which is a rare (mainly error) case. So:
1221        //       DO NOT CACHE.
1222        return sql.replaceAll("[\\s]*\\n+[\\s]*", " ");
1223    }
1224
1225    /**
1226     * Holder type for a prepared statement.
1227     *
1228     * Although this object holds a pointer to a native statement object, it
1229     * does not have a finalizer.  This is deliberate.  The {@link SQLiteConnection}
1230     * owns the statement object and will take care of freeing it when needed.
1231     * In particular, closing the connection requires a guarantee of deterministic
1232     * resource disposal because all native statement objects must be freed before
1233     * the native database object can be closed.  So no finalizers here.
1234     */
1235    private static final class PreparedStatement {
1236        // Next item in pool.
1237        public PreparedStatement mPoolNext;
1238
1239        // The SQL from which the statement was prepared.
1240        public String mSql;
1241
1242        // The native sqlite3_stmt object pointer.
1243        // Lifetime is managed explicitly by the connection.
1244        public long mStatementPtr;
1245
1246        // The number of parameters that the prepared statement has.
1247        public int mNumParameters;
1248
1249        // The statement type.
1250        public int mType;
1251
1252        // True if the statement is read-only.
1253        public boolean mReadOnly;
1254
1255        // True if the statement is in the cache.
1256        public boolean mInCache;
1257
1258        // True if the statement is in use (currently executing).
1259        // We need this flag because due to the use of custom functions in triggers, it's
1260        // possible for SQLite calls to be re-entrant.  Consequently we need to prevent
1261        // in use statements from being finalized until they are no longer in use.
1262        public boolean mInUse;
1263    }
1264
1265    private final class PreparedStatementCache
1266            extends LruCache<String, PreparedStatement> {
1267        public PreparedStatementCache(int size) {
1268            super(size);
1269        }
1270
1271        @Override
1272        protected void entryRemoved(boolean evicted, String key,
1273                PreparedStatement oldValue, PreparedStatement newValue) {
1274            oldValue.mInCache = false;
1275            if (!oldValue.mInUse) {
1276                finalizePreparedStatement(oldValue);
1277            }
1278        }
1279
1280        public void dump(Printer printer) {
1281            printer.println("  Prepared statement cache:");
1282            Map<String, PreparedStatement> cache = snapshot();
1283            if (!cache.isEmpty()) {
1284                int i = 0;
1285                for (Map.Entry<String, PreparedStatement> entry : cache.entrySet()) {
1286                    PreparedStatement statement = entry.getValue();
1287                    if (statement.mInCache) { // might be false due to a race with entryRemoved
1288                        String sql = entry.getKey();
1289                        printer.println("    " + i + ": statementPtr=0x"
1290                                + Long.toHexString(statement.mStatementPtr)
1291                                + ", numParameters=" + statement.mNumParameters
1292                                + ", type=" + statement.mType
1293                                + ", readOnly=" + statement.mReadOnly
1294                                + ", sql=\"" + trimSqlForDisplay(sql) + "\"");
1295                    }
1296                    i += 1;
1297                }
1298            } else {
1299                printer.println("    <none>");
1300            }
1301        }
1302    }
1303
1304    private static final class OperationLog {
1305        private static final int MAX_RECENT_OPERATIONS = 20;
1306        private static final int COOKIE_GENERATION_SHIFT = 8;
1307        private static final int COOKIE_INDEX_MASK = 0xff;
1308
1309        private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
1310        private int mIndex;
1311        private int mGeneration;
1312        private final SQLiteConnectionPool mPool;
1313
1314        OperationLog(SQLiteConnectionPool pool) {
1315            mPool = pool;
1316        }
1317
1318        public int beginOperation(String kind, String sql, Object[] bindArgs) {
1319            synchronized (mOperations) {
1320                final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
1321                Operation operation = mOperations[index];
1322                if (operation == null) {
1323                    operation = new Operation();
1324                    mOperations[index] = operation;
1325                } else {
1326                    operation.mFinished = false;
1327                    operation.mException = null;
1328                    if (operation.mBindArgs != null) {
1329                        operation.mBindArgs.clear();
1330                    }
1331                }
1332                operation.mStartWallTime = System.currentTimeMillis();
1333                operation.mStartTime = SystemClock.uptimeMillis();
1334                operation.mKind = kind;
1335                operation.mSql = sql;
1336                if (bindArgs != null) {
1337                    if (operation.mBindArgs == null) {
1338                        operation.mBindArgs = new ArrayList<Object>();
1339                    } else {
1340                        operation.mBindArgs.clear();
1341                    }
1342                    for (int i = 0; i < bindArgs.length; i++) {
1343                        final Object arg = bindArgs[i];
1344                        if (arg != null && arg instanceof byte[]) {
1345                            // Don't hold onto the real byte array longer than necessary.
1346                            operation.mBindArgs.add(EMPTY_BYTE_ARRAY);
1347                        } else {
1348                            operation.mBindArgs.add(arg);
1349                        }
1350                    }
1351                }
1352                operation.mCookie = newOperationCookieLocked(index);
1353                if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
1354                    Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
1355                            operation.mCookie);
1356                }
1357                mIndex = index;
1358                return operation.mCookie;
1359            }
1360        }
1361
1362        public void failOperation(int cookie, Exception ex) {
1363            synchronized (mOperations) {
1364                final Operation operation = getOperationLocked(cookie);
1365                if (operation != null) {
1366                    operation.mException = ex;
1367                }
1368            }
1369        }
1370
1371        public void endOperation(int cookie) {
1372            synchronized (mOperations) {
1373                if (endOperationDeferLogLocked(cookie)) {
1374                    logOperationLocked(cookie, null);
1375                }
1376            }
1377        }
1378
1379        public boolean endOperationDeferLog(int cookie) {
1380            synchronized (mOperations) {
1381                return endOperationDeferLogLocked(cookie);
1382            }
1383        }
1384
1385        public void logOperation(int cookie, String detail) {
1386            synchronized (mOperations) {
1387                logOperationLocked(cookie, detail);
1388            }
1389        }
1390
1391        private boolean endOperationDeferLogLocked(int cookie) {
1392            final Operation operation = getOperationLocked(cookie);
1393            if (operation != null) {
1394                if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
1395                    Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
1396                            operation.mCookie);
1397                }
1398                operation.mEndTime = SystemClock.uptimeMillis();
1399                operation.mFinished = true;
1400                final long execTime = operation.mEndTime - operation.mStartTime;
1401                mPool.onStatementExecuted(execTime);
1402                return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
1403                        execTime);
1404            }
1405            return false;
1406        }
1407
1408        private void logOperationLocked(int cookie, String detail) {
1409            final Operation operation = getOperationLocked(cookie);
1410            StringBuilder msg = new StringBuilder();
1411            operation.describe(msg, false);
1412            if (detail != null) {
1413                msg.append(", ").append(detail);
1414            }
1415            Log.d(TAG, msg.toString());
1416        }
1417
1418        private int newOperationCookieLocked(int index) {
1419            final int generation = mGeneration++;
1420            return generation << COOKIE_GENERATION_SHIFT | index;
1421        }
1422
1423        private Operation getOperationLocked(int cookie) {
1424            final int index = cookie & COOKIE_INDEX_MASK;
1425            final Operation operation = mOperations[index];
1426            return operation.mCookie == cookie ? operation : null;
1427        }
1428
1429        public String describeCurrentOperation() {
1430            synchronized (mOperations) {
1431                final Operation operation = mOperations[mIndex];
1432                if (operation != null && !operation.mFinished) {
1433                    StringBuilder msg = new StringBuilder();
1434                    operation.describe(msg, false);
1435                    return msg.toString();
1436                }
1437                return null;
1438            }
1439        }
1440
1441        public void dump(Printer printer, boolean verbose) {
1442            synchronized (mOperations) {
1443                printer.println("  Most recently executed operations:");
1444                int index = mIndex;
1445                Operation operation = mOperations[index];
1446                if (operation != null) {
1447                    // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created,
1448                    // and is relatively expensive to create during preloading. This method is only
1449                    // used when dumping a connection, which is a rare (mainly error) case.
1450                    SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
1451                    int n = 0;
1452                    do {
1453                        StringBuilder msg = new StringBuilder();
1454                        msg.append("    ").append(n).append(": [");
1455                        String formattedStartTime = opDF.format(new Date(operation.mStartWallTime));
1456                        msg.append(formattedStartTime);
1457                        msg.append("] ");
1458                        operation.describe(msg, verbose);
1459                        printer.println(msg.toString());
1460
1461                        if (index > 0) {
1462                            index -= 1;
1463                        } else {
1464                            index = MAX_RECENT_OPERATIONS - 1;
1465                        }
1466                        n += 1;
1467                        operation = mOperations[index];
1468                    } while (operation != null && n < MAX_RECENT_OPERATIONS);
1469                } else {
1470                    printer.println("    <none>");
1471                }
1472            }
1473        }
1474    }
1475
1476    private static final class Operation {
1477        // Trim all SQL statements to 256 characters inside the trace marker.
1478        // This limit gives plenty of context while leaving space for other
1479        // entries in the trace buffer (and ensures atrace doesn't truncate the
1480        // marker for us, potentially losing metadata in the process).
1481        private static final int MAX_TRACE_METHOD_NAME_LEN = 256;
1482
1483        public long mStartWallTime; // in System.currentTimeMillis()
1484        public long mStartTime; // in SystemClock.uptimeMillis();
1485        public long mEndTime; // in SystemClock.uptimeMillis();
1486        public String mKind;
1487        public String mSql;
1488        public ArrayList<Object> mBindArgs;
1489        public boolean mFinished;
1490        public Exception mException;
1491        public int mCookie;
1492
1493        public void describe(StringBuilder msg, boolean verbose) {
1494            msg.append(mKind);
1495            if (mFinished) {
1496                msg.append(" took ").append(mEndTime - mStartTime).append("ms");
1497            } else {
1498                msg.append(" started ").append(System.currentTimeMillis() - mStartWallTime)
1499                        .append("ms ago");
1500            }
1501            msg.append(" - ").append(getStatus());
1502            if (mSql != null) {
1503                msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\"");
1504            }
1505            if (verbose && mBindArgs != null && mBindArgs.size() != 0) {
1506                msg.append(", bindArgs=[");
1507                final int count = mBindArgs.size();
1508                for (int i = 0; i < count; i++) {
1509                    final Object arg = mBindArgs.get(i);
1510                    if (i != 0) {
1511                        msg.append(", ");
1512                    }
1513                    if (arg == null) {
1514                        msg.append("null");
1515                    } else if (arg instanceof byte[]) {
1516                        msg.append("<byte[]>");
1517                    } else if (arg instanceof String) {
1518                        msg.append("\"").append((String)arg).append("\"");
1519                    } else {
1520                        msg.append(arg);
1521                    }
1522                }
1523                msg.append("]");
1524            }
1525            if (mException != null) {
1526                msg.append(", exception=\"").append(mException.getMessage()).append("\"");
1527            }
1528        }
1529
1530        private String getStatus() {
1531            if (!mFinished) {
1532                return "running";
1533            }
1534            return mException != null ? "failed" : "succeeded";
1535        }
1536
1537        private String getTraceMethodName() {
1538            String methodName = mKind + " " + mSql;
1539            if (methodName.length() > MAX_TRACE_METHOD_NAME_LEN)
1540                return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN);
1541            return methodName;
1542        }
1543
1544    }
1545}
1546