blob: 8dcedf2fdd0570be2240efb54a8ed04c34a9bf55 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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.AbstractWindowedCursor;
20import android.database.CursorWindow;
Jeff Brown650de3d2011-10-27 14:52:28 -070021import android.database.DatabaseUtils;
Brad Fitzpatrick32e60c72010-09-30 16:22:36 -070022import android.os.StrictMode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.util.Log;
24
25import java.util.HashMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import java.util.Map;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027
28/**
29 * A Cursor implementation that exposes results from a query on a
30 * {@link SQLiteDatabase}.
Jeff Hamiltonf3ca9a52010-05-12 15:04:33 -070031 *
32 * SQLiteCursor is not internally synchronized so code using a SQLiteCursor from multiple
33 * threads should perform its own synchronization when using the SQLiteCursor.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034 */
35public class SQLiteCursor extends AbstractWindowedCursor {
Vasu Nori071df262010-09-14 16:02:02 -070036 static final String TAG = "SQLiteCursor";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037 static final int NO_COUNT = -1;
38
39 /** The name of the table to edit */
Vasu Nori65a88832010-07-16 15:14:08 -070040 private final String mEditTable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041
42 /** The names of the columns in the rows */
Vasu Nori65a88832010-07-16 15:14:08 -070043 private final String[] mColumns;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044
45 /** The query object for the cursor */
46 private SQLiteQuery mQuery;
47
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048 /** The compiled query this cursor came from */
Vasu Nori65a88832010-07-16 15:14:08 -070049 private final SQLiteCursorDriver mDriver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050
51 /** The number of rows in the cursor */
Jeff Brown650de3d2011-10-27 14:52:28 -070052 private int mCount = NO_COUNT;
53
54 /** The number of rows that can fit in the cursor window, 0 if unknown */
55 private int mCursorWindowCapacity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056
57 /** A mapping of column names to column indices, to speed up lookups */
58 private Map<String, Integer> mColumnNameMap;
59
Vasu Norid606b4b2010-02-24 12:54:20 -080060 /** Used to find out where a cursor was allocated in case it never got released. */
Vasu Nori65a88832010-07-16 15:14:08 -070061 private final Throwable mStackTrace;
Makoto Onuki25897162010-04-29 11:26:20 -070062
63 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 * Execute a query and provide access to its result set through a Cursor
65 * interface. For a query such as: {@code SELECT name, birth, phone FROM
66 * myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth,
67 * phone) would be in the projection argument and everything from
68 * {@code FROM} onward would be in the params argument. This constructor
69 * has package scope.
70 *
71 * @param db a reference to a Database object that is already constructed
Vasu Nori65a88832010-07-16 15:14:08 -070072 * and opened. This param is not used any longer
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073 * @param editTable the name of the table used for this query
74 * @param query the rest of the query terms
75 * cursor is finalized
Vasu Nori65a88832010-07-16 15:14:08 -070076 * @deprecated use {@link #SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)} instead
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077 */
Vasu Nori65a88832010-07-16 15:14:08 -070078 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079 public SQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver,
80 String editTable, SQLiteQuery query) {
Vasu Nori65a88832010-07-16 15:14:08 -070081 this(driver, editTable, query);
82 }
83
84 /**
85 * Execute a query and provide access to its result set through a Cursor
86 * interface. For a query such as: {@code SELECT name, birth, phone FROM
87 * myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth,
88 * phone) would be in the projection argument and everything from
89 * {@code FROM} onward would be in the params argument. This constructor
90 * has package scope.
91 *
92 * @param editTable the name of the table used for this query
93 * @param query the {@link SQLiteQuery} object associated with this cursor object.
94 */
95 public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {
Vasu Nori65a88832010-07-16 15:14:08 -070096 if (query == null) {
97 throw new IllegalArgumentException("query object cannot be null");
98 }
99 if (query.mDatabase == null) {
100 throw new IllegalArgumentException("query.mDatabase cannot be null");
101 }
Jeff Sharkey7978a412011-10-25 17:09:09 -0700102 if (StrictMode.vmSqliteObjectLeaksEnabled()) {
103 mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
104 } else {
105 mStackTrace = null;
106 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107 mDriver = driver;
108 mEditTable = editTable;
109 mColumnNameMap = null;
110 mQuery = query;
111
Vasu Nori16057fa2011-03-18 11:40:37 -0700112 query.mDatabase.lock(query.mSql);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113 try {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 // Setup the list of columns
115 int columnCount = mQuery.columnCountLocked();
116 mColumns = new String[columnCount];
117
118 // Read in all column names
119 for (int i = 0; i < columnCount; i++) {
120 String columnName = mQuery.columnNameLocked(i);
121 mColumns[i] = columnName;
Joe Onorato43a17652011-04-06 19:22:23 -0700122 if (false) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800123 Log.v("DatabaseWindow", "mColumns[" + i + "] is "
124 + mColumns[i]);
125 }
126
127 // Make note of the row ID column index for quick access to it
128 if ("_id".equals(columnName)) {
129 mRowIdColumnIndex = i;
130 }
131 }
132 } finally {
Vasu Nori65a88832010-07-16 15:14:08 -0700133 query.mDatabase.unlock();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800134 }
135 }
136
137 /**
138 * @return the SQLiteDatabase that this cursor is associated with.
139 */
140 public SQLiteDatabase getDatabase() {
Vasu Nori65a88832010-07-16 15:14:08 -0700141 synchronized (this) {
142 return mQuery.mDatabase;
143 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144 }
145
146 @Override
147 public boolean onMove(int oldPosition, int newPosition) {
148 // Make sure the row at newPosition is present in the window
149 if (mWindow == null || newPosition < mWindow.getStartPosition() ||
150 newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
151 fillWindow(newPosition);
152 }
153
154 return true;
155 }
156
157 @Override
158 public int getCount() {
159 if (mCount == NO_COUNT) {
160 fillWindow(0);
161 }
162 return mCount;
163 }
164
Jeff Brown650de3d2011-10-27 14:52:28 -0700165 private void fillWindow(int requiredPos) {
Jeff Brown5e5d6d82011-10-12 15:41:34 -0700166 clearOrCreateWindow(getDatabase().getPath());
Jeff Brown650de3d2011-10-27 14:52:28 -0700167
168 if (mCount == NO_COUNT) {
169 int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
170 mCount = getQuery().fillWindow(mWindow, startPos, requiredPos, true);
171 mCursorWindowCapacity = mWindow.getNumRows();
Vasu Norib18f27d2010-08-12 18:16:35 -0700172 if (Log.isLoggable(TAG, Log.DEBUG)) {
Jeff Brown650de3d2011-10-27 14:52:28 -0700173 Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
Vasu Norib18f27d2010-08-12 18:16:35 -0700174 }
Jeff Brown650de3d2011-10-27 14:52:28 -0700175 } else {
176 int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
177 mCursorWindowCapacity);
178 getQuery().fillWindow(mWindow, startPos, requiredPos, false);
Vasu Norib18f27d2010-08-12 18:16:35 -0700179 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 }
181
Vasu Nori65a88832010-07-16 15:14:08 -0700182 private synchronized SQLiteQuery getQuery() {
183 return mQuery;
184 }
185
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186 @Override
187 public int getColumnIndex(String columnName) {
188 // Create mColumnNameMap on demand
189 if (mColumnNameMap == null) {
190 String[] columns = mColumns;
191 int columnCount = columns.length;
192 HashMap<String, Integer> map = new HashMap<String, Integer>(columnCount, 1);
193 for (int i = 0; i < columnCount; i++) {
194 map.put(columns[i], i);
195 }
196 mColumnNameMap = map;
197 }
198
199 // Hack according to bug 903852
200 final int periodIndex = columnName.lastIndexOf('.');
201 if (periodIndex != -1) {
202 Exception e = new Exception();
203 Log.e(TAG, "requesting column name with table name -- " + columnName, e);
204 columnName = columnName.substring(periodIndex + 1);
205 }
206
207 Integer i = mColumnNameMap.get(columnName);
208 if (i != null) {
209 return i.intValue();
210 } else {
211 return -1;
212 }
213 }
214
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215 @Override
216 public String[] getColumnNames() {
217 return mColumns;
218 }
219
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 @Override
221 public void deactivate() {
222 super.deactivate();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800223 mDriver.cursorDeactivated();
224 }
225
226 @Override
227 public void close() {
228 super.close();
Vasu Nori65a88832010-07-16 15:14:08 -0700229 synchronized (this) {
Vasu Nori65a88832010-07-16 15:14:08 -0700230 mQuery.close();
231 mDriver.cursorClosed();
232 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800233 }
234
235 @Override
236 public boolean requery() {
237 if (isClosed()) {
238 return false;
239 }
240 long timeStart = 0;
Joe Onorato43a17652011-04-06 19:22:23 -0700241 if (false) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800242 timeStart = System.currentTimeMillis();
243 }
Vasu Nori65a88832010-07-16 15:14:08 -0700244
245 synchronized (this) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246 if (mWindow != null) {
247 mWindow.clear();
248 }
249 mPos = -1;
Vasu Norica748972011-01-06 16:35:37 -0800250 SQLiteDatabase db = null;
251 try {
252 db = mQuery.mDatabase.getDatabaseHandle(mQuery.mSql);
253 } catch (IllegalStateException e) {
254 // for backwards compatibility, just return false
Vasu Nori324dbe52011-01-06 17:45:32 -0800255 Log.w(TAG, "requery() failed " + e.getMessage(), e);
Vasu Norica748972011-01-06 16:35:37 -0800256 return false;
257 }
Vasu Nori65a88832010-07-16 15:14:08 -0700258 if (!db.equals(mQuery.mDatabase)) {
259 // since we need to use a different database connection handle,
260 // re-compile the query
Vasu Norica748972011-01-06 16:35:37 -0800261 try {
Vasu Nori16057fa2011-03-18 11:40:37 -0700262 db.lock(mQuery.mSql);
Vasu Norica748972011-01-06 16:35:37 -0800263 } catch (IllegalStateException e) {
264 // for backwards compatibility, just return false
Vasu Nori324dbe52011-01-06 17:45:32 -0800265 Log.w(TAG, "requery() failed " + e.getMessage(), e);
Vasu Norica748972011-01-06 16:35:37 -0800266 return false;
267 }
Vasu Nori65a88832010-07-16 15:14:08 -0700268 try {
269 // close the old mQuery object and open a new one
270 mQuery.close();
271 mQuery = new SQLiteQuery(db, mQuery);
Vasu Norica748972011-01-06 16:35:37 -0800272 } catch (IllegalStateException e) {
273 // for backwards compatibility, just return false
Vasu Nori324dbe52011-01-06 17:45:32 -0800274 Log.w(TAG, "requery() failed " + e.getMessage(), e);
Vasu Norica748972011-01-06 16:35:37 -0800275 return false;
Vasu Nori65a88832010-07-16 15:14:08 -0700276 } finally {
277 db.unlock();
278 }
279 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800280 // This one will recreate the temp table, and get its count
281 mDriver.cursorRequeried(this);
282 mCount = NO_COUNT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800283 try {
284 mQuery.requery();
Vasu Norica748972011-01-06 16:35:37 -0800285 } catch (IllegalStateException e) {
286 // for backwards compatibility, just return false
Vasu Nori324dbe52011-01-06 17:45:32 -0800287 Log.w(TAG, "requery() failed " + e.getMessage(), e);
Vasu Norica748972011-01-06 16:35:37 -0800288 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800289 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800290 }
291
Joe Onorato43a17652011-04-06 19:22:23 -0700292 if (false) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800293 Log.v("DatabaseWindow", "closing window in requery()");
294 Log.v(TAG, "--- Requery()ed cursor " + this + ": " + mQuery);
295 }
296
Vasu Norica748972011-01-06 16:35:37 -0800297 boolean result = false;
298 try {
299 result = super.requery();
300 } catch (IllegalStateException e) {
301 // for backwards compatibility, just return false
Vasu Nori324dbe52011-01-06 17:45:32 -0800302 Log.w(TAG, "requery() failed " + e.getMessage(), e);
Vasu Norica748972011-01-06 16:35:37 -0800303 }
Joe Onorato43a17652011-04-06 19:22:23 -0700304 if (false) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800305 long timeEnd = System.currentTimeMillis();
306 Log.v(TAG, "requery (" + (timeEnd - timeStart) + " ms): " + mDriver.toString());
307 }
308 return result;
309 }
310
311 @Override
Jeff Brown7ce74522011-10-07 13:29:37 -0700312 public void setWindow(CursorWindow window) {
313 super.setWindow(window);
314 mCount = NO_COUNT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800315 }
316
317 /**
318 * Changes the selection arguments. The new values take effect after a call to requery().
319 */
320 public void setSelectionArguments(String[] selectionArgs) {
321 mDriver.setBindArguments(selectionArgs);
322 }
323
324 /**
325 * Release the native resources, if they haven't been released yet.
326 */
327 @Override
328 protected void finalize() {
329 try {
Vasu Nori42960e82010-01-06 23:12:02 -0800330 // if the cursor hasn't been closed yet, close it first
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800331 if (mWindow != null) {
Jeff Sharkey7978a412011-10-25 17:09:09 -0700332 if (mStackTrace != null) {
Brad Fitzpatrick32e60c72010-09-30 16:22:36 -0700333 int len = mQuery.mSql.length();
334 StrictMode.onSqliteObjectLeaked(
335 "Finalizing a Cursor that has not been deactivated or closed. " +
Vasu Nori65a88832010-07-16 15:14:08 -0700336 "database = " + mQuery.mDatabase.getPath() + ", table = " + mEditTable +
Vasu Norib73cf1c2010-10-25 11:55:56 -0700337 ", query = " + mQuery.mSql.substring(0, (len > 1000) ? 1000 : len),
Vasu Nori2cc1df02010-03-23 10:17:48 -0700338 mStackTrace);
Brad Fitzpatrick32e60c72010-09-30 16:22:36 -0700339 }
Vasu Nori2cc1df02010-03-23 10:17:48 -0700340 close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341 SQLiteDebug.notifyActiveCursorFinalized();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 } else {
Joe Onorato43a17652011-04-06 19:22:23 -0700343 if (false) {
Vasu Nori65a88832010-07-16 15:14:08 -0700344 Log.v(TAG, "Finalizing cursor on database = " + mQuery.mDatabase.getPath() +
Vasu Nori42960e82010-01-06 23:12:02 -0800345 ", table = " + mEditTable + ", query = " + mQuery.mSql);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800346 }
347 }
348 } finally {
349 super.finalize();
350 }
351 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352}