SQLiteProgram.java revision cc1eaf6f7b81eaae21c01cbf4cf6f27dc3970774
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.database.Cursor;
21
22import java.util.HashMap;
23
24/**
25 * A base class for compiled SQLite programs.
26 *<p>
27 * SQLiteProgram is NOT internally synchronized so code using a SQLiteProgram from multiple
28 * threads should perform its own synchronization when using the SQLiteProgram.
29 */
30public abstract class SQLiteProgram extends SQLiteClosable {
31
32    private static final String TAG = "SQLiteProgram";
33
34    /** The database this program is compiled against.
35     * @deprecated do not use this
36     */
37    @Deprecated
38    protected SQLiteDatabase mDatabase;
39
40    /** The SQL used to create this query */
41    /* package */ final String mSql;
42
43    /**
44     * Native linkage, do not modify. This comes from the database and should not be modified
45     * in here or in the native code.
46     * @deprecated do not use this
47     */
48    @Deprecated
49    protected int nHandle;
50
51    /**
52     * the SQLiteCompiledSql object for the given sql statement.
53     */
54    /* package */ SQLiteCompiledSql mCompiledSql;
55
56    /**
57     * SQLiteCompiledSql statement id is populated with the corresponding object from the above
58     * member. This member is used by the native_bind_* methods
59     * @deprecated do not use this
60     */
61    @Deprecated
62    protected int nStatement;
63
64    /**
65     * In the case of {@link SQLiteStatement}, this member stores the bindargs passed
66     * to the following methods, instead of actually doing the binding.
67     * <ul>
68     *   <li>{@link #bindBlob(int, byte[])}</li>
69     *   <li>{@link #bindDouble(int, double)}</li>
70     *   <li>{@link #bindLong(int, long)}</li>
71     *   <li>{@link #bindNull(int)}</li>
72     *   <li>{@link #bindString(int, String)}</li>
73     * </ul>
74     * <p>
75     * Each entry in the array is a Pair of
76     * <ol>
77     *   <li>bind arg position number</li>
78     *   <li>the value to be bound to the bindarg</li>
79     * </ol>
80     * <p>
81     * It is lazily initialized in the above bind methods
82     * and it is cleared in {@link #clearBindings()} method.
83     * <p>
84     * It is protected (in multi-threaded environment) by {@link SQLiteProgram}.this
85     */
86    /* package */ HashMap<Integer, Object> mBindArgs = null;
87    /* package */ final int mStatementType;
88    /* package */ static final int STATEMENT_CACHEABLE = 16;
89    /* package */ static final int STATEMENT_DONT_PREPARE = 32;
90    /* package */ static final int STATEMENT_USE_POOLED_CONN = 64;
91    /* package */ static final int STATEMENT_TYPE_MASK = 0x0f;
92
93    /* package */ SQLiteProgram(SQLiteDatabase db, String sql) {
94        this(db, sql, null, true);
95    }
96
97    /* package */ SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs,
98            boolean compileFlag) {
99        mSql = sql.trim();
100        int n = DatabaseUtils.getSqlStatementType(mSql);
101        switch (n) {
102            case DatabaseUtils.STATEMENT_UPDATE:
103                mStatementType = n | STATEMENT_CACHEABLE;
104                break;
105            case DatabaseUtils.STATEMENT_SELECT:
106                mStatementType = n | STATEMENT_CACHEABLE | STATEMENT_USE_POOLED_CONN;
107                break;
108            case DatabaseUtils.STATEMENT_BEGIN:
109            case DatabaseUtils.STATEMENT_COMMIT:
110            case DatabaseUtils.STATEMENT_ABORT:
111                mStatementType = n | STATEMENT_DONT_PREPARE;
112                break;
113            default:
114                mStatementType = n;
115        }
116        db.acquireReference();
117        db.addSQLiteClosable(this);
118        mDatabase = db;
119        nHandle = db.mNativeHandle;
120        if (bindArgs != null) {
121            int size = bindArgs.length;
122            for (int i = 0; i < size; i++) {
123                this.addToBindArgs(i + 1, bindArgs[i]);
124            }
125        }
126        if (compileFlag) {
127            compileAndbindAllArgs();
128        }
129    }
130
131    private void compileSql() {
132        // only cache CRUD statements
133        if ((mStatementType & STATEMENT_CACHEABLE) == 0) {
134            mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
135            nStatement = mCompiledSql.nStatement;
136            // since it is not in the cache, no need to acquire() it.
137            return;
138        }
139
140        mCompiledSql = mDatabase.getCompiledStatementForSql(mSql);
141        if (mCompiledSql == null) {
142            // create a new compiled-sql obj
143            mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
144
145            // add it to the cache of compiled-sqls
146            // but before adding it and thus making it available for anyone else to use it,
147            // make sure it is acquired by me.
148            mCompiledSql.acquire();
149            mDatabase.addToCompiledQueries(mSql, mCompiledSql);
150        } else {
151            // it is already in compiled-sql cache.
152            // try to acquire the object.
153            if (!mCompiledSql.acquire()) {
154                int last = mCompiledSql.nStatement;
155                // the SQLiteCompiledSql in cache is in use by some other SQLiteProgram object.
156                // we can't have two different SQLiteProgam objects can't share the same
157                // CompiledSql object. create a new one.
158                // finalize it when I am done with it in "this" object.
159                mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
160                // since it is not in the cache, no need to acquire() it.
161            }
162        }
163        nStatement = mCompiledSql.nStatement;
164    }
165
166    @Override
167    protected void onAllReferencesReleased() {
168        release();
169        mDatabase.removeSQLiteClosable(this);
170        mDatabase.releaseReference();
171    }
172
173    @Override
174    protected void onAllReferencesReleasedFromContainer() {
175        release();
176        mDatabase.releaseReference();
177    }
178
179    /* package */ void release() {
180        if (mCompiledSql == null) {
181            return;
182        }
183        mDatabase.releaseCompiledSqlObj(mSql, mCompiledSql);
184        mCompiledSql = null;
185        nStatement = 0;
186    }
187
188    /**
189     * Returns a unique identifier for this program.
190     *
191     * @return a unique identifier for this program
192     * @deprecated do not use this method. it is not guaranteed to be the same across executions of
193     * the SQL statement contained in this object.
194     */
195    @Deprecated
196    public final int getUniqueId() {
197      return -1;
198    }
199
200    /**
201     * used only for testing purposes
202     */
203    /* package */ int getSqlStatementId() {
204      synchronized(this) {
205        return (mCompiledSql == null) ? 0 : nStatement;
206      }
207    }
208
209    /* package */ String getSqlString() {
210        return mSql;
211    }
212
213    /**
214     * @deprecated This method is deprecated and must not be used.
215     *
216     * @param sql the SQL string to compile
217     * @param forceCompilation forces the SQL to be recompiled in the event that there is an
218     *  existing compiled SQL program already around
219     */
220    @Deprecated
221    protected void compile(String sql, boolean forceCompilation) {
222        // TODO is there a need for this?
223    }
224
225    private void bind(int type, int index, Object value) {
226        mDatabase.verifyDbIsOpen();
227        addToBindArgs(index, (type == Cursor.FIELD_TYPE_NULL) ? null : value);
228        if (nStatement > 0) {
229            // bind only if the SQL statement is compiled
230            acquireReference();
231            try {
232                switch (type) {
233                    case Cursor.FIELD_TYPE_NULL:
234                        native_bind_null(index);
235                        break;
236                    case Cursor.FIELD_TYPE_BLOB:
237                        native_bind_blob(index, (byte[]) value);
238                        break;
239                    case Cursor.FIELD_TYPE_FLOAT:
240                        native_bind_double(index, (Double) value);
241                        break;
242                    case Cursor.FIELD_TYPE_INTEGER:
243                        native_bind_long(index, (Long) value);
244                        break;
245                    case Cursor.FIELD_TYPE_STRING:
246                    default:
247                        native_bind_string(index, (String) value);
248                        break;
249                }
250            } finally {
251                releaseReference();
252            }
253        }
254    }
255
256    /**
257     * Bind a NULL value to this statement. The value remains bound until
258     * {@link #clearBindings} is called.
259     *
260     * @param index The 1-based index to the parameter to bind null to
261     */
262    public void bindNull(int index) {
263        bind(Cursor.FIELD_TYPE_NULL, index, null);
264    }
265
266    /**
267     * Bind a long value to this statement. The value remains bound until
268     * {@link #clearBindings} is called.
269     *addToBindArgs
270     * @param index The 1-based index to the parameter to bind
271     * @param value The value to bind
272     */
273    public void bindLong(int index, long value) {
274        bind(Cursor.FIELD_TYPE_INTEGER, index, value);
275    }
276
277    /**
278     * Bind a double value to this statement. The value remains bound until
279     * {@link #clearBindings} is called.
280     *
281     * @param index The 1-based index to the parameter to bind
282     * @param value The value to bind
283     */
284    public void bindDouble(int index, double value) {
285        bind(Cursor.FIELD_TYPE_FLOAT, index, value);
286    }
287
288    /**
289     * Bind a String value to this statement. The value remains bound until
290     * {@link #clearBindings} is called.
291     *
292     * @param index The 1-based index to the parameter to bind
293     * @param value The value to bind
294     */
295    public void bindString(int index, String value) {
296        if (value == null) {
297            throw new IllegalArgumentException("the bind value at index " + index + " is null");
298        }
299        bind(Cursor.FIELD_TYPE_STRING, index, value);
300    }
301
302    /**
303     * Bind a byte array value to this statement. The value remains bound until
304     * {@link #clearBindings} is called.
305     *
306     * @param index The 1-based index to the parameter to bind
307     * @param value The value to bind
308     */
309    public void bindBlob(int index, byte[] value) {
310        if (value == null) {
311            throw new IllegalArgumentException("the bind value at index " + index + " is null");
312        }
313        bind(Cursor.FIELD_TYPE_BLOB, index, value);
314    }
315
316    /**
317     * Clears all existing bindings. Unset bindings are treated as NULL.
318     */
319    public void clearBindings() {
320        mBindArgs = null;
321        if (this.nStatement == 0) {
322            return;
323        }
324        mDatabase.verifyDbIsOpen();
325        acquireReference();
326        try {
327            native_clear_bindings();
328        } finally {
329            releaseReference();
330        }
331    }
332
333    /**
334     * Release this program's resources, making it invalid.
335     */
336    public void close() {
337        mBindArgs = null;
338        if (nHandle == 0 || !mDatabase.isOpen()) {
339            return;
340        }
341        releaseReference();
342    }
343
344    private void addToBindArgs(int index, Object value) {
345        if (mBindArgs == null) {
346            mBindArgs = new HashMap<Integer, Object>();
347        }
348        mBindArgs.put(index, value);
349    }
350
351    /* package */ void compileAndbindAllArgs() {
352        if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
353            if (mBindArgs != null) {
354                throw new IllegalArgumentException("Can't pass bindargs for this sql :" + mSql);
355            }
356            // no need to prepare this SQL statement
357            return;
358        }
359        if (nStatement == 0) {
360            // SQL statement is not compiled yet. compile it now.
361            compileSql();
362        }
363        if (mBindArgs == null) {
364            return;
365        }
366        for (int index : mBindArgs.keySet()) {
367            Object value = mBindArgs.get(index);
368            if (value == null) {
369                native_bind_null(index);
370            } else if (value instanceof Double || value instanceof Float) {
371                native_bind_double(index, ((Number) value).doubleValue());
372            } else if (value instanceof Number) {
373                native_bind_long(index, ((Number) value).longValue());
374            } else if (value instanceof Boolean) {
375                Boolean bool = (Boolean)value;
376                native_bind_long(index, (bool) ? 1 : 0);
377                if (bool) {
378                    native_bind_long(index, 1);
379                } else {
380                    native_bind_long(index, 0);
381                }
382            } else if (value instanceof byte[]){
383                native_bind_blob(index, (byte[]) value);
384            } else {
385                native_bind_string(index, value.toString());
386            }
387        }
388    }
389
390    /**
391     * Given an array of String bindArgs, this method binds all of them in one single call.
392     *
393     * @param bindArgs the String array of bind args.
394     */
395    public void bindAllArgsAsStrings(String[] bindArgs) {
396        if (bindArgs == null) {
397            return;
398        }
399        int size = bindArgs.length;
400        for (int i = 0; i < size; i++) {
401            bindString(i + 1, bindArgs[i]);
402        }
403    }
404
405    /* package */ synchronized final void setNativeHandle(int nHandle) {
406        this.nHandle = nHandle;
407    }
408
409    /**
410     * @deprecated This method is deprecated and must not be used.
411     * Compiles SQL into a SQLite program.
412     *
413     * <P>The database lock must be held when calling this method.
414     * @param sql The SQL to compile.
415     */
416    @Deprecated
417    protected final native void native_compile(String sql);
418
419    /**
420     * @deprecated This method is deprecated and must not be used.
421     */
422    @Deprecated
423    protected final native void native_finalize();
424
425    protected final native void native_bind_null(int index);
426    protected final native void native_bind_long(int index, long value);
427    protected final native void native_bind_double(int index, double value);
428    protected final native void native_bind_string(int index, String value);
429    protected final native void native_bind_blob(int index, byte[] value);
430    private final native void native_clear_bindings();
431}
432
433