SQLite Android Bindings

Check-in [365586dcaf]
Login

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
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 365586dcafe43f880021b0de52b6a19f02fc6ee1
User & Date: dan 2013-12-21 16:04:55.246
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
Unified Diff Ignore Whitespace Patch
Changes to jni/android_database_SQLiteConnection.cpp.
20
21
22
23
24
25
26

27
28
29
30
31
32
33
#include <JNIHelp.h>
#include "ALog-priv.h"


#include <sys/mman.h>
#include <string.h>
#include <unistd.h>


#if 0
#include <androidfw/CursorWindow.h>
#endif

#include <sqlite3.h>
#if 0







>







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <JNIHelp.h>
#include "ALog-priv.h"


#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>

#if 0
#include <androidfw/CursorWindow.h>
#endif

#include <sqlite3.h>
#if 0
602
603
604
605
606
607
608




609
610


611
612





613
614

615
616
617
618
619
620
621
622
623
624

625



626
627
628
629
630
631
632
633
634

635
636
637
638
639


640


641



642
643
644

645


646
647



648
649

650
651
652
653
654
655

656
657
658
659
660
661
662
663
664
665
666
667

668
669
670
671
672
673
674
675
676
677
678

679
680
681

682
683
684
685
686
687
688
689
690
691
692

693
694
695
696
697
698
699
700
701
702

703
704
705
706

707


708
709


710
711



712
713






714
715
716
717
718







719
720
721







722


723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741

742
743


744
745
746

747
748





749
750
751

752
753
754
755
756
757
758
759
760
761
762
763
764
765
766



767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804


805
806
807
808
809






810
811





812

813



814



815


816

817
818





819
820
821



822
823
824
825
826
827
828
                return createAshmemRegionWithData(env, blob, length);
            }
        }
    }
    return -1;
}





enum CopyRowResult {
    CPR_OK,


    CPR_FULL,
    CPR_ERROR,





};


static jlong nativeExecuteForCursorWindow(
  JNIEnv* env, jclass clazz,
  jint connectionPtr, 
  jint statementPtr, 
  jint windowPtr,
  jint startPos, 
  jint requiredPos, 
  jboolean countAllRows
) {
  jniThrowIOException(env, -1);

  return -1;



}

#if 0
static CopyRowResult copyRow(JNIEnv* env, CursorWindow* window,
        sqlite3_stmt* statement, int numColumns, int startPos, int addedRows) {
    // Allocate a new field directory for the row.
    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


            ALOGE("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 nativeExecuteForCursorWindow(JNIEnv* env, jclass clazz,


        jint connectionPtr, jint statementPtr, jint windowPtr,
        jint startPos, jint requiredPos, jboolean countAllRows) {
    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);

    status_t status = window->clear();
    if (status) {
        char *zMsg = sqlite3_mprintf(
            "Failed to clear the cursor window, status=%d", status
        );
        throw_sqlite3_exception(env, connection->db, zMsg);
        sqlite3_free(zMsg);
        return 0;
    }

    int numColumns = sqlite3_column_count(statement);
    status = window->setNumColumns(numColumns);
    if (status) {

        char *zMsg = sqlite3_mprintf(
            "Failed to set the cursor window column count to %d, status=%d",


            numColumns, status
        );
        throw_sqlite3_exception(env, connection->db, zMsg);

        sqlite3_free(zMsg);
        return 0;





    }

    int retryCount = 0;

    int totalRows = 0;
    int addedRows = 0;
    bool windowFull = false;
    bool gotException = false;
    while (!gotException && (!windowFull || countAllRows)) {
        int err = sqlite3_step(statement);
        if (err == SQLITE_ROW) {
            LOG_WINDOW("Stepped statement %p to row %d", statement, totalRows);
            retryCount = 0;
            totalRows += 1;

            // Skip the row if the window is full or we haven't reached the start position yet.
            if (startPos >= totalRows || windowFull) {
                continue;
            }




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

            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
            LOG_WINDOW("Processed all rows");
            break;
        } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {
            // The table is locked, retry
            LOG_WINDOW("Database locked, retrying");
            if (retryCount > 50) {
                ALOGE("Bailing on database busy retry");
                throw_sqlite3_exception(env, connection->db, "retrycount exceeded");
                gotException = true;
            } else {
                // Sleep to give the thread holding the lock a chance to finish
                usleep(1000);
                retryCount++;
            }
        } else {
            throw_sqlite3_exception(env, connection->db);


            gotException = true;
        }
    }

    LOG_WINDOW("Resetting statement %p after fetching %d rows and adding %d rows"






            "to the window in %d bytes",
            statement, totalRows, addedRows, window->size() - window->freeSpace());





    sqlite3_reset(statement);





    // Report the total number of rows on request.



    if (startPos > totalRows) {


        ALOGE("startPos %d > actual rows %d", startPos, totalRows);

    }
    jlong result = jlong(startPos) << 32 | jlong(totalRows);





    return result;
}
#endif




