• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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