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