SQLiteCursor.java revision e5360fbf3efe85427f7e7f59afe7bbeddb4949ac
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.database.DatabaseUtils; 22import android.os.StrictMode; 23import android.util.Log; 24 25import java.util.HashMap; 26import java.util.Map; 27 28/** 29 * A Cursor implementation that exposes results from a query on a 30 * {@link SQLiteDatabase}. 31 * 32 * SQLiteCursor is not internally synchronized so code using a SQLiteCursor from multiple 33 * threads should perform its own synchronization when using the SQLiteCursor. 34 */ 35public class SQLiteCursor extends AbstractWindowedCursor { 36 static final String TAG = "SQLiteCursor"; 37 static final int NO_COUNT = -1; 38 39 /** The name of the table to edit */ 40 private final String mEditTable; 41 42 /** The names of the columns in the rows */ 43 private final String[] mColumns; 44 45 /** The query object for the cursor */ 46 private final SQLiteQuery mQuery; 47 48 /** The compiled query this cursor came from */ 49 private final SQLiteCursorDriver mDriver; 50 51 /** The number of rows in the cursor */ 52 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; 56 57 /** A mapping of column names to column indices, to speed up lookups */ 58 private Map<String, Integer> mColumnNameMap; 59 60 /** Used to find out where a cursor was allocated in case it never got released. */ 61 private final Throwable mStackTrace; 62 63 /** 64 * 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 72 * and opened. This param is not used any longer 73 * @param editTable the name of the table used for this query 74 * @param query the rest of the query terms 75 * cursor is finalized 76 * @deprecated use {@link #SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)} instead 77 */ 78 @Deprecated 79 public SQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver, 80 String editTable, SQLiteQuery query) { 81 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) { 96 if (query == null) { 97 throw new IllegalArgumentException("query object cannot be null"); 98 } 99 if (StrictMode.vmSqliteObjectLeaksEnabled()) { 100 mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace(); 101 } else { 102 mStackTrace = null; 103 } 104 mDriver = driver; 105 mEditTable = editTable; 106 mColumnNameMap = null; 107 mQuery = query; 108 109 mColumns = query.getColumnNames(); 110 for (int i = 0; i < mColumns.length; i++) { 111 // Make note of the row ID column index for quick access to it 112 if ("_id".equals(mColumns[i])) { 113 mRowIdColumnIndex = i; 114 } 115 } 116 } 117 118 /** 119 * Get the database that this cursor is associated with. 120 * @return the SQLiteDatabase that this cursor is associated with. 121 */ 122 public SQLiteDatabase getDatabase() { 123 return mQuery.getDatabase(); 124 } 125 126 @Override 127 public boolean onMove(int oldPosition, int newPosition) { 128 // Make sure the row at newPosition is present in the window 129 if (mWindow == null || newPosition < mWindow.getStartPosition() || 130 newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) { 131 fillWindow(newPosition); 132 } 133 134 return true; 135 } 136 137 @Override 138 public int getCount() { 139 if (mCount == NO_COUNT) { 140 fillWindow(0); 141 } 142 return mCount; 143 } 144 145 private void fillWindow(int requiredPos) { 146 clearOrCreateWindow(getDatabase().getPath()); 147 148 if (mCount == NO_COUNT) { 149 int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0); 150 mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true); 151 mCursorWindowCapacity = mWindow.getNumRows(); 152 if (Log.isLoggable(TAG, Log.DEBUG)) { 153 Log.d(TAG, "received count(*) from native_fill_window: " + mCount); 154 } 155 } else { 156 int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 157 mCursorWindowCapacity); 158 mQuery.fillWindow(mWindow, startPos, requiredPos, false); 159 } 160 } 161 162 @Override 163 public int getColumnIndex(String columnName) { 164 // Create mColumnNameMap on demand 165 if (mColumnNameMap == null) { 166 String[] columns = mColumns; 167 int columnCount = columns.length; 168 HashMap<String, Integer> map = new HashMap<String, Integer>(columnCount, 1); 169 for (int i = 0; i < columnCount; i++) { 170 map.put(columns[i], i); 171 } 172 mColumnNameMap = map; 173 } 174 175 // Hack according to bug 903852 176 final int periodIndex = columnName.lastIndexOf('.'); 177 if (periodIndex != -1) { 178 Exception e = new Exception(); 179 Log.e(TAG, "requesting column name with table name -- " + columnName, e); 180 columnName = columnName.substring(periodIndex + 1); 181 } 182 183 Integer i = mColumnNameMap.get(columnName); 184 if (i != null) { 185 return i.intValue(); 186 } else { 187 return -1; 188 } 189 } 190 191 @Override 192 public String[] getColumnNames() { 193 return mColumns; 194 } 195 196 @Override 197 public void deactivate() { 198 super.deactivate(); 199 mDriver.cursorDeactivated(); 200 } 201 202 @Override 203 public void close() { 204 super.close(); 205 synchronized (this) { 206 mQuery.close(); 207 mDriver.cursorClosed(); 208 } 209 } 210 211 @Override 212 public boolean requery() { 213 if (isClosed()) { 214 return false; 215 } 216 217 synchronized (this) { 218 if (!mQuery.getDatabase().isOpen()) { 219 return false; 220 } 221 222 if (mWindow != null) { 223 mWindow.clear(); 224 } 225 mPos = -1; 226 mCount = NO_COUNT; 227 228 mDriver.cursorRequeried(this); 229 } 230 231 try { 232 return super.requery(); 233 } catch (IllegalStateException e) { 234 // for backwards compatibility, just return false 235 Log.w(TAG, "requery() failed " + e.getMessage(), e); 236 return false; 237 } 238 } 239 240 @Override 241 public void setWindow(CursorWindow window) { 242 super.setWindow(window); 243 mCount = NO_COUNT; 244 } 245 246 /** 247 * Changes the selection arguments. The new values take effect after a call to requery(). 248 */ 249 public void setSelectionArguments(String[] selectionArgs) { 250 mDriver.setBindArguments(selectionArgs); 251 } 252 253 /** 254 * Release the native resources, if they haven't been released yet. 255 */ 256 @Override 257 protected void finalize() { 258 try { 259 // if the cursor hasn't been closed yet, close it first 260 if (mWindow != null) { 261 if (mStackTrace != null) { 262 String sql = mQuery.getSql(); 263 int len = sql.length(); 264 StrictMode.onSqliteObjectLeaked( 265 "Finalizing a Cursor that has not been deactivated or closed. " + 266 "database = " + mQuery.getDatabase().getLabel() + 267 ", table = " + mEditTable + 268 ", query = " + sql.substring(0, (len > 1000) ? 1000 : len), 269 mStackTrace); 270 } 271 close(); 272 SQLiteDebug.notifyActiveCursorFinalized(); 273 } 274 } finally { 275 super.finalize(); 276 } 277 } 278} 279