static jint nativeGetDbLookaside(JNIEnv* env, jobject clazz, jint connectionPtr) {
    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);

    int cur = -1;
    int unused;
    sqlite3_db_status(connection->db, SQLITE_DBSTATUS_LOOKASIDE_USED, &cur, &unused, 0);







>
>
>
>
|
<
>
>
|
|
>
>
>
>
>


>
|
<
<
<
<
<
<
<
<
<
>
|
>
>
>
|

<
<
<
<
<
<
<
>
|
<
<
|
|
>
>
|
>
>
|
>
>
>
|
<
|
>
|
>
>
|
|
>
>
>
|
<
>
|
<
<
<
<
<
>
|
|
|
<
|
<
|
<
<
<
<
<
>
|
|
|
|
<
|
<
<
<
<
|
>
|
|
|
>
|
|
|
|
|
|
|
|
<
|
|
>
|
<
<
<
<
|
<
<
<
<
>
|
|
|
|
>
|
>
>
|
|
>
>
|
|
>
>
>
|
|
>
>
>
>
>
>
|
|
|
|
<
>
>
>
>
>
>
>
|
<
|
>
>
>
>
>
>
>
|
>
>
|
<
<
|
|
|
<
<
<
<
<
<
<
<
<
|
|
<
|
>
|
|
>
>
|
<
<
>
|
<
>
>
>
>
>
|
|
|
>
|
<
|
<
<
<
<
<
<
<
|
<
<
<
|
>
>
>
|
|
|
|
<
<
|
<
<
|
<
|
|
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
>
>
|
|
<
|
<
>
>
>
>
>
>
|
<
>
>
>
>
>
|
>
|
>
>
>
|
>
>
>
|
>
>
|
>
|
|
>
>
>
>
>
|
|
|
>
>
>







603
604
605
606
607
608
609
610
611
612
613
614

615
616
617
618
619
620
621
622
623
624
625
626
627









628
629
630
631
632
633
634







635
636


637
638
639
640
641
642
643
644
645
646
647
648

649
650
651
652
653
654
655
656
657
658
659

660
661





662
663
664
665

666

667





668
669
670
671
672

673




674
675
676
677
678
679
680
681
682
683
684
685
686
687

688
689
690
691




692




693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722

723
724
725
726
727
728
729
730

731
732
733
734
735
736
737
738
739
740
741
742


743
744
745









746
747

748
749
750
751
752
753
754


755
756

757
758
759
760
761
762
763
764
765
766

767







768



769
770
771
772
773
774
775
776


777


778

779
780






781















782


783
784
785
786

787

788
789
790
791
792
793
794

795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
                return createAshmemRegionWithData(env, blob, length);
            }
        }
    }
    return -1;
}

/*
** Note: The following symbols must be in the same order as the corresponding
** elements in the aMethod[] array in function nativeExecuteForCursorWindow().
*/
enum CWMethodNames {

