SQLiteCursor.java revision d2183654e03d589b120467f4e98da1b178ceeadb
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.AbstractWindowedCursor; 20import android.database.CursorWindow; 21import android.os.StrictMode; 22import android.util.Log; 23 24import java.util.HashMap; 25import java.util.Map; 26 27/** 28 * A Cursor implementation that exposes results from a query on a 29 * {@link SQLiteDatabase}. 30 * 31 * SQLiteCursor is not internally synchronized so code using a SQLiteCursor from multiple 32 * threads should perform its own synchronization when using the SQLiteCursor. 33 */ 34public class SQLiteCursor extends AbstractWindowedCursor { 35 static final String TAG = "SQLiteCursor"; 36 static final int NO_COUNT = -1; 37 38 /** The name of the table to edit */ 39 private final String mEditTable; 40 41 /** The names of the columns in the rows */ 42 private final String[] mColumns; 43 44 /** The query object for the cursor */ 45 private SQLiteQuery mQuery; 46 47 /** The compiled query this cursor came from */ 48 private final SQLiteCursorDriver mDriver; 49 50 /** The number of rows in the cursor */ 51 private volatile int mCount = NO_COUNT; 52 53 /** A mapping of column names to column indices, to speed up lookups */ 54 private Map<String, Integer> mColumnNameMap; 55 56 /** Used to find out where a cursor was allocated in case it never got released. */ 57 private final Throwable mStackTrace; 58 59 /** 60 * Execute a query and provide access to its result set through a Cursor 61 * interface. For a query such as: {@code SELECT name, birth, phone FROM 62 * myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth, 63 * phone) would be in the projection argument and everything from 64 * {@code FROM} onward would be in the params argument. This constructor 65 * has package scope. 66 * 67 * @param db a reference to a Database object that is already constructed 68 * and opened. This param is not used any longer 69 * @param editTable the name of the table used for this query 70 * @param query the rest of the query terms 71 * cursor is finalized 72 * @deprecated use {@link #SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)} instead 73 */ 74 @Deprecated 75 public SQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver, 76 String editTable, SQLiteQuery query) { 77 this(driver, editTable, query); 78 } 79 80 /** 81 * Execute a query and provide access to its result set through a Cursor 82 * interface. For a query such as: {@code SELECT name, birth, phone FROM 83 * myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth, 84 * phone) would be in the projection argument and everything from 85 * {@code FROM} onward would be in the params argument. This constructor 86 * has package scope. 87 * 88 * @param editTable the name of the table used for this query 89 * @param query the {@link SQLiteQuery} object associated with this cursor object. 90 */ 91 public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) { 92 if (query == null) { 93 throw new IllegalArgumentException("query object cannot be null"); 94 } 95 if (query.mDatabase == null) { 96 throw new IllegalArgumentException("query.mDatabase cannot be null"); 97 } 98 mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace(); 99 mDriver = driver; 100 mEditTable = editTable; 101 mColumnNameMap = null; 102 mQuery = query; 103 104 query.mDatabase.lock(query.mSql); 105 try { 106 // Setup the list of columns 107 int columnCount = mQuery.columnCountLocked(); 108 mColumns = new String[columnCount]; 109 110 // Read in all column names 111 for (int i = 0; i < columnCount; i++) { 112 String columnName = mQuery.columnNameLocked(i); 113 mColumns[i] = columnName; 114 if (false) { 115 Log.v("DatabaseWindow", "mColumns[" + i + "] is " 116 + mColumns[i]); 117 } 118 119 // Make note of the row ID column index for quick access to it 120 if ("_id".equals(columnName)) { 121 mRowIdColumnIndex = i; 122 } 123 } 124 } finally { 125 query.mDatabase.unlock(); 126 } 127 } 128 129 /** 130 * @return the SQLiteDatabase that this cursor is associated with. 131 */ 132 public SQLiteDatabase getDatabase() { 133 synchronized (this) { 134 return mQuery.mDatabase; 135 } 136 } 137 138 @Override 139 public boolean onMove(int oldPosition, int newPosition) { 140 // Make sure the row at newPosition is present in the window 141 if (mWindow == null || newPosition < mWindow.getStartPosition() || 142 newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) { 143 fillWindow(newPosition); 144 } 145 146 return true; 147 } 148 149 @Override 150 public int getCount() { 151 if (mCount == NO_COUNT) { 152 fillWindow(0); 153 } 154 return mCount; 155 } 156 157 private void fillWindow(int startPos) { 158 clearOrCreateLocalWindow(); 159 mWindow.setStartPosition(startPos); 160 int count = getQuery().fillWindow(mWindow); 161 if (startPos == 0) { // fillWindow returns count(*) only for startPos = 0 162 if (Log.isLoggable(TAG, Log.DEBUG)) { 163 Log.d(TAG, "received count(*) from native_fill_window: " + count); 164 } 165 mCount = count; 166 } else if (mCount <= 0) { 167 throw new IllegalStateException("Row count should never be zero or negative " 168 + "when the start position is non-zero"); 169 } 170 } 171 172 private synchronized SQLiteQuery getQuery() { 173 return mQuery; 174 } 175 176 @Override 177 public int getColumnIndex(String columnName) { 178 // Create mColumnNameMap on demand 179 if (mColumnNameMap == null) { 180 String[] columns = mColumns; 181 int columnCount = columns.length; 182 HashMap<String, Integer> map = new HashMap<String, Integer>(columnCount, 1); 183 for (int i = 0; i < columnCount; i++) { 184 map.put(columns[i], i); 185 } 186 mColumnNameMap = map; 187 } 188 189 // Hack according to bug 903852 190 final int periodIndex = columnName.lastIndexOf('.'); 191 if (periodIndex != -1) { 192 Exception e = new Exception(); 193 Log.e(TAG, "requesting column name with table name -- " + columnName, e); 194 columnName = columnName.substring(periodIndex + 1); 195 } 196 197 Integer i = mColumnNameMap.get(columnName); 198 if (i != null) { 199 return i.intValue(); 200 } else { 201 return -1; 202 } 203 } 204 205 @Override 206 public String[] getColumnNames() { 207 return mColumns; 208 } 209 210 @Override 211 public void deactivate() { 212 super.deactivate(); 213 mDriver.cursorDeactivated(); 214 } 215 216 @Override 217 public void close() { 218 super.close(); 219 synchronized (this) { 220 mQuery.close(); 221 mDriver.cursorClosed(); 222 } 223 } 224 225 @Override 226 public boolean requery() { 227 if (isClosed()) { 228 return false; 229 } 230 long timeStart = 0; 231 if (false) { 232 timeStart = System.currentTimeMillis(); 233 } 234 235 synchronized (this) { 236 if (mWindow != null) { 237 mWindow.clear(); 238 } 239 mPos = -1; 240 SQLiteDatabase db = null; 241 try { 242 db = mQuery.mDatabase.getDatabaseHandle(mQuery.mSql); 243 } catch (IllegalStateException e) { 244 // for backwards compatibility, just return false 245 Log.w(TAG, "requery() failed " + e.getMessage(), e); 246 return false; 247 } 248 if (!db.equals(mQuery.mDatabase)) { 249 // since we need to use a different database connection handle, 250 // re-compile the query 251 try { 252 db.lock(mQuery.mSql); 253 } catch (IllegalStateException e) { 254 // for backwards compatibility, just return false 255 Log.w(TAG, "requery() failed " + e.getMessage(), e); 256 return false; 257 } 258 try { 259 // close the old mQuery object and open a new one 260 mQuery.close(); 261 mQuery = new SQLiteQuery(db, mQuery); 262 } catch (IllegalStateException e) { 263 // for backwards compatibility, just return false 264 Log.w(TAG, "requery() failed " + e.getMessage(), e); 265 return false; 266 } finally { 267 db.unlock(); 268 } 269 } 270 // This one will recreate the temp table, and get its count 271 mDriver.cursorRequeried(this); 272 mCount = NO_COUNT; 273 try { 274 mQuery.requery(); 275 } catch (IllegalStateException e) { 276 // for backwards compatibility, just return false 277 Log.w(TAG, "requery() failed " + e.getMessage(), e); 278 return false; 279 } 280 } 281 282 if (false) { 283 Log.v("DatabaseWindow", "closing window in requery()"); 284 Log.v(TAG, "--- Requery()ed cursor " + this + ": " + mQuery); 285 } 286 287 boolean result = false; 288 try { 289 result = super.requery(); 290 } catch (IllegalStateException e) { 291 // for backwards compatibility, just return false 292 Log.w(TAG, "requery() failed " + e.getMessage(), e); 293 } 294 if (false) { 295 long timeEnd = System.currentTimeMillis(); 296 Log.v(TAG, "requery (" + (timeEnd - timeStart) + " ms): " + mDriver.toString()); 297 } 298 return result; 299 } 300 301 @Override 302 public void setWindow(CursorWindow window) { 303 super.setWindow(window); 304 mCount = NO_COUNT; 305 } 306 307 /** 308 * Changes the selection arguments. The new values take effect after a call to requery(). 309 */ 310 public void setSelectionArguments(String[] selectionArgs) { 311 mDriver.setBindArguments(selectionArgs); 312 } 313 314 /** 315 * Release the native resources, if they haven't been released yet. 316 */ 317 @Override 318 protected void finalize() { 319 try { 320 // if the cursor hasn't been closed yet, close it first 321 if (mWindow != null) { 322 if (StrictMode.vmSqliteObjectLeaksEnabled()) { 323 int len = mQuery.mSql.length(); 324 StrictMode.onSqliteObjectLeaked( 325 "Finalizing a Cursor that has not been deactivated or closed. " + 326 "database = " + mQuery.mDatabase.getPath() + ", table = " + mEditTable + 327 ", query = " + mQuery.mSql.substring(0, (len > 1000) ? 1000 : len), 328 mStackTrace); 329 } 330 close(); 331 SQLiteDebug.notifyActiveCursorFinalized(); 332 } else { 333 if (false) { 334 Log.v(TAG, "Finalizing cursor on database = " + mQuery.mDatabase.getPath() + 335 ", table = " + mEditTable + ", query = " + mQuery.mSql); 336 } 337 } 338 } finally { 339 super.finalize(); 340 } 341 } 342} 343