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