/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.database.sqlite; import android.database.DatabaseUtils; import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.util.Log; import java.io.IOException; import dalvik.system.BlockGuard; /** * A pre-compiled statement against a {@link SQLiteDatabase} that can be reused. * The statement cannot return multiple rows, but 1x1 result sets are allowed. * Don't use SQLiteStatement constructor directly, please use * {@link SQLiteDatabase#compileStatement(String)} *<p> * SQLiteStatement is NOT internally synchronized so code using a SQLiteStatement from multiple * threads should perform its own synchronization when using the SQLiteStatement. */ @SuppressWarnings("deprecation") public class SQLiteStatement extends SQLiteProgram { private static final String TAG = "SQLiteStatement"; private static final boolean READ = true; private static final boolean WRITE = false; private SQLiteDatabase mOrigDb; private int mState; /** possible value for {@link #mState}. indicates that a transaction is started. */ private static final int TRANS_STARTED = 1; /** possible value for {@link #mState}. indicates that a lock is acquired. */ private static final int LOCK_ACQUIRED = 2; /** * Don't use SQLiteStatement constructor directly, please use * {@link SQLiteDatabase#compileStatement(String)} * @param db * @param sql */ /* package */ SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) { super(db, sql, bindArgs, false /* don't compile sql statement */); } /** * Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example * CREATE / DROP table, view, trigger, index etc. * * @throws android.database.SQLException If the SQL string is invalid for * some reason */ public void execute() { executeUpdateDelete(); } /** * Execute this SQL statement, if the the number of rows affected by execution of this SQL * statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements. * * @return the number of rows affected by this SQL statement execution. * @throws android.database.SQLException If the SQL string is invalid for * some reason */ public int executeUpdateDelete() { try { saveSqlAsLastSqlStatement(); acquireAndLock(WRITE); int numChanges = 0; if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) { // since the statement doesn't have to be prepared, // call the following native method which will not prepare // the query plan native_executeSql(mSql); } else { numChanges = native_execute(); } return numChanges; } finally { releaseAndUnlock(); } } /** * Execute this SQL statement and return the ID of the row inserted due to this call. * The SQL statement should be an INSERT for this to be a useful call. * * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise. * * @throws android.database.SQLException If the SQL string is invalid for * some reason */ public long executeInsert() { try { saveSqlAsLastSqlStatement(); acquireAndLock(WRITE); return native_executeInsert(); } finally { releaseAndUnlock(); } } private void saveSqlAsLastSqlStatement() { if (((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_UPDATE) || (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_BEGIN) { mDatabase.setLastSqlStatement(mSql); } } /** * Execute a statement that returns a 1 by 1 table with a numeric value. * For example, SELECT COUNT(*) FROM table; * * @return The result of the query. * * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows */ public long simpleQueryForLong() { try { long timeStart = acquireAndLock(READ); long retValue = native_1x1_long(); mDatabase.logTimeStat(mSql, timeStart); return retValue; } catch (SQLiteDoneException e) { throw new SQLiteDoneException( "expected 1 row from this query but query returned no data. check the query: " + mSql); } finally { releaseAndUnlock(); } } /** * Execute a statement that returns a 1 by 1 table with a text value. * For example, SELECT COUNT(*) FROM table; * * @return The result of the query. * * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows */ public String simpleQueryForString() { try { long timeStart = acquireAndLock(READ); String retValue = native_1x1_string(); mDatabase.logTimeStat(mSql, timeStart); return retValue; } catch (SQLiteDoneException e) { throw new SQLiteDoneException( "expected 1 row from this query but query returned no data. check the query: " + mSql); } finally { releaseAndUnlock(); } } /** * Executes a statement that returns a 1 by 1 table with a blob value. * * @return A read-only file descriptor for a copy of the blob value, or {@code null} * if the value is null or could not be read for some reason. * * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows */ public ParcelFileDescriptor simpleQueryForBlobFileDescriptor() { try { long timeStart = acquireAndLock(READ); ParcelFileDescriptor retValue = native_1x1_blob_ashmem(); mDatabase.logTimeStat(mSql, timeStart); return retValue; } catch (IOException ex) { Log.e(TAG, "simpleQueryForBlobFileDescriptor() failed", ex); return null; } catch (SQLiteDoneException e) { throw new SQLiteDoneException( "expected 1 row from this query but query returned no data. check the query: " + mSql); } finally { releaseAndUnlock(); } } /** * Called before every method in this class before executing a SQL statement, * this method does the following: * <ul> * <li>make sure the database is open</li> * <li>get a database connection from the connection pool,if possible</li> * <li>notifies {@link BlockGuard} of read/write</li> * <li>if the SQL statement is an update, start transaction if not already in one. * otherwise, get lock on the database</li> * <li>acquire reference on this object</li> * <li>and then return the current time _after_ the database lock was acquired</li> * </ul> * <p> * This method removes the duplicate code from the other public * methods in this class. */ private long acquireAndLock(boolean rwFlag) { mState = 0; // use pooled database connection handles for SELECT SQL statements mDatabase.verifyDbIsOpen(); SQLiteDatabase db = ((mStatementType & SQLiteProgram.STATEMENT_USE_POOLED_CONN) > 0) ? mDatabase.getDbConnection(mSql) : mDatabase; // use the database connection obtained above mOrigDb = mDatabase; mDatabase = db; setNativeHandle(mDatabase.mNativeHandle); if (rwFlag == WRITE) { BlockGuard.getThreadPolicy().onWriteToDisk(); } else { BlockGuard.getThreadPolicy().onReadFromDisk(); } /* * Special case handling of SQLiteDatabase.execSQL("BEGIN transaction"). * we know it is execSQL("BEGIN transaction") from the caller IF there is no lock held. * beginTransaction() methods in SQLiteDatabase call lockForced() before * calling execSQL("BEGIN transaction"). */ if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_BEGIN) { if (!mDatabase.isDbLockedByCurrentThread()) { // transaction is NOT started by calling beginTransaction() methods in // SQLiteDatabase mDatabase.setTransactionUsingExecSqlFlag(); } } else if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_UPDATE) { // got update SQL statement. if there is NO pending transaction, start one if (!mDatabase.inTransaction()) { mDatabase.beginTransactionNonExclusive(); mState = TRANS_STARTED; } } // do I have database lock? if not, grab it. if (!mDatabase.isDbLockedByCurrentThread()) { mDatabase.lock(mSql); mState = LOCK_ACQUIRED; } acquireReference(); long startTime = SystemClock.uptimeMillis(); mDatabase.closePendingStatements(); compileAndbindAllArgs(); return startTime; } /** * this method releases locks and references acquired in {@link #acquireAndLock(boolean)} */ private void releaseAndUnlock() { releaseReference(); if (mState == TRANS_STARTED) { try { mDatabase.setTransactionSuccessful(); } finally { mDatabase.endTransaction(); } } else if (mState == LOCK_ACQUIRED) { mDatabase.unlock(); } if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_COMMIT || (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_ABORT) { mDatabase.resetTransactionUsingExecSqlFlag(); } clearBindings(); // release the compiled sql statement so that the caller's SQLiteStatement no longer // has a hard reference to a database object that may get deallocated at any point. release(); // restore the database connection handle to the original value mDatabase = mOrigDb; setNativeHandle(mDatabase.mNativeHandle); } private final native int native_execute(); private final native long native_executeInsert(); private final native long native_1x1_long(); private final native String native_1x1_string(); private final native ParcelFileDescriptor native_1x1_blob_ashmem() throws IOException; private final native void native_executeSql(String sql); }