SQLiteCursor.java (9249B)
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 /* 17 ** Modified to support SQLite extensions by the SQLite developers: 18 ** sqlite-dev@sqlite.org. 19 */ 20 21 package org.sqlite.database.sqlite; 22 23 import android.database.AbstractWindowedCursor; 24 import android.database.CursorWindow; 25 import org.sqlite.database.DatabaseUtils; 26 27 import android.os.StrictMode; 28 import android.util.Log; 29 30 import java.util.HashMap; 31 import java.util.Map; 32 33 /** 34 * A Cursor implementation that exposes results from a query on a 35 * {@link SQLiteDatabase}. 36 * 37 * SQLiteCursor is not internally synchronized so code using a SQLiteCursor from multiple 38 * threads should perform its own synchronization when using the SQLiteCursor. 39 */ 40 public class SQLiteCursor extends AbstractWindowedCursor { 41 static final String TAG = "SQLiteCursor"; 42 static final int NO_COUNT = -1; 43 44 /** The name of the table to edit */ 45 private final String mEditTable; 46 47 /** The names of the columns in the rows */ 48 private final String[] mColumns; 49 50 /** The query object for the cursor */ 51 private final SQLiteQuery mQuery; 52 53 /** The compiled query this cursor came from */ 54 private final SQLiteCursorDriver mDriver; 55 56 /** The number of rows in the cursor */ 57 private int mCount = NO_COUNT; 58 59 /** The number of rows that can fit in the cursor window, 0 if unknown */ 60 private int mCursorWindowCapacity; 61 62 /** A mapping of column names to column indices, to speed up lookups */ 63 private Map<String, Integer> mColumnNameMap; 64 65 /** 66 * Execute a query and provide access to its result set through a Cursor 67 * interface. For a query such as: {@code SELECT name, birth, phone FROM 68 * myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth, 69 * phone) would be in the projection argument and everything from 70 * {@code FROM} onward would be in the params argument. 71 * 72 * @param db a reference to a Database object that is already constructed 73 * and opened. This param is not used any longer 74 * @param editTable the name of the table used for this query 75 * @param query the rest of the query terms 76 * cursor is finalized 77 * @deprecated use {@link #SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)} instead 78 */ 79 @Deprecated 80 public SQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver, 81 String editTable, SQLiteQuery query) { 82 this(driver, editTable, query); 83 } 84 85 /** 86 * Execute a query and provide access to its result set through a Cursor 87 * interface. For a query such as: {@code SELECT name, birth, phone FROM 88 * myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth, 89 * phone) would be in the projection argument and everything from 90 * {@code FROM} onward would be in the params argument. 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 mDriver = driver; 100 mEditTable = editTable; 101 mColumnNameMap = null; 102 mQuery = query; 103 104 mColumns = query.getColumnNames(); 105 } 106 107 /** 108 * Get the database that this cursor is associated with. 109 * @return the SQLiteDatabase that this cursor is associated with. 110 */ 111 public SQLiteDatabase getDatabase() { 112 return mQuery.getDatabase(); 113 } 114 115 @Override 116 public boolean onMove(int oldPosition, int newPosition) { 117 // Make sure the row at newPosition is present in the window 118 if (mWindow == null || newPosition < mWindow.getStartPosition() || 119 newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) { 120 fillWindow(newPosition); 121 } 122 123 return true; 124 } 125 126 @Override 127 public int getCount() { 128 if (mCount == NO_COUNT) { 129 fillWindow(0); 130 } 131 return mCount; 132 } 133 134 /* 135 ** The AbstractWindowClass contains protected methods clearOrCreateWindow() and 136 ** closeWindow(), which are used by the android.database.sqlite.* version of this 137 ** class. But, since they are marked with "@hide", the following replacement 138 ** versions are required. 139 */ 140 private void awc_clearOrCreateWindow(String name){ 141 CursorWindow win = getWindow(); 142 if( win==null ){ 143 win = new CursorWindow(name); 144 setWindow(win); 145 }else{ 146 win.clear(); 147 } 148 } 149 private void awc_closeWindow(){ 150 setWindow(null); 151 } 152 153 private void fillWindow(int requiredPos) { 154 awc_clearOrCreateWindow(getDatabase().getPath()); 155 156 try { 157 if (mCount == NO_COUNT) { 158 int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0); 159 mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true); 160 mCursorWindowCapacity = mWindow.getNumRows(); 161 if (Log.isLoggable(TAG, Log.DEBUG)) { 162 Log.d(TAG, "received count(*) from native_fill_window: " + mCount); 163 } 164 } else { 165 int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 166 mCursorWindowCapacity); 167 mQuery.fillWindow(mWindow, startPos, requiredPos, false); 168 } 169 } catch (RuntimeException ex) { 170 // Close the cursor window if the query failed and therefore will 171 // not produce any results. This helps to avoid accidentally leaking 172 // the cursor window if the client does not correctly handle exceptions 173 // and fails to close the cursor. 174 awc_closeWindow(); 175 throw ex; 176 } 177 } 178 179 @Override 180 public int getColumnIndex(String columnName) { 181 // Create mColumnNameMap on demand 182 if (mColumnNameMap == null) { 183 String[] columns = mColumns; 184 int columnCount = columns.length; 185 HashMap<String, Integer> map = new HashMap<String, Integer>(columnCount, 1); 186 for (int i = 0; i < columnCount; i++) { 187 map.put(columns[i], i); 188 } 189 mColumnNameMap = map; 190 } 191 192 // Hack according to bug 903852 193 final int periodIndex = columnName.lastIndexOf('.'); 194 if (periodIndex != -1) { 195 Exception e = new Exception(); 196 Log.e(TAG, "requesting column name with table name -- " + columnName, e); 197 columnName = columnName.substring(periodIndex + 1); 198 } 199 200 Integer i = mColumnNameMap.get(columnName); 201 if (i != null) { 202 return i.intValue(); 203 } else { 204 return -1; 205 } 206 } 207 208 @Override 209 public String[] getColumnNames() { 210 return mColumns; 211 } 212 213 @Override 214 public void deactivate() { 215 super.deactivate(); 216 mDriver.cursorDeactivated(); 217 } 218 219 @Override 220 public void close() { 221 super.close(); 222 synchronized (this) { 223 mQuery.close(); 224 mDriver.cursorClosed(); 225 } 226 } 227 228 @Override 229 public boolean requery() { 230 if (isClosed()) { 231 return false; 232 } 233 234 synchronized (this) { 235 if (!mQuery.getDatabase().isOpen()) { 236 return false; 237 } 238 239 if (mWindow != null) { 240 mWindow.clear(); 241 } 242 mPos = -1; 243 mCount = NO_COUNT; 244 245 mDriver.cursorRequeried(this); 246 } 247 248 try { 249 return super.requery(); 250 } catch (IllegalStateException e) { 251 // for backwards compatibility, just return false 252 Log.w(TAG, "requery() failed " + e.getMessage(), e); 253 return false; 254 } 255 } 256 257 @Override 258 public void setWindow(CursorWindow window) { 259 super.setWindow(window); 260 mCount = NO_COUNT; 261 } 262 263 /** 264 * Changes the selection arguments. The new values take effect after a call to requery(). 265 */ 266 public void setSelectionArguments(String[] selectionArgs) { 267 mDriver.setBindArguments(selectionArgs); 268 } 269 270 /** 271 * Release the native resources, if they haven't been released yet. 272 */ 273 @Override 274 protected void finalize() { 275 try { 276 // if the cursor hasn't been closed yet, close it first 277 if (mWindow != null) { 278 close(); 279 } 280 } finally { 281 super.finalize(); 282 } 283 } 284 }