SQLite Android Bindings
Check-in [365586dcaf]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Replace nativeExecuteForCursorWindow() with an implementation that builds with the NDK. Seems to work, but is not yet tested. Exception handling is almost certainly still wrong.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 365586dcafe43f880021b0de52b6a19f02fc6ee1
User & Date: dan 2013-12-21 16:04:55
References
2014-06-11
19:48 New ticket [8d03600d81] suspected bug introduced in jni/android_database_SQLiteConnection.cpp. artifact: f5aaef6ef9 user: anonymous
Context
2013-12-21
17:58
Re-enable some disabled code required by user-defined function implementations. check-in: e01e48cd52 user: dan tags: trunk
16:04
Replace nativeExecuteForCursorWindow() with an implementation that builds with the NDK. Seems to work, but is not yet tested. Exception handling is almost certainly still wrong. check-in: 365586dcaf user: dan tags: trunk
2013-12-20
17:02
Start setting up some infrastructure code for a test suite. Add a test demonstrating the problem with type Cursor. check-in: 69b389af43 user: dan tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to jni/android_database_SQLiteConnection.cpp.

    20     20   #include <JNIHelp.h>
    21     21   #include "ALog-priv.h"
    22     22   
    23     23   
    24     24   #include <sys/mman.h>
    25     25   #include <string.h>
    26     26   #include <unistd.h>
           27  +#include <assert.h>
    27     28   
    28     29   #if 0
    29     30   #include <androidfw/CursorWindow.h>
    30     31   #endif
    31     32   
    32     33   #include <sqlite3.h>
    33     34   #if 0