  CW_CLEAR         = 0,
  CW_SETNUMCOLUMNS = 1,
  CW_ALLOCROW      = 2,
  CW_FREELASTROW   = 3,
  CW_PUTNULL       = 4,
  CW_PUTLONG       = 5,
  CW_PUTDOUBLE     = 6,
  CW_PUTSTRING     = 7,
  CW_PUTBLOB       = 8
};

/*
** An instance of this structure represents a single CursorWindow java method.









*/
struct CWMethod {
  jmethodID id;                   /* Method id */
  const char *zName;              /* Method name */
  const char *zSig;               /* Method JNI signature */
};








/*
** Append the contents of the row that SQL statement pStmt currently points to


** to the CursorWindow object passed as the second argument. The CursorWindow
** currently contains iRow rows. Return true on success or false if an error
** occurs.
*/
static jboolean copyRowToWindow(
  JNIEnv *pEnv,
  jobject win,
  int iRow,
  sqlite3_stmt *pStmt,
  CWMethod *aMethod
){
  int nCol = sqlite3_column_count(pStmt);

  int i;
  jboolean bOk;

  bOk = pEnv->CallBooleanMethod(win, aMethod[CW_ALLOCROW].id);
  for(i=0; bOk && i<nCol; i++){
    switch( sqlite3_column_type(pStmt, i) ){
      case SQLITE_NULL: {
        bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTNULL].id, iRow, i);
        break;
      }


      case SQLITE_INTEGER: {
        jlong val = sqlite3_column_int64(pStmt, i);





        bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTLONG].id, val, iRow, i);
        break;
      }


      case SQLITE_FLOAT: {

        jdouble val = sqlite3_column_double(pStmt, i);





        bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTDOUBLE].id, val, iRow, i);
        break;
      }

      case SQLITE_TEXT: {

        const char *zVal = (const char*)sqlite3_column_text(pStmt, i);




        jstring val = pEnv->NewStringUTF(zVal);
        bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTSTRING].id, val, iRow, i);
        break;
      }

      default: {
        assert( sqlite3_column_type(pStmt, i)==SQLITE_BLOB );

        const jbyte *p = (const jbyte*)sqlite3_column_blob(pStmt, i);
        int n = sqlite3_column_bytes(pStmt, i);

        jbyteArray val = pEnv->NewByteArray(n);
        pEnv->SetByteArrayRegion(val, 0, n, p);
        bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTBLOB].id, val, iRow, i);

        break;
      }
    }





    if( bOk==0 ){




      pEnv->CallVoidMethod(win, aMethod[CW_FREELASTROW].id);
    }
  }

  return bOk;
}

static jboolean setWindowNumColumns(
  JNIEnv *pEnv,
  jobject win,
  sqlite3_stmt *pStmt,
  CWMethod *aMethod
){
  int nCol;

  pEnv->CallVoidMethod(win, aMethod[CW_CLEAR].id);
  nCol = sqlite3_column_count(pStmt);
  return pEnv->CallBooleanMethod(win, aMethod[CW_SETNUMCOLUMNS].id, (jint)nCol);
}

