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 17 package android.database.sqlite; 18 19 import android.database.DatabaseUtils; 20 import android.os.ParcelFileDescriptor; 21 import android.os.SystemClock; 22 import android.util.Log; 23 24 import java.io.IOException; 25 26 import 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") 38 public 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 */ SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs)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 */ execute()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 */ executeUpdateDelete()81 public int executeUpdateDelete() { 82 try { 83 saveSqlAsLastSqlStatement(); 84 acquireAndLock(WRITE); 85 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(); 93 } 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 */ executeInsert()109 public long executeInsert() { 110 try { 111 saveSqlAsLastSqlStatement(); 112 acquireAndLock(WRITE); 113 return native_executeInsert(); 114 } finally { 115 releaseAndUnlock(); 116 } 117 } 118 saveSqlAsLastSqlStatement()119 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 } 127 /** 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 */ simpleQueryForLong()135 public long simpleQueryForLong() { 136 try { 137 long timeStart = acquireAndLock(READ); 138 long retValue = native_1x1_long(); 139 mDatabase.logTimeStat(mSql, timeStart); 140 return retValue; 141 } catch (SQLiteDoneException e) { 142 throw new SQLiteDoneException( 143 "expected 1 row from this query but query returned no data. check the query: " + 144 mSql); 145 } finally { 146 releaseAndUnlock(); 147 } 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 */ simpleQueryForString()158 public String simpleQueryForString() { 159 try { 160 long timeStart = acquireAndLock(READ); 161 String retValue = native_1x1_string(); 162 mDatabase.logTimeStat(mSql, timeStart); 163 return retValue; 164 } catch (SQLiteDoneException e) { 165 throw new SQLiteDoneException( 166 "expected 1 row from this query but query returned no data. check the query: " + 167 mSql); 168 } finally { 169 releaseAndUnlock(); 170 } 171 } 172 173 /** 174 * 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 */ simpleQueryForBlobFileDescriptor()181 public ParcelFileDescriptor simpleQueryForBlobFileDescriptor() { 182 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; 190 } catch (SQLiteDoneException e) { 191 throw new SQLiteDoneException( 192 "expected 1 row from this query but query returned no data. check the query: " + 193 mSql); 194 } finally { 195 releaseAndUnlock(); 196 } 197 } 198 199 /** 200 * 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> 204 * <li>get a database connection from the connection pool,if possible</li> 205 * <li>notifies {@link BlockGuard} of read/write</li> 206 * <li>if the SQL statement is an update, start transaction if not already in one. 207 * otherwise, get lock on the database</li> 208 * <li>acquire reference on this object</li> 209 * <li>and then return the current time _after_ the database lock was acquired</li> 210 * </ul> 211 * <p> 212 * This method removes the duplicate code from the other public 213 * methods in this class. 214 */ acquireAndLock(boolean rwFlag)215 private long acquireAndLock(boolean rwFlag) { 216 mState = 0; 217 // use pooled database connection handles for SELECT SQL statements 218 mDatabase.verifyDbIsOpen(); 219 SQLiteDatabase db = ((mStatementType & SQLiteProgram.STATEMENT_USE_POOLED_CONN) > 0) 220 ? mDatabase.getDbConnection(mSql) : mDatabase; 221 // use the database connection obtained above 222 mOrigDb = mDatabase; 223 mDatabase = db; 224 setNativeHandle(mDatabase.mNativeHandle); 225 if (rwFlag == WRITE) { 226 BlockGuard.getThreadPolicy().onWriteToDisk(); 227 } else { 228 BlockGuard.getThreadPolicy().onReadFromDisk(); 229 } 230 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 */ 237 if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_BEGIN) { 238 if (!mDatabase.isDbLockedByCurrentThread()) { 239 // transaction is NOT started by calling beginTransaction() methods in 240 // SQLiteDatabase 241 mDatabase.setTransactionUsingExecSqlFlag(); 242 } 243 } else if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == 244 DatabaseUtils.STATEMENT_UPDATE) { 245 // got update SQL statement. if there is NO pending transaction, start one 246 if (!mDatabase.inTransaction()) { 247 mDatabase.beginTransactionNonExclusive(); 248 mState = TRANS_STARTED; 249 } 250 } 251 // do I have database lock? if not, grab it. 252 if (!mDatabase.isDbLockedByCurrentThread()) { 253 mDatabase.lock(mSql); 254 mState = LOCK_ACQUIRED; 255 } 256 257 acquireReference(); 258 long startTime = SystemClock.uptimeMillis(); 259 mDatabase.closePendingStatements(); 260 compileAndbindAllArgs(); 261 return startTime; 262 } 263 264 /** 265 * this method releases locks and references acquired in {@link #acquireAndLock(boolean)} 266 */ releaseAndUnlock()267 private void releaseAndUnlock() { 268 releaseReference(); 269 if (mState == TRANS_STARTED) { 270 try { 271 mDatabase.setTransactionSuccessful(); 272 } finally { 273 mDatabase.endTransaction(); 274 } 275 } else if (mState == LOCK_ACQUIRED) { 276 mDatabase.unlock(); 277 } 278 if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == 279 DatabaseUtils.STATEMENT_COMMIT || 280 (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == 281 DatabaseUtils.STATEMENT_ABORT) { 282 mDatabase.resetTransactionUsingExecSqlFlag(); 283 } 284 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. 287 release(); 288 // restore the database connection handle to the original value 289 mDatabase = mOrigDb; 290 setNativeHandle(mDatabase.mNativeHandle); 291 } 292 native_execute()293 private final native int native_execute(); native_executeInsert()294 private final native long native_executeInsert(); native_1x1_long()295 private final native long native_1x1_long(); native_1x1_string()296 private final native String native_1x1_string(); native_1x1_blob_ashmem()297 private final native ParcelFileDescriptor native_1x1_blob_ashmem() throws IOException; native_executeSql(String sql)298 private final native void native_executeSql(String sql); 299 } 300