................................................................................
   602    603                   return createAshmemRegionWithData(env, blob, length);
   603    604               }
   604    605           }
   605    606       }
   606    607       return -1;
   607    608   }
   608    609   
   609         -enum CopyRowResult {
   610         -    CPR_OK,
   611         -    CPR_FULL,
   612         -    CPR_ERROR,
          610  +/*
          611  +** Note: The following symbols must be in the same order as the corresponding
          612  +** elements in the aMethod[] array in function nativeExecuteForCursorWindow().
          613  +*/
          614  +enum CWMethodNames {
          615  +  CW_CLEAR         = 0,
          616  +  CW_SETNUMCOLUMNS = 1,
          617  +  CW_ALLOCROW      = 2,
          618  +  CW_FREELASTROW   = 3,
          619  +  CW_PUTNULL       = 4,
          620  +  CW_PUTLONG       = 5,
          621  +  CW_PUTDOUBLE     = 6,
          622  +  CW_PUTSTRING     = 7,
          623  +  CW_PUTBLOB       = 8
          624  +};
          625  +
          626  +/*
          627  +** An instance of this structure represents a single CursorWindow java method.
          628  +*/
          629  +struct CWMethod {
          630  +  jmethodID id;                   /* Method id */
          631  +  const char *zName;              /* Method name */
          632  +  const char *zSig;               /* Method JNI signature */
   613    633   };
   614    634   
   615         -static jlong nativeExecuteForCursorWindow(
   616         -  JNIEnv* env, jclass clazz,
   617         -  jint connectionPtr, 
   618         -  jint statementPtr, 
   619         -  jint windowPtr,
   620         -  jint startPos, 
   621         -  jint requiredPos, 
   622         -  jboolean countAllRows
   623         -) {
   624         -  jniThrowIOException(env, -1);
   625         -  return -1;
          635  +/*
          636  +** Append the contents of the row that SQL statement pStmt currently points to
          637  +** to the CursorWindow object passed as the second argument. The CursorWindow
          638  +** currently contains iRow rows. Return true on success or false if an error
          639  +** occurs.
          640  +*/
          641  +static jboolean copyRowToWindow(
          642  +  JNIEnv *pEnv,
          643  +  jobject win,
          644  +  int iRow,
          645  +  sqlite3_stmt *pStmt,
          646  +  CWMethod *aMethod
          647  +){
          648  +  int nCol = sqlite3_column_count(pStmt);
          649  +  int i;
          650  +  jboolean bOk;
          651  +
          652  +  bOk = pEnv->CallBooleanMethod(win, aMethod[CW_ALLOCROW].id);
          653  +  for(i=0; bOk && i<nCol; i++){
          654  +    switch( sqlite3_column_type(pStmt, i) ){
          655  +      case SQLITE_NULL: {
          656  +        bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTNULL].id, iRow, i);
          657  +        break;
          658  +      }
          659  +
          660  +      case SQLITE_INTEGER: {
          661  +        jlong val = sqlite3_column_int64(pStmt, i);
          662  +        bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTLONG].id, val, iRow, i);
          663  +        break;
          664  +      }
          665  +
          666  +      case SQLITE_FLOAT: {
          667  +        jdouble val = sqlite3_column_double(pStmt, i);
          668  +        bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTDOUBLE].id, val, iRow, i);
          669  +        break;
          670  +      }
          671  +
          672  +      case SQLITE_TEXT: {
          673  +        const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
          674  +        jstring val = pEnv->NewStringUTF(zVal);
          675  +        bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTSTRING].id, val, iRow, i);
          676  +        break;
          677  +      }
          678  +
          679  +      default: {
          680  +        assert( sqlite3_column_type(pStmt, i)==SQLITE_BLOB );
          681  +
          682  +        const jbyte *p = (const jbyte*)sqlite3_column_blob(pStmt, i);
          683  +        int n = sqlite3_column_bytes(pStmt, i);
          684  +
          685  +        jbyteArray val = pEnv->NewByteArray(n);
          686  +        pEnv->SetByteArrayRegion(val, 0, n, p);
          687  +        bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTBLOB].id, val, iRow, i);
          688  +        break;
          689  +      }
          690  +    }
          691  +
          692  +    if( bOk==0 ){
          693  +      pEnv->CallVoidMethod(win, aMethod[CW_FREELASTROW].id);
          694  +    }
          695  +  }
          696  +
          697  +  return bOk;
   626    698   }
   627    699   
   628         -#if 0
   629         -static CopyRowResult copyRow(JNIEnv* env, CursorWindow* window,
   630         -        sqlite3_stmt* statement, int numColumns, int startPos, int addedRows) {
   631         -    // Allocate a new field directory for the row.
   632         -    status_t status = window->allocRow();
   633         -    if (status) {
   634         -        LOG_WINDOW("Failed allocating fieldDir at startPos %d row %d, error=%d",
   635         -                startPos, addedRows, status);
   636         -        return CPR_FULL;
   637         -    }
   638         -
   639         -    // Pack the row into the window.
   640         -    CopyRowResult result = CPR_OK;
   641         -    for (int i = 0; i < numColumns; i++) {
   642         -        int type = sqlite3_column_type(statement, i);
   643         -        if (type == SQLITE_TEXT) {
   644         -            // TEXT data
   645         -            const char* text = reinterpret_cast<const char*>(
   646         -                    sqlite3_column_text(statement, i));
   647         -            // SQLite does not include the NULL terminator in size, but does
   648         -            // ensure all strings are NULL terminated, so increase size by
   649         -            // one to make sure we store the terminator.
   650         -            size_t sizeIncludingNull = sqlite3_column_bytes(statement, i) + 1;
   651         -            status = window->putString(addedRows, i, text, sizeIncludingNull);
   652         -            if (status) {
   653         -                LOG_WINDOW("Failed allocating %u bytes for text at %d,%d, error=%d",
   654         -                        sizeIncludingNull, startPos + addedRows, i, status);
   655         -                result = CPR_FULL;
   656         -                break;
   657         -            }
   658         -            LOG_WINDOW("%d,%d is TEXT with %u bytes",
   659         -                    startPos + addedRows, i, sizeIncludingNull);
   660         -        } else if (type == SQLITE_INTEGER) {
   661         -            // INTEGER data
   662         -            int64_t value = sqlite3_column_int64(statement, i);
   663         -            status = window->putLong(addedRows, i, value);
   664         -            if (status) {
   665         -                LOG_WINDOW("Failed allocating space for a long in column %d, error=%d",
   666         -                        i, status);
   667         -                result = CPR_FULL;
   668         -                break;
   669         -            }
   670         -            LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + addedRows, i, value);
   671         -        } else if (type == SQLITE_FLOAT) {
   672         -            // FLOAT data
   673         -            double value = sqlite3_column_double(statement, i);
   674         -            status = window->putDouble(addedRows, i, value);
   675         -            if (status) {
   676         -                LOG_WINDOW("Failed allocating space for a double in column %d, error=%d",
   677         -                        i, status);
   678         -                result = CPR_FULL;
   679         -                break;
   680         -            }
   681         -            LOG_WINDOW("%d,%d is FLOAT %lf", startPos + addedRows, i, value);
   682         -        } else if (type == SQLITE_BLOB) {
   683         -            // BLOB data
   684         -            const void* blob = sqlite3_column_blob(statement, i);
   685         -            size_t size = sqlite3_column_bytes(statement, i);
   686         -            status = window->putBlob(addedRows, i, blob, size);
   687         -            if (status) {
   688         -                LOG_WINDOW("Failed allocating %u bytes for blob at %d,%d, error=%d",
   689         -                        size, startPos + addedRows, i, status);
   690         -                result = CPR_FULL;
   691         -                break;
   692         -            }
   693         -            LOG_WINDOW("%d,%d is Blob with %u bytes",
   694         -                    startPos + addedRows, i, size);
   695         -        } else if (type == SQLITE_NULL) {
   696         -            // NULL field
   697         -            status = window->putNull(addedRows, i);
   698         -            if (status) {
   699         -                LOG_WINDOW("Failed allocating space for a null in column %d, error=%d",
   700         -                        i, status);
   701         -                result = CPR_FULL;
   702         -                break;
   703         -            }
   704         -
   705         -            LOG_WINDOW("%d,%d is NULL", startPos + addedRows, i);
   706         -        } else {
   707         -            // Unknown data
   708         -            ALOGE("Unknown column type when filling database window");
   709         -            throw_sqlite3_exception(env, "Unknown column type when filling window");
   710         -            result = CPR_ERROR;
   711         -            break;
   712         -        }
   713         -    }
   714         -
   715         -    // Free the last row if if was not successfully copied.
   716         -    if (result != CPR_OK) {
   717         -        window->freeLastRow();
   718         -    }
   719         -    return result;
          700  +static jboolean setWindowNumColumns(
          701  +  JNIEnv *pEnv,
          702  +  jobject win,
          703  +  sqlite3_stmt *pStmt,
          704  +  CWMethod *aMethod
          705  +){
          706  +  int nCol;
          707  +
          708  +  pEnv->CallVoidMethod(win, aMethod[CW_CLEAR].id);
          709  +  nCol = sqlite3_column_count(pStmt);
          710  +  return pEnv->CallBooleanMethod(win, aMethod[CW_SETNUMCOLUMNS].id, (jint)nCol);
   720    711   }
   721    712   
   722         -static jlong nativeExecuteForCursorWindow(JNIEnv* env, jclass clazz,
   723         -        jint connectionPtr, jint statementPtr, jint windowPtr,
   724         -        jint startPos, jint requiredPos, jboolean countAllRows) {
   725         -    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
   726         -    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
   727         -    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
   728         -
   729         -    status_t status = window->clear();
   730         -    if (status) {
   731         -        char *zMsg = sqlite3_mprintf(
   732         -            "Failed to clear the cursor window, status=%d", status
   733         -        );
   734         -        throw_sqlite3_exception(env, connection->db, zMsg);
   735         -        sqlite3_free(zMsg);
   736         -        return 0;
   737         -    }
   738         -
   739         -    int numColumns = sqlite3_column_count(statement);
   740         -    status = window->setNumColumns(numColumns);
   741         -    if (status) {
   742         -        char *zMsg = sqlite3_mprintf(
   743         -            "Failed to set the cursor window column count to %d, status=%d",
   744         -            numColumns, status
   745         -        );
   746         -        throw_sqlite3_exception(env, connection->db, zMsg);
   747         -        sqlite3_free(zMsg);
   748         -        return 0;
          713  +/*
          714  +** This method has been rewritten for org.sqlite.database.*. The original 
          715  +** android implementation used the C++ interface to populate a CursorWindow
          716  +** object. Since the NDK does not export this interface, we invoke the Java
          717  +** interface using standard JNI methods to do the same thing.
          718  +**
          719  +** This function executes the SQLite statement object passed as the 4th 
          720  +** argument and copies one or more returned rows into the CursorWindow
          721  +** object passed as the 5th argument. The set of rows copied into the 
          722  +** CursorWindow is always contiguous.
          723  +**
          724  +** The only row that *must* be copied into the CursorWindow is row 
          725  +** iRowRequired. Ideally, all rows from iRowStart through to the end
          726  +** of the query are copied into the CursorWindow. If this is not possible
          727  +** (CursorWindow objects have a finite capacity), some compromise position
          728  +** is found (see comments embedded in the code below for details).
          729  +**
          730  +** The return value is a 64-bit integer calculated as follows:
          731  +**
          732  +**      (iStart << 32) | nRow
          733  +**
          734  +** where iStart is the index of the first row copied into the CursorWindow.
          735  +** If the countAllRows argument is true, nRow is the total number of rows
          736  +** returned by the query. Otherwise, nRow is one greater than the index of 
          737  +** the last row copied into the CursorWindow.
          738  +*/
          739  +static jlong nativeExecuteForCursorWindow(
          740  +  JNIEnv *pEnv, 
          741  +  jclass clazz,
          742  +  jint connectionPtr,             /* Pointer to SQLiteConnection C++ object */
          743  +  jint statementPtr,              /* Pointer to sqlite3_stmt object */
          744  +  jobject win,                    /* The CursorWindow object to populate */
          745  +  jint startPos,                  /* First row to add (advisory) */
          746  +  jint iRowRequired,              /* Required row */
          747  +  jboolean countAllRows
          748  +) {
          749  +  SQLiteConnection *pConnection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
          750  +  sqlite3_stmt *pStmt = reinterpret_cast<sqlite3_stmt*>(statementPtr);
          751  +
          752  +  CWMethod aMethod[] = {
          753  +    {0, "clear",         "()V"},
          754  +    {0, "setNumColumns", "(I)Z"},
          755  +    {0, "allocRow",      "()Z"},
          756  +    {0, "freeLastRow",   "()V"},
          757  +    {0, "putNull",       "(II)Z"},
          758  +    {0, "putLong",       "(JII)Z"},
          759  +    {0, "putDouble",     "(DII)Z"},
          760  +    {0, "putString",     "(Ljava/lang/String;II)Z"},
          761  +    {0, "putBlob",       "([BII)Z"},
          762  +  };
          763  +  jclass cls;                     /* Class android.database.CursorWindow */
          764  +  int i;                          /* Iterator variable */
          765  +  int nCol;                       /* Number of columns returned by pStmt */
          766  +  int nRow;
          767  +  jboolean bOk;
          768  +  int iStart;                     /* First row copied to CursorWindow */
          769  +
          770  +  /* Locate all required CursorWindow methods. */
          771  +  cls = pEnv->FindClass("android/database/CursorWindow");
          772  +  for(i=0; i<(sizeof(aMethod)/sizeof(struct CWMethod)); i++){
          773  +    aMethod[i].id = pEnv->GetMethodID(cls, aMethod[i].zName, aMethod[i].zSig);
          774  +    if( aMethod[i].id==NULL ){
          775  +      jniThrowExceptionFmt(pEnv, "java/lang/Exception", 
          776  +          "Failed to find method CursorWindow.%s()", aMethod[i].zName
          777  +      );
          778  +      return 0;
   749    779       }
   750         -
   751         -    int retryCount = 0;
   752         -    int totalRows = 0;
   753         -    int addedRows = 0;
   754         -    bool windowFull = false;
   755         -    bool gotException = false;
   756         -    while (!gotException && (!windowFull || countAllRows)) {
   757         -        int err = sqlite3_step(statement);
   758         -        if (err == SQLITE_ROW) {
   759         -            LOG_WINDOW("Stepped statement %p to row %d", statement, totalRows);
   760         -            retryCount = 0;
   761         -            totalRows += 1;
   762         -
   763         -            // Skip the row if the window is full or we haven't reached the start position yet.
   764         -            if (startPos >= totalRows || windowFull) {
   765         -                continue;
   766         -            }
   767         -
   768         -            CopyRowResult cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
   769         -            if (cpr == CPR_FULL && addedRows && startPos + addedRows <= requiredPos) {
   770         -                // We filled the window before we got to the one row that we really wanted.
   771         -                // Clear the window and start filling it again from here.
   772         -                // TODO: Would be nicer if we could progressively replace earlier rows.
   773         -                window->clear();
   774         -                window->setNumColumns(numColumns);
   775         -                startPos += addedRows;
   776         -                addedRows = 0;
   777         -                cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
   778         -            }
   779         -
   780         -            if (cpr == CPR_OK) {
   781         -                addedRows += 1;
   782         -            } else if (cpr == CPR_FULL) {
   783         -                windowFull = true;
   784         -            } else {
   785         -                gotException = true;
   786         -            }
   787         -        } else if (err == SQLITE_DONE) {
   788         -            // All rows processed, bail
   789         -            LOG_WINDOW("Processed all rows");
   790         -            break;
   791         -        } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {
   792         -            // The table is locked, retry
   793         -            LOG_WINDOW("Database locked, retrying");
   794         -            if (retryCount > 50) {
   795         -                ALOGE("Bailing on database busy retry");
   796         -                throw_sqlite3_exception(env, connection->db, "retrycount exceeded");
   797         -                gotException = true;
   798         -            } else {
   799         -                // Sleep to give the thread holding the lock a chance to finish
   800         -                usleep(1000);
   801         -                retryCount++;
   802         -            }
   803         -        } else {
   804         -            throw_sqlite3_exception(env, connection->db);
   805         -            gotException = true;
          780  +  }
          781  +
          782  +
          783  +  /* Set the number of columns in the window */
          784  +  bOk = setWindowNumColumns(pEnv, win, pStmt, aMethod);
          785  +  if( bOk==0 ) return 0;
          786  +
          787  +  nRow = 0;
          788  +  iStart = startPos;
          789  +  while( sqlite3_step(pStmt)==SQLITE_ROW ){
          790  +    /* Only copy in rows that occur at or after row index iStart. */
          791  +    if( nRow>=iStart && bOk ){
          792  +      bOk = copyRowToWindow(pEnv, win, (nRow - iStart), pStmt, aMethod);
          793  +      if( bOk==0 ){
          794  +        /* The CursorWindow object ran out of memory. If row iRowRequired was
          795  +        ** not successfully added before this happened, clear the CursorWindow
          796  +        ** and try to add the current row again.  */
          797  +        if( nRow<=iRowRequired ){
          798  +          bOk = setWindowNumColumns(pEnv, win, pStmt, aMethod);
          799  +          if( bOk==0 ){
          800  +            sqlite3_reset(pStmt);
          801  +            return 0;
          802  +          }
          803  +          iStart = nRow;
          804  +          bOk = copyRowToWindow(pEnv, win, (nRow - iStart), pStmt, aMethod);
   806    805           }
          806  +
          807  +        /* If the CursorWindow is still full and the countAllRows flag is not
          808  +        ** set, break out of the loop here. If countAllRows is set, continue
          809  +        ** so as to set variable nRow correctly.  */
          810  +        if( bOk==0 && countAllRows==0 ) break;
          811  +      }
   807    812       }
   808    813   
   809         -    LOG_WINDOW("Resetting statement %p after fetching %d rows and adding %d rows"
   810         -            "to the window in %d bytes",
   811         -            statement, totalRows, addedRows, window->size() - window->freeSpace());
   812         -    sqlite3_reset(statement);
          814  +    nRow++;
          815  +  }
   813    816   
   814         -    // Report the total number of rows on request.
   815         -    if (startPos > totalRows) {
   816         -        ALOGE("startPos %d > actual rows %d", startPos, totalRows);
   817         -    }
   818         -    jlong result = jlong(startPos) << 32 | jlong(totalRows);
   819         -    return result;
          817  +  /* Finalize the statement. If this indicates an error occurred, throw an
          818  +  ** SQLiteException exception.  */
          819  +  int rc = sqlite3_reset(pStmt);
          820  +  if( rc!=SQLITE_OK ){
          821  +    throw_sqlite3_exception(pEnv, sqlite3_db_handle(pStmt));
          822  +    return 0;
          823  +  }
          824  +
          825  +  jlong lRet = jlong(iStart) << 32 | jlong(nRow);
          826  +  return lRet;
   820    827   }
   821         -#endif
   822    828   
   823    829   static jint nativeGetDbLookaside(JNIEnv* env, jobject clazz, jint connectionPtr) {
   824    830       SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
   825    831   
   826    832       int cur = -1;
   827    833       int unused;
   828    834       sqlite3_db_status(connection->db, SQLITE_DBSTATUS_LOOKASIDE_USED, &cur, &unused, 0);
................................................................................
   891    897               (void*)nativeExecuteForString },
   892    898       { "nativeExecuteForBlobFileDescriptor", "(II)I",
   893    899               (void*)nativeExecuteForBlobFileDescriptor },
   894    900       { "nativeExecuteForChangedRowCount", "(II)I",
   895    901               (void*)nativeExecuteForChangedRowCount },
   896    902       { "nativeExecuteForLastInsertedRowId", "(II)J",
   897    903               (void*)nativeExecuteForLastInsertedRowId },
   898         -    { "nativeExecuteForCursorWindow", "(IIIIIZ)J",
          904  +    { "nativeExecuteForCursorWindow", "(IILandroid/database/CursorWindow;IIZ)J",
   899    905               (void*)nativeExecuteForCursorWindow },
   900    906       { "nativeGetDbLookaside", "(I)I",
   901    907               (void*)nativeGetDbLookaside },
   902    908       { "nativeCancel", "(I)V",
   903    909               (void*)nativeCancel },
   904    910       { "nativeResetCancel", "(IZ)V",
   905    911               (void*)nativeResetCancel },

Changes to jni/android_database_SQLiteGlobal.cpp.

    38     38   
    39     39   
    40     40   // Called each time a message is logged.
    41     41   static void sqliteLogCallback(void* data, int iErrCode, const char* zMsg) {
    42     42       bool verboseLog = !!data;
    43     43       if (iErrCode == 0 || iErrCode == SQLITE_CONSTRAINT || iErrCode == SQLITE_SCHEMA) {
    44     44           if (verboseLog) {
    45         -            ALOGV(LOG_VERBOSE, SQLITE_LOG_TAG, "(%d) %s\n", iErrCode, zMsg);
           45  +            ALOG(LOG_VERBOSE, SQLITE_LOG_TAG, "(%d) %s\n", iErrCode, zMsg);
    46     46           }
    47     47       } else {
    48     48           ALOG(LOG_ERROR, SQLITE_LOG_TAG, "(%d) %s\n", iErrCode, zMsg);
    49     49       }
    50     50   }
    51     51   
    52     52   // Sets the global SQLite configuration.

Changes to res/layout/main.xml.

     1      1   <?xml version="1.0" encoding="utf-8"?>
            2  +<ScrollView
            3  +  xmlns:android="http://schemas.android.com/apk/res/android"
            4  +    android:layout_width="fill_parent"
            5  +      android:layout_height="fill_parent">
            6  +
     2      7   <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     3      8       android:orientation="vertical"
     4      9       android:layout_width="fill_parent"
     5     10       android:layout_height="fill_parent"
     6     11       >
     7     12   
     8     13   <TextView
................................................................................
    23     28       android:id="@+id/tv_widget"
    24     29       android:layout_width="fill_parent"
    25     30       android:layout_height="wrap_content"
    26     31       android:text="&lt;this text should be replaced by the test output&gt;"
    27     32       android:typeface="monospace"
    28     33       />
    29     34   </LinearLayout>
           35  +</ScrollView>
    30     36   

Changes to src/org/sqlite/app/customsqlite/CustomSqlite.java.

    38     38   
    39     39       db = SQLiteDatabase.openOrCreateDatabase(":memory:", null);
    40     40       st = db.compileStatement("SELECT sqlite_version()");
    41     41       res = st.simpleQueryForString();
    42     42   
    43     43       myTV.append("SQLite version " + res + "\n\n");
    44     44     }
           45  +
           46  +  public void test_warning(String name, String warning){
           47  +    myTV.append("WARNING:" + name + ": " + warning + "\n");
           48  +  }
    45     49   
    46     50     public void test_result(String name, String res, String expected){
    47     51       myTV.append(name + "... ");
    48     52       myNTest++;
    49     53   
    50     54       if( res.equals(expected) ){
    51     55         myTV.append("ok\n");
................................................................................
    70     74       Cursor c = db.rawQuery("SELECT x FROM t1", null);
    71     75       if( c!=null ){
    72     76         boolean bRes;
    73     77         for(bRes=c.moveToFirst(); bRes; bRes=c.moveToNext()){
    74     78           String x = c.getString(0);
    75     79           res = res + "." + x;
    76     80         }
           81  +    }else{
           82  +      test_warning("csr_test_1", "c==NULL");
    77     83       }
    78     84   
    79     85       test_result("csr_test_1", res, ".one.two.three");
    80     86     }
    81     87   
    82     88     public void run_the_tests(View view){
    83     89       System.loadLibrary("sqliteX");
................................................................................
    88     94   
    89     95       try {
    90     96         report_version();
    91     97         csr_test_1();
    92     98   
    93     99         myTV.append("\n" + myNErr + " errors from " + myNTest + " tests\n");
    94    100       } catch(Exception e) {
    95         -      myTV.append("Exception: " + e.toString());
          101  +      myTV.append("Exception: " + e.toString() + "\n");
          102  +      myTV.append(android.util.Log.getStackTraceString(e) + "\n");
    96    103       }
    97    104     }
    98    105   }
    99    106   
   100    107   

Changes to src/org/sqlite/database/sqlite/ExtraUtils.java.

    82     82           for (int i = 0; i < length; i++) {
    83     83               if (columnNames[i].equals("_id")) {
    84     84                   return i;
    85     85               }
    86     86           }
    87     87           return -1;
    88     88       }
           89  +
           90  +    /**
           91  +     * Picks a start position for {@link Cursor#fillWindow} such that the
           92  +     * window will contain the requested row and a useful range of rows
           93  +     * around it.
           94  +     *
           95  +     * When the data set is too large to fit in a cursor window, seeking the
           96  +     * cursor can become a very expensive operation since we have to run the
           97  +     * query again when we move outside the bounds of the current window.
           98  +     *
           99  +     * We try to choose a start position for the cursor window such that
          100  +     * 1/3 of the window's capacity is used to hold rows before the requested
          101  +     * position and 2/3 of the window's capacity is used to hold rows after the
          102  +     * requested position.
          103  +     *
          104  +     * @param cursorPosition The row index of the row we want to get.
          105  +     * @param cursorWindowCapacity The estimated number of rows that can fit in
          106  +     * a cursor window, or 0 if unknown.
          107  +     * @return The recommended start position, always less than or equal to
          108  +     * the requested row.
          109  +     * @hide
          110  +     */
          111  +    public static int cursorPickFillWindowStartPosition(
          112  +            int cursorPosition, int cursorWindowCapacity) {
          113  +        return Math.max(cursorPosition - cursorWindowCapacity / 3, 0);
          114  +    }
    89    115   }

Changes to src/org/sqlite/database/sqlite/SQLiteConnection.java.

   147    147       private static native String nativeExecuteForString(int connectionPtr, int statementPtr);
   148    148       private static native int nativeExecuteForBlobFileDescriptor(
   149    149               int connectionPtr, int statementPtr);
   150    150       private static native int nativeExecuteForChangedRowCount(int connectionPtr, int statementPtr);
   151    151       private static native long nativeExecuteForLastInsertedRowId(
   152    152               int connectionPtr, int statementPtr);
   153    153       private static native long nativeExecuteForCursorWindow(
   154         -            int connectionPtr, int statementPtr, int windowPtr,
          154  +            int connectionPtr, int statementPtr, CursorWindow win,
   155    155               int startPos, int requiredPos, boolean countAllRows);
   156    156       private static native int nativeGetDbLookaside(int connectionPtr);
   157    157       private static native void nativeCancel(int connectionPtr);
   158    158       private static native void nativeResetCancel(int connectionPtr, boolean cancelable);
   159    159   
   160    160       private SQLiteConnection(SQLiteConnectionPool pool,
   161    161               SQLiteDatabaseConfiguration configuration,
................................................................................
   816    816        * @throws SQLiteException if an error occurs, such as a syntax error
   817    817        * or invalid number of bind arguments.
   818    818        * @throws OperationCanceledException if the operation was canceled.
   819    819        */
   820    820       public int executeForCursorWindow(String sql, Object[] bindArgs,
   821    821               CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
   822    822               CancellationSignal cancellationSignal) {
   823         -      /*
   824    823           if (sql == null) {
   825    824               throw new IllegalArgumentException("sql must not be null.");
   826    825           }
   827    826           if (window == null) {
   828    827               throw new IllegalArgumentException("window must not be null.");
   829    828           }
   830    829   
................................................................................
   840    839                   try {
   841    840                       throwIfStatementForbidden(statement);
   842    841                       bindArguments(statement, bindArgs);
   843    842                       applyBlockGuardPolicy(statement);
   844    843                       attachCancellationSignal(cancellationSignal);
   845    844                       try {
   846    845                           final long result = nativeExecuteForCursorWindow(
   847         -                                mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
          846  +                                mConnectionPtr, statement.mStatementPtr, window,
   848    847                                   startPos, requiredPos, countAllRows);
   849    848                           actualPos = (int)(result >> 32);
   850    849                           countedRows = (int)result;
   851    850                           filledRows = window.getNumRows();
   852    851                           window.setStartPosition(actualPos);
   853    852                           return countedRows;
   854    853                       } finally {
................................................................................
   868    867                               + ", filledRows=" + filledRows
   869    868                               + ", countedRows=" + countedRows);
   870    869                   }
   871    870               }
   872    871           } finally {
   873    872               window.releaseReference();
   874    873           }
   875         -   */
   876         -      return -1;
   877    874       }
   878    875   
   879    876       private PreparedStatement acquirePreparedStatement(String sql) {
   880    877           PreparedStatement statement = mPreparedStatementCache.get(sql);
   881    878           boolean skipCache = false;
   882    879           if (statement != null) {
   883    880               if (!statement.mInUse) {

Changes to src/org/sqlite/database/sqlite/SQLiteCursor.java.

    17     17   package org.sqlite.database.sqlite;
    18     18   
    19     19   import org.sqlite.database.ExtraUtils;
    20     20   
    21     21   import android.database.AbstractWindowedCursor;
    22     22   import android.database.CursorWindow;
    23     23   
    24         -import android.database.DatabaseUtils;
    25     24   import android.os.StrictMode;
    26     25   import android.util.Log;
    27     26   
    28     27   import java.util.HashMap;
    29     28   import java.util.Map;
    30     29   
    31     30   /**
................................................................................
   133    132       @Override
   134    133       public int getCount() {
   135    134           if (mCount == NO_COUNT) {
   136    135               fillWindow(0);
   137    136           }
   138    137           return mCount;
   139    138       }
          139  +
          140  +    /* 
          141  +    ** The AbstractWindowClass contains protected methods clearOrCreateWindow() and
          142  +    ** closeWindow(), which are used by the android.database.sqlite.* version of this
          143  +    ** class. But, since they are marked with "@hide", the following replacement 
          144  +    ** versions are required.
          145  +    */
          146  +    private void awc_clearOrCreateWindow(String name){
          147  +      CursorWindow win = getWindow();
          148  +      if( win==null ){
          149  +        win = new CursorWindow(name);
          150  +        setWindow(win);
          151  +      }else{
          152  +        win.clear();
          153  +      }
          154  +    }
          155  +    private void awc_closeWindow(){
          156  +      setWindow(null);
          157  +    }
   140    158   
   141    159       private void fillWindow(int requiredPos) {
   142         -      /*
   143         -        clearOrCreateWindow(getDatabase().getPath());
          160  +        awc_clearOrCreateWindow(getDatabase().getPath());
   144    161   
   145    162           try {
   146    163               if (mCount == NO_COUNT) {
   147         -                int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
          164  +                int startPos = ExtraUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
   148    165                   mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
   149    166                   mCursorWindowCapacity = mWindow.getNumRows();
   150    167                   if (Log.isLoggable(TAG, Log.DEBUG)) {
   151    168                       Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
   152    169                   }
   153    170               } else {
   154         -                int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
          171  +                int startPos = ExtraUtils.cursorPickFillWindowStartPosition(requiredPos,
   155    172                           mCursorWindowCapacity);
   156    173                   mQuery.fillWindow(mWindow, startPos, requiredPos, false);
   157    174               }
   158    175           } catch (RuntimeException ex) {
   159    176               // Close the cursor window if the query failed and therefore will
   160    177               // not produce any results.  This helps to avoid accidentally leaking
   161    178               // the cursor window if the client does not correctly handle exceptions
   162    179               // and fails to close the cursor.
   163         -            closeWindow();
          180  +            awc_closeWindow();
   164    181               throw ex;
   165    182           }
   166         -        */
   167    183       }
   168    184   
   169    185       @Override
   170    186       public int getColumnIndex(String columnName) {
   171    187           // Create mColumnNameMap on demand
   172    188           if (mColumnNameMap == null) {
   173    189               String[] columns = mColumns;