/*
** This method has been rewritten for org.sqlite.database.*. The original 
** android implementation used the C++ interface to populate a CursorWindow
** object. Since the NDK does not export this interface, we invoke the Java
** interface using standard JNI methods to do the same thing.
**
** This function executes the SQLite statement object passed as the 4th 
** argument and copies one or more returned rows into the CursorWindow
** object passed as the 5th argument. The set of rows copied into the 
** CursorWindow is always contiguous.

**
** The only row that *must* be copied into the CursorWindow is row 
** iRowRequired. Ideally, all rows from iRowStart through to the end
** of the query are copied into the CursorWindow. If this is not possible
** (CursorWindow objects have a finite capacity), some compromise position
** is found (see comments embedded in the code below for details).
**
** The return value is a 64-bit integer calculated as follows:

**
**      (iStart << 32) | nRow
**
** where iStart is the index of the first row copied into the CursorWindow.
** If the countAllRows argument is true, nRow is the total number of rows
** returned by the query. Otherwise, nRow is one greater than the index of 
** the last row copied into the CursorWindow.
*/
static jlong nativeExecuteForCursorWindow(
  JNIEnv *pEnv, 
  jclass clazz,
  jint connectionPtr,             /* Pointer to SQLiteConnection C++ object */


  jint statementPtr,              /* Pointer to sqlite3_stmt object */
  jobject win,                    /* The CursorWindow object to populate */
  jint startPos,                  /* First row to add (advisory) */









  jint iRowRequired,              /* Required row */
  jboolean countAllRows

) {
  SQLiteConnection *pConnection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
  sqlite3_stmt *pStmt = reinterpret_cast<sqlite3_stmt*>(statementPtr);

  CWMethod aMethod[] = {
    {0, "clear",         "()V"},
    {0, "setNumColumns", "(I)Z"},


    {0, "allocRow",      "()Z"},
    {0, "freeLastRow",   "()V"},

    {0, "putNull",       "(II)Z"},
    {0, "putLong",       "(JII)Z"},
    {0, "putDouble",     "(DII)Z"},
    {0, "putString",     "(Ljava/lang/String;II)Z"},
    {0, "putBlob",       "([BII)Z"},
  };
  jclass cls;                     /* Class android.database.CursorWindow */
  int i;                          /* Iterator variable */
  int nCol;                       /* Number of columns returned by pStmt */
  int nRow;

  jboolean bOk;







  int iStart;                     /* First row copied to CursorWindow */




  /* Locate all required CursorWindow methods. */
  cls = pEnv->FindClass("android/database/CursorWindow");
  for(i=0; i<(sizeof(aMethod)/sizeof(struct CWMethod)); i++){
    aMethod[i].id = pEnv->GetMethodID(cls, aMethod[i].zName, aMethod[i].zSig);
    if( aMethod[i].id==NULL ){
      jniThrowExceptionFmt(pEnv, "java/lang/Exception", 
          "Failed to find method CursorWindow.%s()", aMethod[i].zName


      );


      return 0;

    }
  }

























  /* Set the number of columns in the window */
  bOk = setWindowNumColumns(pEnv, win, pStmt, aMethod);
  if( bOk==0 ) return 0;


  nRow = 0;

  iStart = startPos;
  while( sqlite3_step(pStmt)==SQLITE_ROW ){
    /* Only copy in rows that occur at or after row index iStart. */
    if( nRow>=iStart && bOk ){
      bOk = copyRowToWindow(pEnv, win, (nRow - iStart), pStmt, aMethod);
      if( bOk==0 ){
        /* The CursorWindow object ran out of memory. If row iRowRequired was

        ** not successfully added before this happened, clear the CursorWindow
        ** and try to add the current row again.  */
        if( nRow<=iRowRequired ){
          bOk = setWindowNumColumns(pEnv, win, pStmt, aMethod);
          if( bOk==0 ){
            sqlite3_reset(pStmt);
            return 0;
          }
          iStart = nRow;
          bOk = copyRowToWindow(pEnv, win, (nRow - iStart), pStmt, aMethod);
        }

        /* If the CursorWindow is still full and the countAllRows flag is not
        ** set, break out of the loop here. If countAllRows is set, continue
        ** so as to set variable nRow correctly.  */
        if( bOk==0 && countAllRows==0 ) break;
      }
    }

    nRow++;
  }

  /* Finalize the statement. If this indicates an error occurred, throw an
  ** SQLiteException exception.  */
  int rc = sqlite3_reset(pStmt);
  if( rc!=SQLITE_OK ){
    throw_sqlite3_exception(pEnv, sqlite3_db_handle(pStmt));
    return 0;
  }

  jlong lRet = jlong(iStart) << 32 | jlong(nRow);
  return lRet;
}

