Loading core/java/android/database/sqlite/SQLiteCursor.java +32 −7 Original line number Diff line number Diff line Loading @@ -56,7 +56,7 @@ public class SQLiteCursor extends AbstractWindowedCursor { private final SQLiteCursorDriver mDriver; /** The number of rows in the cursor */ private int mCount = NO_COUNT; private volatile int mCount = NO_COUNT; /** A mapping of column names to column indices, to speed up lookups */ private Map<String, Integer> mColumnNameMap; Loading Loading @@ -138,13 +138,21 @@ public class SQLiteCursor extends AbstractWindowedCursor { } try { int count = getQuery().fillWindow(cw, mMaxRead, mCount); // return -1 means not finished // return -1 means there is still more data to be retrieved from the resultset if (count != 0) { if (count == NO_COUNT){ mCount += mMaxRead; if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "received -1 from native_fill_window. read " + mCount + " rows so far"); } sendMessage(); } else { mCount = count; mCount += count; if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "received all data from native_fill_window. read " + mCount + " rows."); } sendMessage(); break; } Loading Loading @@ -308,12 +316,22 @@ public class SQLiteCursor extends AbstractWindowedCursor { } } mWindow.setStartPosition(startPos); mCount = getQuery().fillWindow(mWindow, mInitialRead, 0); // return -1 means not finished if (mCount == NO_COUNT){ int count = getQuery().fillWindow(mWindow, mInitialRead, 0); // return -1 means there is still more data to be retrieved from the resultset if (count == NO_COUNT){ mCount = startPos + mInitialRead; if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "received -1 from native_fill_window. read " + mCount + " rows so far"); } Thread t = new Thread(new QueryThread(mCursorState), "query thread"); t.start(); } else if (startPos == 0) { // native_fill_window returns count(*) only for startPos = 0 if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "received count(*) from native_fill_window: " + count); } mCount = count; } else if (mCount <= 0) { throw new IllegalStateException("count should never be non-zero negative number"); } } Loading Loading @@ -504,4 +522,11 @@ public class SQLiteCursor extends AbstractWindowedCursor { super.finalize(); } } /** * this is only for testing purposes. */ /* package */ int getMCount() { return mCount; } } core/java/android/database/sqlite/SQLiteQuery.java +7 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.database.sqlite; import android.database.CursorWindow; import android.os.SystemClock; import android.util.Log; /** * A SQLite program that represents a query that reads the resulting rows into a CursorWindow. Loading Loading @@ -58,6 +59,7 @@ public class SQLiteQuery extends SQLiteProgram { /* package */ SQLiteQuery(SQLiteDatabase db, SQLiteQuery query) { super(db, query.mSql); this.mBindArgs = query.mBindArgs; this.mOffsetIndex = query.mOffsetIndex; } /** Loading @@ -78,8 +80,8 @@ public class SQLiteQuery extends SQLiteProgram { // if the start pos is not equal to 0, then most likely window is // too small for the data set, loading by another thread // is not safe in this situation. the native code will ignore maxRead int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex, maxRead, lastPos); int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex, maxRead, lastPos); mDatabase.logTimeStat(mSql, timeStart); return numRows; } catch (IllegalStateException e){ Loading @@ -88,6 +90,9 @@ public class SQLiteQuery extends SQLiteProgram { } catch (SQLiteDatabaseCorruptException e) { mDatabase.onCorruption(); throw e; } catch (SQLiteException e) { Log.e(TAG, "exception: " + e.getMessage() + "; query: " + mSql); throw e; } finally { window.releaseReference(); } Loading core/jni/android_database_SQLiteQuery.cpp +32 −14 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ */ #undef LOG_TAG #define LOG_TAG "Cursor" #define LOG_TAG "SqliteCursor.cpp" #include <jni.h> #include <JNIHelp.h> Loading Loading @@ -116,6 +116,7 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, int retryCount; int boundParams; CursorWindow * window; bool gotAllRows = true; if (statement == NULL) { LOGE("Invalid statement in fillWindow()"); Loading @@ -131,8 +132,7 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, err = sqlite3_bind_int(statement, offsetParam, startPos); if (err != SQLITE_OK) { LOGE("Unable to bind offset position, offsetParam = %d", offsetParam); jniThrowException(env, "java/lang/IllegalArgumentException", sqlite3_errmsg(GET_HANDLE(env, object))); throw_sqlite3_exception(env, GET_HANDLE(env, object)); return 0; } LOG_WINDOW("Bound to startPos %d", startPos); Loading Loading @@ -182,7 +182,8 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, field_slot_t * fieldDir = window->allocRow(); if (!fieldDir) { LOGE("Failed allocating fieldDir at startPos %d row %d", startPos, numRows); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; gotAllRows = false; goto return_count; } } Loading @@ -207,7 +208,8 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, window->freeLastRow(); LOGD("Failed allocating %u bytes for text/blob at %d,%d", size, startPos + numRows, i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; gotAllRows = false; goto return_count; } window->copyIn(offset, text, size); Loading @@ -225,8 +227,9 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, int64_t value = sqlite3_column_int64(statement, i); if (!window->putLong(numRows, i, value)) { window->freeLastRow(); LOGD("Failed allocating space for a long in column %d", i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; LOGE("Failed allocating space for a long in column %d", i); gotAllRows = false; goto return_count; } LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + numRows, i, value); } else if (type == SQLITE_FLOAT) { Loading @@ -234,8 +237,9 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, double value = sqlite3_column_double(statement, i); if (!window->putDouble(numRows, i, value)) { window->freeLastRow(); LOGD("Failed allocating space for a double in column %d", i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; LOGE("Failed allocating space for a double in column %d", i); gotAllRows = false; goto return_count; } LOG_WINDOW("%d,%d is FLOAT %lf", startPos + numRows, i, value); } else if (type == SQLITE_BLOB) { Loading @@ -247,7 +251,8 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, window->freeLastRow(); LOGD("Failed allocating %u bytes for blob at %d,%d", size, startPos + numRows, i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; gotAllRows = false; goto return_count; } window->copyIn(offset, blob, size); Loading Loading @@ -306,12 +311,24 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, LOG_WINDOW("Resetting statement %p after fetching %d rows in %d bytes\n\n\n\n", statement, numRows, window->size() - window->freeSpace()); // LOGI("Filled window with %d rows in %d bytes", numRows, window->size() - window->freeSpace()); LOG_WINDOW("Filled window with %d rows in %d bytes", numRows, window->size() - window->freeSpace()); if (err == SQLITE_ROW) { // there is more data to be returned. let the caller know by returning -1 return -1; } else { } return_count: if (startPos) { sqlite3_reset(statement); return startPos + numRows; LOG_WINDOW("Not doing count(*) because startPos %d is non-zero", startPos); return 0; } else if (gotAllRows) { sqlite3_reset(statement); LOG_WINDOW("Not doing count(*) because we already know the count(*)"); return numRows; } else { // since startPos == 0, we need to get the count(*) of the result set return numRows + 1 + finish_program_and_get_row_count(statement); } } Loading @@ -336,7 +353,8 @@ static jstring native_column_name(JNIEnv* env, jobject object, jint columnIndex) static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"native_fill_window", "(Landroid/database/CursorWindow;IIII)I", (void *)native_fill_window}, {"native_fill_window", "(Landroid/database/CursorWindow;IIII)I", (void *)native_fill_window}, {"native_column_count", "()I", (void*)native_column_count}, {"native_column_name", "(I)Ljava/lang/String;", (void *)native_column_name}, }; Loading core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java +100 −0 Original line number Diff line number Diff line Loading @@ -16,11 +16,16 @@ package android.database.sqlite; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import java.io.File; import java.util.HashSet; import java.util.Set; public class SQLiteCursorTest extends AndroidTestCase { private SQLiteDatabase mDatabase; Loading Loading @@ -91,4 +96,99 @@ public class SQLiteCursorTest extends AndroidTestCase { assertTrue(mDatabase.mConnectionPool.getConnectionList().contains(db)); assertTrue(db.isOpen()); } @SmallTest public void testFillWindow() { // create schema final String testTable = "testV"; mDatabase.beginTransaction(); mDatabase.execSQL("CREATE TABLE " + testTable + " (col1 int, desc text not null);"); mDatabase.setTransactionSuccessful(); mDatabase.endTransaction(); // populate the table with data // create a big string that will almost fit a page but not quite. // since sqlite wants to make sure each row is in a page, this string will allocate // a new database page for each row. StringBuilder buff = new StringBuilder(); for (int i = 0; i < 500; i++) { buff.append(i % 10 + ""); } ContentValues values = new ContentValues(); values.put("desc", buff.toString()); // insert more than 1MB of data in the table. this should ensure that the entire tabledata // will need more than one CursorWindow int N = 5000; Set<Integer> rows = new HashSet<Integer>(); mDatabase.beginTransaction(); for (int j = 0; j < N; j++) { values.put("col1", j); mDatabase.insert(testTable, null, values); rows.add(j); // store in a hashtable so we can verify the results from cursor later on } mDatabase.setTransactionSuccessful(); mDatabase.endTransaction(); assertEquals(N, rows.size()); Cursor c1 = mDatabase.rawQuery("select * from " + testTable, null); assertEquals(N, c1.getCount()); c1.close(); // scroll through ALL data in the table using a cursor. should cause multiple calls to // native_fill_window (and re-fills of the CursorWindow object) Cursor c = mDatabase.query(testTable, new String[]{"col1", "desc"}, null, null, null, null, null); int i = 0; while (c.moveToNext()) { int val = c.getInt(0); assertTrue(rows.contains(val)); assertTrue(rows.remove(val)); } // did I see all the rows in the table? assertTrue(rows.isEmpty()); // change data and make sure the cursor picks up new data & count rows = new HashSet<Integer>(); mDatabase.beginTransaction(); int M = N + 1000; for (int j = 0; j < M; j++) { rows.add(j); if (j < N) { continue; } values.put("col1", j); mDatabase.insert(testTable, null, values); } mDatabase.setTransactionSuccessful(); mDatabase.endTransaction(); assertEquals(M, rows.size()); c.requery(); i = 0; while (c.moveToNext()) { int val = c.getInt(0); assertTrue(rows.contains(val)); assertTrue(rows.remove(val)); } // did I see all data from the modified table assertTrue(rows.isEmpty()); // move cursor back to 1st row and scroll to about halfway in the result set // and then delete 75% of data - and then do requery c.moveToFirst(); int K = N / 2; for (int p = 0; p < K && c.moveToNext(); p++) { // nothing to do - just scrolling to about half-point in the resultset } mDatabase.beginTransaction(); mDatabase.delete(testTable, "col1 < ?", new String[]{ (3 * M / 4) + ""}); mDatabase.setTransactionSuccessful(); mDatabase.endTransaction(); c.requery(); assertEquals(M / 4, c.getCount()); while (c.moveToNext()) { // just move the cursor to next row - to make sure it can go through the entire // resultset without any problems } c.close(); } } Loading
core/java/android/database/sqlite/SQLiteCursor.java +32 −7 Original line number Diff line number Diff line Loading @@ -56,7 +56,7 @@ public class SQLiteCursor extends AbstractWindowedCursor { private final SQLiteCursorDriver mDriver; /** The number of rows in the cursor */ private int mCount = NO_COUNT; private volatile int mCount = NO_COUNT; /** A mapping of column names to column indices, to speed up lookups */ private Map<String, Integer> mColumnNameMap; Loading Loading @@ -138,13 +138,21 @@ public class SQLiteCursor extends AbstractWindowedCursor { } try { int count = getQuery().fillWindow(cw, mMaxRead, mCount); // return -1 means not finished // return -1 means there is still more data to be retrieved from the resultset if (count != 0) { if (count == NO_COUNT){ mCount += mMaxRead; if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "received -1 from native_fill_window. read " + mCount + " rows so far"); } sendMessage(); } else { mCount = count; mCount += count; if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "received all data from native_fill_window. read " + mCount + " rows."); } sendMessage(); break; } Loading Loading @@ -308,12 +316,22 @@ public class SQLiteCursor extends AbstractWindowedCursor { } } mWindow.setStartPosition(startPos); mCount = getQuery().fillWindow(mWindow, mInitialRead, 0); // return -1 means not finished if (mCount == NO_COUNT){ int count = getQuery().fillWindow(mWindow, mInitialRead, 0); // return -1 means there is still more data to be retrieved from the resultset if (count == NO_COUNT){ mCount = startPos + mInitialRead; if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "received -1 from native_fill_window. read " + mCount + " rows so far"); } Thread t = new Thread(new QueryThread(mCursorState), "query thread"); t.start(); } else if (startPos == 0) { // native_fill_window returns count(*) only for startPos = 0 if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "received count(*) from native_fill_window: " + count); } mCount = count; } else if (mCount <= 0) { throw new IllegalStateException("count should never be non-zero negative number"); } } Loading Loading @@ -504,4 +522,11 @@ public class SQLiteCursor extends AbstractWindowedCursor { super.finalize(); } } /** * this is only for testing purposes. */ /* package */ int getMCount() { return mCount; } }
core/java/android/database/sqlite/SQLiteQuery.java +7 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.database.sqlite; import android.database.CursorWindow; import android.os.SystemClock; import android.util.Log; /** * A SQLite program that represents a query that reads the resulting rows into a CursorWindow. Loading Loading @@ -58,6 +59,7 @@ public class SQLiteQuery extends SQLiteProgram { /* package */ SQLiteQuery(SQLiteDatabase db, SQLiteQuery query) { super(db, query.mSql); this.mBindArgs = query.mBindArgs; this.mOffsetIndex = query.mOffsetIndex; } /** Loading @@ -78,8 +80,8 @@ public class SQLiteQuery extends SQLiteProgram { // if the start pos is not equal to 0, then most likely window is // too small for the data set, loading by another thread // is not safe in this situation. the native code will ignore maxRead int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex, maxRead, lastPos); int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex, maxRead, lastPos); mDatabase.logTimeStat(mSql, timeStart); return numRows; } catch (IllegalStateException e){ Loading @@ -88,6 +90,9 @@ public class SQLiteQuery extends SQLiteProgram { } catch (SQLiteDatabaseCorruptException e) { mDatabase.onCorruption(); throw e; } catch (SQLiteException e) { Log.e(TAG, "exception: " + e.getMessage() + "; query: " + mSql); throw e; } finally { window.releaseReference(); } Loading
core/jni/android_database_SQLiteQuery.cpp +32 −14 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ */ #undef LOG_TAG #define LOG_TAG "Cursor" #define LOG_TAG "SqliteCursor.cpp" #include <jni.h> #include <JNIHelp.h> Loading Loading @@ -116,6 +116,7 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, int retryCount; int boundParams; CursorWindow * window; bool gotAllRows = true; if (statement == NULL) { LOGE("Invalid statement in fillWindow()"); Loading @@ -131,8 +132,7 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, err = sqlite3_bind_int(statement, offsetParam, startPos); if (err != SQLITE_OK) { LOGE("Unable to bind offset position, offsetParam = %d", offsetParam); jniThrowException(env, "java/lang/IllegalArgumentException", sqlite3_errmsg(GET_HANDLE(env, object))); throw_sqlite3_exception(env, GET_HANDLE(env, object)); return 0; } LOG_WINDOW("Bound to startPos %d", startPos); Loading Loading @@ -182,7 +182,8 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, field_slot_t * fieldDir = window->allocRow(); if (!fieldDir) { LOGE("Failed allocating fieldDir at startPos %d row %d", startPos, numRows); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; gotAllRows = false; goto return_count; } } Loading @@ -207,7 +208,8 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, window->freeLastRow(); LOGD("Failed allocating %u bytes for text/blob at %d,%d", size, startPos + numRows, i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; gotAllRows = false; goto return_count; } window->copyIn(offset, text, size); Loading @@ -225,8 +227,9 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, int64_t value = sqlite3_column_int64(statement, i); if (!window->putLong(numRows, i, value)) { window->freeLastRow(); LOGD("Failed allocating space for a long in column %d", i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; LOGE("Failed allocating space for a long in column %d", i); gotAllRows = false; goto return_count; } LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + numRows, i, value); } else if (type == SQLITE_FLOAT) { Loading @@ -234,8 +237,9 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, double value = sqlite3_column_double(statement, i); if (!window->putDouble(numRows, i, value)) { window->freeLastRow(); LOGD("Failed allocating space for a double in column %d", i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; LOGE("Failed allocating space for a double in column %d", i); gotAllRows = false; goto return_count; } LOG_WINDOW("%d,%d is FLOAT %lf", startPos + numRows, i, value); } else if (type == SQLITE_BLOB) { Loading @@ -247,7 +251,8 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, window->freeLastRow(); LOGD("Failed allocating %u bytes for blob at %d,%d", size, startPos + numRows, i); return startPos + numRows + finish_program_and_get_row_count(statement) + 1; gotAllRows = false; goto return_count; } window->copyIn(offset, blob, size); Loading Loading @@ -306,12 +311,24 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow, LOG_WINDOW("Resetting statement %p after fetching %d rows in %d bytes\n\n\n\n", statement, numRows, window->size() - window->freeSpace()); // LOGI("Filled window with %d rows in %d bytes", numRows, window->size() - window->freeSpace()); LOG_WINDOW("Filled window with %d rows in %d bytes", numRows, window->size() - window->freeSpace()); if (err == SQLITE_ROW) { // there is more data to be returned. let the caller know by returning -1 return -1; } else { } return_count: if (startPos) { sqlite3_reset(statement); return startPos + numRows; LOG_WINDOW("Not doing count(*) because startPos %d is non-zero", startPos); return 0; } else if (gotAllRows) { sqlite3_reset(statement); LOG_WINDOW("Not doing count(*) because we already know the count(*)"); return numRows; } else { // since startPos == 0, we need to get the count(*) of the result set return numRows + 1 + finish_program_and_get_row_count(statement); } } Loading @@ -336,7 +353,8 @@ static jstring native_column_name(JNIEnv* env, jobject object, jint columnIndex) static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"native_fill_window", "(Landroid/database/CursorWindow;IIII)I", (void *)native_fill_window}, {"native_fill_window", "(Landroid/database/CursorWindow;IIII)I", (void *)native_fill_window}, {"native_column_count", "()I", (void*)native_column_count}, {"native_column_name", "(I)Ljava/lang/String;", (void *)native_column_name}, }; Loading
core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java +100 −0 Original line number Diff line number Diff line Loading @@ -16,11 +16,16 @@ package android.database.sqlite; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import java.io.File; import java.util.HashSet; import java.util.Set; public class SQLiteCursorTest extends AndroidTestCase { private SQLiteDatabase mDatabase; Loading Loading @@ -91,4 +96,99 @@ public class SQLiteCursorTest extends AndroidTestCase { assertTrue(mDatabase.mConnectionPool.getConnectionList().contains(db)); assertTrue(db.isOpen()); } @SmallTest public void testFillWindow() { // create schema final String testTable = "testV"; mDatabase.beginTransaction(); mDatabase.execSQL("CREATE TABLE " + testTable + " (col1 int, desc text not null);"); mDatabase.setTransactionSuccessful(); mDatabase.endTransaction(); // populate the table with data // create a big string that will almost fit a page but not quite. // since sqlite wants to make sure each row is in a page, this string will allocate // a new database page for each row. StringBuilder buff = new StringBuilder(); for (int i = 0; i < 500; i++) { buff.append(i % 10 + ""); } ContentValues values = new ContentValues(); values.put("desc", buff.toString()); // insert more than 1MB of data in the table. this should ensure that the entire tabledata // will need more than one CursorWindow int N = 5000; Set<Integer> rows = new HashSet<Integer>(); mDatabase.beginTransaction(); for (int j = 0; j < N; j++) { values.put("col1", j); mDatabase.insert(testTable, null, values); rows.add(j); // store in a hashtable so we can verify the results from cursor later on } mDatabase.setTransactionSuccessful(); mDatabase.endTransaction(); assertEquals(N, rows.size()); Cursor c1 = mDatabase.rawQuery("select * from " + testTable, null); assertEquals(N, c1.getCount()); c1.close(); // scroll through ALL data in the table using a cursor. should cause multiple calls to // native_fill_window (and re-fills of the CursorWindow object) Cursor c = mDatabase.query(testTable, new String[]{"col1", "desc"}, null, null, null, null, null); int i = 0; while (c.moveToNext()) { int val = c.getInt(0); assertTrue(rows.contains(val)); assertTrue(rows.remove(val)); } // did I see all the rows in the table? assertTrue(rows.isEmpty()); // change data and make sure the cursor picks up new data & count rows = new HashSet<Integer>(); mDatabase.beginTransaction(); int M = N + 1000; for (int j = 0; j < M; j++) { rows.add(j); if (j < N) { continue; } values.put("col1", j); mDatabase.insert(testTable, null, values); } mDatabase.setTransactionSuccessful(); mDatabase.endTransaction(); assertEquals(M, rows.size()); c.requery(); i = 0; while (c.moveToNext()) { int val = c.getInt(0); assertTrue(rows.contains(val)); assertTrue(rows.remove(val)); } // did I see all data from the modified table assertTrue(rows.isEmpty()); // move cursor back to 1st row and scroll to about halfway in the result set // and then delete 75% of data - and then do requery c.moveToFirst(); int K = N / 2; for (int p = 0; p < K && c.moveToNext(); p++) { // nothing to do - just scrolling to about half-point in the resultset } mDatabase.beginTransaction(); mDatabase.delete(testTable, "col1 < ?", new String[]{ (3 * M / 4) + ""}); mDatabase.setTransactionSuccessful(); mDatabase.endTransaction(); c.requery(); assertEquals(M / 4, c.getCount()); while (c.moveToNext()) { // just move the cursor to next row - to make sure it can go through the entire // resultset without any problems } c.close(); } }