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