summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Brown <jeffbrown@google.com>2011-10-27 14:52:28 -0700
committerJeff Brown <jeffbrown@google.com>2011-10-28 01:46:18 -0700
commit650de3dcfcbc7635da3c070410ef1dc4027ae464 (patch)
tree93cb485d70a4388a76397e6f65a80c1a059425fb
parent4257e3b18118200e29a3125a12044c4572c1baf5 (diff)
downloadframeworks_base-650de3dcfcbc7635da3c070410ef1dc4027ae464.zip
frameworks_base-650de3dcfcbc7635da3c070410ef1dc4027ae464.tar.gz
frameworks_base-650de3dcfcbc7635da3c070410ef1dc4027ae464.tar.bz2
Optimize fillWindow to improve reverse-seek performance.
Bug: 5520301 When an application requests a row from a SQLiteCursor that is not in the window, instead of filling from the requested row position onwards, fill from a little bit ahead of the requested row position. This fixes a problem with applications that seek backwards in large cursor windows. Previously the application could end up refilling the window every time it moved back one position. We try to fill about 1/3 before the requested position and 2/3 after which substantially improves scrolling responsiveness when the list is bound to a data set that does not fit entirely within one cursor window. Change-Id: I168ff1d3aed1a41ac96267be34a026c108590e52
-rw-r--r--core/java/android/database/BulkCursorNative.java4
-rw-r--r--core/java/android/database/CursorToBulkCursorAdaptor.java11
-rw-r--r--core/java/android/database/CursorWindow.java18
-rw-r--r--core/java/android/database/DatabaseUtils.java26
-rw-r--r--core/java/android/database/IBulkCursor.java12
-rw-r--r--core/java/android/database/sqlite/SQLiteCursor.java26
-rw-r--r--core/java/android/database/sqlite/SQLiteQuery.java36
-rw-r--r--core/jni/android_database_CursorWindow.cpp7
-rw-r--r--core/jni/android_database_SQLiteQuery.cpp223
-rw-r--r--libs/binder/CursorWindow.cpp2
10 files changed, 240 insertions, 125 deletions
diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java
index 20a9c67..67cf0f8 100644
--- a/core/java/android/database/BulkCursorNative.java
+++ b/core/java/android/database/BulkCursorNative.java
@@ -180,13 +180,13 @@ final class BulkCursorProxy implements IBulkCursor {
return mRemote;
}
- public CursorWindow getWindow(int startPos) throws RemoteException
+ public CursorWindow getWindow(int position) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IBulkCursor.descriptor);
- data.writeInt(startPos);
+ data.writeInt(position);
mRemote.transact(GET_CURSOR_WINDOW_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java
index 215035d..aa0f61e 100644
--- a/core/java/android/database/CursorToBulkCursorAdaptor.java
+++ b/core/java/android/database/CursorToBulkCursorAdaptor.java
@@ -132,11 +132,11 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
}
@Override
- public CursorWindow getWindow(int startPos) {
+ public CursorWindow getWindow(int position) {
synchronized (mLock) {
throwIfCursorIsClosed();
- if (!mCursor.moveToPosition(startPos)) {
+ if (!mCursor.moveToPosition(position)) {
closeFilledWindowLocked();
return null;
}
@@ -149,12 +149,11 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
if (window == null) {
mFilledWindow = new CursorWindow(mProviderName);
window = mFilledWindow;
- mCursor.fillWindow(startPos, window);
- } else if (startPos < window.getStartPosition()
- || startPos >= window.getStartPosition() + window.getNumRows()) {
+ } else if (position < window.getStartPosition()
+ || position >= window.getStartPosition() + window.getNumRows()) {
window.clear();
- mCursor.fillWindow(startPos, window);
}
+ mCursor.fillWindow(position, window);
}
// Acquire a reference before returning from this RPC.
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index 31e6f02..e9675e8 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -55,6 +55,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
public int mWindowPtr;
private int mStartPos;
+ private final String mName;
private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -84,6 +85,8 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
private static native boolean nativePutDouble(int windowPtr, double value, int row, int column);
private static native boolean nativePutNull(int windowPtr, int row, int column);
+ private static native String nativeGetName(int windowPtr);
+
/**
* Creates a new empty cursor window and gives it a name.
* <p>
@@ -95,6 +98,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
*/
public CursorWindow(String name) {
mStartPos = 0;
+ mName = name;
mWindowPtr = nativeCreate(name, sCursorWindowSize);
if (mWindowPtr == 0) {
throw new CursorWindowAllocationException("Cursor window allocation of " +
@@ -129,6 +133,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
throw new CursorWindowAllocationException("Cursor window could not be "
+ "created from binder.");
}
+ mName = nativeGetName(mWindowPtr);
mCloseGuard.open("close");
}
@@ -156,6 +161,14 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
}
/**
+ * Gets the name of this cursor window.
+ * @hide
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
* Closes the cursor window and frees its underlying resources when all other
* remaining references have been released.
*/
@@ -758,4 +771,9 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
String s = (buff.length() > 980) ? buff.substring(0, 980) : buff.toString();
return "# Open Cursors=" + total + s;
}
+
+ @Override
+ public String toString() {
+ return getName() + " {" + Integer.toHexString(mWindowPtr) + "}";
+ }
}
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index a10ca15..a8ba9a3 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -726,6 +726,32 @@ public class DatabaseUtils {
}
/**
+ * Picks a start position for {@link Cursor#fillWindow} such that the
+ * window will contain the requested row and a useful range of rows
+ * around it.
+ *
+ * When the data set is too large to fit in a cursor window, seeking the
+ * cursor can become a very expensive operation since we have to run the
+ * query again when we move outside the bounds of the current window.
+ *
+ * We try to choose a start position for the cursor window such that
+ * 1/3 of the window's capacity is used to hold rows before the requested
+ * position and 2/3 of the window's capacity is used to hold rows after the
+ * requested position.
+ *
+ * @param cursorPosition The row index of the row we want to get.
+ * @param cursorWindowCapacity The estimated number of rows that can fit in
+ * a cursor window, or 0 if unknown.
+ * @return The recommended start position, always less than or equal to
+ * the requested row.
+ * @hide
+ */
+ public static int cursorPickFillWindowStartPosition(
+ int cursorPosition, int cursorWindowCapacity) {
+ return Math.max(cursorPosition - cursorWindowCapacity / 3, 0);
+ }
+
+ /**
* Query the table for the number of rows in the table.
* @param db the database the table is in
* @param table the name of the table to query
diff --git a/core/java/android/database/IBulkCursor.java b/core/java/android/database/IBulkCursor.java
index 7c96797..0f4500a 100644
--- a/core/java/android/database/IBulkCursor.java
+++ b/core/java/android/database/IBulkCursor.java
@@ -30,11 +30,17 @@ import android.os.RemoteException;
*/
public interface IBulkCursor extends IInterface {
/**
- * Returns a BulkCursorWindow, which either has a reference to a shared
- * memory segment with the rows, or an array of JSON strings.
+ * Gets a cursor window that contains the specified position.
+ * The window will contain a range of rows around the specified position.
*/
- public CursorWindow getWindow(int startPos) throws RemoteException;
+ public CursorWindow getWindow(int position) throws RemoteException;
+ /**
+ * Notifies the cursor that the position has changed.
+ * Only called when {@link #getWantsAllOnMoveCalls()} returns true.
+ *
+ * @param position The new position
+ */
public void onMove(int position) throws RemoteException;
/**
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index c24acd4..8dcedf2 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -18,6 +18,7 @@ package android.database.sqlite;
import android.database.AbstractWindowedCursor;
import android.database.CursorWindow;
+import android.database.DatabaseUtils;
import android.os.StrictMode;
import android.util.Log;
@@ -48,7 +49,10 @@ public class SQLiteCursor extends AbstractWindowedCursor {
private final SQLiteCursorDriver mDriver;
/** The number of rows in the cursor */
- private volatile int mCount = NO_COUNT;
+ 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;
@@ -158,18 +162,20 @@ public class SQLiteCursor extends AbstractWindowedCursor {
return mCount;
}
- private void fillWindow(int startPos) {
+ private void fillWindow(int requiredPos) {
clearOrCreateWindow(getDatabase().getPath());
- mWindow.setStartPosition(startPos);
- int count = getQuery().fillWindow(mWindow);
- if (startPos == 0) { // fillWindow returns count(*) only for startPos = 0
+
+ if (mCount == NO_COUNT) {
+ int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
+ mCount = getQuery().fillWindow(mWindow, startPos, requiredPos, true);
+ mCursorWindowCapacity = mWindow.getNumRows();
if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "received count(*) from native_fill_window: " + count);
+ Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
}
- mCount = count;
- } else if (mCount <= 0) {
- throw new IllegalStateException("Row count should never be zero or negative "
- + "when the start position is non-zero");
+ } else {
+ int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
+ mCursorWindowCapacity);
+ getQuery().fillWindow(mWindow, startPos, requiredPos, false);
}
}
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index 7db0914..5229f12 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -30,8 +30,10 @@ import android.util.Log;
public class SQLiteQuery extends SQLiteProgram {
private static final String TAG = "SQLiteQuery";
- private static native int nativeFillWindow(int databasePtr, int statementPtr, int windowPtr,
- int startPos, int offsetParam);
+ private static final boolean DEBUG_FILL_WINDOW_PERFORMANCE = false;
+
+ private static native long nativeFillWindow(int databasePtr, int statementPtr, int windowPtr,
+ int offsetParam, int startPos, int requiredPos, boolean countAllRows);
private static native int nativeColumnCount(int statementPtr);
private static native String nativeColumnName(int statementPtr, int columnIndex);
@@ -71,19 +73,39 @@ public class SQLiteQuery extends SQLiteProgram {
* Reads rows into a buffer. This method acquires the database lock.
*
* @param window The window to fill into
- * @return number of total rows in the query
+ * @param startPos The start position for filling the window.
+ * @param requiredPos The position of a row that MUST be in the window.
+ * If it won't fit, then the query should discard part of what it filled.
+ * @param countAllRows True to count all rows that the query would
+ * return regardless of whether they fit in the window.
+ * @return Number of rows that were enumerated. Might not be all rows
+ * unless countAllRows is true.
*/
- /* package */ int fillWindow(CursorWindow window) {
+ /* package */ int fillWindow(CursorWindow window,
+ int startPos, int requiredPos, boolean countAllRows) {
mDatabase.lock(mSql);
long timeStart = SystemClock.uptimeMillis();
try {
acquireReference();
try {
window.acquireReference();
- int numRows = nativeFillWindow(nHandle, nStatement, window.mWindowPtr,
- window.getStartPosition(), mOffsetIndex);
+ long result = nativeFillWindow(nHandle, nStatement, window.mWindowPtr,
+ mOffsetIndex, startPos, requiredPos, countAllRows);
+ int actualPos = (int)(result >> 32);
+ int countedRows = (int)result;
+ window.setStartPosition(actualPos);
+ if (DEBUG_FILL_WINDOW_PERFORMANCE) {
+ Log.d(TAG, "fillWindow: window=\"" + window
+ + "\", startPos=" + startPos + ", requiredPos=" + requiredPos
+ + ", countAllRows=" + countAllRows
+ + ", offset=" + mOffsetIndex
+ + ", actualPos=" + actualPos + ", filledRows=" + window.getNumRows()
+ + ", countedRows=" + countedRows
+ + ", took " + (SystemClock.uptimeMillis() - timeStart)
+ + " ms, query=\"" + mSql + "\"");
+ }
mDatabase.logTimeStat(mSql, timeStart);
- return numRows;
+ return countedRows;
} catch (IllegalStateException e){
// simply ignore it
return 0;
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index 4a9fcf2..9a87a10 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -103,6 +103,11 @@ static void nativeDispose(JNIEnv* env, jclass clazz, jint windowPtr) {
}
}
+static jstring nativeGetName(JNIEnv* env, jclass clazz, jint windowPtr) {
+ CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
+ return env->NewStringUTF(window->name().string());
+}
+
static void nativeWriteToParcel(JNIEnv * env, jclass clazz, jint windowPtr,
jobject parcelObj) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
@@ -484,6 +489,8 @@ static JNINativeMethod sMethods[] =
(void*)nativeDispose },
{ "nativeWriteToParcel", "(ILandroid/os/Parcel;)V",
(void*)nativeWriteToParcel },
+ { "nativeGetName", "(I)Ljava/lang/String;",
+ (void*)nativeGetName },
{ "nativeClear", "(I)V",
(void*)nativeClear },
{ "nativeGetNumRows", "(I)I",
diff --git a/core/jni/android_database_SQLiteQuery.cpp b/core/jni/android_database_SQLiteQuery.cpp
index 8170f46..ea4200a 100644
--- a/core/jni/android_database_SQLiteQuery.cpp
+++ b/core/jni/android_database_SQLiteQuery.cpp
@@ -35,8 +35,110 @@
namespace android {
-static jint nativeFillWindow(JNIEnv* env, jclass clazz, jint databasePtr,
- jint statementPtr, jint windowPtr, jint startPos, jint offsetParam) {
+enum CopyRowResult {
+ CPR_OK,
+ CPR_FULL,
+ CPR_ERROR,
+};
+
+static CopyRowResult copyRow(JNIEnv* env, CursorWindow* window,
+ sqlite3_stmt* statement, int numColumns, int startPos, int addedRows) {
+ // Allocate a new field directory for the row. This pointer is not reused
+ // since it may be possible for it to be relocated on a call to alloc() when
+ // the field data is being allocated.
+ status_t status = window->allocRow();
+ if (status) {
+ LOG_WINDOW("Failed allocating fieldDir at startPos %d row %d, error=%d",
+ startPos, addedRows, status);
+ return CPR_FULL;
+ }
+
+ // Pack the row into the window.
+ CopyRowResult result = CPR_OK;
+ for (int i = 0; i < numColumns; i++) {
+ int type = sqlite3_column_type(statement, i);
+ if (type == SQLITE_TEXT) {
+ // TEXT data
+ const char* text = reinterpret_cast<const char*>(
+ sqlite3_column_text(statement, i));
+ // SQLite does not include the NULL terminator in size, but does
+ // ensure all strings are NULL terminated, so increase size by
+ // one to make sure we store the terminator.
+ size_t sizeIncludingNull = sqlite3_column_bytes(statement, i) + 1;
+ status = window->putString(addedRows, i, text, sizeIncludingNull);
+ if (status) {
+ LOG_WINDOW("Failed allocating %u bytes for text at %d,%d, error=%d",
+ sizeIncludingNull, startPos + addedRows, i, status);
+ result = CPR_FULL;
+ break;
+ }
+ LOG_WINDOW("%d,%d is TEXT with %u bytes",
+ startPos + addedRows, i, sizeIncludingNull);
+ } else if (type == SQLITE_INTEGER) {
+ // INTEGER data
+ int64_t value = sqlite3_column_int64(statement, i);
+ status = window->putLong(addedRows, i, value);
+ if (status) {
+ LOG_WINDOW("Failed allocating space for a long in column %d, error=%d",
+ i, status);
+ result = CPR_FULL;
+ break;
+ }
+ LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + addedRows, i, value);
+ } else if (type == SQLITE_FLOAT) {
+ // FLOAT data
+ double value = sqlite3_column_double(statement, i);
+ status = window->putDouble(addedRows, i, value);
+ if (status) {
+ LOG_WINDOW("Failed allocating space for a double in column %d, error=%d",
+ i, status);
+ result = CPR_FULL;
+ break;
+ }
+ LOG_WINDOW("%d,%d is FLOAT %lf", startPos + addedRows, i, value);
+ } else if (type == SQLITE_BLOB) {
+ // BLOB data
+ const void* blob = sqlite3_column_blob(statement, i);
+ size_t size = sqlite3_column_bytes(statement, i);
+ status = window->putBlob(addedRows, i, blob, size);
+ if (status) {
+ LOG_WINDOW("Failed allocating %u bytes for blob at %d,%d, error=%d",
+ size, startPos + addedRows, i, status);
+ result = CPR_FULL;
+ break;
+ }
+ LOG_WINDOW("%d,%d is Blob with %u bytes",
+ startPos + addedRows, i, size);
+ } else if (type == SQLITE_NULL) {
+ // NULL field
+ status = window->putNull(addedRows, i);
+ if (status) {
+ LOG_WINDOW("Failed allocating space for a null in column %d, error=%d",
+ i, status);
+ result = CPR_FULL;
+ break;
+ }
+
+ LOG_WINDOW("%d,%d is NULL", startPos + addedRows, i);
+ } else {
+ // Unknown data
+ LOGE("Unknown column type when filling database window");
+ throw_sqlite3_exception(env, "Unknown column type when filling window");
+ result = CPR_ERROR;
+ break;
+ }
+ }
+
+ // Free the last row if if was not successfully copied.
+ if (result != CPR_OK) {
+ window->freeLastRow();
+ }
+ return result;
+}
+
+static jlong nativeFillWindow(JNIEnv* env, jclass clazz, jint databasePtr,
+ jint statementPtr, jint windowPtr, jint offsetParam,
+ jint startPos, jint requiredPos, jboolean countAllRows) {
sqlite3* database = reinterpret_cast<sqlite3*>(databasePtr);
sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
@@ -45,15 +147,17 @@ static jint nativeFillWindow(JNIEnv* env, jclass clazz, jint databasePtr,
// offsetParam will be set to 0, an invalid value.
if (offsetParam > 0) {
// Bind the offset parameter, telling the program which row to start with
+ // If an offset parameter is used, we cannot simply clear the window if it
+ // turns out that the requiredPos won't fit because the result set may
+ // depend on startPos, so we set startPos to requiredPos.
+ startPos = requiredPos;
int err = sqlite3_bind_int(statement, offsetParam, startPos);
if (err != SQLITE_OK) {
LOGE("Unable to bind offset position, offsetParam = %d", offsetParam);
throw_sqlite3_exception(env, database);
return 0;
}
- LOG_WINDOW("Bound to startPos %d", startPos);
- } else {
- LOG_WINDOW("Not binding to startPos %d", startPos);
+ LOG_WINDOW("Bound offset position to startPos %d", startPos);
}
// We assume numRows is initially 0.
@@ -73,7 +177,6 @@ static jint nativeFillWindow(JNIEnv* env, jclass clazz, jint databasePtr,
int addedRows = 0;
bool windowFull = false;
bool gotException = false;
- const bool countAllRows = (startPos == 0); // when startPos is 0, we count all rows
while (!gotException && (!windowFull || countAllRows)) {
int err = sqlite3_step(statement);
if (err == SQLITE_ROW) {
@@ -86,97 +189,24 @@ static jint nativeFillWindow(JNIEnv* env, jclass clazz, jint databasePtr,
continue;
}
- // Allocate a new field directory for the row. This pointer is not reused
- // since it may be possible for it to be relocated on a call to alloc() when
- // the field data is being allocated.
- status = window->allocRow();
- if (status) {
- LOG_WINDOW("Failed allocating fieldDir at startPos %d row %d, error=%d",
- startPos, addedRows, status);
- windowFull = true;
- continue;
- }
-
- // Pack the row into the window.
- for (int i = 0; i < numColumns; i++) {
- int type = sqlite3_column_type(statement, i);
- if (type == SQLITE_TEXT) {
- // TEXT data
- const char* text = reinterpret_cast<const char*>(
- sqlite3_column_text(statement, i));
- // SQLite does not include the NULL terminator in size, but does
- // ensure all strings are NULL terminated, so increase size by
- // one to make sure we store the terminator.
- size_t sizeIncludingNull = sqlite3_column_bytes(statement, i) + 1;
- status = window->putString(addedRows, i, text, sizeIncludingNull);
- if (status) {
- LOG_WINDOW("Failed allocating %u bytes for text at %d,%d, error=%d",
- sizeIncludingNull, startPos + addedRows, i, status);
- windowFull = true;
- break;
- }
- LOG_WINDOW("%d,%d is TEXT with %u bytes",
- startPos + addedRows, i, sizeIncludingNull);
- } else if (type == SQLITE_INTEGER) {
- // INTEGER data
- int64_t value = sqlite3_column_int64(statement, i);
- status = window->putLong(addedRows, i, value);
- if (status) {
- LOG_WINDOW("Failed allocating space for a long in column %d, error=%d",
- i, status);
- windowFull = true;
- break;
- }
- LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + addedRows, i, value);
- } else if (type == SQLITE_FLOAT) {
- // FLOAT data
- double value = sqlite3_column_double(statement, i);
- status = window->putDouble(addedRows, i, value);
- if (status) {
- LOG_WINDOW("Failed allocating space for a double in column %d, error=%d",
- i, status);
- windowFull = true;
- break;
- }
- LOG_WINDOW("%d,%d is FLOAT %lf", startPos + addedRows, i, value);
- } else if (type == SQLITE_BLOB) {
- // BLOB data
- const void* blob = sqlite3_column_blob(statement, i);
- size_t size = sqlite3_column_bytes(statement, i);
- status = window->putBlob(addedRows, i, blob, size);
- if (status) {
- LOG_WINDOW("Failed allocating %u bytes for blob at %d,%d, error=%d",
- size, startPos + addedRows, i, status);
- windowFull = true;
- break;
- }
- LOG_WINDOW("%d,%d is Blob with %u bytes",
- startPos + addedRows, i, size);
- } else if (type == SQLITE_NULL) {
- // NULL field
- status = window->putNull(addedRows, i);
- if (status) {
- LOG_WINDOW("Failed allocating space for a null in column %d, error=%d",
- i, status);
- windowFull = true;
- break;
- }
-
- LOG_WINDOW("%d,%d is NULL", startPos + addedRows, i);
- } else {
- // Unknown data
- LOGE("Unknown column type when filling database window");
- throw_sqlite3_exception(env, "Unknown column type when filling window");
- gotException = true;
- break;
- }
+ CopyRowResult cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
+ if (cpr == CPR_FULL && addedRows && startPos + addedRows < requiredPos) {
+ // We filled the window before we got to the one row that we really wanted.
+ // Clear the window and start filling it again from here.
+ // TODO: Would be nicer if we could progressively replace earlier rows.
+ window->clear();
+ window->setNumColumns(numColumns);
+ startPos += addedRows;
+ addedRows = 0;
+ cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
}
- // Update the final row tally.
- if (windowFull || gotException) {
- window->freeLastRow();
- } else {
+ if (cpr == CPR_OK) {
addedRows += 1;
+ } else if (cpr == CPR_FULL) {
+ windowFull = true;
+ } else {
+ gotException = true;
}
} else if (err == SQLITE_DONE) {
// All rows processed, bail
@@ -209,7 +239,8 @@ static jint nativeFillWindow(JNIEnv* env, jclass clazz, jint databasePtr,
if (startPos > totalRows) {
LOGE("startPos %d > actual rows %d", startPos, totalRows);
}
- return countAllRows ? totalRows : 0;
+ jlong result = jlong(startPos) << 32 | jlong(totalRows);
+ return result;
}
static jint nativeColumnCount(JNIEnv* env, jclass clazz, jint statementPtr) {
@@ -228,7 +259,7 @@ static jstring nativeColumnName(JNIEnv* env, jclass clazz, jint statementPtr,
static JNINativeMethod sMethods[] =
{
/* name, signature, funcPtr */
- { "nativeFillWindow", "(IIIII)I",
+ { "nativeFillWindow", "(IIIIIIZ)J",
(void*)nativeFillWindow },
{ "nativeColumnCount", "(I)I",
(void*)nativeColumnCount},
diff --git a/libs/binder/CursorWindow.cpp b/libs/binder/CursorWindow.cpp
index bf8d7a6..0733378 100644
--- a/libs/binder/CursorWindow.cpp
+++ b/libs/binder/CursorWindow.cpp
@@ -209,7 +209,7 @@ uint32_t CursorWindow::alloc(size_t size, bool aligned) {
uint32_t offset = mHeader->freeOffset + padding;
uint32_t nextFreeOffset = offset + size;
if (nextFreeOffset > mSize) {
- LOGE("Window is full: requested allocation %d bytes, "
+ LOGW("Window is full: requested allocation %d bytes, "
"free space %d bytes, window size %d bytes",
size, freeSpace(), mSize);
return 0;