SQLiteStatement.java revision 3045bbaf58574ad3168466b198b835b29d174c18
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.database.sqlite;
18
19import android.database.DatabaseUtils;
20import android.os.ParcelFileDescriptor;
21import android.os.SystemClock;
22import android.util.Log;
23
24import java.io.IOException;
25
26import dalvik.system.BlockGuard;
27
28/**
29 * A pre-compiled statement against a {@link SQLiteDatabase} that can be reused.
30 * The statement cannot return multiple rows, but 1x1 result sets are allowed.
31 * Don't use SQLiteStatement constructor directly, please use
32 * {@link SQLiteDatabase#compileStatement(String)}
33 *<p>
34 * SQLiteStatement is NOT internally synchronized so code using a SQLiteStatement from multiple
35 * threads should perform its own synchronization when using the SQLiteStatement.
36 */
37@SuppressWarnings("deprecation")
38public class SQLiteStatement extends SQLiteProgram
39{
40    private static final String TAG = "SQLiteStatement";
41
42    private static final boolean READ = true;
43    private static final boolean WRITE = false;
44
45    private SQLiteDatabase mOrigDb;
46    private int mState;
47    /** possible value for {@link #mState}. indicates that a transaction is started. */
48    private static final int TRANS_STARTED = 1;
49    /** possible value for {@link #mState}. indicates that a lock is acquired. */
50    private static final int LOCK_ACQUIRED = 2;
51
52    /**
53     * Don't use SQLiteStatement constructor directly, please use
54     * {@link SQLiteDatabase#compileStatement(String)}
55     * @param db
56     * @param sql
57     */
58    /* package */ SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) {
59        super(db, sql, bindArgs, false /* don't compile sql statement */);
60    }
61
62    /**
63     * Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example
64     * CREATE / DROP table, view, trigger, index etc.
65     *
66     * @throws android.database.SQLException If the SQL string is invalid for
67     *         some reason
68     */
69    public void execute() {
70        executeUpdateDelete();
71    }
72
73    /**
74     * Execute this SQL statement, if the the number of rows affected by execution of this SQL
75     * statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements.
76     *
77     * @return the number of rows affected by this SQL statement execution.
78     * @throws android.database.SQLException If the SQL string is invalid for
79     *         some reason
80     */
81    public int executeUpdateDelete() {
82        try {
83            long timeStart = acquireAndLock(WRITE);
84            int numChanges = 0;
85            if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
86                // since the statement doesn't have to be prepared,
87                // call the following native method which will not prepare
88                // the query plan
89                native_executeSql(mSql);
90            } else {
91                numChanges = native_execute();
92            }
93            mDatabase.logTimeStat(mSql, timeStart);
94            return numChanges;
95        } finally {
96            releaseAndUnlock();
97        }
98    }
99
100    /**
101     * Execute this SQL statement and return the ID of the row inserted due to this call.
102     * The SQL statement should be an INSERT for this to be a useful call.
103     *
104     * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise.
105     *
106     * @throws android.database.SQLException If the SQL string is invalid for
107     *         some reason
108     */
109    public long executeInsert() {
110        try {
111            long timeStart = acquireAndLock(WRITE);
112            long lastInsertedRowId = native_executeInsert();
113            mDatabase.logTimeStat(mSql, timeStart);
114            return lastInsertedRowId;
115        } finally {
116            releaseAndUnlock();
117        }
118    }
119
120    /**
121     * Execute a statement that returns a 1 by 1 table with a numeric value.
122     * For example, SELECT COUNT(*) FROM table;
123     *
124     * @return The result of the query.
125     *
126     * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
127     */
128    public long simpleQueryForLong() {
129        try {
130            long timeStart = acquireAndLock(READ);
131            long retValue = native_1x1_long();
132            mDatabase.logTimeStat(mSql, timeStart);
133            return retValue;
134        } catch (SQLiteDoneException e) {
135            throw new SQLiteDoneException(
136                    "expected 1 row from this query but query returned no data. check the query: " +
137                    mSql);
138        } finally {
139            releaseAndUnlock();
140        }
141    }
142
143    /**
144     * Execute a statement that returns a 1 by 1 table with a text value.
145     * For example, SELECT COUNT(*) FROM table;
146     *
147     * @return The result of the query.
148     *
149     * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
150     */
151    public String simpleQueryForString() {
152        try {
153            long timeStart = acquireAndLock(READ);
154            String retValue = native_1x1_string();
155            mDatabase.logTimeStat(mSql, timeStart);
156            return retValue;
157        } catch (SQLiteDoneException e) {
158            throw new SQLiteDoneException(
159                    "expected 1 row from this query but query returned no data. check the query: " +
160                    mSql);
161        } finally {
162            releaseAndUnlock();
163        }
164    }
165
166    /**
167     * Executes a statement that returns a 1 by 1 table with a blob value.
168     *
169     * @return A read-only file descriptor for a copy of the blob value, or {@code null}
170     *         if the value is null or could not be read for some reason.
171     *
172     * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
173     */
174    public ParcelFileDescriptor simpleQueryForBlobFileDescriptor() {
175        try {
176            long timeStart = acquireAndLock(READ);
177            ParcelFileDescriptor retValue = native_1x1_blob_ashmem();
178            mDatabase.logTimeStat(mSql, timeStart);
179            return retValue;
180        } catch (IOException ex) {
181            Log.e(TAG, "simpleQueryForBlobFileDescriptor() failed", ex);
182            return null;
183        } catch (SQLiteDoneException e) {
184            throw new SQLiteDoneException(
185                    "expected 1 row from this query but query returned no data. check the query: " +
186                    mSql);
187        } finally {
188            releaseAndUnlock();
189        }
190    }
191
192    /**
193     * Called before every method in this class before executing a SQL statement,
194     * this method does the following:
195     * <ul>
196     *   <li>make sure the database is open</li>
197     *   <li>get a database connection from the connection pool,if possible</li>
198     *   <li>notifies {@link BlockGuard} of read/write</li>
199     *   <li>if the SQL statement is an update, start transaction if not already in one.
200     *   otherwise, get lock on the database</li>
201     *   <li>acquire reference on this object</li>
202     *   <li>and then return the current time _before_ the database lock was acquired</li>
203     * </ul>
204     * <p>
205     * This method removes the duplicate code from the other public
206     * methods in this class.
207     */
208    private long acquireAndLock(boolean rwFlag) {
209        mState = 0;
210        // use pooled database connection handles for SELECT SQL statements
211        mDatabase.verifyDbIsOpen();
212        SQLiteDatabase db = ((mStatementType & SQLiteProgram.STATEMENT_USE_POOLED_CONN) > 0)
213                ? mDatabase.getDbConnection(mSql) : mDatabase;
214        // use the database connection obtained above
215        mOrigDb = mDatabase;
216        mDatabase = db;
217        setNativeHandle(mDatabase.mNativeHandle);
218        if (rwFlag == WRITE) {
219            BlockGuard.getThreadPolicy().onWriteToDisk();
220        } else {
221            BlockGuard.getThreadPolicy().onReadFromDisk();
222        }
223
224        /*
225         * Special case handling of SQLiteDatabase.execSQL("BEGIN transaction").
226         * we know it is execSQL("BEGIN transaction") from the caller IF there is no lock held.
227         * beginTransaction() methods in SQLiteDatabase call lockForced() before
228         * calling execSQL("BEGIN transaction").
229         */
230        if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_BEGIN) {
231            if (!mDatabase.isDbLockedByCurrentThread()) {
232                // transaction is  NOT started by calling beginTransaction() methods in
233                // SQLiteDatabase
234                mDatabase.setTransactionUsingExecSqlFlag();
235            }
236        } else if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
237                DatabaseUtils.STATEMENT_UPDATE) {
238            // got update SQL statement. if there is NO pending transaction, start one
239            if (!mDatabase.inTransaction()) {
240                mDatabase.beginTransactionNonExclusive();
241                mState = TRANS_STARTED;
242            }
243        }
244        // do I have database lock? if not, grab it.
245        if (!mDatabase.isDbLockedByCurrentThread()) {
246            mDatabase.lock();
247            mState = LOCK_ACQUIRED;
248        }
249
250        acquireReference();
251        long startTime = SystemClock.uptimeMillis();
252        mDatabase.closePendingStatements();
253        compileAndbindAllArgs();
254        return startTime;
255    }
256
257    /**
258     * this method releases locks and references acquired in {@link #acquireAndLock(boolean)}
259     */
260    private void releaseAndUnlock() {
261        releaseReference();
262        if (mState == TRANS_STARTED) {
263            try {
264                mDatabase.setTransactionSuccessful();
265            } finally {
266                mDatabase.endTransaction();
267            }
268        } else if (mState == LOCK_ACQUIRED) {
269            mDatabase.unlock();
270        }
271        if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
272                DatabaseUtils.STATEMENT_COMMIT ||
273                (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
274                DatabaseUtils.STATEMENT_ABORT) {
275            mDatabase.resetTransactionUsingExecSqlFlag();
276        }
277        clearBindings();
278        // release the compiled sql statement so that the caller's SQLiteStatement no longer
279        // has a hard reference to a database object that may get deallocated at any point.
280        release();
281        // restore the database connection handle to the original value
282        mDatabase = mOrigDb;
283        setNativeHandle(mDatabase.mNativeHandle);
284    }
285
286    private final native int native_execute();
287    private final native long native_executeInsert();
288    private final native long native_1x1_long();
289    private final native String native_1x1_string();
290    private final native ParcelFileDescriptor native_1x1_blob_ashmem() throws IOException;
291    private final native void native_executeSql(String sql);
292}
293