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