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