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