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.CursorWindow;
20import android.os.SystemClock;
21import android.util.Log;
22
23/**
24 * A SQLite program that represents a query that reads the resulting rows into a CursorWindow.
25 * This class is used by SQLiteCursor and isn't useful itself.
26 *
27 * SQLiteQuery is not internally synchronized so code using a SQLiteQuery from multiple
28 * threads should perform its own synchronization when using the SQLiteQuery.
29 */
30public class SQLiteQuery extends SQLiteProgram {
31    private static final String TAG = "Cursor";
32
33    /** The index of the unbound OFFSET parameter */
34    private int mOffsetIndex;
35
36    /** Args to bind on requery */
37    private String[] mBindArgs;
38
39    private boolean mClosed = false;
40
41    /**
42     * Create a persistent query object.
43     *
44     * @param db The database that this query object is associated with
45     * @param query The SQL string for this query.
46     * @param offsetIndex The 1-based index to the OFFSET parameter,
47     */
48    /* package */ SQLiteQuery(SQLiteDatabase db, String query, int offsetIndex, String[] bindArgs) {
49        super(db, query);
50
51        mOffsetIndex = offsetIndex;
52        mBindArgs = bindArgs;
53    }
54
55    /**
56     * Reads rows into a buffer. This method acquires the database lock.
57     *
58     * @param window The window to fill into
59     * @return number of total rows in the query
60     */
61    /* package */ int fillWindow(CursorWindow window,
62            int maxRead, int lastPos) {
63        long timeStart = SystemClock.uptimeMillis();
64        mDatabase.lock();
65        mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX);
66        try {
67            acquireReference();
68            try {
69                window.acquireReference();
70                // if the start pos is not equal to 0, then most likely window is
71                // too small for the data set, loading by another thread
72                // is not safe in this situation. the native code will ignore maxRead
73                int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
74                        maxRead, lastPos);
75
76                // Logging
77                if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
78                    Log.d(TAG, "fillWindow(): " + mSql);
79                }
80                mDatabase.logTimeStat(mSql, timeStart);
81                return numRows;
82            } catch (IllegalStateException e){
83                // simply ignore it
84                return 0;
85            } catch (SQLiteDatabaseCorruptException e) {
86                mDatabase.onCorruption();
87                throw e;
88            } finally {
89                window.releaseReference();
90            }
91        } finally {
92            releaseReference();
93            mDatabase.unlock();
94        }
95    }
96
97    /**
98     * Get the column count for the statement. Only valid on query based
99     * statements. The database must be locked
100     * when calling this method.
101     *
102     * @return The number of column in the statement's result set.
103     */
104    /* package */ int columnCountLocked() {
105        acquireReference();
106        try {
107            return native_column_count();
108        } finally {
109            releaseReference();
110        }
111    }
112
113    /**
114     * Retrieves the column name for the given column index. The database must be locked
115     * when calling this method.
116     *
117     * @param columnIndex the index of the column to get the name for
118     * @return The requested column's name
119     */
120    /* package */ String columnNameLocked(int columnIndex) {
121        acquireReference();
122        try {
123            return native_column_name(columnIndex);
124        } finally {
125            releaseReference();
126        }
127    }
128
129    @Override
130    public String toString() {
131        return "SQLiteQuery: " + mSql;
132    }
133
134    @Override
135    public void close() {
136        super.close();
137        mClosed = true;
138    }
139
140    /**
141     * Called by SQLiteCursor when it is requeried.
142     */
143    /* package */ void requery() {
144        if (mBindArgs != null) {
145            int len = mBindArgs.length;
146            try {
147                for (int i = 0; i < len; i++) {
148                    super.bindString(i + 1, mBindArgs[i]);
149                }
150            } catch (SQLiteMisuseException e) {
151                StringBuilder errMsg = new StringBuilder("mSql " + mSql);
152                for (int i = 0; i < len; i++) {
153                    errMsg.append(" ");
154                    errMsg.append(mBindArgs[i]);
155                }
156                errMsg.append(" ");
157                IllegalStateException leakProgram = new IllegalStateException(
158                        errMsg.toString(), e);
159                throw leakProgram;
160            }
161        }
162    }
163
164    @Override
165    public void bindNull(int index) {
166        mBindArgs[index - 1] = null;
167        if (!mClosed) super.bindNull(index);
168    }
169
170    @Override
171    public void bindLong(int index, long value) {
172        mBindArgs[index - 1] = Long.toString(value);
173        if (!mClosed) super.bindLong(index, value);
174    }
175
176    @Override
177    public void bindDouble(int index, double value) {
178        mBindArgs[index - 1] = Double.toString(value);
179        if (!mClosed) super.bindDouble(index, value);
180    }
181
182    @Override
183    public void bindString(int index, String value) {
184        mBindArgs[index - 1] = value;
185        if (!mClosed) super.bindString(index, value);
186    }
187
188    private final native int native_fill_window(CursorWindow window,
189            int startPos, int offsetParam, int maxRead, int lastPos);
190
191    private final native int native_column_count();
192
193    private final native String native_column_name(int columnIndex);
194}
195