SQLiteStatement.java revision 0732f7912ccec9a1cc379b535ac0b56ae50972b3
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.SystemClock; 21 22import dalvik.system.BlockGuard; 23 24/** 25 * A pre-compiled statement against a {@link SQLiteDatabase} that can be reused. 26 * The statement cannot return multiple rows, but 1x1 result sets are allowed. 27 * Don't use SQLiteStatement constructor directly, please use 28 * {@link SQLiteDatabase#compileStatement(String)} 29 *<p> 30 * SQLiteStatement is not internally synchronized so code using a SQLiteStatement from multiple 31 * threads should perform its own synchronization when using the SQLiteStatement. 32 */ 33@SuppressWarnings("deprecation") 34public class SQLiteStatement extends SQLiteProgram 35{ 36 private static final boolean READ = true; 37 private static final boolean WRITE = false; 38 39 private SQLiteDatabase mOrigDb; 40 private int mState; 41 /** possible value for {@link #mState}. indicates that a transaction is started.} */ 42 private static final int TRANS_STARTED = 1; 43 /** possible value for {@link #mState}. indicates that a lock is acquired.} */ 44 private static final int LOCK_ACQUIRED = 2; 45 46 /** 47 * Don't use SQLiteStatement constructor directly, please use 48 * {@link SQLiteDatabase#compileStatement(String)} 49 * @param db 50 * @param sql 51 */ 52 /* package */ SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) { 53 super(db, sql, bindArgs, false /* don't compile sql statement */); 54 } 55 56 /** 57 * Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example 58 * CREATE / DROP table, view, trigger, index etc. 59 * 60 * @throws android.database.SQLException If the SQL string is invalid for 61 * some reason 62 */ 63 public void execute() { 64 executeUpdateDelete(); 65 } 66 67 /** 68 * Execute this SQL statement, if the the number of rows affected by exection of this SQL 69 * statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements. 70 * 71 * @return the number of rows affected by this SQL statement execution. 72 * @throws android.database.SQLException If the SQL string is invalid for 73 * some reason 74 */ 75 public int executeUpdateDelete() { 76 synchronized(this) { 77 long timeStart = acquireAndLock(WRITE); 78 try { 79 int numChanges = native_execute(); 80 mDatabase.logTimeStat(mSql, timeStart); 81 return numChanges; 82 } finally { 83 releaseAndUnlock(); 84 } 85 } 86 } 87 88 /** 89 * Execute this SQL statement and return the ID of the row inserted due to this call. 90 * The SQL statement should be an INSERT for this to be a useful call. 91 * 92 * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise. 93 * 94 * @throws android.database.SQLException If the SQL string is invalid for 95 * some reason 96 */ 97 public long executeInsert() { 98 synchronized(this) { 99 long timeStart = acquireAndLock(WRITE); 100 try { 101 long lastInsertedRowId = native_executeInsert(); 102 mDatabase.logTimeStat(mSql, timeStart); 103 return lastInsertedRowId; 104 } finally { 105 releaseAndUnlock(); 106 } 107 } 108 } 109 110 /** 111 * Execute a statement that returns a 1 by 1 table with a numeric value. 112 * For example, SELECT COUNT(*) FROM table; 113 * 114 * @return The result of the query. 115 * 116 * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows 117 */ 118 public long simpleQueryForLong() { 119 synchronized(this) { 120 long timeStart = acquireAndLock(READ); 121 try { 122 long retValue = native_1x1_long(); 123 mDatabase.logTimeStat(mSql, timeStart); 124 return retValue; 125 } finally { 126 releaseAndUnlock(); 127 } 128 } 129 } 130 131 /** 132 * Execute a statement that returns a 1 by 1 table with a text value. 133 * For example, SELECT COUNT(*) FROM table; 134 * 135 * @return The result of the query. 136 * 137 * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows 138 */ 139 public String simpleQueryForString() { 140 synchronized(this) { 141 long timeStart = acquireAndLock(READ); 142 try { 143 String retValue = native_1x1_string(); 144 mDatabase.logTimeStat(mSql, timeStart); 145 return retValue; 146 } finally { 147 releaseAndUnlock(); 148 } 149 } 150 } 151 152 /** 153 * Called before every method in this class before executing a SQL statement, 154 * this method does the following: 155 * <ul> 156 * <li>make sure the database is open</li> 157 * <li>get a database connection from the connection pool,if possible</li> 158 * <li>notifies {@link BlockGuard} of read/write</li> 159 * <li>if the SQL statement is an update, start transaction if not already in one. 160 * otherwise, get lock on the database</li> 161 * <li>acquire reference on this object</li> 162 * <li>and then return the current time _before_ the database lock was acquired</li> 163 * </ul> 164 * <p> 165 * This method removes the duplicate code from the other public 166 * methods in this class. 167 */ 168 private long acquireAndLock(boolean rwFlag) { 169 mState = 0; 170 // use pooled database connection handles for SELECT SQL statements 171 mDatabase.verifyDbIsOpen(); 172 SQLiteDatabase db = (mStatementType != DatabaseUtils.STATEMENT_SELECT) ? mDatabase 173 : mDatabase.getDbConnection(mSql); 174 // use the database connection obtained above 175 mOrigDb = mDatabase; 176 mDatabase = db; 177 nHandle = mDatabase.mNativeHandle; 178 if (rwFlag == WRITE) { 179 BlockGuard.getThreadPolicy().onWriteToDisk(); 180 } else { 181 BlockGuard.getThreadPolicy().onReadFromDisk(); 182 } 183 184 /* 185 * Special case handling of SQLiteDatabase.execSQL("BEGIN transaction"). 186 * we know it is execSQL("BEGIN transaction") from the caller IF there is no lock held. 187 * beginTransaction() methods in SQLiteDatabase call lockForced() before 188 * calling execSQL("BEGIN transaction"). 189 */ 190 if (mStatementType == DatabaseUtils.STATEMENT_BEGIN) { 191 if (!mDatabase.isDbLockedByCurrentThread()) { 192 // transaction is NOT started by calling beginTransaction() methods in 193 // SQLiteDatabase 194 mDatabase.setTransactionUsingExecSqlFlag(); 195 } 196 } else if (mStatementType == DatabaseUtils.STATEMENT_UPDATE) { 197 // got update SQL statement. if there is NO pending transaction, start one 198 if (!mDatabase.inTransaction()) { 199 mDatabase.beginTransactionNonExclusive(); 200 mState = TRANS_STARTED; 201 } 202 } 203 // do I have database lock? if not, grab it. 204 if (!mDatabase.isDbLockedByCurrentThread()) { 205 mDatabase.lock(); 206 mState = LOCK_ACQUIRED; 207 } 208 209 acquireReference(); 210 long startTime = SystemClock.uptimeMillis(); 211 mDatabase.closePendingStatements(); 212 compileAndbindAllArgs(); 213 return startTime; 214 } 215 216 /** 217 * this method releases locks and references acquired in {@link #acquireAndLock(boolean)} 218 */ 219 private void releaseAndUnlock() { 220 releaseReference(); 221 if (mState == TRANS_STARTED) { 222 try { 223 mDatabase.setTransactionSuccessful(); 224 } finally { 225 mDatabase.endTransaction(); 226 } 227 } else if (mState == LOCK_ACQUIRED) { 228 mDatabase.unlock(); 229 } 230 if (mStatementType == DatabaseUtils.STATEMENT_COMMIT || 231 mStatementType == DatabaseUtils.STATEMENT_ABORT) { 232 mDatabase.resetTransactionUsingExecSqlFlag(); 233 } 234 clearBindings(); 235 // release the compiled sql statement so that the caller's SQLiteStatement no longer 236 // has a hard reference to a database object that may get deallocated at any point. 237 releaseCompiledSqlIfNotInCache(); 238 // restore the database connection handle to the original value 239 mDatabase = mOrigDb; 240 nHandle = mDatabase.mNativeHandle; 241 } 242 243 private final native int native_execute(); 244 private final native long native_executeInsert(); 245 private final native long native_1x1_long(); 246 private final native String native_1x1_string(); 247} 248