static jint nativeGetDbLookaside(JNIEnv* env, jobject clazz, jint connectionPtr) {
    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);

    int cur = -1;
    int unused;
    sqlite3_db_status(connection->db, SQLITE_DBSTATUS_LOOKASIDE_USED, &cur, &unused, 0);
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
            (void*)nativeExecuteForString },
    { "nativeExecuteForBlobFileDescriptor", "(II)I",
            (void*)nativeExecuteForBlobFileDescriptor },
    { "nativeExecuteForChangedRowCount", "(II)I",
            (void*)nativeExecuteForChangedRowCount },
    { "nativeExecuteForLastInsertedRowId", "(II)J",
            (void*)nativeExecuteForLastInsertedRowId },
    { "nativeExecuteForCursorWindow", "(IIIIIZ)J",
            (void*)nativeExecuteForCursorWindow },
    { "nativeGetDbLookaside", "(I)I",
            (void*)nativeGetDbLookaside },
    { "nativeCancel", "(I)V",
            (void*)nativeCancel },
    { "nativeResetCancel", "(IZ)V",
            (void*)nativeResetCancel },







|







897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
            (void*)nativeExecuteForString },
    { "nativeExecuteForBlobFileDescriptor", "(II)I",
            (void*)nativeExecuteForBlobFileDescriptor },
    { "nativeExecuteForChangedRowCount", "(II)I",
            (void*)nativeExecuteForChangedRowCount },
    { "nativeExecuteForLastInsertedRowId", "(II)J",
            (void*)nativeExecuteForLastInsertedRowId },
    { "nativeExecuteForCursorWindow", "(IILandroid/database/CursorWindow;IIZ)J",
            (void*)nativeExecuteForCursorWindow },
    { "nativeGetDbLookaside", "(I)I",
            (void*)nativeGetDbLookaside },
    { "nativeCancel", "(I)V",
            (void*)nativeCancel },
    { "nativeResetCancel", "(IZ)V",
            (void*)nativeResetCancel },
Changes to jni/android_database_SQLiteGlobal.cpp.
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52


// Called each time a message is logged.
static void sqliteLogCallback(void* data, int iErrCode, const char* zMsg) {
    bool verboseLog = !!data;
    if (iErrCode == 0 || iErrCode == SQLITE_CONSTRAINT || iErrCode == SQLITE_SCHEMA) {
        if (verboseLog) {
            ALOGV(LOG_VERBOSE, SQLITE_LOG_TAG, "(%d) %s\n", iErrCode, zMsg);
        }
    } else {
        ALOG(LOG_ERROR, SQLITE_LOG_TAG, "(%d) %s\n", iErrCode, zMsg);
    }
}

// Sets the global SQLite configuration.







|







38
39
40
41
42
43
44
45
46
47
48
49
50
51
52


// Called each time a message is logged.
static void sqliteLogCallback(void* data, int iErrCode, const char* zMsg) {
    bool verboseLog = !!data;
    if (iErrCode == 0 || iErrCode == SQLITE_CONSTRAINT || iErrCode == SQLITE_SCHEMA) {
        if (verboseLog) {
            ALOG(LOG_VERBOSE, SQLITE_LOG_TAG, "(%d) %s\n", iErrCode, zMsg);
        }
    } else {
        ALOG(LOG_ERROR, SQLITE_LOG_TAG, "(%d) %s\n", iErrCode, zMsg);
    }
}

// Sets the global SQLite configuration.
Changes to res/layout/main.xml.
1





2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>





<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >

<TextView

>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
  xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
      android:layout_height="fill_parent">

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >

<TextView
23
24
25
26
27
28
29

30
    android:id="@+id/tv_widget"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="&lt;this text should be replaced by the test output&gt;"
    android:typeface="monospace"
    />
</LinearLayout>









>

28
29
30
31
32
33
34
35
36
    android:id="@+id/tv_widget"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="&lt;this text should be replaced by the test output&gt;"
    android:typeface="monospace"
    />
