SQLiteStatement.java revision 4e874edf69ce9900eb847629dc4d3616972a3468
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 synchronized(this) { 83 try { 84 long timeStart = 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 mDatabase.logTimeStat(mSql, timeStart); 95 return numChanges; 96 } finally { 97 releaseAndUnlock(); 98 } 99 } 100 } 101 102 /** 103 * Execute this SQL statement and return the ID of the row inserted due to this call. 104 * The SQL statement should be an INSERT for this to be a useful call. 105 * 106 * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise. 107 * 108 * @throws android.database.SQLException If the SQL string is invalid for 109 * some reason 110 */ 111 public long executeInsert() { 112 synchronized(this) { 113 try { 114 long timeStart = acquireAndLock(WRITE); 115 long lastInsertedRowId = native_executeInsert(); 116 mDatabase.logTimeStat(mSql, timeStart); 117 return lastInsertedRowId; 118 } finally { 119 releaseAndUnlock(); 120 } 121 } 122 } 123 124 /** 125 * Execute a statement that returns a 1 by 1 table with a numeric value. 126 * For example, SELECT COUNT(*) FROM table; 127 * 128 * @return The result of the query. 129 * 130 * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows 131 */ 132 public long simpleQueryForLong() { 133 synchronized(this) { 134 try { 135 long timeStart = acquireAndLock(READ); 136 long retValue = native_1x1_long(); 137 mDatabase.logTimeStat(mSql, timeStart); 138 return retValue; 139 } finally { 140 releaseAndUnlock(); 141 } 142 } 143 } 144 145 /** 146 * Execute a statement that returns a 1 by 1 table with a text value. 147 * For example, SELECT COUNT(*) FROM table; 148 * 149 * @return The result of the query. 150 * 151 * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows 152 */ 153 public String simpleQueryForString() { 154 synchronized(this) { 155 try { 156 long timeStart = acquireAndLock(READ); 157 String retValue = native_1x1_string(); 158 mDatabase.logTimeStat(mSql, timeStart); 159 return retValue; 160 } finally { 161 releaseAndUnlock(); 162 } 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 synchronized(this) { 176 try { 177 long timeStart = acquireAndLock(READ); 178 ParcelFileDescriptor retValue = native_1x1_blob_ashmem(); 179 mDatabase.logTimeStat(mSql, timeStart); 180 return retValue; 181 } catch (IOException ex) { 182 Log.e(TAG, "simpleQueryForBlobFileDescriptor() failed", ex); 183 return null; 184 } finally { 185 releaseAndUnlock(); 186 } 187 } 188 } 189 190 /** 191 * Called before every method in this class before executing a SQL statement, 192 * this method does the following: 193 * <ul> 194 * <li>make sure the database is open</li> 195 * <li>get a database connection from the connection pool,if possible</li> 196 * <li>notifies {@link BlockGuard} of read/write</li> 197 * <li>if the SQL statement is an update, start transaction if not already in one. 198 * otherwise, get lock on the database</li> 199 * <li>acquire reference on this object</li> 200 * <li>and then return the current time _before_ the database lock was acquired</li> 201 * </ul> 202 * <p> 203 * This method removes the duplicate code from the other public 204 * methods in this class. 205 */ 206 private long acquireAndLock(boolean rwFlag) { 207 mState = 0; 208 // use pooled database connection handles for SELECT SQL statements 209 mDatabase.verifyDbIsOpen(); 210 SQLiteDatabase db = ((mStatementType & SQLiteProgram.STATEMENT_USE_POOLED_CONN) > 0) 211 ? mDatabase.getDbConnection(mSql) : mDatabase; 212 // use the database connection obtained above 213 mOrigDb = mDatabase; 214 mDatabase = db; 215 setNativeHandle(mDatabase.mNativeHandle); 216 if (rwFlag == WRITE) { 217 BlockGuard.getThreadPolicy().onWriteToDisk(); 218 } else { 219 BlockGuard.getThreadPolicy().onReadFromDisk(); 220 } 221 222 /* 223 * Special case handling of SQLiteDatabase.execSQL("BEGIN transaction"). 224 * we know it is execSQL("BEGIN transaction") from the caller IF there is no lock held. 225 * beginTransaction() methods in SQLiteDatabase call lockForced() before 226 * calling execSQL("BEGIN transaction"). 227 */ 228 if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_BEGIN) { 229 if (!mDatabase.isDbLockedByCurrentThread()) { 230 // transaction is NOT started by calling beginTransaction() methods in 231 // SQLiteDatabase 232 mDatabase.setTransactionUsingExecSqlFlag(); 233 } 234 } else if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == 235 DatabaseUtils.STATEMENT_UPDATE) { 236 // got update SQL statement. if there is NO pending transaction, start one 237 if (!mDatabase.inTransaction()) { 238 mDatabase.beginTransactionNonExclusive(); 239 mState = TRANS_STARTED; 240 } 241 } 242 // do I have database lock? if not, grab it. 243 if (!mDatabase.isDbLockedByCurrentThread()) { 244 mDatabase.lock(); 245 mState = LOCK_ACQUIRED; 246 } 247 248 acquireReference(); 249 long startTime = SystemClock.uptimeMillis(); 250 mDatabase.closePendingStatements(); 251 compileAndbindAllArgs(); 252 return startTime; 253 } 254 255 /** 256 * this method releases locks and references acquired in {@link #acquireAndLock(boolean)} 257 */ 258 private void releaseAndUnlock() { 259 releaseReference(); 260 if (mState == TRANS_STARTED) { 261 try { 262 mDatabase.setTransactionSuccessful(); 263 } finally { 264 mDatabase.endTransaction(); 265 } 266 } else if (mState == LOCK_ACQUIRED) { 267 mDatabase.unlock(); 268 } 269 if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == 270 DatabaseUtils.STATEMENT_COMMIT || 271 (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == 272 DatabaseUtils.STATEMENT_ABORT) { 273 mDatabase.resetTransactionUsingExecSqlFlag(); 274 } 275 clearBindings(); 276 // release the compiled sql statement so that the caller's SQLiteStatement no longer 277 // has a hard reference to a database object that may get deallocated at any point. 278 release(); 279 // restore the database connection handle to the original value 280 mDatabase = mOrigDb; 281 setNativeHandle(mDatabase.mNativeHandle); 282 } 283 284 private final native int native_execute(); 285 private final native long native_executeInsert(); 286 private final native long native_1x1_long(); 287 private final native String native_1x1_string(); 288 private final native ParcelFileDescriptor native_1x1_blob_ashmem() throws IOException; 289 private final native void native_executeSql(String sql); 290} 291