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