</LinearLayout>
</ScrollView>

Changes to src/org/sqlite/app/customsqlite/CustomSqlite.java.
38
39
40
41
42
43
44




45
46
47
48
49
50
51

    db = SQLiteDatabase.openOrCreateDatabase(":memory:", null);
    st = db.compileStatement("SELECT sqlite_version()");
    res = st.simpleQueryForString();

    myTV.append("SQLite version " + res + "\n\n");
  }





  public void test_result(String name, String res, String expected){
    myTV.append(name + "... ");
    myNTest++;

    if( res.equals(expected) ){
      myTV.append("ok\n");







>
>
>
>







38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

    db = SQLiteDatabase.openOrCreateDatabase(":memory:", null);
    st = db.compileStatement("SELECT sqlite_version()");
    res = st.simpleQueryForString();

    myTV.append("SQLite version " + res + "\n\n");
  }

  public void test_warning(String name, String warning){
    myTV.append("WARNING:" + name + ": " + warning + "\n");
  }

  public void test_result(String name, String res, String expected){
    myTV.append(name + "... ");
    myNTest++;

    if( res.equals(expected) ){
      myTV.append("ok\n");
70
71
72
73
74
75
76


77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

96
97
98
99
100
    Cursor c = db.rawQuery("SELECT x FROM t1", null);
    if( c!=null ){
      boolean bRes;
      for(bRes=c.moveToFirst(); bRes; bRes=c.moveToNext()){
        String x = c.getString(0);
        res = res + "." + x;
      }


    }

    test_result("csr_test_1", res, ".one.two.three");
  }

  public void run_the_tests(View view){
    System.loadLibrary("sqliteX");

    myTV.setText("");
    myNErr = 0;
    myNTest = 0;

    try {
      report_version();
      csr_test_1();

      myTV.append("\n" + myNErr + " errors from " + myNTest + " tests\n");
    } catch(Exception e) {
      myTV.append("Exception: " + e.toString());

    }
  }
}









>
>


















|
>





74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
    Cursor c = db.rawQuery("SELECT x FROM t1", null);
    if( c!=null ){
      boolean bRes;
      for(bRes=c.moveToFirst(); bRes; bRes=c.moveToNext()){
        String x = c.getString(0);
        res = res + "." + x;
      }
    }else{
      test_warning("csr_test_1", "c==NULL");
    }

    test_result("csr_test_1", res, ".one.two.three");
  }

  public void run_the_tests(View view){
    System.loadLibrary("sqliteX");

    myTV.setText("");
    myNErr = 0;
    myNTest = 0;

    try {
      report_version();
      csr_test_1();

      myTV.append("\n" + myNErr + " errors from " + myNTest + " tests\n");
    } catch(Exception e) {
      myTV.append("Exception: " + e.toString() + "\n");
      myTV.append(android.util.Log.getStackTraceString(e) + "\n");
    }
  }
}


Changes to src/org/sqlite/database/sqlite/ExtraUtils.java.
82
83
84
85
86
87
88


























