Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bcd3387e authored by Vasu Nori's avatar Vasu Nori Committed by Android (Google) Code Review
Browse files

Merge "Return count of rows in a resultset only once (when startPos = 0)"

parents aa03045e b18f27db
Loading
Loading
Loading
Loading
+32 −7
Original line number Diff line number Diff line
@@ -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;
@@ -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;
                        }
@@ -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");
        }
    }

@@ -504,4 +522,11 @@ public class SQLiteCursor extends AbstractWindowedCursor {
            super.finalize();
        }
    }

    /**
     * this is only for testing purposes.
     */
    /* package */ int getMCount() {
        return mCount;
    }
}
+7 −2
Original line number Diff line number Diff line
@@ -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.
@@ -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;
    }

    /**
@@ -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){
@@ -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();
            }
+32 −14
Original line number Diff line number Diff line
@@ -15,7 +15,7 @@
 */

#undef LOG_TAG
#define LOG_TAG "Cursor"
#define LOG_TAG "SqliteCursor.cpp"

#include <jni.h>
#include <JNIHelp.h>
@@ -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()");
@@ -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);
@@ -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;
                }
            }

@@ -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);
@@ -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) {
@@ -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) {
@@ -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);
@@ -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);
    }
}

@@ -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},
};
+100 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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();
    }
}