295 lines
9.9 KiB
Java
295 lines
9.9 KiB
Java
/*
|
|
* Copyright (C) 2006 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.database.sqlite;
|
|
|
|
import android.compat.annotation.UnsupportedAppUsage;
|
|
import android.database.AbstractWindowedCursor;
|
|
import android.database.CursorWindow;
|
|
import android.database.DatabaseUtils;
|
|
import android.os.StrictMode;
|
|
import android.util.Log;
|
|
|
|
import com.android.internal.util.Preconditions;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* A Cursor implementation that exposes results from a query on a
|
|
* {@link SQLiteDatabase}.
|
|
*
|
|
* SQLiteCursor is not internally synchronized so code using a SQLiteCursor from multiple
|
|
* threads should perform its own synchronization when using the SQLiteCursor.
|
|
*/
|
|
public class SQLiteCursor extends AbstractWindowedCursor {
|
|
static final String TAG = "SQLiteCursor";
|
|
static final int NO_COUNT = -1;
|
|
|
|
/** The name of the table to edit */
|
|
@UnsupportedAppUsage
|
|
private final String mEditTable;
|
|
|
|
/** The names of the columns in the rows */
|
|
private final String[] mColumns;
|
|
|
|
/** The query object for the cursor */
|
|
@UnsupportedAppUsage
|
|
private final SQLiteQuery mQuery;
|
|
|
|
/** The compiled query this cursor came from */
|
|
private final SQLiteCursorDriver mDriver;
|
|
|
|
/** The number of rows in the cursor */
|
|
private int mCount = NO_COUNT;
|
|
|
|
/** The number of rows that can fit in the cursor window, 0 if unknown */
|
|
private int mCursorWindowCapacity;
|
|
|
|
/** A mapping of column names to column indices, to speed up lookups */
|
|
private Map<String, Integer> mColumnNameMap;
|
|
|
|
/** Controls fetching of rows relative to requested position **/
|
|
private boolean mFillWindowForwardOnly;
|
|
|
|
/**
|
|
* Execute a query and provide access to its result set through a Cursor
|
|
* interface. For a query such as: {@code SELECT name, birth, phone FROM
|
|
* myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth,
|
|
* phone) would be in the projection argument and everything from
|
|
* {@code FROM} onward would be in the params argument.
|
|
*
|
|
* @param db a reference to a Database object that is already constructed
|
|
* and opened. This param is not used any longer
|
|
* @param editTable the name of the table used for this query
|
|
* @param query the rest of the query terms
|
|
* cursor is finalized
|
|
* @deprecated use {@link #SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)} instead
|
|
*/
|
|
@Deprecated
|
|
public SQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver,
|
|
String editTable, SQLiteQuery query) {
|
|
this(driver, editTable, query);
|
|
}
|
|
|
|
/**
|
|
* Execute a query and provide access to its result set through a Cursor
|
|
* interface. For a query such as: {@code SELECT name, birth, phone FROM
|
|
* myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth,
|
|
* phone) would be in the projection argument and everything from
|
|
* {@code FROM} onward would be in the params argument.
|
|
*
|
|
* @param editTable the name of the table used for this query
|
|
* @param query the {@link SQLiteQuery} object associated with this cursor object.
|
|
*/
|
|
public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {
|
|
if (query == null) {
|
|
throw new IllegalArgumentException("query object cannot be null");
|
|
}
|
|
mDriver = driver;
|
|
mEditTable = editTable;
|
|
mColumnNameMap = null;
|
|
mQuery = query;
|
|
|
|
mColumns = query.getColumnNames();
|
|
}
|
|
|
|
/**
|
|
* Get the database that this cursor is associated with.
|
|
* @return the SQLiteDatabase that this cursor is associated with.
|
|
*/
|
|
public SQLiteDatabase getDatabase() {
|
|
return mQuery.getDatabase();
|
|
}
|
|
|
|
@Override
|
|
public boolean onMove(int oldPosition, int newPosition) {
|
|
// Make sure the row at newPosition is present in the window
|
|
if (mWindow == null || newPosition < mWindow.getStartPosition() ||
|
|
newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
|
|
fillWindow(newPosition);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public int getCount() {
|
|
if (mCount == NO_COUNT) {
|
|
fillWindow(0);
|
|
}
|
|
return mCount;
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
private void fillWindow(int requiredPos) {
|
|
clearOrCreateWindow(getDatabase().getPath());
|
|
try {
|
|
Preconditions.checkArgumentNonnegative(requiredPos,
|
|
"requiredPos cannot be negative");
|
|
|
|
if (mCount == NO_COUNT) {
|
|
mCount = mQuery.fillWindow(mWindow, requiredPos, requiredPos, true);
|
|
mCursorWindowCapacity = mWindow.getNumRows();
|
|
if (SQLiteDebug.NoPreloadHolder.DEBUG_SQL_LOG) {
|
|
Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
|
|
}
|
|
} else {
|
|
int startPos = mFillWindowForwardOnly ? requiredPos : DatabaseUtils
|
|
.cursorPickFillWindowStartPosition(requiredPos, mCursorWindowCapacity);
|
|
mQuery.fillWindow(mWindow, startPos, requiredPos, false);
|
|
}
|
|
} catch (RuntimeException ex) {
|
|
// Close the cursor window if the query failed and therefore will
|
|
// not produce any results. This helps to avoid accidentally leaking
|
|
// the cursor window if the client does not correctly handle exceptions
|
|
// and fails to close the cursor.
|
|
closeWindow();
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getColumnIndex(String columnName) {
|
|
// Create mColumnNameMap on demand
|
|
if (mColumnNameMap == null) {
|
|
String[] columns = mColumns;
|
|
int columnCount = columns.length;
|
|
HashMap<String, Integer> map = new HashMap<String, Integer>(columnCount, 1);
|
|
for (int i = 0; i < columnCount; i++) {
|
|
map.put(columns[i], i);
|
|
}
|
|
mColumnNameMap = map;
|
|
}
|
|
|
|
// Hack according to bug 903852
|
|
final int periodIndex = columnName.lastIndexOf('.');
|
|
if (periodIndex != -1) {
|
|
Exception e = new Exception();
|
|
Log.e(TAG, "requesting column name with table name -- " + columnName, e);
|
|
columnName = columnName.substring(periodIndex + 1);
|
|
}
|
|
|
|
Integer i = mColumnNameMap.get(columnName);
|
|
if (i != null) {
|
|
return i.intValue();
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String[] getColumnNames() {
|
|
return mColumns;
|
|
}
|
|
|
|
@Override
|
|
public void deactivate() {
|
|
super.deactivate();
|
|
mDriver.cursorDeactivated();
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
super.close();
|
|
synchronized (this) {
|
|
mQuery.close();
|
|
mDriver.cursorClosed();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean requery() {
|
|
if (isClosed()) {
|
|
return false;
|
|
}
|
|
|
|
synchronized (this) {
|
|
if (!mQuery.getDatabase().isOpen()) {
|
|
return false;
|
|
}
|
|
|
|
if (mWindow != null) {
|
|
mWindow.clear();
|
|
}
|
|
mPos = -1;
|
|
mCount = NO_COUNT;
|
|
|
|
mDriver.cursorRequeried(this);
|
|
}
|
|
|
|
try {
|
|
return super.requery();
|
|
} catch (IllegalStateException e) {
|
|
// for backwards compatibility, just return false
|
|
Log.w(TAG, "requery() failed " + e.getMessage(), e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setWindow(CursorWindow window) {
|
|
super.setWindow(window);
|
|
mCount = NO_COUNT;
|
|
}
|
|
|
|
/**
|
|
* Changes the selection arguments. The new values take effect after a call to requery().
|
|
*/
|
|
public void setSelectionArguments(String[] selectionArgs) {
|
|
mDriver.setBindArguments(selectionArgs);
|
|
}
|
|
|
|
/**
|
|
* Controls fetching of rows relative to requested position.
|
|
*
|
|
* <p>Calling this method defines how rows will be loaded, but it doesn't affect rows that
|
|
* are already in the window. This setting is preserved if a new window is
|
|
* {@link #setWindow(CursorWindow) set}
|
|
*
|
|
* @param fillWindowForwardOnly if true, rows will be fetched starting from requested position
|
|
* up to the window's capacity. Default value is false.
|
|
*/
|
|
public void setFillWindowForwardOnly(boolean fillWindowForwardOnly) {
|
|
mFillWindowForwardOnly = fillWindowForwardOnly;
|
|
}
|
|
|
|
/**
|
|
* Release the native resources, if they haven't been released yet.
|
|
*/
|
|
@Override
|
|
protected void finalize() {
|
|
try {
|
|
// if the cursor hasn't been closed yet, close it first
|
|
if (mWindow != null) {
|
|
// Report original sql statement
|
|
if (StrictMode.vmSqliteObjectLeaksEnabled()) {
|
|
String sql = mQuery.getSql();
|
|
int len = sql.length();
|
|
StrictMode.onSqliteObjectLeaked(
|
|
"Finalizing a Cursor that has not been deactivated or closed. "
|
|
+ "database = " + mQuery.getDatabase().getLabel()
|
|
+ ", table = " + mEditTable
|
|
+ ", query = " + sql.substring(0, (len > 1000) ? 1000 : len),
|
|
null);
|
|
}
|
|
}
|
|
} finally {
|
|
super.finalize();
|
|
}
|
|
}
|
|
}
|