89
        for (int i = 0; i < length; i++) {
            if (columnNames[i].equals("_id")) {
                return i;
            }
        }
        return -1;
    }


























}







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
        for (int i = 0; i < length; i++) {
            if (columnNames[i].equals("_id")) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 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);
    }
}
Changes to src/org/sqlite/database/sqlite/SQLiteConnection.java.
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
    private static native String nativeExecuteForString(int connectionPtr, int statementPtr);
    private static native int nativeExecuteForBlobFileDescriptor(
            int connectionPtr, int statementPtr);
    private static native int nativeExecuteForChangedRowCount(int connectionPtr, int statementPtr);
    private static native long nativeExecuteForLastInsertedRowId(
            int connectionPtr, int statementPtr);
    private static native long nativeExecuteForCursorWindow(
            int connectionPtr, int statementPtr, int windowPtr,
            int startPos, int requiredPos, boolean countAllRows);
    private static native int nativeGetDbLookaside(int connectionPtr);
    private static native void nativeCancel(int connectionPtr);
    private static native void nativeResetCancel(int connectionPtr, boolean cancelable);

    private SQLiteConnection(SQLiteConnectionPool pool,
            SQLiteDatabaseConfiguration configuration,







|







147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
    private static native String nativeExecuteForString(int connectionPtr, int statementPtr);
    private static native int nativeExecuteForBlobFileDescriptor(
            int connectionPtr, int statementPtr);
    private static native int nativeExecuteForChangedRowCount(int connectionPtr, int statementPtr);
    private static native long nativeExecuteForLastInsertedRowId(
            int connectionPtr, int statementPtr);
    private static native long nativeExecuteForCursorWindow(
            int connectionPtr, int statementPtr, CursorWindow win,
            int startPos, int requiredPos, boolean countAllRows);
    private static native int nativeGetDbLookaside(int connectionPtr);
    private static native void nativeCancel(int connectionPtr);
    private static native void nativeResetCancel(int connectionPtr, boolean cancelable);

    private SQLiteConnection(SQLiteConnectionPool pool,
            SQLiteDatabaseConfiguration configuration,
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
     * @throws SQLiteException if an error occurs, such as a syntax error
     * or invalid number of bind arguments.
     * @throws OperationCanceledException if the operation was canceled.
     */
    public int executeForCursorWindow(String sql, Object[] bindArgs,
            CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
            CancellationSignal cancellationSignal) {
      /*
        if (sql == null) {
            throw new IllegalArgumentException("sql must not be null.");
        }
        if (window == null) {
            throw new IllegalArgumentException("window must not be null.");
        }








<







816
817
818
819
820
821
822

823
824
825
826
827
828
829
     * @throws SQLiteException if an error occurs, such as a syntax error
     * or invalid number of bind arguments.
     * @throws OperationCanceledException if the operation was canceled.
     */
    public int executeForCursorWindow(String sql, Object[] bindArgs,
            CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
            CancellationSignal cancellationSignal) {

        if (sql == null) {
            throw new IllegalArgumentException("sql must not be null.");
        }
        if (window == null) {
            throw new IllegalArgumentException("window must not be null.");
        }

840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
                try {
                    throwIfStatementForbidden(statement);
                    bindArguments(statement, bindArgs);
                    applyBlockGuardPolicy(statement);
                    attachCancellationSignal(cancellationSignal);
                    try {
                        final long result = nativeExecuteForCursorWindow(
                                mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
                                startPos, requiredPos, countAllRows);
                        actualPos = (int)(result >> 32);
                        countedRows = (int)result;
                        filledRows = window.getNumRows();
                        window.setStartPosition(actualPos);
                        return countedRows;
                    } finally {







|







839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
                try {
                    throwIfStatementForbidden(statement);
                    bindArguments(statement, bindArgs);
                    applyBlockGuardPolicy(statement);
                    attachCancellationSignal(cancellationSignal);
                    try {
                        final long result = nativeExecuteForCursorWindow(
                                mConnectionPtr, statement.mStatementPtr, window,
                                startPos, requiredPos, countAllRows);
                        actualPos = (int)(result >> 32);
                        countedRows = (int)result;
                        filledRows = window.getNumRows();
                        window.setStartPosition(actualPos);
                        return countedRows;
                    } finally {
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
                            + ", filledRows=" + filledRows
                            + ", countedRows=" + countedRows);
                }
            }
        } finally {
            window.releaseReference();
        }
   */
      return -1;
    }

    private PreparedStatement acquirePreparedStatement(String sql) {
        PreparedStatement statement = mPreparedStatementCache.get(sql);
        boolean skipCache = false;
        if (statement != null) {
            if (!statement.mInUse) {







<
<







867
868
869
870
871
872
873


874
875
876
877
878
879
880
                            + ", filledRows=" + filledRows
                            + ", countedRows=" + countedRows);
                }
            }
        } finally {
            window.releaseReference();
        }


    }

    private PreparedStatement acquirePreparedStatement(String sql) {
        PreparedStatement statement = mPreparedStatementCache.get(sql);
        boolean skipCache = false;
        if (statement != null) {
            if (!statement.mInUse) {
Changes to src/org/sqlite/database/sqlite/SQLiteCursor.java.
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package org.sqlite.database.sqlite;

import org.sqlite.database.ExtraUtils;

import android.database.AbstractWindowedCursor;
import android.database.CursorWindow;

import android.database.DatabaseUtils;
import android.os.StrictMode;
import android.util.Log;

import java.util.HashMap;
import java.util.Map;

/**







<







17
18
19
20
21
22
23

24
25
26
27
28
29
30
package org.sqlite.database.sqlite;

import org.sqlite.database.ExtraUtils;

import android.database.AbstractWindowedCursor;
import android.database.CursorWindow;


import android.os.StrictMode;
import android.util.Log;

import java.util.HashMap;
import java.util.Map;

/**
133
134
135
136
137
138
139
140
141
142



















143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
    @Override
    public int getCount() {
        if (mCount == NO_COUNT) {
            fillWindow(0);
        }
        return mCount;
    }

    private void fillWindow(int requiredPos) {
      /*



















        clearOrCreateWindow(getDatabase().getPath());

        try {
            if (mCount == NO_COUNT) {
                int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
                mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
                mCursorWindowCapacity = mWindow.getNumRows();
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
                }
            } else {
                int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
                        mCursorWindowCapacity);
                mQuery.fillWindow(mWindow, startPos, requiredPos, false);
            }
        } catch (RuntimeException ex) {
            // Close the cursor window if the query failed and therefore will
            // not produce any results.  This helps to avoid accidentally leaking
            // the cursor window if the client does not correctly handle exceptions
            // and fails to close the cursor.
            closeWindow();
            throw ex;
        }
        */
    }

    @Override
    public int getColumnIndex(String columnName) {
        // Create mColumnNameMap on demand
        if (mColumnNameMap == null) {
            String[] columns = mColumns;








<
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|



|






|








|


<







132
133
134
135
136
137
138
139

140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182

183
184
185
186
187
188
189
    @Override
    public int getCount() {
        if (mCount == NO_COUNT) {
            fillWindow(0);
        }
        return mCount;
    }


    /* 
    ** The AbstractWindowClass contains protected methods clearOrCreateWindow() and
    ** closeWindow(), which are used by the android.database.sqlite.* version of this
    ** class. But, since they are marked with "@hide", the following replacement 
    ** versions are required.
    */
    private void awc_clearOrCreateWindow(String name){
      CursorWindow win = getWindow();
      if( win==null ){
        win = new CursorWindow(name);
        setWindow(win);
      }else{
        win.clear();
      }
    }
    private void awc_closeWindow(){
      setWindow(null);
    }

    private void fillWindow(int requiredPos) {
        awc_clearOrCreateWindow(getDatabase().getPath());

        try {
            if (mCount == NO_COUNT) {
                int startPos = ExtraUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
                mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
                mCursorWindowCapacity = mWindow.getNumRows();
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
                }
            } else {
                int startPos = ExtraUtils.cursorPickFillWindowStartPosition(requiredPos,
                        mCursorWindowCapacity);
                mQuery.fillWindow(mWindow, startPos, requiredPos, false);
            }
        } catch (RuntimeException ex) {
            // Close the cursor window if the query failed and therefore will
            // not produce any results.  This helps to avoid accidentally leaking
            // the cursor window if the client does not correctly handle exceptions
            // and fails to close the cursor.
            awc_closeWindow();
            throw ex;
        }

    }

    @Override
    public int getColumnIndex(String columnName) {
        // Create mColumnNameMap on demand
        if (mColumnNameMap == null) {
            String[] columns = mColumns;