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
Side-by-Side 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
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
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 CopyRowResult {
enum CWMethodNames {
    CPR_OK,
    CPR_FULL,
    CPR_ERROR,
  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
};

/*
static jlong nativeExecuteForCursorWindow(
** An instance of this structure represents a single CursorWindow java method.
  JNIEnv* env, jclass clazz,
  jint connectionPtr, 
  jint statementPtr, 
  jint windowPtr,
  jint startPos, 
  jint requiredPos, 
  jboolean countAllRows
) {
  jniThrowIOException(env, -1);
  return -1;
}
*/
struct CWMethod {
  jmethodID id;                   /* Method id */
  const char *zName;              /* Method name */
  const char *zSig;               /* Method JNI signature */
};

#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);
/*
** Append the contents of the row that SQL statement pStmt currently points to
        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);
** 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);
        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
  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;
      }

            // one to make sure we store the terminator.
            size_t sizeIncludingNull = sqlite3_column_bytes(statement, i) + 1;
      case SQLITE_INTEGER: {
        jlong val = sqlite3_column_int64(pStmt, i);
            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",
        bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTLONG].id, val, iRow, i);
        break;
      }

                    startPos + addedRows, i, sizeIncludingNull);
        } else if (type == SQLITE_INTEGER) {
      case SQLITE_FLOAT: {
            // INTEGER data
            int64_t value = sqlite3_column_int64(statement, i);
        jdouble val = sqlite3_column_double(pStmt, 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) {
        bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTDOUBLE].id, val, iRow, i);
        break;
      }

      case SQLITE_TEXT: {
            // FLOAT data
            double value = sqlite3_column_double(statement, i);
        const char *zVal = (const char*)sqlite3_column_text(pStmt, 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);
        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);
                result = CPR_FULL;
                break;
            }
            LOG_WINDOW("%d,%d is Blob with %u bytes",
        break;
      }
    }

                    startPos + addedRows, i, size);
        } else if (type == SQLITE_NULL) {
            // NULL field
            status = window->putNull(addedRows, i);
            if (status) {
    if( bOk==0 ){
                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();
      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.
    }
    return result;
**
** 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:
}

static jlong nativeExecuteForCursorWindow(JNIEnv* env, jclass clazz,
        jint connectionPtr, jint statementPtr, jint windowPtr,
**
**      (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 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);

  jint statementPtr,              /* Pointer to sqlite3_stmt object */
  jobject win,                    /* The CursorWindow object to populate */
  jint startPos,                  /* First row to add (advisory) */
    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);
  jint iRowRequired,              /* Required row */
  jboolean countAllRows
    status = window->setNumColumns(numColumns);
    if (status) {
        char *zMsg = sqlite3_mprintf(
            "Failed to set the cursor window column count to %d, status=%d",
            numColumns, status
) {
  SQLiteConnection *pConnection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
  sqlite3_stmt *pStmt = reinterpret_cast<sqlite3_stmt*>(statementPtr);

  CWMethod aMethod[] = {
    {0, "clear",         "()V"},
    {0, "setNumColumns", "(I)Z"},
        );
        throw_sqlite3_exception(env, connection->db, zMsg);
        sqlite3_free(zMsg);
    {0, "allocRow",      "()Z"},
    {0, "freeLastRow",   "()V"},
        return 0;
    }

    int retryCount = 0;
    int totalRows = 0;
    {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;
    int addedRows = 0;
    bool windowFull = false;
  jboolean bOk;
    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;

  int iStart;                     /* First row copied to CursorWindow */
            // 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.

  /* 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
                // 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;
      return 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;
        }
  /* Set the number of columns in the window */
  bOk = setWindowNumColumns(pEnv, win, pStmt, aMethod);
  if( bOk==0 ) return 0;

    }

  nRow = 0;
    LOG_WINDOW("Resetting statement %p after fetching %d rows and adding %d rows"
            "to the window in %d bytes",
  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
            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
        ** 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
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", "(IIIIIZ)J",
    { "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
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);
            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
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
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
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
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());
      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
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
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 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
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
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.mWindowPtr,
                                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
867
868
869
870
871
872
873


874
875
876
877
878
879
880







-
-







                            + ", 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) {
Changes to src/org/sqlite/database/sqlite/SQLiteCursor.java.
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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.database.DatabaseUtils;
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
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;
    }

    private void fillWindow(int requiredPos) {
      /*
        clearOrCreateWindow(getDatabase().getPath());
    /* 
    ** 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 = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
                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 = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
                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.
            closeWindow();
            awc_closeWindow();
            throw ex;
        }
        */
    }

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