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.compat.annotation.UnsupportedAppUsage;
20 import android.database.DatabaseUtils;
21 import android.os.CancellationSignal;
22 
23 import java.util.Arrays;
24 
25 /**
26  * A base class for compiled SQLite programs.
27  * <p>
28  * This class is not thread-safe.
29  * </p>
30  */
31 public abstract class SQLiteProgram extends SQLiteClosable {
32     private static final String[] EMPTY_STRING_ARRAY = new String[0];
33 
34     private final SQLiteDatabase mDatabase;
35     @UnsupportedAppUsage
36     private final String mSql;
37     private final boolean mReadOnly;
38     private final String[] mColumnNames;
39     private final int mNumParameters;
40     @UnsupportedAppUsage
41     private final Object[] mBindArgs;
42 
SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs, CancellationSignal cancellationSignalForPrepare)43     SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs,
44             CancellationSignal cancellationSignalForPrepare) {
45         mDatabase = db;
46         mSql = sql.trim();
47 
48         int n = DatabaseUtils.getSqlStatementType(mSql);
49         switch (n) {
50             case DatabaseUtils.STATEMENT_BEGIN:
51             case DatabaseUtils.STATEMENT_COMMIT:
52             case DatabaseUtils.STATEMENT_ABORT:
53                 mReadOnly = false;
54                 mColumnNames = EMPTY_STRING_ARRAY;
55                 mNumParameters = 0;
56                 break;
57 
58             default:
59                 boolean assumeReadOnly = (n == DatabaseUtils.STATEMENT_SELECT);
60                 SQLiteStatementInfo info = new SQLiteStatementInfo();
61                 db.getThreadSession().prepare(mSql,
62                         db.getThreadDefaultConnectionFlags(assumeReadOnly),
63                         cancellationSignalForPrepare, info);
64                 mReadOnly = info.readOnly;
65                 mColumnNames = info.columnNames;
66                 mNumParameters = info.numParameters;
67                 break;
68         }
69 
70         if (bindArgs != null && bindArgs.length > mNumParameters) {
71             throw new IllegalArgumentException("Too many bind arguments.  "
72                     + bindArgs.length + " arguments were provided but the statement needs "
73                     + mNumParameters + " arguments.");
74         }
75 
76         if (mNumParameters != 0) {
77             mBindArgs = new Object[mNumParameters];
78             if (bindArgs != null) {
79                 System.arraycopy(bindArgs, 0, mBindArgs, 0, bindArgs.length);
80             }
81         } else {
82             mBindArgs = null;
83         }
84     }
85 
getDatabase()86     final SQLiteDatabase getDatabase() {
87         return mDatabase;
88     }
89 
getSql()90     final String getSql() {
91         return mSql;
92     }
93 
getBindArgs()94     final Object[] getBindArgs() {
95         return mBindArgs;
96     }
97 
getColumnNames()98     final String[] getColumnNames() {
99         return mColumnNames;
100     }
101 
102     /** @hide */
getSession()103     protected final SQLiteSession getSession() {
104         return mDatabase.getThreadSession();
105     }
106 
107     /** @hide */
getConnectionFlags()108     protected final int getConnectionFlags() {
109         return mDatabase.getThreadDefaultConnectionFlags(mReadOnly);
110     }
111 
112     /** @hide */
onCorruption()113     protected final void onCorruption() {
114         mDatabase.onCorruption();
115     }
116 
117     /**
118      * Unimplemented.
119      * @deprecated This method is deprecated and must not be used.
120      */
121     @Deprecated
getUniqueId()122     public final int getUniqueId() {
123         return -1;
124     }
125 
126     /**
127      * Bind a NULL value to this statement. The value remains bound until
128      * {@link #clearBindings} is called.
129      *
130      * @param index The 1-based index to the parameter to bind null to
131      */
bindNull(int index)132     public void bindNull(int index) {
133         bind(index, null);
134     }
135 
136     /**
137      * Bind a long value to this statement. The value remains bound until
138      * {@link #clearBindings} is called.
139      *addToBindArgs
140      * @param index The 1-based index to the parameter to bind
141      * @param value The value to bind
142      */
bindLong(int index, long value)143     public void bindLong(int index, long value) {
144         bind(index, value);
145     }
146 
147     /**
148      * Bind a double value to this statement. The value remains bound until
149      * {@link #clearBindings} is called.
150      *
151      * @param index The 1-based index to the parameter to bind
152      * @param value The value to bind
153      */
bindDouble(int index, double value)154     public void bindDouble(int index, double value) {
155         bind(index, value);
156     }
157 
158     /**
159      * Bind a String value to this statement. The value remains bound until
160      * {@link #clearBindings} is called.
161      *
162      * @param index The 1-based index to the parameter to bind
163      * @param value The value to bind, must not be null
164      */
bindString(int index, String value)165     public void bindString(int index, String value) {
166         if (value == null) {
167             throw new IllegalArgumentException("the bind value at index " + index + " is null");
168         }
169         bind(index, value);
170     }
171 
172     /**
173      * Bind a byte array value to this statement. The value remains bound until
174      * {@link #clearBindings} is called.
175      *
176      * @param index The 1-based index to the parameter to bind
177      * @param value The value to bind, must not be null
178      */
bindBlob(int index, byte[] value)179     public void bindBlob(int index, byte[] value) {
180         if (value == null) {
181             throw new IllegalArgumentException("the bind value at index " + index + " is null");
182         }
183         bind(index, value);
184     }
185 
186     /**
187      * Clears all existing bindings. Unset bindings are treated as NULL.
188      */
clearBindings()189     public void clearBindings() {
190         if (mBindArgs != null) {
191             Arrays.fill(mBindArgs, null);
192         }
193     }
194 
195     /**
196      * Given an array of String bindArgs, this method binds all of them in one single call.
197      *
198      * @param bindArgs the String array of bind args, none of which must be null.
199      */
bindAllArgsAsStrings(String[] bindArgs)200     public void bindAllArgsAsStrings(String[] bindArgs) {
201         if (bindArgs != null) {
202             for (int i = bindArgs.length; i != 0; i--) {
203                 bindString(i, bindArgs[i - 1]);
204             }
205         }
206     }
207 
208     @Override
onAllReferencesReleased()209     protected void onAllReferencesReleased() {
210         clearBindings();
211     }
212 
bind(int index, Object value)213     private void bind(int index, Object value) {
214         if (index < 1 || index > mNumParameters) {
215             throw new IllegalArgumentException("Cannot bind argument at index "
216                     + index + " because the index is out of range.  "
217                     + "The statement has " + mNumParameters + " parameters.");
218         }
219         mBindArgs[index - 1] = value;
220     }
221 }
222