blob: c99a6fb8cd5fffcf4c65e12595cf09cffa34e236 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Vasu Norice38b982010-07-22 13:57:13 -070019import android.database.DatabaseUtils;
Bjorn Bringerta006b4722010-04-14 14:43:26 +010020import android.os.ParcelFileDescriptor;
Brad Fitzpatrick6a353872010-02-11 18:04:49 -080021import android.os.SystemClock;
Bjorn Bringerta006b4722010-04-14 14:43:26 +010022import android.util.Log;
23
24import java.io.IOException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025
Brad Fitzpatrickcfda9f32010-06-03 12:52:54 -070026import dalvik.system.BlockGuard;
27
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028/**
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)}
Vasu Noriccd95442010-05-28 17:04:16 -070033 *<p>
Vasu Nori24675612010-09-27 14:54:19 -070034 * SQLiteStatement is NOT internally synchronized so code using a SQLiteStatement from multiple
Jeff Hamiltonf3ca9a52010-05-12 15:04:33 -070035 * threads should perform its own synchronization when using the SQLiteStatement.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036 */
Vasu Nori2827d6d2010-07-04 00:26:18 -070037@SuppressWarnings("deprecation")
Jeff Brownd5064be2011-12-14 14:25:13 -080038public final class SQLiteStatement extends SQLiteProgram
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039{
Bjorn Bringerta006b4722010-04-14 14:43:26 +010040 private static final String TAG = "SQLiteStatement";
41
Vasu Nori75010102010-07-01 16:23:06 -070042 private static final boolean READ = true;
43 private static final boolean WRITE = false;
44
Vasu Norie25539f2010-07-08 17:06:13 -070045 private SQLiteDatabase mOrigDb;
Vasu Nori0732f792010-07-29 17:24:12 -070046 private int mState;
Vasu Norid31211f2010-09-13 15:47:49 -070047 /** possible value for {@link #mState}. indicates that a transaction is started. */
Vasu Norice38b982010-07-22 13:57:13 -070048 private static final int TRANS_STARTED = 1;
Vasu Norid31211f2010-09-13 15:47:49 -070049 /** possible value for {@link #mState}. indicates that a lock is acquired. */
Vasu Norice38b982010-07-22 13:57:13 -070050 private static final int LOCK_ACQUIRED = 2;
Vasu Norie25539f2010-07-08 17:06:13 -070051
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052 /**
53 * Don't use SQLiteStatement constructor directly, please use
54 * {@link SQLiteDatabase#compileStatement(String)}
55 * @param db
56 * @param sql
57 */
Vasu Nori0732f792010-07-29 17:24:12 -070058 /* package */ SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) {
59 super(db, sql, bindArgs, false /* don't compile sql statement */);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060 }
61
62 /**
Vasu Norifb16cbd2010-07-25 16:38:48 -070063 * Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example
64 * CREATE / DROP table, view, trigger, index etc.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065 *
66 * @throws android.database.SQLException If the SQL string is invalid for
67 * some reason
68 */
69 public void execute() {
Vasu Norifb16cbd2010-07-25 16:38:48 -070070 executeUpdateDelete();
71 }
72
73 /**
Vasu Nori4e874ed2010-09-15 18:40:49 -070074 * Execute this SQL statement, if the the number of rows affected by execution of this SQL
Vasu Norifb16cbd2010-07-25 16:38:48 -070075 * 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() {
Vasu Nori24675612010-09-27 14:54:19 -070082 try {
Vasu Nori16057fa2011-03-18 11:40:37 -070083 saveSqlAsLastSqlStatement();
84 acquireAndLock(WRITE);
Vasu Nori24675612010-09-27 14:54:19 -070085 int numChanges = 0;
86 if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
87 // since the statement doesn't have to be prepared,
88 // call the following native method which will not prepare
89 // the query plan
90 native_executeSql(mSql);
91 } else {
92 numChanges = native_execute();
Vasu Norie25539f2010-07-08 17:06:13 -070093 }
Vasu Nori24675612010-09-27 14:54:19 -070094 return numChanges;
95 } finally {
96 releaseAndUnlock();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080097 }
98 }
99
100 /**
Vasu Nori5bf67242010-03-16 09:55:13 -0700101 * 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.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 *
Vasu Nori5bf67242010-03-16 09:55:13 -0700104 * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105 *
106 * @throws android.database.SQLException If the SQL string is invalid for
107 * some reason
108 */
109 public long executeInsert() {
Vasu Nori24675612010-09-27 14:54:19 -0700110 try {
Vasu Nori16057fa2011-03-18 11:40:37 -0700111 saveSqlAsLastSqlStatement();
112 acquireAndLock(WRITE);
113 return native_executeInsert();
Vasu Nori24675612010-09-27 14:54:19 -0700114 } finally {
115 releaseAndUnlock();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 }
117 }
118
Vasu Nori16057fa2011-03-18 11:40:37 -0700119 private void saveSqlAsLastSqlStatement() {
120 if (((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
121 DatabaseUtils.STATEMENT_UPDATE) ||
122 (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
123 DatabaseUtils.STATEMENT_BEGIN) {
124 mDatabase.setLastSqlStatement(mSql);
125 }
126 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127 /**
128 * Execute a statement that returns a 1 by 1 table with a numeric value.
129 * For example, SELECT COUNT(*) FROM table;
130 *
131 * @return The result of the query.
132 *
133 * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
134 */
135 public long simpleQueryForLong() {
Vasu Nori24675612010-09-27 14:54:19 -0700136 try {
137 long timeStart = acquireAndLock(READ);
138 long retValue = native_1x1_long();
139 mDatabase.logTimeStat(mSql, timeStart);
140 return retValue;
Vasu Nori3045bba2011-01-10 14:29:04 -0800141 } catch (SQLiteDoneException e) {
142 throw new SQLiteDoneException(
143 "expected 1 row from this query but query returned no data. check the query: " +
144 mSql);
Vasu Nori24675612010-09-27 14:54:19 -0700145 } finally {
146 releaseAndUnlock();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800147 }
148 }
149
150 /**
151 * Execute a statement that returns a 1 by 1 table with a text value.
152 * For example, SELECT COUNT(*) FROM table;
153 *
154 * @return The result of the query.
155 *
156 * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
157 */
158 public String simpleQueryForString() {
Vasu Nori24675612010-09-27 14:54:19 -0700159 try {
160 long timeStart = acquireAndLock(READ);
161 String retValue = native_1x1_string();
162 mDatabase.logTimeStat(mSql, timeStart);
163 return retValue;
Vasu Nori3045bba2011-01-10 14:29:04 -0800164 } catch (SQLiteDoneException e) {
165 throw new SQLiteDoneException(
166 "expected 1 row from this query but query returned no data. check the query: " +
167 mSql);
Vasu Nori24675612010-09-27 14:54:19 -0700168 } finally {
169 releaseAndUnlock();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170 }
171 }
172
Vasu Nori75010102010-07-01 16:23:06 -0700173 /**
Bjorn Bringerta006b4722010-04-14 14:43:26 +0100174 * Executes a statement that returns a 1 by 1 table with a blob value.
175 *
176 * @return A read-only file descriptor for a copy of the blob value, or {@code null}
177 * if the value is null or could not be read for some reason.
178 *
179 * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
180 */
181 public ParcelFileDescriptor simpleQueryForBlobFileDescriptor() {
Vasu Nori24675612010-09-27 14:54:19 -0700182 try {
183 long timeStart = acquireAndLock(READ);
184 ParcelFileDescriptor retValue = native_1x1_blob_ashmem();
185 mDatabase.logTimeStat(mSql, timeStart);
186 return retValue;
187 } catch (IOException ex) {
188 Log.e(TAG, "simpleQueryForBlobFileDescriptor() failed", ex);
189 return null;
Vasu Nori3045bba2011-01-10 14:29:04 -0800190 } catch (SQLiteDoneException e) {
191 throw new SQLiteDoneException(
192 "expected 1 row from this query but query returned no data. check the query: " +
193 mSql);
Vasu Nori24675612010-09-27 14:54:19 -0700194 } finally {
195 releaseAndUnlock();
Bjorn Bringerta006b4722010-04-14 14:43:26 +0100196 }
197 }
198
199 /**
Vasu Nori75010102010-07-01 16:23:06 -0700200 * Called before every method in this class before executing a SQL statement,
201 * this method does the following:
202 * <ul>
203 * <li>make sure the database is open</li>
Vasu Norie25539f2010-07-08 17:06:13 -0700204 * <li>get a database connection from the connection pool,if possible</li>
Vasu Nori75010102010-07-01 16:23:06 -0700205 * <li>notifies {@link BlockGuard} of read/write</li>
Vasu Norice38b982010-07-22 13:57:13 -0700206 * <li>if the SQL statement is an update, start transaction if not already in one.
207 * otherwise, get lock on the database</li>
Vasu Nori75010102010-07-01 16:23:06 -0700208 * <li>acquire reference on this object</li>
Vasu Nori16057fa2011-03-18 11:40:37 -0700209 * <li>and then return the current time _after_ the database lock was acquired</li>
Vasu Nori75010102010-07-01 16:23:06 -0700210 * </ul>
211 * <p>
Vasu Norice38b982010-07-22 13:57:13 -0700212 * This method removes the duplicate code from the other public
Vasu Nori75010102010-07-01 16:23:06 -0700213 * methods in this class.
214 */
215 private long acquireAndLock(boolean rwFlag) {
Vasu Nori0732f792010-07-29 17:24:12 -0700216 mState = 0;
Vasu Norie25539f2010-07-08 17:06:13 -0700217 // use pooled database connection handles for SELECT SQL statements
218 mDatabase.verifyDbIsOpen();
Vasu Nori4e874ed2010-09-15 18:40:49 -0700219 SQLiteDatabase db = ((mStatementType & SQLiteProgram.STATEMENT_USE_POOLED_CONN) > 0)
220 ? mDatabase.getDbConnection(mSql) : mDatabase;
Vasu Norie25539f2010-07-08 17:06:13 -0700221 // use the database connection obtained above
222 mOrigDb = mDatabase;
223 mDatabase = db;
Vasu Nori19666322010-09-10 14:09:00 -0700224 setNativeHandle(mDatabase.mNativeHandle);
Vasu Nori75010102010-07-01 16:23:06 -0700225 if (rwFlag == WRITE) {
226 BlockGuard.getThreadPolicy().onWriteToDisk();
227 } else {
228 BlockGuard.getThreadPolicy().onReadFromDisk();
229 }
Vasu Norice38b982010-07-22 13:57:13 -0700230
231 /*
232 * Special case handling of SQLiteDatabase.execSQL("BEGIN transaction").
233 * we know it is execSQL("BEGIN transaction") from the caller IF there is no lock held.
234 * beginTransaction() methods in SQLiteDatabase call lockForced() before
235 * calling execSQL("BEGIN transaction").
236 */
Vasu Nori4e874ed2010-09-15 18:40:49 -0700237 if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_BEGIN) {
Vasu Norice38b982010-07-22 13:57:13 -0700238 if (!mDatabase.isDbLockedByCurrentThread()) {
239 // transaction is NOT started by calling beginTransaction() methods in
240 // SQLiteDatabase
241 mDatabase.setTransactionUsingExecSqlFlag();
242 }
Vasu Nori4e874ed2010-09-15 18:40:49 -0700243 } else if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
244 DatabaseUtils.STATEMENT_UPDATE) {
Vasu Norice38b982010-07-22 13:57:13 -0700245 // got update SQL statement. if there is NO pending transaction, start one
246 if (!mDatabase.inTransaction()) {
247 mDatabase.beginTransactionNonExclusive();
Vasu Nori0732f792010-07-29 17:24:12 -0700248 mState = TRANS_STARTED;
Vasu Norice38b982010-07-22 13:57:13 -0700249 }
250 }
251 // do I have database lock? if not, grab it.
252 if (!mDatabase.isDbLockedByCurrentThread()) {
Vasu Nori16057fa2011-03-18 11:40:37 -0700253 mDatabase.lock(mSql);
Vasu Nori0732f792010-07-29 17:24:12 -0700254 mState = LOCK_ACQUIRED;
Vasu Norice38b982010-07-22 13:57:13 -0700255 }
256
Vasu Nori75010102010-07-01 16:23:06 -0700257 acquireReference();
Vasu Norice38b982010-07-22 13:57:13 -0700258 long startTime = SystemClock.uptimeMillis();
Vasu Nori75010102010-07-01 16:23:06 -0700259 mDatabase.closePendingStatements();
Vasu Norie25539f2010-07-08 17:06:13 -0700260 compileAndbindAllArgs();
Vasu Nori75010102010-07-01 16:23:06 -0700261 return startTime;
262 }
263
264 /**
Vasu Norice38b982010-07-22 13:57:13 -0700265 * this method releases locks and references acquired in {@link #acquireAndLock(boolean)}
Vasu Nori75010102010-07-01 16:23:06 -0700266 */
267 private void releaseAndUnlock() {
268 releaseReference();
Vasu Nori0732f792010-07-29 17:24:12 -0700269 if (mState == TRANS_STARTED) {
Vasu Norice38b982010-07-22 13:57:13 -0700270 try {
271 mDatabase.setTransactionSuccessful();
272 } finally {
273 mDatabase.endTransaction();
274 }
Vasu Nori0732f792010-07-29 17:24:12 -0700275 } else if (mState == LOCK_ACQUIRED) {
Vasu Norice38b982010-07-22 13:57:13 -0700276 mDatabase.unlock();
277 }
Vasu Nori4e874ed2010-09-15 18:40:49 -0700278 if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
279 DatabaseUtils.STATEMENT_COMMIT ||
280 (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
281 DatabaseUtils.STATEMENT_ABORT) {
Vasu Norice38b982010-07-22 13:57:13 -0700282 mDatabase.resetTransactionUsingExecSqlFlag();
283 }
Vasu Nori2827d6d2010-07-04 00:26:18 -0700284 clearBindings();
285 // release the compiled sql statement so that the caller's SQLiteStatement no longer
286 // has a hard reference to a database object that may get deallocated at any point.
Vasu Noricc6f5492010-08-23 17:05:25 -0700287 release();
Vasu Norie25539f2010-07-08 17:06:13 -0700288 // restore the database connection handle to the original value
289 mDatabase = mOrigDb;
Vasu Nori19666322010-09-10 14:09:00 -0700290 setNativeHandle(mDatabase.mNativeHandle);
Vasu Nori75010102010-07-01 16:23:06 -0700291 }
292
Vasu Norifb16cbd2010-07-25 16:38:48 -0700293 private final native int native_execute();
294 private final native long native_executeInsert();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800295 private final native long native_1x1_long();
296 private final native String native_1x1_string();
Bjorn Bringerta006b4722010-04-14 14:43:26 +0100297 private final native ParcelFileDescriptor native_1x1_blob_ashmem() throws IOException;
Vasu Nori4e874ed2010-09-15 18:40:49 -0700298 private final native void native_executeSql(String sql);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800299}