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