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