CD to `c:\Tcl\lib`. In that subfolder make a copy of the
+ "`tcl86t.lib`" file to the alternative name "`tcl86.lib`"
+ (omitting the second 't'). Leave the copy in the same directory
+ as the original.
+
CD to `c:\Tcl\bin`. Make a copy of the "`tclsh86t.exe`"
+ file into "`tclsh.exe`" (without the "86t") in the same directory.
+
Add `c:\Tcl\bin` to your %PATH%. To do this, go to Settings
+ and search for "path". Select "edit environment variables for
+ your account" and modify your default PATH accordingly.
+ You will need to close and reopen your command prompts after
+ making this change.
+
+
+ 4. Download the SQLite source tree and unpack it. CD into the
+ toplevel directory of the source tree.
+
+ 5. Set the TCLDIR environment variable to point to your TCL installation.
+ Like this:
+
+
`set TCLDIR=c:\Tcl`
+
+
+ 6. Run the "`Makefile.msc`" makefile with an appropriate target.
+ Examples:
+
+
`nmake /f makefile.msc`
+
`nmake /f makefile.msc sqlite3.c`
+
`nmake /f makefile.msc devtest`
+
`nmake /f makefile.msc releasetest`
+
+
+## 32-bit Builds
+
+Doing a 32-bit build is just like doing a 64-bit build with the
+following minor changes:
+
+ 1. Use the "x86 Native Tools Command Prompt" instead of
+ "x64 Native Tools Command Prompt". "**x86**" instead of "**x64**".
+
+ 2. Use a different installation directory for TCL.
+ The recommended directory is `c:\tcl32`. Thus you end up
+ with two TCL builds:
+
+
`c:\tcl` ← 64-bit (the default)
+
`c:\tcl32` ← 32-bit
+
+
+ 3. Ensure that `c:\tcl32\bin` comes before `c:\tcl\bin` on
+ your PATH environment variable. You can achieve this using
+ a command like:
+
+
`set PATH=c:\tcl32\bin;%PATH%`
+
+
+## Statically Linking The TCL Library
+
+Some utility programs associated with SQLite need to be linked
+with TCL in order to function. The [sqlite3_analyzer.exe program](https://sqlite.org/sqlanalyze.html)
+is an example. You can build as described above, and then
+enter:
+
+> ~~~~
+nmake /f Makefile.msc sqlite3_analyzer.exe
+~~~~
+
+And you will end up with a working executable. However, that executable
+will depend on having the "tcl86.dll" library somewhere on your %PATH%.
+Use the following steps to build an executable that has the TCL library
+statically linked so that it does not depend on separate DLL:
+
+ 1. Use the appropriate "Command Prompt" window - either x86 or
+ x64, depending on whether you want a 32-bit or 64-bit executable.
+
+ 2. Untar the TCL source tarball into a fresh directory. CD into
+ the "win/" subfolder.
+
+ 3. Run: `nmake /f makefile.vc OPTS=nothreads,static shell`
+
+
+ 4. CD into the "Release*" subfolder that is created (note the
+ wildcard - the full name of the directory might vary). There
+ you will find the "tcl86s.lib" file. Copy this file into the
+ same directory that you put the "tcl86.lib" on your initial
+ installation. (In this document, that directory is
+ "`C:\Tcl32\lib`" for 32-bit builds and
+ "`C:\Tcl\lib`" for 64-bit builds.)
+
+ 5. CD into your SQLite source code directory and build the desired
+ utility program, but add the following extra arguments to the
+ nmake command line:
+
Lemon was originally written by Richard Hipp sometime in the late
1980s on a Sun4 Workstation using K&R C.
-There was a companion LL(1) parser generator program named "Lime", the
-source code to which as been lost.
+There was a companion LL(1) parser generator program named "Lime".
+The Lime source code has been lost.
The lemon.c source file was originally many separate files that were
compiled together to generate the "lemon" executable. Sometime in the
diff -Nru sqlite3-3.42.0/doc/testrunner.md sqlite3-3.44.0-0/doc/testrunner.md
--- sqlite3-3.42.0/doc/testrunner.md 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/doc/testrunner.md 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,284 @@
+
+
+# The testrunner.tcl Script
+
+# 1. Overview
+
+testrunner.tcl is a Tcl script used to run multiple SQLite tests using
+multiple jobs. It supports the following types of tests:
+
+ * Tcl test scripts.
+
+ * Tests run with [make] commands. Specifically, at time of writing,
+ [make fuzztest], [make mptest], [make sourcetest] and [make threadtest].
+
+testrunner.tcl pipes the output of all tests and builds run into log file
+**testrunner.log**, created in the cwd directory. Searching this file for
+"failed" is a good way to find the output of a failed test.
+
+testrunner.tcl also populates SQLite database **testrunner.db**. This database
+contains details of all tests run, running and to be run. A useful query
+might be:
+
+```
+ SELECT * FROM script WHERE state='failed'
+```
+
+Running the command:
+
+```
+ ./testfixture $(TESTDIR)/testrunner.tcl status
+```
+
+in the directory containing the testrunner.db database runs various queries
+to produce a succinct report on the state of a running testrunner.tcl script.
+Running:
+
+```
+ watch ./testfixture $(TESTDIR)/testrunner.tcl status
+```
+
+in another terminal is a good way to keep an eye on a long running test.
+
+Sometimes testrunner.tcl uses the [testfixture] binary that it is run with
+to run tests (see "Binary Tests" below). Sometimes it builds testfixture and
+other binaries in specific configurations to test (see "Source Tests").
+
+# 2. Binary Tests
+
+The commands described in this section all run various combinations of the Tcl
+test scripts using the [testfixture] binary used to run the testrunner.tcl
+script (i.e. they do not invoke the compiler to build new binaries, or the
+[make] command to run tests that are not Tcl scripts). The procedure to run
+these tests is therefore:
+
+ 1. Build the "testfixture" (or "testfixture.exe" for windows) binary using
+ whatever method seems convenient.
+
+ 2. Test the binary built in step 1 by running testrunner.tcl with it,
+ perhaps with various options.
+
+The following sub-sections describe the various options that can be
+passed to testrunner.tcl to test binary testfixture builds.
+
+## 2.1. Organization of Tcl Tests
+
+Tcl tests are stored in files that match the pattern *\*.test*. They are
+found in both the $TOP/test/ directory, and in the various sub-directories
+of the $TOP/ext/ directory of the source tree. Not all *\*.test* files
+contain Tcl tests - a handful are Tcl scripts designed to invoke other
+*\*.test* files.
+
+The **veryquick** set of tests is a subset of all Tcl test scripts in the
+source tree. In includes most tests, but excludes some that are very slow.
+Almost all fault-injection tests (those that test the response of the library
+to OOM or IO errors) are excluded. It is defined in source file
+*test/permutations.test*.
+
+The **full** set of tests includes all Tcl test scripts in the source tree.
+To run a "full" test is to run all Tcl test scripts that can be found in the
+source tree.
+
+File *permutations.test* defines various test "permutations". A permutation
+consists of:
+
+ * A subset of Tcl test scripts, and
+
+ * Runtime configuration to apply before running each test script
+ (e.g. enabling auto-vacuum, or disable lookaside).
+
+Running **all** tests is to run all tests in the full test set, plus a dozen
+or so permutations. The specific permutations that are run as part of "all"
+are defined in file *testrunner_data.tcl*.
+
+## 2.2. Commands to Run Tests
+
+To run the "veryquick" test set, use either of the following:
+
+```
+ ./testfixture $TESTDIR/testrunner.tcl
+ ./testfixture $TESTDIR/testrunner.tcl veryquick
+```
+
+To run the "full" test suite:
+
+```
+ ./testfixture $TESTDIR/testrunner.tcl full
+```
+
+To run the subset of the "full" test suite for which the test file name matches
+a specified pattern (e.g. all tests that start with "fts5"), either of:
+
+```
+ ./testfixture $TESTDIR/testrunner.tcl fts5%
+ ./testfixture $TESTDIR/testrunner.tcl 'fts5*'
+```
+
+To run "all" tests (full + permutations):
+
+```
+ ./testfixture $TESTDIR/testrunner.tcl all
+```
+
+
+## 2.3. Investigating Binary Test Failures
+
+If a test fails, testrunner.tcl reports name of the Tcl test script and, if
+applicable, the name of the permutation, to stdout. This information can also
+be retrieved from either *testrunner.log* or *testrunner.db*.
+
+If there is no permutation, the individual test script may be run with:
+
+```
+ ./testfixture $PATH_TO_SCRIPT
+```
+
+Or, if the failure occured as part of a permutation:
+
+```
+ ./testfixture $TESTDIR/testrunner.tcl $PERMUTATION $PATH_TO_SCRIPT
+```
+
+TODO: An example instead of "$PERMUTATION" and $PATH\_TO\_SCRIPT?
+
+# 3. Source Code Tests
+
+The commands described in this section invoke the C compiler to build
+binaries from the source tree, then use those binaries to run Tcl and
+other tests. The advantages of this are that:
+
+ * it is possible to test multiple build configurations with a single
+ command, and
+
+ * it ensures that tests are always run using binaries created with the
+ same set of compiler options.
+
+The testrunner.tcl commands described in this section may be run using
+either a *testfixture* (or testfixture.exe) build, or with any other Tcl
+shell that supports SQLite 3.31.1 or newer via "package require sqlite3".
+
+TODO: ./configure + Makefile.msc build systems.
+
+## Commands to Run SQLite Tests
+
+The **mdevtest** command is equivalent to running the veryquick tests and
+the [make fuzztest] target once for each of two --enable-all builds - one
+with debugging enabled and one without:
+
+```
+ tclsh $TESTDIR/testrunner.tcl mdevtest
+```
+
+In other words, it is equivalent to running:
+
+```
+ $TOP/configure --enable-all --enable-debug
+ make fuzztest
+ make testfixture
+ ./testfixture $TOP/test/testrunner.tcl veryquick
+
+ # Then, after removing files created by the tests above:
+ $TOP/configure --enable-all OPTS="-O0"
+ make fuzztest
+ make testfixture
+ ./testfixture $TOP/test/testrunner.tcl veryquick
+```
+
+The **sdevtest** command is identical to the mdevtest command, except that the
+second of the two builds is a sanitizer build. Specifically, this means that
+OPTS="-fsanitize=address,undefined" is specified instead of OPTS="-O0":
+
+```
+ tclsh $TESTDIR/testrunner.tcl sdevtest
+```
+
+The **release** command runs lots of tests under lots of builds. It runs
+different combinations of builds and tests depending on whether it is run
+on Linux, Windows or OSX. Refer to *testrunner\_data.tcl* for the details
+of the specific tests run.
+
+```
+ tclsh $TESTDIR/testrunner.tcl release
+```
+
+## Running ZipVFS Tests
+
+testrunner.tcl can build a zipvfs-enabled testfixture and use it to run
+tests from the Zipvfs project with the following command:
+
+```
+ tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS
+```
+
+This can be combined with any of "mdevtest", "sdevtest" or "release" to
+test both SQLite and Zipvfs with a single command:
+
+```
+ tclsh $TESTDIR/testrunner.tcl --zipvfs $PATH_TO_ZIPVFS mdevtest
+```
+
+## Investigating Source Code Test Failures
+
+Investigating a test failure that occurs during source code testing is a
+two step process:
+
+ 1. Recreating the build configuration in which the test failed, and
+
+ 2. Re-running the actual test.
+
+To recreate a build configuration, use the testrunner.tcl **script** command
+to create a build script. A build script is a bash script on Linux or OSX, or
+a dos \*.bat file on windows. For example:
+
+```
+ # Create a script that recreates build configuration "Device-One" on
+ # Linux or OSX:
+ tclsh $TESTDIR/testrunner.tcl script Device-One > make.sh
+
+ # Create a script that recreates build configuration "Have-Not" on Windows:
+ tclsh $TESTDIR/testrunner.tcl script Have-Not > make.bat
+```
+
+The generated bash or \*.bat file script accepts a single argument - a makefile
+target to build. This may be used either to run a [make] command test directly,
+or else to build a testfixture (or testfixture.exe) binary with which to
+run a Tcl test script, as described above.
+
+
+
+# 4. Controlling CPU Core Utilization
+
+When running either binary or source code tests, testrunner.tcl reports the
+number of jobs it intends to use to stdout. e.g.
+
+```
+ $ ./testfixture $TESTDIR/testrunner.tcl
+ splitting work across 16 jobs
+ ... more output ...
+```
+
+By default, testfixture.tcl attempts to set the number of jobs to the number
+of real cores on the machine. This can be overridden using the "--jobs" (or -j)
+switch:
+
+```
+ $ ./testfixture $TESTDIR/testrunner.tcl --jobs 8
+ splitting work across 8 jobs
+ ... more output ...
+```
+
+The number of jobs may also be changed while an instance of testrunner.tcl is
+running by exucuting the following command from the directory containing the
+testrunner.log and testrunner.db files:
+
+```
+ $ ./testfixture $TESTDIR/testrunner.tcl njob $NEW_NUMBER_OF_JOBS
+```
+
+
+
+
+
+
+
+
diff -Nru sqlite3-3.42.0/ext/expert/expert1.test sqlite3-3.44.0-0/ext/expert/expert1.test
--- sqlite3-3.42.0/ext/expert/expert1.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/expert/expert1.test 2023-11-04 14:23:58.000000000 +0000
@@ -464,4 +464,23 @@
t2 t2_idx_0001295b {100 20 5}
}
+if 0 {
+do_test expert1-6.0 {
+ catchcmd :memory: {
+.expert
+select base64('');
+.expert
+select name from pragma_collation_list order by name collate uint;
+}
+} {0 {(no new indexes)
+
+SCAN CONSTANT ROW
+
+(no new indexes)
+
+SCAN pragma_collation_list VIRTUAL TABLE INDEX 0:
+USE TEMP B-TREE FOR ORDER BY
+}}
+}
+
finish_test
diff -Nru sqlite3-3.42.0/ext/expert/sqlite3expert.c sqlite3-3.44.0-0/ext/expert/sqlite3expert.c
--- sqlite3-3.42.0/ext/expert/sqlite3expert.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/expert/sqlite3expert.c 2023-11-04 14:23:58.000000000 +0000
@@ -32,7 +32,7 @@
#endif /* !defined(SQLITE_AMALGAMATION) */
-#ifndef SQLITE_OMIT_VIRTUALTABLE
+#ifndef SQLITE_OMIT_VIRTUALTABLE
typedef sqlite3_int64 i64;
typedef sqlite3_uint64 u64;
@@ -662,6 +662,7 @@
0, /* xRelease */
0, /* xRollbackTo */
0, /* xShadowName */
+ 0, /* xIntegrity */
};
return sqlite3_create_module(p->dbv, "expert", &expertModule, (void*)p);
@@ -1819,6 +1820,88 @@
}
/*
+** Define and possibly pretend to use a useless collation sequence.
+** This pretense allows expert to accept SQL using custom collations.
+*/
+int dummyCompare(void *up1, int up2, const void *up3, int up4, const void *up5){
+ (void)up1;
+ (void)up2;
+ (void)up3;
+ (void)up4;
+ (void)up5;
+ assert(0); /* VDBE should never be run. */
+ return 0;
+}
+/* And a callback to register above upon actual need */
+void useDummyCS(void *up1, sqlite3 *db, int etr, const char *zName){
+ (void)up1;
+ sqlite3_create_collation_v2(db, zName, etr, 0, dummyCompare, 0);
+}
+
+#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) \
+ && !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS)
+/*
+** dummy functions for no-op implementation of UDFs during expert's work
+*/
+void dummyUDF(sqlite3_context *up1, int up2, sqlite3_value **up3){
+ (void)up1;
+ (void)up2;
+ (void)up3;
+ assert(0); /* VDBE should never be run. */
+}
+void dummyUDFvalue(sqlite3_context *up1){
+ (void)up1;
+ assert(0); /* VDBE should never be run. */
+}
+
+/*
+** Register UDFs from user database with another.
+*/
+int registerUDFs(sqlite3 *dbSrc, sqlite3 *dbDst){
+ sqlite3_stmt *pStmt;
+ int rc = sqlite3_prepare_v2(dbSrc,
+ "SELECT name,type,enc,narg,flags "
+ "FROM pragma_function_list() "
+ "WHERE builtin==0", -1, &pStmt, 0);
+ if( rc==SQLITE_OK ){
+ while( SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){
+ int nargs = sqlite3_column_int(pStmt,3);
+ int flags = sqlite3_column_int(pStmt,4);
+ const char *name = (char*)sqlite3_column_text(pStmt,0);
+ const char *type = (char*)sqlite3_column_text(pStmt,1);
+ const char *enc = (char*)sqlite3_column_text(pStmt,2);
+ if( name==0 || type==0 || enc==0 ){
+ /* no-op. Only happens on OOM */
+ }else{
+ int ienc = SQLITE_UTF8;
+ int rcf = SQLITE_ERROR;
+ if( strcmp(enc,"utf16le")==0 ) ienc = SQLITE_UTF16LE;
+ else if( strcmp(enc,"utf16be")==0 ) ienc = SQLITE_UTF16BE;
+ ienc |= (flags & (SQLITE_DETERMINISTIC|SQLITE_DIRECTONLY));
+ if( strcmp(type,"w")==0 ){
+ rcf = sqlite3_create_window_function(dbDst,name,nargs,ienc,0,
+ dummyUDF,dummyUDFvalue,0,0,0);
+ }else if( strcmp(type,"a")==0 ){
+ rcf = sqlite3_create_function(dbDst,name,nargs,ienc,0,
+ 0,dummyUDF,dummyUDFvalue);
+ }else if( strcmp(type,"s")==0 ){
+ rcf = sqlite3_create_function(dbDst,name,nargs,ienc,0,
+ dummyUDF,0,0);
+ }
+ if( rcf!=SQLITE_OK ){
+ rc = rcf;
+ break;
+ }
+ }
+ }
+ sqlite3_finalize(pStmt);
+ if( rc==SQLITE_DONE ) rc = SQLITE_OK;
+ }
+ return rc;
+}
+#endif
+
+/*
** Allocate a new sqlite3expert object.
*/
sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErrmsg){
@@ -1844,7 +1927,21 @@
sqlite3_db_config(pNew->dbm, SQLITE_DBCONFIG_TRIGGER_EQP, 1, (int*)0);
}
}
-
+
+ /* Allow custom collations to be dealt with through prepare. */
+ if( rc==SQLITE_OK ) rc = sqlite3_collation_needed(pNew->dbm,0,useDummyCS);
+ if( rc==SQLITE_OK ) rc = sqlite3_collation_needed(pNew->dbv,0,useDummyCS);
+
+#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) \
+ && !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS)
+ /* Register UDFs from database [db] with [dbm] and [dbv]. */
+ if( rc==SQLITE_OK ){
+ rc = registerUDFs(pNew->db, pNew->dbm);
+ }
+ if( rc==SQLITE_OK ){
+ rc = registerUDFs(pNew->db, pNew->dbv);
+ }
+#endif
/* Copy the entire schema of database [db] into [dbm]. */
if( rc==SQLITE_OK ){
@@ -1920,6 +2017,10 @@
while( rc==SQLITE_OK && zStmt && zStmt[0] ){
sqlite3_stmt *pStmt = 0;
+ /* Ensure that the provided statement compiles against user's DB. */
+ rc = idxPrepareStmt(p->db, &pStmt, pzErr, zStmt);
+ if( rc!=SQLITE_OK ) break;
+ sqlite3_finalize(pStmt);
rc = sqlite3_prepare_v2(p->dbv, zStmt, -1, &pStmt, &zStmt);
if( rc==SQLITE_OK ){
if( pStmt ){
diff -Nru sqlite3-3.42.0/ext/fts3/fts3_aux.c sqlite3-3.44.0-0/ext/fts3/fts3_aux.c
--- sqlite3-3.42.0/ext/fts3/fts3_aux.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts3/fts3_aux.c 2023-11-04 14:23:58.000000000 +0000
@@ -545,7 +545,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
int rc; /* Return code */
diff -Nru sqlite3-3.42.0/ext/fts3/fts3.c sqlite3-3.44.0-0/ext/fts3/fts3.c
--- sqlite3-3.42.0/ext/fts3/fts3.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts3/fts3.c 2023-11-04 14:23:58.000000000 +0000
@@ -640,6 +640,7 @@
zLanguageid = (p->zLanguageid ? p->zLanguageid : "__langid");
sqlite3_vtab_config(p->db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
+ sqlite3_vtab_config(p->db, SQLITE_VTAB_INNOCUOUS);
/* Create a list of user columns for the virtual table */
zCols = sqlite3_mprintf("%Q, ", p->azColumn[0]);
@@ -3889,6 +3890,8 @@
rc = sqlite3Fts3PendingTermsFlush(p);
}
+ p->bIgnoreSavepoint = 1;
+
if( p->zContentTbl==0 ){
fts3DbExec(&rc, db,
"ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';",
@@ -3916,6 +3919,8 @@
"ALTER TABLE %Q.'%q_segdir' RENAME TO '%q_segdir';",
p->zDb, p->zName, zName
);
+
+ p->bIgnoreSavepoint = 0;
return rc;
}
@@ -3926,12 +3931,28 @@
*/
static int fts3SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
int rc = SQLITE_OK;
- UNUSED_PARAMETER(iSavepoint);
- assert( ((Fts3Table *)pVtab)->inTransaction );
- assert( ((Fts3Table *)pVtab)->mxSavepoint <= iSavepoint );
- TESTONLY( ((Fts3Table *)pVtab)->mxSavepoint = iSavepoint );
- if( ((Fts3Table *)pVtab)->bIgnoreSavepoint==0 ){
- rc = fts3SyncMethod(pVtab);
+ Fts3Table *pTab = (Fts3Table*)pVtab;
+ assert( pTab->inTransaction );
+ assert( pTab->mxSavepoint<=iSavepoint );
+ TESTONLY( pTab->mxSavepoint = iSavepoint );
+
+ if( pTab->bIgnoreSavepoint==0 ){
+ if( fts3HashCount(&pTab->aIndex[0].hPending)>0 ){
+ char *zSql = sqlite3_mprintf("INSERT INTO %Q.%Q(%Q) VALUES('flush')",
+ pTab->zDb, pTab->zName, pTab->zName
+ );
+ if( zSql ){
+ pTab->bIgnoreSavepoint = 1;
+ rc = sqlite3_exec(pTab->db, zSql, 0, 0, 0);
+ pTab->bIgnoreSavepoint = 0;
+ sqlite3_free(zSql);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ }
+ if( rc==SQLITE_OK ){
+ pTab->iSavepoint = iSavepoint+1;
+ }
}
return rc;
}
@@ -3942,12 +3963,11 @@
** This is a no-op.
*/
static int fts3ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
- TESTONLY( Fts3Table *p = (Fts3Table*)pVtab );
- UNUSED_PARAMETER(iSavepoint);
- UNUSED_PARAMETER(pVtab);
- assert( p->inTransaction );
- assert( p->mxSavepoint >= iSavepoint );
- TESTONLY( p->mxSavepoint = iSavepoint-1 );
+ Fts3Table *pTab = (Fts3Table*)pVtab;
+ assert( pTab->inTransaction );
+ assert( pTab->mxSavepoint >= iSavepoint );
+ TESTONLY( pTab->mxSavepoint = iSavepoint-1 );
+ pTab->iSavepoint = iSavepoint;
return SQLITE_OK;
}
@@ -3957,11 +3977,13 @@
** Discard the contents of the pending terms table.
*/
static int fts3RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
- Fts3Table *p = (Fts3Table*)pVtab;
+ Fts3Table *pTab = (Fts3Table*)pVtab;
UNUSED_PARAMETER(iSavepoint);
- assert( p->inTransaction );
- TESTONLY( p->mxSavepoint = iSavepoint );
- sqlite3Fts3PendingTermsClear(p);
+ assert( pTab->inTransaction );
+ TESTONLY( pTab->mxSavepoint = iSavepoint );
+ if( (iSavepoint+1)<=pTab->iSavepoint ){
+ sqlite3Fts3PendingTermsClear(pTab);
+ }
return SQLITE_OK;
}
@@ -3980,8 +4002,49 @@
return 0;
}
+/*
+** Implementation of the xIntegrity() method on the FTS3/FTS4 virtual
+** table.
+*/
+static int fts3Integrity(
+ sqlite3_vtab *pVtab, /* The virtual table to be checked */
+ const char *zSchema, /* Name of schema in which pVtab lives */
+ const char *zTabname, /* Name of the pVTab table */
+ int isQuick, /* True if this is a quick_check */
+ char **pzErr /* Write error message here */
+){
+ Fts3Table *p = (Fts3Table*)pVtab;
+ char *zSql;
+ int rc;
+ char *zErr = 0;
+
+ assert( pzErr!=0 );
+ assert( *pzErr==0 );
+ UNUSED_PARAMETER(isQuick);
+ zSql = sqlite3_mprintf(
+ "INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');",
+ zSchema, zTabname, zTabname);
+ if( zSql==0 ){
+ return SQLITE_NOMEM;
+ }
+ rc = sqlite3_exec(p->db, zSql, 0, 0, &zErr);
+ sqlite3_free(zSql);
+ if( (rc&0xff)==SQLITE_CORRUPT ){
+ *pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s",
+ p->bFts4 ? 4 : 3, zSchema, zTabname);
+ }else if( rc!=SQLITE_OK ){
+ *pzErr = sqlite3_mprintf("unable to validate the inverted index for"
+ " FTS%d table %s.%s: %s",
+ p->bFts4 ? 4 : 3, zSchema, zTabname, zErr);
+ }
+ sqlite3_free(zErr);
+ return SQLITE_OK;
+}
+
+
+
static const sqlite3_module fts3Module = {
- /* iVersion */ 3,
+ /* iVersion */ 4,
/* xCreate */ fts3CreateMethod,
/* xConnect */ fts3ConnectMethod,
/* xBestIndex */ fts3BestIndexMethod,
@@ -4005,6 +4068,7 @@
/* xRelease */ fts3ReleaseMethod,
/* xRollbackTo */ fts3RollbackToMethod,
/* xShadowName */ fts3ShadowName,
+ /* xIntegrity */ fts3Integrity,
};
/*
diff -Nru sqlite3-3.42.0/ext/fts3/fts3Int.h sqlite3-3.44.0-0/ext/fts3/fts3Int.h
--- sqlite3-3.42.0/ext/fts3/fts3Int.h 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts3/fts3Int.h 2023-11-04 14:23:58.000000000 +0000
@@ -265,6 +265,7 @@
int nPgsz; /* Page size for host database */
char *zSegmentsTbl; /* Name of %_segments table */
sqlite3_blob *pSegments; /* Blob handle open on %_segments table */
+ int iSavepoint;
/*
** The following array of hash tables is used to buffer pending index
diff -Nru sqlite3-3.42.0/ext/fts3/fts3_term.c sqlite3-3.44.0-0/ext/fts3/fts3_term.c
--- sqlite3-3.42.0/ext/fts3/fts3_term.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts3/fts3_term.c 2023-11-04 14:23:58.000000000 +0000
@@ -362,7 +362,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
int rc; /* Return code */
diff -Nru sqlite3-3.42.0/ext/fts3/fts3_tokenize_vtab.c sqlite3-3.44.0-0/ext/fts3/fts3_tokenize_vtab.c
--- sqlite3-3.42.0/ext/fts3/fts3_tokenize_vtab.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts3/fts3_tokenize_vtab.c 2023-11-04 14:23:58.000000000 +0000
@@ -445,7 +445,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
int rc; /* Return code */
diff -Nru sqlite3-3.42.0/ext/fts3/fts3_write.c sqlite3-3.44.0-0/ext/fts3/fts3_write.c
--- sqlite3-3.42.0/ext/fts3/fts3_write.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts3/fts3_write.c 2023-11-04 14:23:58.000000000 +0000
@@ -3325,7 +3325,6 @@
rc = fts3SegmentMerge(p, p->iPrevLangid, i, FTS3_SEGCURSOR_PENDING);
if( rc==SQLITE_DONE ) rc = SQLITE_OK;
}
- sqlite3Fts3PendingTermsClear(p);
/* Determine the auto-incr-merge setting if unknown. If enabled,
** estimate the number of leaf blocks of content to be written
@@ -3347,6 +3346,10 @@
rc = sqlite3_reset(pStmt);
}
}
+
+ if( rc==SQLITE_OK ){
+ sqlite3Fts3PendingTermsClear(p);
+ }
return rc;
}
@@ -3978,6 +3981,8 @@
blobGrowBuffer(pPrev, nTerm, &rc);
if( rc!=SQLITE_OK ) return rc;
+ assert( pPrev!=0 );
+ assert( pPrev->a!=0 );
nPrefix = fts3PrefixCompress(pPrev->a, pPrev->n, zTerm, nTerm);
nSuffix = nTerm - nPrefix;
@@ -4034,9 +4039,13 @@
nSpace += sqlite3Fts3VarintLen(nDoclist) + nDoclist;
/* If the current block is not empty, and if adding this term/doclist
- ** to the current block would make it larger than Fts3Table.nNodeSize
- ** bytes, write this block out to the database. */
- if( pLeaf->block.n>0 && (pLeaf->block.n + nSpace)>p->nNodeSize ){
+ ** to the current block would make it larger than Fts3Table.nNodeSize bytes,
+ ** and if there is still room for another leaf page, write this block out to
+ ** the database. */
+ if( pLeaf->block.n>0
+ && (pLeaf->block.n + nSpace)>p->nNodeSize
+ && pLeaf->iBlock < (pWriter->iStart + pWriter->nLeafEst)
+ ){
rc = fts3WriteSegment(p, pLeaf->iBlock, pLeaf->block.a, pLeaf->block.n);
pWriter->nWork++;
@@ -4347,6 +4356,7 @@
for(i=nHeight; i>=0 && rc==SQLITE_OK; i--){
NodeReader reader;
+ memset(&reader, 0, sizeof(reader));
pNode = &pWriter->aNodeWriter[i];
if( pNode->block.a){
@@ -4367,7 +4377,7 @@
rc = sqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock,0);
blobGrowBuffer(&pNode->block,
MAX(nBlock, p->nNodeSize)+FTS3_NODE_PADDING, &rc
- );
+ );
if( rc==SQLITE_OK ){
memcpy(pNode->block.a, aBlock, nBlock);
pNode->block.n = nBlock;
@@ -5217,7 +5227,7 @@
int rc;
u64 cksum = 0;
- assert( *pRc==SQLITE_OK );
+ if( *pRc ) return 0;
memset(&filter, 0, sizeof(filter));
memset(&csr, 0, sizeof(csr));
@@ -5432,8 +5442,11 @@
rc = fts3DoIncrmerge(p, &zVal[6]);
}else if( nVal>10 && 0==sqlite3_strnicmp(zVal, "automerge=", 10) ){
rc = fts3DoAutoincrmerge(p, &zVal[10]);
+ }else if( nVal==5 && 0==sqlite3_strnicmp(zVal, "flush", 5) ){
+ rc = sqlite3Fts3PendingTermsFlush(p);
+ }
#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
- }else{
+ else{
int v;
if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){
v = atoi(&zVal[9]);
@@ -5451,8 +5464,8 @@
if( v>=4 && v<=FTS3_MERGE_COUNT && (v&1)==0 ) p->nMergeCount = v;
rc = SQLITE_OK;
}
-#endif
}
+#endif
return rc;
}
diff -Nru sqlite3-3.42.0/ext/fts5/fts5_aux.c sqlite3-3.44.0-0/ext/fts5/fts5_aux.c
--- sqlite3-3.42.0/ext/fts5/fts5_aux.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_aux.c 2023-11-04 14:23:58.000000000 +0000
@@ -110,15 +110,19 @@
*/
typedef struct HighlightContext HighlightContext;
struct HighlightContext {
- CInstIter iter; /* Coalesced Instance Iterator */
- int iPos; /* Current token offset in zIn[] */
+ /* Constant parameters to fts5HighlightCb() */
int iRangeStart; /* First token to include */
int iRangeEnd; /* If non-zero, last token to include */
const char *zOpen; /* Opening highlight */
const char *zClose; /* Closing highlight */
const char *zIn; /* Input text */
int nIn; /* Size of input text in bytes */
- int iOff; /* Current offset within zIn[] */
+
+ /* Variables modified by fts5HighlightCb() */
+ CInstIter iter; /* Coalesced Instance Iterator */
+ int iPos; /* Current token offset in zIn[] */
+ int iOff; /* Have copied up to this offset in zIn[] */
+ int bOpen; /* True if highlight is open */
char *zOut; /* Output value */
};
@@ -151,8 +155,8 @@
int tflags, /* Mask of FTS5_TOKEN_* flags */
const char *pToken, /* Buffer containing token */
int nToken, /* Size of token in bytes */
- int iStartOff, /* Start offset of token */
- int iEndOff /* End offset of token */
+ int iStartOff, /* Start byte offset of token */
+ int iEndOff /* End byte offset of token */
){
HighlightContext *p = (HighlightContext*)pContext;
int rc = SQLITE_OK;
@@ -168,30 +172,47 @@
if( p->iRangeStart && iPos==p->iRangeStart ) p->iOff = iStartOff;
}
- if( iPos==p->iter.iStart ){
+ /* If the parenthesis is open, and this token is not part of the current
+ ** phrase, and the starting byte offset of this token is past the point
+ ** that has currently been copied into the output buffer, close the
+ ** parenthesis. */
+ if( p->bOpen
+ && (iPos<=p->iter.iStart || p->iter.iStart<0)
+ && iStartOff>p->iOff
+ ){
+ fts5HighlightAppend(&rc, p, p->zClose, -1);
+ p->bOpen = 0;
+ }
+
+ /* If this is the start of a new phrase, and the highlight is not open:
+ **
+ ** * copy text from the input up to the start of the phrase, and
+ ** * open the highlight.
+ */
+ if( iPos==p->iter.iStart && p->bOpen==0 ){
fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iStartOff - p->iOff);
fts5HighlightAppend(&rc, p, p->zOpen, -1);
p->iOff = iStartOff;
+ p->bOpen = 1;
}
if( iPos==p->iter.iEnd ){
- if( p->iRangeEnd>=0 && p->iter.iStartiRangeStart ){
+ if( p->bOpen==0 ){
+ assert( p->iRangeEnd>=0 );
fts5HighlightAppend(&rc, p, p->zOpen, -1);
+ p->bOpen = 1;
}
fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
- fts5HighlightAppend(&rc, p, p->zClose, -1);
p->iOff = iEndOff;
+
if( rc==SQLITE_OK ){
rc = fts5CInstIterNext(&p->iter);
}
}
- if( p->iRangeEnd>=0 && iPos==p->iRangeEnd ){
+ if( iPos==p->iRangeEnd ){
fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
p->iOff = iEndOff;
- if( iPos>=p->iter.iStart && iPositer.iEnd ){
- fts5HighlightAppend(&rc, p, p->zClose, -1);
- }
}
return rc;
@@ -232,6 +253,9 @@
if( rc==SQLITE_OK ){
rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb);
}
+ if( ctx.bOpen ){
+ fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1);
+ }
fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff);
if( rc==SQLITE_OK ){
@@ -510,6 +534,9 @@
if( rc==SQLITE_OK ){
rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb);
}
+ if( ctx.bOpen ){
+ fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1);
+ }
if( ctx.iRangeEnd>=(nColSize-1) ){
fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff);
}else{
diff -Nru sqlite3-3.42.0/ext/fts5/fts5_config.c sqlite3-3.44.0-0/ext/fts5/fts5_config.c
--- sqlite3-3.42.0/ext/fts5/fts5_config.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_config.c 2023-11-04 14:23:58.000000000 +0000
@@ -22,6 +22,8 @@
#define FTS5_DEFAULT_CRISISMERGE 16
#define FTS5_DEFAULT_HASHSIZE (1024*1024)
+#define FTS5_DEFAULT_DELETE_AUTOMERGE 10 /* default 10% */
+
/* Maximum allowed page size */
#define FTS5_MAX_PAGE_SIZE (64*1024)
@@ -352,6 +354,16 @@
return rc;
}
+ if( sqlite3_strnicmp("contentless_delete", zCmd, nCmd)==0 ){
+ if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){
+ *pzErr = sqlite3_mprintf("malformed contentless_delete=... directive");
+ rc = SQLITE_ERROR;
+ }else{
+ pConfig->bContentlessDelete = (zArg[0]=='1');
+ }
+ return rc;
+ }
+
if( sqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){
if( pConfig->zContentRowid ){
*pzErr = sqlite3_mprintf("multiple content_rowid=... directives");
@@ -596,6 +608,28 @@
sqlite3_free(zTwo);
}
+ /* We only allow contentless_delete=1 if the table is indeed contentless. */
+ if( rc==SQLITE_OK
+ && pRet->bContentlessDelete
+ && pRet->eContent!=FTS5_CONTENT_NONE
+ ){
+ *pzErr = sqlite3_mprintf(
+ "contentless_delete=1 requires a contentless table"
+ );
+ rc = SQLITE_ERROR;
+ }
+
+ /* We only allow contentless_delete=1 if columnsize=0 is not present.
+ **
+ ** This restriction may be removed at some point.
+ */
+ if( rc==SQLITE_OK && pRet->bContentlessDelete && pRet->bColumnsize==0 ){
+ *pzErr = sqlite3_mprintf(
+ "contentless_delete=1 is incompatible with columnsize=0"
+ );
+ rc = SQLITE_ERROR;
+ }
+
/* If a tokenizer= option was successfully parsed, the tokenizer has
** already been allocated. Otherwise, allocate an instance of the default
** tokenizer (unicode61) now. */
@@ -890,6 +924,18 @@
}
}
+ else if( 0==sqlite3_stricmp(zKey, "deletemerge") ){
+ int nVal = -1;
+ if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
+ nVal = sqlite3_value_int(pVal);
+ }else{
+ *pbBadkey = 1;
+ }
+ if( nVal<0 ) nVal = FTS5_DEFAULT_DELETE_AUTOMERGE;
+ if( nVal>100 ) nVal = 0;
+ pConfig->nDeleteMerge = nVal;
+ }
+
else if( 0==sqlite3_stricmp(zKey, "rank") ){
const char *zIn = (const char*)sqlite3_value_text(pVal);
char *zRank;
@@ -938,6 +984,7 @@
pConfig->nUsermerge = FTS5_DEFAULT_USERMERGE;
pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE;
pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE;
+ pConfig->nDeleteMerge = FTS5_DEFAULT_DELETE_AUTOMERGE;
zSql = sqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName);
if( zSql ){
diff -Nru sqlite3-3.42.0/ext/fts5/fts5_expr.c sqlite3-3.44.0-0/ext/fts5/fts5_expr.c
--- sqlite3-3.42.0/ext/fts5/fts5_expr.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_expr.c 2023-11-04 14:23:58.000000000 +0000
@@ -2477,7 +2477,7 @@
return pRet;
}
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){
sqlite3_int64 nByte = 0;
Fts5ExprTerm *p;
@@ -2583,6 +2583,8 @@
if( zRet==0 ) return 0;
}
+ }else if( pExpr->eType==0 ){
+ zRet = sqlite3_mprintf("{}");
}else{
char const *zOp = 0;
int i;
@@ -2844,14 +2846,14 @@
sqlite3_result_int(pCtx, sqlite3Fts5UnicodeFold(iCode, bRemoveDiacritics));
}
}
-#endif /* ifdef SQLITE_TEST */
+#endif /* if SQLITE_TEST || SQLITE_FTS5_DEBUG */
/*
** This is called during initialization to register the fts5_expr() scalar
** UDF with the SQLite handle passed as the only argument.
*/
int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
struct Fts5ExprFunc {
const char *z;
void (*x)(sqlite3_context*,int,sqlite3_value**);
diff -Nru sqlite3-3.42.0/ext/fts5/fts5.h sqlite3-3.44.0-0/ext/fts5/fts5.h
--- sqlite3-3.42.0/ext/fts5/fts5.h 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5.h 2023-11-04 14:23:58.000000000 +0000
@@ -263,7 +263,7 @@
** See xPhraseFirstColumn above.
*/
struct Fts5ExtensionApi {
- int iVersion; /* Currently always set to 3 */
+ int iVersion; /* Currently always set to 2 */
void *(*xUserData)(Fts5Context*);
@@ -492,8 +492,8 @@
** as separate queries of the FTS index are required for each synonym.
**
** When using methods (2) or (3), it is important that the tokenizer only
-** provide synonyms when tokenizing document text (method (2)) or query
-** text (method (3)), not both. Doing so will not cause any errors, but is
+** provide synonyms when tokenizing document text (method (3)) or query
+** text (method (2)), not both. Doing so will not cause any errors, but is
** inefficient.
*/
typedef struct Fts5Tokenizer Fts5Tokenizer;
@@ -541,7 +541,7 @@
int (*xCreateTokenizer)(
fts5_api *pApi,
const char *zName,
- void *pContext,
+ void *pUserData,
fts5_tokenizer *pTokenizer,
void (*xDestroy)(void*)
);
@@ -550,7 +550,7 @@
int (*xFindTokenizer)(
fts5_api *pApi,
const char *zName,
- void **ppContext,
+ void **ppUserData,
fts5_tokenizer *pTokenizer
);
@@ -558,7 +558,7 @@
int (*xCreateFunction)(
fts5_api *pApi,
const char *zName,
- void *pContext,
+ void *pUserData,
fts5_extension_function xFunction,
void (*xDestroy)(void*)
);
diff -Nru sqlite3-3.42.0/ext/fts5/fts5_hash.c sqlite3-3.44.0-0/ext/fts5/fts5_hash.c
--- sqlite3-3.42.0/ext/fts5/fts5_hash.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_hash.c 2023-11-04 14:23:58.000000000 +0000
@@ -475,7 +475,6 @@
pList = fts5HashEntryMerge(pList, ap[i]);
}
- pHash->nEntry = 0;
sqlite3_free(ap);
*ppSorted = pList;
return SQLITE_OK;
@@ -529,6 +528,28 @@
return fts5HashEntrySort(p, pTerm, nTerm, &p->pScan);
}
+#ifdef SQLITE_DEBUG
+static int fts5HashCount(Fts5Hash *pHash){
+ int nEntry = 0;
+ int ii;
+ for(ii=0; iinSlot; ii++){
+ Fts5HashEntry *p = 0;
+ for(p=pHash->aSlot[ii]; p; p=p->pHashNext){
+ nEntry++;
+ }
+ }
+ return nEntry;
+}
+#endif
+
+/*
+** Return true if the hash table is empty, false otherwise.
+*/
+int sqlite3Fts5HashIsEmpty(Fts5Hash *pHash){
+ assert( pHash->nEntry==fts5HashCount(pHash) );
+ return pHash->nEntry==0;
+}
+
void sqlite3Fts5HashScanNext(Fts5Hash *p){
assert( !sqlite3Fts5HashScanEof(p) );
p->pScan = p->pScan->pScanNext;
diff -Nru sqlite3-3.42.0/ext/fts5/fts5_index.c sqlite3-3.44.0-0/ext/fts5/fts5_index.c
--- sqlite3-3.42.0/ext/fts5/fts5_index.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_index.c 2023-11-04 14:23:58.000000000 +0000
@@ -57,13 +57,31 @@
#define FTS5_MAX_LEVEL 64
/*
+** There are two versions of the format used for the structure record:
+**
+** 1. the legacy format, that may be read by all fts5 versions, and
+**
+** 2. the V2 format, which is used by contentless_delete=1 databases.
+**
+** Both begin with a 4-byte "configuration cookie" value. Then, a legacy
+** format structure record contains a varint - the number of levels in
+** the structure. Whereas a V2 structure record contains the constant
+** 4 bytes [0xff 0x00 0x00 0x01]. This is unambiguous as the value of a
+** varint has to be at least 16256 to begin with "0xFF". And the default
+** maximum number of levels is 64.
+**
+** See below for more on structure record formats.
+*/
+#define FTS5_STRUCTURE_V2 "\xFF\x00\x00\x01"
+
+/*
** Details:
**
** The %_data table managed by this module,
**
** CREATE TABLE %_data(id INTEGER PRIMARY KEY, block BLOB);
**
-** , contains the following 5 types of records. See the comments surrounding
+** , contains the following 6 types of records. See the comments surrounding
** the FTS5_*_ROWID macros below for a description of how %_data rowids are
** assigned to each fo them.
**
@@ -71,13 +89,13 @@
**
** The set of segments that make up an index - the index structure - are
** recorded in a single record within the %_data table. The record consists
-** of a single 32-bit configuration cookie value followed by a list of
-** SQLite varints. If the FTS table features more than one index (because
-** there are one or more prefix indexes), it is guaranteed that all share
-** the same cookie value.
+** of a single 32-bit configuration cookie value followed by a list of
+** SQLite varints.
**
-** Immediately following the configuration cookie, the record begins with
-** three varints:
+** If the structure record is a V2 record, the configuration cookie is
+** followed by the following 4 bytes: [0xFF 0x00 0x00 0x01].
+**
+** Next, the record continues with three varints:
**
** + number of levels,
** + total number of segments on all levels,
@@ -92,6 +110,12 @@
** + first leaf page number (often 1, always greater than 0)
** + final leaf page number
**
+** Then, for V2 structures only:
+**
+** + lower origin counter value,
+** + upper origin counter value,
+** + the number of tombstone hash pages.
+**
** 2. The Averages Record:
**
** A single record within the %_data table. The data is a list of varints.
@@ -207,6 +231,38 @@
** * A list of delta-encoded varints - the first rowid on each subsequent
** child page.
**
+** 6. Tombstone Hash Page
+**
+** These records are only ever present in contentless_delete=1 tables.
+** There are zero or more of these associated with each segment. They
+** are used to store the tombstone rowids for rows contained in the
+** associated segments.
+**
+** The set of nHashPg tombstone hash pages associated with a single
+** segment together form a single hash table containing tombstone rowids.
+** To find the page of the hash on which a key might be stored:
+**
+** iPg = (rowid % nHashPg)
+**
+** Then, within page iPg, which has nSlot slots:
+**
+** iSlot = (rowid / nHashPg) % nSlot
+**
+** Each tombstone hash page begins with an 8 byte header:
+**
+** 1-byte: Key-size (the size in bytes of each slot). Either 4 or 8.
+** 1-byte: rowid-0-tombstone flag. This flag is only valid on the
+** first tombstone hash page for each segment (iPg=0). If set,
+** the hash table contains rowid 0. If clear, it does not.
+** Rowid 0 is handled specially.
+** 2-bytes: unused.
+** 4-bytes: Big-endian integer containing number of entries on page.
+**
+** Following this are nSlot 4 or 8 byte slots (depending on the key-size
+** in the first byte of the page header). The number of slots may be
+** determined based on the size of the page record and the key-size:
+**
+** nSlot = (nByte - 8) / key-size
*/
/*
@@ -240,6 +296,7 @@
#define FTS5_SEGMENT_ROWID(segid, pgno) fts5_dri(segid, 0, 0, pgno)
#define FTS5_DLIDX_ROWID(segid, height, pgno) fts5_dri(segid, 1, height, pgno)
+#define FTS5_TOMBSTONE_ROWID(segid,ipg) fts5_dri(segid+(1<<16), 0, 0, ipg)
#ifdef SQLITE_DEBUG
int sqlite3Fts5Corrupt() { return SQLITE_CORRUPT_VTAB; }
@@ -275,6 +332,12 @@
/*
** One object per %_data table.
+**
+** nContentlessDelete:
+** The number of contentless delete operations since the most recent
+** call to fts5IndexFlush() or fts5IndexDiscardData(). This is tracked
+** so that extra auto-merge work can be done by fts5IndexFlush() to
+** account for the delete operations.
*/
struct Fts5Index {
Fts5Config *pConfig; /* Virtual table configuration */
@@ -289,6 +352,8 @@
int nPendingData; /* Current bytes of pending data */
i64 iWriteRowid; /* Rowid for current doc being written */
int bDelete; /* Current write is a delete */
+ int nContentlessDelete; /* Number of contentless delete ops */
+ int nPendingRow; /* Number of INSERT in hash table */
/* Error state. */
int rc; /* Current error code */
@@ -323,11 +388,23 @@
** The contents of the "structure" record for each index are represented
** using an Fts5Structure record in memory. Which uses instances of the
** other Fts5StructureXXX types as components.
+**
+** nOriginCntr:
+** This value is set to non-zero for structure records created for
+** contentlessdelete=1 tables only. In that case it represents the
+** origin value to apply to the next top-level segment created.
*/
struct Fts5StructureSegment {
int iSegid; /* Segment id */
int pgnoFirst; /* First leaf page number in segment */
int pgnoLast; /* Last leaf page number in segment */
+
+ /* contentlessdelete=1 tables only: */
+ u64 iOrigin1;
+ u64 iOrigin2;
+ int nPgTombstone; /* Number of tombstone hash table pages */
+ u64 nEntryTombstone; /* Number of tombstone entries that "count" */
+ u64 nEntry; /* Number of rows in this segment */
};
struct Fts5StructureLevel {
int nMerge; /* Number of segments in incr-merge */
@@ -337,6 +414,7 @@
struct Fts5Structure {
int nRef; /* Object reference count */
u64 nWriteCounter; /* Total leaves written to level 0 */
+ u64 nOriginCntr; /* Origin value for next top-level segment */
int nSegment; /* Total segments in this structure */
int nLevel; /* Number of levels in this index */
Fts5StructureLevel aLevel[1]; /* Array of nLevel level objects */
@@ -425,6 +503,13 @@
**
** iTermIdx:
** Index of current term on iTermLeafPgno.
+**
+** apTombstone/nTombstone:
+** These are used for contentless_delete=1 tables only. When the cursor
+** is first allocated, the apTombstone[] array is allocated so that it
+** is large enough for all tombstones hash pages associated with the
+** segment. The pages themselves are loaded lazily from the database as
+** they are required.
*/
struct Fts5SegIter {
Fts5StructureSegment *pSeg; /* Segment to iterate through */
@@ -433,6 +518,8 @@
Fts5Data *pLeaf; /* Current leaf data */
Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */
i64 iLeafOffset; /* Byte offset within current leaf */
+ Fts5Data **apTombstone; /* Array of tombstone pages */
+ int nTombstone;
/* Next method */
void (*xNext)(Fts5Index*, Fts5SegIter*, int*);
@@ -563,6 +650,60 @@
}
/*
+** The only argument points to a buffer at least 8 bytes in size. This
+** function interprets the first 8 bytes of the buffer as a 64-bit big-endian
+** unsigned integer and returns the result.
+*/
+static u64 fts5GetU64(u8 *a){
+ return ((u64)a[0] << 56)
+ + ((u64)a[1] << 48)
+ + ((u64)a[2] << 40)
+ + ((u64)a[3] << 32)
+ + ((u64)a[4] << 24)
+ + ((u64)a[5] << 16)
+ + ((u64)a[6] << 8)
+ + ((u64)a[7] << 0);
+}
+
+/*
+** The only argument points to a buffer at least 4 bytes in size. This
+** function interprets the first 4 bytes of the buffer as a 32-bit big-endian
+** unsigned integer and returns the result.
+*/
+static u32 fts5GetU32(const u8 *a){
+ return ((u32)a[0] << 24)
+ + ((u32)a[1] << 16)
+ + ((u32)a[2] << 8)
+ + ((u32)a[3] << 0);
+}
+
+/*
+** Write iVal, formated as a 64-bit big-endian unsigned integer, to the
+** buffer indicated by the first argument.
+*/
+static void fts5PutU64(u8 *a, u64 iVal){
+ a[0] = ((iVal >> 56) & 0xFF);
+ a[1] = ((iVal >> 48) & 0xFF);
+ a[2] = ((iVal >> 40) & 0xFF);
+ a[3] = ((iVal >> 32) & 0xFF);
+ a[4] = ((iVal >> 24) & 0xFF);
+ a[5] = ((iVal >> 16) & 0xFF);
+ a[6] = ((iVal >> 8) & 0xFF);
+ a[7] = ((iVal >> 0) & 0xFF);
+}
+
+/*
+** Write iVal, formated as a 32-bit big-endian unsigned integer, to the
+** buffer indicated by the first argument.
+*/
+static void fts5PutU32(u8 *a, u32 iVal){
+ a[0] = ((iVal >> 24) & 0xFF);
+ a[1] = ((iVal >> 16) & 0xFF);
+ a[2] = ((iVal >> 8) & 0xFF);
+ a[3] = ((iVal >> 0) & 0xFF);
+}
+
+/*
** Allocate and return a buffer at least nByte bytes in size.
**
** If an OOM error is encountered, return NULL and set the error code in
@@ -789,10 +930,17 @@
/*
** Remove all records associated with segment iSegid.
*/
-static void fts5DataRemoveSegment(Fts5Index *p, int iSegid){
+static void fts5DataRemoveSegment(Fts5Index *p, Fts5StructureSegment *pSeg){
+ int iSegid = pSeg->iSegid;
i64 iFirst = FTS5_SEGMENT_ROWID(iSegid, 0);
i64 iLast = FTS5_SEGMENT_ROWID(iSegid+1, 0)-1;
fts5DataDelete(p, iFirst, iLast);
+
+ if( pSeg->nPgTombstone ){
+ i64 iTomb1 = FTS5_TOMBSTONE_ROWID(iSegid, 0);
+ i64 iTomb2 = FTS5_TOMBSTONE_ROWID(iSegid, pSeg->nPgTombstone-1);
+ fts5DataDelete(p, iTomb1, iTomb2);
+ }
if( p->pIdxDeleter==0 ){
Fts5Config *pConfig = p->pConfig;
fts5IndexPrepareStmt(p, &p->pIdxDeleter, sqlite3_mprintf(
@@ -903,11 +1051,19 @@
int nSegment = 0;
sqlite3_int64 nByte; /* Bytes of space to allocate at pRet */
Fts5Structure *pRet = 0; /* Structure object to return */
+ int bStructureV2 = 0; /* True for FTS5_STRUCTURE_V2 */
+ u64 nOriginCntr = 0; /* Largest origin value seen so far */
/* Grab the cookie value */
if( piCookie ) *piCookie = sqlite3Fts5Get32(pData);
i = 4;
+ /* Check if this is a V2 structure record. Set bStructureV2 if it is. */
+ if( 0==memcmp(&pData[i], FTS5_STRUCTURE_V2, 4) ){
+ i += 4;
+ bStructureV2 = 1;
+ }
+
/* Read the total number of levels and segments from the start of the
** structure record. */
i += fts5GetVarint32(&pData[i], nLevel);
@@ -958,6 +1114,14 @@
i += fts5GetVarint32(&pData[i], pSeg->iSegid);
i += fts5GetVarint32(&pData[i], pSeg->pgnoFirst);
i += fts5GetVarint32(&pData[i], pSeg->pgnoLast);
+ if( bStructureV2 ){
+ i += fts5GetVarint(&pData[i], &pSeg->iOrigin1);
+ i += fts5GetVarint(&pData[i], &pSeg->iOrigin2);
+ i += fts5GetVarint32(&pData[i], pSeg->nPgTombstone);
+ i += fts5GetVarint(&pData[i], &pSeg->nEntryTombstone);
+ i += fts5GetVarint(&pData[i], &pSeg->nEntry);
+ nOriginCntr = MAX(nOriginCntr, pSeg->iOrigin2);
+ }
if( pSeg->pgnoLastpgnoFirst ){
rc = FTS5_CORRUPT;
break;
@@ -968,6 +1132,9 @@
}
}
if( nSegment!=0 && rc==SQLITE_OK ) rc = FTS5_CORRUPT;
+ if( bStructureV2 ){
+ pRet->nOriginCntr = nOriginCntr+1;
+ }
if( rc!=SQLITE_OK ){
fts5StructureRelease(pRet);
@@ -1180,6 +1347,7 @@
Fts5Buffer buf; /* Buffer to serialize record into */
int iLvl; /* Used to iterate through levels */
int iCookie; /* Cookie value to store */
+ int nHdr = (pStruct->nOriginCntr>0 ? (4+4+9+9+9) : (4+9+9));
assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) );
memset(&buf, 0, sizeof(Fts5Buffer));
@@ -1188,9 +1356,12 @@
iCookie = p->pConfig->iCookie;
if( iCookie<0 ) iCookie = 0;
- if( 0==sqlite3Fts5BufferSize(&p->rc, &buf, 4+9+9+9) ){
+ if( 0==sqlite3Fts5BufferSize(&p->rc, &buf, nHdr) ){
sqlite3Fts5Put32(buf.p, iCookie);
buf.n = 4;
+ if( pStruct->nOriginCntr>0 ){
+ fts5BufferSafeAppendBlob(&buf, FTS5_STRUCTURE_V2, 4);
+ }
fts5BufferSafeAppendVarint(&buf, pStruct->nLevel);
fts5BufferSafeAppendVarint(&buf, pStruct->nSegment);
fts5BufferSafeAppendVarint(&buf, (i64)pStruct->nWriteCounter);
@@ -1204,9 +1375,17 @@
assert( pLvl->nMerge<=pLvl->nSeg );
for(iSeg=0; iSegnSeg; iSeg++){
- fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].iSegid);
- fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoFirst);
- fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoLast);
+ Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->iSegid);
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoFirst);
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoLast);
+ if( pStruct->nOriginCntr>0 ){
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin1);
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin2);
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->nPgTombstone);
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntryTombstone);
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntry);
+ }
}
}
@@ -1730,6 +1909,23 @@
}
/*
+** Allocate a tombstone hash page array (pIter->apTombstone) for the
+** iterator passed as the second argument. If an OOM error occurs, leave
+** an error in the Fts5Index object.
+*/
+static void fts5SegIterAllocTombstone(Fts5Index *p, Fts5SegIter *pIter){
+ const int nTomb = pIter->pSeg->nPgTombstone;
+ if( nTomb>0 ){
+ Fts5Data **apTomb = 0;
+ apTomb = (Fts5Data**)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data)*nTomb);
+ if( apTomb ){
+ pIter->apTombstone = apTomb;
+ pIter->nTombstone = nTomb;
+ }
+ }
+}
+
+/*
** Initialize the iterator object pIter to iterate through the entries in
** segment pSeg. The iterator is left pointing to the first entry when
** this function returns.
@@ -1770,6 +1966,7 @@
pIter->iPgidxOff = pIter->pLeaf->szLeaf+1;
fts5SegIterLoadTerm(p, pIter, 0);
fts5SegIterLoadNPos(p, pIter);
+ fts5SegIterAllocTombstone(p, pIter);
}
}
@@ -2471,6 +2668,7 @@
}
fts5SegIterSetNext(p, pIter);
+ fts5SegIterAllocTombstone(p, pIter);
/* Either:
**
@@ -2552,12 +2750,27 @@
}
/*
+** Array ap[] contains n elements. Release each of these elements using
+** fts5DataRelease(). Then free the array itself using sqlite3_free().
+*/
+static void fts5IndexFreeArray(Fts5Data **ap, int n){
+ if( ap ){
+ int ii;
+ for(ii=0; iiterm);
fts5DataRelease(pIter->pLeaf);
fts5DataRelease(pIter->pNextLeaf);
+ fts5IndexFreeArray(pIter->apTombstone, pIter->nTombstone);
fts5DlidxIterFree(pIter->pDlidx);
sqlite3_free(pIter->aRowidOffset);
memset(pIter, 0, sizeof(Fts5SegIter));
@@ -2691,7 +2904,6 @@
assert_nc( i2!=0 );
pRes->bTermEq = 1;
if( p1->iRowid==p2->iRowid ){
- p1->bDel = p2->bDel;
return i2;
}
res = ((p1->iRowid > p2->iRowid)==pIter->bRev) ? -1 : +1;
@@ -2896,6 +3108,84 @@
}
/*
+** The argument to this macro must be an Fts5Data structure containing a
+** tombstone hash page. This macro returns the key-size of the hash-page.
+*/
+#define TOMBSTONE_KEYSIZE(pPg) (pPg->p[0]==4 ? 4 : 8)
+
+#define TOMBSTONE_NSLOT(pPg) \
+ ((pPg->nn > 16) ? ((pPg->nn-8) / TOMBSTONE_KEYSIZE(pPg)) : 1)
+
+/*
+** Query a single tombstone hash table for rowid iRowid. Return true if
+** it is found or false otherwise. The tombstone hash table is one of
+** nHashTable tables.
+*/
+static int fts5IndexTombstoneQuery(
+ Fts5Data *pHash, /* Hash table page to query */
+ int nHashTable, /* Number of pages attached to segment */
+ u64 iRowid /* Rowid to query hash for */
+){
+ const int szKey = TOMBSTONE_KEYSIZE(pHash);
+ const int nSlot = TOMBSTONE_NSLOT(pHash);
+ int iSlot = (iRowid / nHashTable) % nSlot;
+ int nCollide = nSlot;
+
+ if( iRowid==0 ){
+ return pHash->p[1];
+ }else if( szKey==4 ){
+ u32 *aSlot = (u32*)&pHash->p[8];
+ while( aSlot[iSlot] ){
+ if( fts5GetU32((u8*)&aSlot[iSlot])==iRowid ) return 1;
+ if( nCollide--==0 ) break;
+ iSlot = (iSlot+1)%nSlot;
+ }
+ }else{
+ u64 *aSlot = (u64*)&pHash->p[8];
+ while( aSlot[iSlot] ){
+ if( fts5GetU64((u8*)&aSlot[iSlot])==iRowid ) return 1;
+ if( nCollide--==0 ) break;
+ iSlot = (iSlot+1)%nSlot;
+ }
+ }
+
+ return 0;
+}
+
+/*
+** Return true if the iterator passed as the only argument points
+** to an segment entry for which there is a tombstone. Return false
+** if there is no tombstone or if the iterator is already at EOF.
+*/
+static int fts5MultiIterIsDeleted(Fts5Iter *pIter){
+ int iFirst = pIter->aFirst[1].iFirst;
+ Fts5SegIter *pSeg = &pIter->aSeg[iFirst];
+
+ if( pSeg->pLeaf && pSeg->nTombstone ){
+ /* Figure out which page the rowid might be present on. */
+ int iPg = ((u64)pSeg->iRowid) % pSeg->nTombstone;
+ assert( iPg>=0 );
+
+ /* If tombstone hash page iPg has not yet been loaded from the
+ ** database, load it now. */
+ if( pSeg->apTombstone[iPg]==0 ){
+ pSeg->apTombstone[iPg] = fts5DataRead(pIter->pIndex,
+ FTS5_TOMBSTONE_ROWID(pSeg->pSeg->iSegid, iPg)
+ );
+ if( pSeg->apTombstone[iPg]==0 ) return 0;
+ }
+
+ return fts5IndexTombstoneQuery(
+ pSeg->apTombstone[iPg],
+ pSeg->nTombstone,
+ pSeg->iRowid
+ );
+ }
+
+ return 0;
+}
+
+/*
** Move the iterator to the next entry.
**
** If an error occurs, an error code is left in Fts5Index.rc. It is not
@@ -2932,7 +3222,9 @@
fts5AssertMultiIterSetup(p, pIter);
assert( pSeg==&pIter->aSeg[pIter->aFirst[1].iFirst] && pSeg->pLeaf );
- if( pIter->bSkipEmpty==0 || pSeg->nPos ){
+ if( (pIter->bSkipEmpty==0 || pSeg->nPos)
+ && 0==fts5MultiIterIsDeleted(pIter)
+ ){
pIter->xSetOutputs(pIter, pSeg);
return;
}
@@ -2964,7 +3256,9 @@
}
fts5AssertMultiIterSetup(p, pIter);
- }while( fts5MultiIterIsEmpty(p, pIter) );
+ }while( (fts5MultiIterIsEmpty(p, pIter) || fts5MultiIterIsDeleted(pIter))
+ && (p->rc==SQLITE_OK)
+ );
}
}
@@ -2977,7 +3271,7 @@
int nSeg
){
Fts5Iter *pNew;
- int nSlot; /* Power of two >= nSeg */
+ i64 nSlot; /* Power of two >= nSeg */
for(nSlot=2; nSlotbSkipEmpty && fts5MultiIterIsEmpty(p, pNew) ){
+ if( (pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew))
+ || fts5MultiIterIsDeleted(pNew)
+ ){
fts5MultiIterNext(p, pNew, 0, 0);
}else if( pNew->base.bEof==0 ){
Fts5SegIter *pSeg = &pNew->aSeg[pNew->aFirst[1].iFirst];
@@ -3697,7 +3993,9 @@
if( p->pHash ){
sqlite3Fts5HashClear(p->pHash);
p->nPendingData = 0;
+ p->nPendingRow = 0;
}
+ p->nContentlessDelete = 0;
}
/*
@@ -4334,6 +4632,12 @@
/* Read input from all segments in the input level */
nInput = pLvl->nSeg;
+
+ /* Set the range of origins that will go into the output segment. */
+ if( pStruct->nOriginCntr>0 ){
+ pSeg->iOrigin1 = pLvl->aSeg[0].iOrigin1;
+ pSeg->iOrigin2 = pLvl->aSeg[pLvl->nSeg-1].iOrigin2;
+ }
}
bOldest = (pLvlOut->nSeg==1 && pStruct->nLevel==iLvl+2);
@@ -4393,8 +4697,11 @@
int i;
/* Remove the redundant segments from the %_data table */
+ assert( pSeg->nEntry==0 );
for(i=0; iaSeg[i].iSegid);
+ Fts5StructureSegment *pOld = &pLvl->aSeg[i];
+ pSeg->nEntry += (pOld->nEntry - pOld->nEntryTombstone);
+ fts5DataRemoveSegment(p, pOld);
}
/* Remove the redundant segments from the input level */
@@ -4421,6 +4728,43 @@
}
/*
+** If this is not a contentless_delete=1 table, or if the 'deletemerge'
+** configuration option is set to 0, then this function always returns -1.
+** Otherwise, it searches the structure object passed as the second argument
+** for a level suitable for merging due to having a large number of
+** tombstones in the tombstone hash. If one is found, its index is returned.
+** Otherwise, if there is no suitable level, -1.
+*/
+static int fts5IndexFindDeleteMerge(Fts5Index *p, Fts5Structure *pStruct){
+ Fts5Config *pConfig = p->pConfig;
+ int iRet = -1;
+ if( pConfig->bContentlessDelete && pConfig->nDeleteMerge>0 ){
+ int ii;
+ int nBest = 0;
+
+ for(ii=0; iinLevel; ii++){
+ Fts5StructureLevel *pLvl = &pStruct->aLevel[ii];
+ i64 nEntry = 0;
+ i64 nTomb = 0;
+ int iSeg;
+ for(iSeg=0; iSegnSeg; iSeg++){
+ nEntry += pLvl->aSeg[iSeg].nEntry;
+ nTomb += pLvl->aSeg[iSeg].nEntryTombstone;
+ }
+ assert_nc( nEntry>0 || pLvl->nSeg==0 );
+ if( nEntry>0 ){
+ int nPercent = (nTomb * 100) / nEntry;
+ if( nPercent>=pConfig->nDeleteMerge && nPercent>nBest ){
+ iRet = ii;
+ nBest = nPercent;
+ }
+ }
+ }
+ }
+ return iRet;
+}
+
+/*
** Do up to nPg pages of automerge work on the index.
**
** Return true if any changes were actually made, or false otherwise.
@@ -4439,14 +4783,15 @@
int iBestLvl = 0; /* Level offering the most input segments */
int nBest = 0; /* Number of input segments on best level */
- /* Set iBestLvl to the level to read input segments from. */
+ /* Set iBestLvl to the level to read input segments from. Or to -1 if
+ ** there is no level suitable to merge segments from. */
assert( pStruct->nLevel>0 );
for(iLvl=0; iLvlnLevel; iLvl++){
Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
if( pLvl->nMerge ){
if( pLvl->nMerge>nBest ){
iBestLvl = iLvl;
- nBest = pLvl->nMerge;
+ nBest = nMin;
}
break;
}
@@ -4455,22 +4800,18 @@
iBestLvl = iLvl;
}
}
-
- /* If nBest is still 0, then the index must be empty. */
-#ifdef SQLITE_DEBUG
- for(iLvl=0; nBest==0 && iLvlnLevel; iLvl++){
- assert( pStruct->aLevel[iLvl].nSeg==0 );
+ if( nBestaLevel[iBestLvl].nMerge==0 ){
- break;
- }
+ if( iBestLvl<0 ) break;
bRet = 1;
fts5IndexMergeLevel(p, &pStruct, iBestLvl, &nRem);
if( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){
fts5StructurePromote(p, iBestLvl+1, pStruct);
}
+
+ if( nMin==1 ) nMin = 2;
}
*ppStruct = pStruct;
return bRet;
@@ -4636,7 +4977,7 @@
pLeaf = 0;
}else if( bDetailNone ){
break;
- }else if( iNext>=pLeaf->szLeaf || iNext<4 ){
+ }else if( iNext>=pLeaf->szLeaf || pLeaf->nnszLeaf || iNext<4 ){
p->rc = FTS5_CORRUPT;
break;
}else{
@@ -4655,9 +4996,13 @@
int i1 = pLeaf->szLeaf;
int i2 = 0;
+ i1 += fts5GetVarint32(&aPg[i1], iFirst);
+ if( iFirstrc = FTS5_CORRUPT;
+ break;
+ }
aIdx = sqlite3Fts5MallocZero(&p->rc, (pLeaf->nn-pLeaf->szLeaf)+2);
if( aIdx==0 ) break;
- i1 += fts5GetVarint32(&aPg[i1], iFirst);
i2 = sqlite3Fts5PutVarint(aIdx, iFirst-nShift);
if( i1nn ){
memcpy(&aIdx[i2], &aPg[i1], pLeaf->nn-i1);
@@ -4702,7 +5047,6 @@
int iPgIdx = pSeg->pLeaf->szLeaf;
u64 iDelta = 0;
- u64 iNextDelta = 0;
int iNextOff = 0;
int iOff = 0;
int nIdx = 0;
@@ -4710,8 +5054,6 @@
int bLastInDoclist = 0;
int iIdx = 0;
int iStart = 0;
- int iKeyOff = 0;
- int iPrevKeyOff = 0;
int iDelKeyOff = 0; /* Offset of deleted key, if any */
nIdx = nPg-iPgIdx;
@@ -4736,10 +5078,21 @@
** This block sets the following variables:
**
** iStart:
+ ** The offset of the first byte of the rowid or delta-rowid
+ ** value for the doclist entry being removed.
+ **
** iDelta:
+ ** The value of the rowid or delta-rowid value for the doclist
+ ** entry being removed.
+ **
+ ** iNextOff:
+ ** The offset of the next entry following the position list
+ ** for the one being removed. If the position list for this
+ ** entry overflows onto the next leaf page, this value will be
+ ** greater than pLeaf->szLeaf.
*/
{
- int iSOP;
+ int iSOP; /* Start-Of-Position-list */
if( pSeg->iLeafPgno==pSeg->iTermLeafPgno ){
iStart = pSeg->iTermLeafOffset;
}else{
@@ -4775,47 +5128,75 @@
}
iOff = iStart;
- if( iNextOff>=iPgIdx ){
- int pgno = pSeg->iLeafPgno+1;
- fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist);
- iNextOff = iPgIdx;
- }else{
- /* Set bLastInDoclist to true if the entry being removed is the last
- ** in its doclist. */
- for(iIdx=0, iKeyOff=0; iIdxbDel==0 ){
+ if( iNextOff>=iPgIdx ){
+ int pgno = pSeg->iLeafPgno+1;
+ fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist);
+ iNextOff = iPgIdx;
+ }else{
+ /* Loop through the page-footer. If iNextOff (offset of the
+ ** entry following the one we are removing) is equal to the
+ ** offset of a key on this page, then the entry is the last
+ ** in its doclist. */
+ int iKeyOff = 0;
+ for(iIdx=0; iIdxbDel ){
+ iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta);
+ aPg[iOff++] = 0x01;
+ }else if( bLastInDoclist==0 ){
if( iNextOff!=iPgIdx ){
+ u64 iNextDelta = 0;
iNextOff += fts5GetVarint(&aPg[iNextOff], &iNextDelta);
iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta + iNextDelta);
}
}else if(
- iStart==pSeg->iTermLeafOffset && pSeg->iLeafPgno==pSeg->iTermLeafPgno
+ pSeg->iLeafPgno==pSeg->iTermLeafPgno
+ && iStart==pSeg->iTermLeafOffset
){
/* The entry being removed was the only position list in its
** doclist. Therefore the term needs to be removed as well. */
int iKey = 0;
- for(iIdx=0, iKeyOff=0; iIdx(u32)iStart ) break;
iKeyOff += iVal;
}
+ assert_nc( iKey>=1 );
+ /* Set iDelKeyOff to the value of the footer entry to remove from
+ ** the page. */
iDelKeyOff = iOff = iKeyOff;
+
if( iNextOff!=iPgIdx ){
+ /* This is the only position-list associated with the term, and there
+ ** is another term following it on this page. So the subsequent term
+ ** needs to be moved to replace the term associated with the entry
+ ** being removed. */
int nPrefix = 0;
int nSuffix = 0;
int nPrefix2 = 0;
@@ -4840,7 +5221,9 @@
iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix);
}
iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix);
- if( nPrefix2>nPrefix ){
+ if( nPrefix2>pSeg->term.n ){
+ p->rc = FTS5_CORRUPT;
+ }else if( nPrefix2>nPrefix ){
memcpy(&aPg[iOff], &pSeg->term.p[nPrefix], nPrefix2-nPrefix);
iOff += (nPrefix2-nPrefix);
}
@@ -4850,80 +5233,88 @@
}
}
}else if( iStart==4 ){
- int iPgno;
+ int iPgno;
- assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno );
- /* The entry being removed may be the only position list in
- ** its doclist. */
- for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){
- Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno));
- int bEmpty = (pPg && pPg->nn==4);
- fts5DataRelease(pPg);
- if( bEmpty==0 ) break;
- }
-
- if( iPgno==pSeg->iTermLeafPgno ){
- i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno);
- Fts5Data *pTerm = fts5DataRead(p, iId);
- if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){
- u8 *aTermIdx = &pTerm->p[pTerm->szLeaf];
- int nTermIdx = pTerm->nn - pTerm->szLeaf;
- int iTermIdx = 0;
- int iTermOff = 0;
-
- while( 1 ){
- u32 iVal = 0;
- int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal);
- iTermOff += iVal;
- if( (iTermIdx+nByte)>=nTermIdx ) break;
- iTermIdx += nByte;
- }
- nTermIdx = iTermIdx;
+ assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno );
+ /* The entry being removed may be the only position list in
+ ** its doclist. */
+ for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){
+ Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno));
+ int bEmpty = (pPg && pPg->nn==4);
+ fts5DataRelease(pPg);
+ if( bEmpty==0 ) break;
+ }
+
+ if( iPgno==pSeg->iTermLeafPgno ){
+ i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno);
+ Fts5Data *pTerm = fts5DataRead(p, iId);
+ if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){
+ u8 *aTermIdx = &pTerm->p[pTerm->szLeaf];
+ int nTermIdx = pTerm->nn - pTerm->szLeaf;
+ int iTermIdx = 0;
+ int iTermOff = 0;
+
+ while( 1 ){
+ u32 iVal = 0;
+ int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal);
+ iTermOff += iVal;
+ if( (iTermIdx+nByte)>=nTermIdx ) break;
+ iTermIdx += nByte;
+ }
+ nTermIdx = iTermIdx;
- memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx);
- fts5PutU16(&pTerm->p[2], iTermOff);
+ memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx);
+ fts5PutU16(&pTerm->p[2], iTermOff);
- fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx);
- if( nTermIdx==0 ){
- fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno);
- }
+ fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx);
+ if( nTermIdx==0 ){
+ fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno);
}
- fts5DataRelease(pTerm);
}
+ fts5DataRelease(pTerm);
}
+ }
- if( p->rc==SQLITE_OK ){
- const int nMove = nPg - iNextOff;
- int nShift = 0;
+ /* Assuming no error has occurred, this block does final edits to the
+ ** leaf page before writing it back to disk. Input variables are:
+ **
+ ** nPg: Total initial size of leaf page.
+ ** iPgIdx: Initial offset of page footer.
+ **
+ ** iOff: Offset to move data to
+ ** iNextOff: Offset to move data from
+ */
+ if( p->rc==SQLITE_OK ){
+ const int nMove = nPg - iNextOff; /* Number of bytes to move */
+ int nShift = iNextOff - iOff; /* Distance to move them */
- memmove(&aPg[iOff], &aPg[iNextOff], nMove);
- iPgIdx -= (iNextOff - iOff);
- nPg = iPgIdx;
- fts5PutU16(&aPg[2], iPgIdx);
+ int iPrevKeyOut = 0;
+ int iKeyIn = 0;
- nShift = iNextOff - iOff;
- for(iIdx=0, iKeyOff=0, iPrevKeyOff=0; iIdxiOff ){
- iKeyOff -= nShift;
- nShift = 0;
- }
- nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOff - iPrevKeyOff);
- iPrevKeyOff = iKeyOff;
- }
- }
+ memmove(&aPg[iOff], &aPg[iNextOff], nMove);
+ iPgIdx -= nShift;
+ nPg = iPgIdx;
+ fts5PutU16(&aPg[2], iPgIdx);
- if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){
- fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno);
+ for(iIdx=0; iIdxiOff ? nShift : 0));
+ nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOut - iPrevKeyOut);
+ iPrevKeyOut = iKeyOut;
}
+ }
- assert_nc( nPg>4 || fts5GetU16(aPg)==0 );
- fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg,nPg);
+ if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){
+ fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno);
}
- sqlite3_free(aIdx);
+
+ assert_nc( nPg>4 || fts5GetU16(aPg)==0 );
+ fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg, nPg);
+ }
+ sqlite3_free(aIdx);
}
/*
@@ -4977,184 +5368,198 @@
/* Obtain a reference to the index structure and allocate a new segment-id
** for the new level-0 segment. */
pStruct = fts5StructureRead(p);
- iSegid = fts5AllocateSegid(p, pStruct);
fts5StructureInvalidate(p);
- if( iSegid ){
- const int pgsz = p->pConfig->pgsz;
- int eDetail = p->pConfig->eDetail;
- int bSecureDelete = p->pConfig->bSecureDelete;
- Fts5StructureSegment *pSeg; /* New segment within pStruct */
- Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */
- Fts5Buffer *pPgidx; /* Buffer in which to assemble pgidx */
-
- Fts5SegWriter writer;
- fts5WriteInit(p, &writer, iSegid);
-
- pBuf = &writer.writer.buf;
- pPgidx = &writer.writer.pgidx;
-
- /* fts5WriteInit() should have initialized the buffers to (most likely)
- ** the maximum space required. */
- assert( p->rc || pBuf->nSpace>=(pgsz + FTS5_DATA_PADDING) );
- assert( p->rc || pPgidx->nSpace>=(pgsz + FTS5_DATA_PADDING) );
-
- /* Begin scanning through hash table entries. This loop runs once for each
- ** term/doclist currently stored within the hash table. */
- if( p->rc==SQLITE_OK ){
- p->rc = sqlite3Fts5HashScanInit(pHash, 0, 0);
- }
- while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){
- const char *zTerm; /* Buffer containing term */
- int nTerm; /* Size of zTerm in bytes */
- const u8 *pDoclist; /* Pointer to doclist for this term */
- int nDoclist; /* Size of doclist in bytes */
-
- /* Get the term and doclist for this entry. */
- sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist);
- nTerm = (int)strlen(zTerm);
- if( bSecureDelete==0 ){
- fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm);
- if( p->rc!=SQLITE_OK ) break;
- assert( writer.bFirstRowidInPage==0 );
- }
-
- if( !bSecureDelete && pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){
- /* The entire doclist will fit on the current leaf. */
- fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist);
- }else{
- int bTermWritten = !bSecureDelete;
- i64 iRowid = 0;
- i64 iPrev = 0;
- int iOff = 0;
-
- /* The entire doclist will not fit on this leaf. The following
- ** loop iterates through the poslists that make up the current
- ** doclist. */
- while( p->rc==SQLITE_OK && iOffpConfig->pgsz;
+ int eDetail = p->pConfig->eDetail;
+ int bSecureDelete = p->pConfig->bSecureDelete;
+ Fts5StructureSegment *pSeg; /* New segment within pStruct */
+ Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */
+ Fts5Buffer *pPgidx; /* Buffer in which to assemble pgidx */
+
+ Fts5SegWriter writer;
+ fts5WriteInit(p, &writer, iSegid);
+
+ pBuf = &writer.writer.buf;
+ pPgidx = &writer.writer.pgidx;
+
+ /* fts5WriteInit() should have initialized the buffers to (most likely)
+ ** the maximum space required. */
+ assert( p->rc || pBuf->nSpace>=(pgsz + FTS5_DATA_PADDING) );
+ assert( p->rc || pPgidx->nSpace>=(pgsz + FTS5_DATA_PADDING) );
+
+ /* Begin scanning through hash table entries. This loop runs once for each
+ ** term/doclist currently stored within the hash table. */
+ if( p->rc==SQLITE_OK ){
+ p->rc = sqlite3Fts5HashScanInit(pHash, 0, 0);
+ }
+ while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){
+ const char *zTerm; /* Buffer containing term */
+ int nTerm; /* Size of zTerm in bytes */
+ const u8 *pDoclist; /* Pointer to doclist for this term */
+ int nDoclist; /* Size of doclist in bytes */
+
+ /* Get the term and doclist for this entry. */
+ sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist);
+ nTerm = (int)strlen(zTerm);
+ if( bSecureDelete==0 ){
+ fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm);
+ if( p->rc!=SQLITE_OK ) break;
+ assert( writer.bFirstRowidInPage==0 );
+ }
+
+ if( !bSecureDelete && pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){
+ /* The entire doclist will fit on the current leaf. */
+ fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist);
+ }else{
+ int bTermWritten = !bSecureDelete;
+ i64 iRowid = 0;
+ i64 iPrev = 0;
+ int iOff = 0;
+
+ /* The entire doclist will not fit on this leaf. The following
+ ** loop iterates through the poslists that make up the current
+ ** doclist. */
+ while( p->rc==SQLITE_OK && iOffrc!=SQLITE_OK || pDoclist[iOff]==0x01 ){
iOff++;
- nDoclist = 0;
- }else{
continue;
}
}
- }else if( (pDoclist[iOff] & 0x01) ){
- fts5FlushSecureDelete(p, pStruct, zTerm, iRowid);
- if( p->rc!=SQLITE_OK || pDoclist[iOff]==0x01 ){
- iOff++;
- continue;
- }
}
- }
-
- if( p->rc==SQLITE_OK && bTermWritten==0 ){
- fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm);
- bTermWritten = 1;
- assert( p->rc!=SQLITE_OK || writer.bFirstRowidInPage==0 );
- }
-
- if( writer.bFirstRowidInPage ){
- fts5PutU16(&pBuf->p[0], (u16)pBuf->n); /* first rowid on page */
- pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid);
- writer.bFirstRowidInPage = 0;
- fts5WriteDlidxAppend(p, &writer, iRowid);
- }else{
- pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid-iPrev);
- }
- if( p->rc!=SQLITE_OK ) break;
- assert( pBuf->n<=pBuf->nSpace );
- iPrev = iRowid;
-
- if( eDetail==FTS5_DETAIL_NONE ){
- if( iOffp[pBuf->n++] = 0;
- iOff++;
+
+ if( p->rc==SQLITE_OK && bTermWritten==0 ){
+ fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm);
+ bTermWritten = 1;
+ assert( p->rc!=SQLITE_OK || writer.bFirstRowidInPage==0 );
+ }
+
+ if( writer.bFirstRowidInPage ){
+ fts5PutU16(&pBuf->p[0], (u16)pBuf->n); /* first rowid on page */
+ pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid);
+ writer.bFirstRowidInPage = 0;
+ fts5WriteDlidxAppend(p, &writer, iRowid);
+ }else{
+ u64 iRowidDelta = (u64)iRowid - (u64)iPrev;
+ pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowidDelta);
+ }
+ if( p->rc!=SQLITE_OK ) break;
+ assert( pBuf->n<=pBuf->nSpace );
+ iPrev = iRowid;
+
+ if( eDetail==FTS5_DETAIL_NONE ){
if( iOffp[pBuf->n++] = 0;
iOff++;
+ if( iOffp[pBuf->n++] = 0;
+ iOff++;
+ }
+ }
+ if( (pBuf->n + pPgidx->n)>=pgsz ){
+ fts5WriteFlushLeaf(p, &writer);
}
- }
- if( (pBuf->n + pPgidx->n)>=pgsz ){
- fts5WriteFlushLeaf(p, &writer);
- }
- }else{
- int bDummy;
- int nPos;
- int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy);
- nCopy += nPos;
- if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){
- /* The entire poslist will fit on the current leaf. So copy
- ** it in one go. */
- fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy);
}else{
- /* The entire poslist will not fit on this leaf. So it needs
- ** to be broken into sections. The only qualification being
- ** that each varint must be stored contiguously. */
- const u8 *pPoslist = &pDoclist[iOff];
- int iPos = 0;
- while( p->rc==SQLITE_OK ){
- int nSpace = pgsz - pBuf->n - pPgidx->n;
- int n = 0;
- if( (nCopy - iPos)<=nSpace ){
- n = nCopy - iPos;
- }else{
- n = fts5PoslistPrefix(&pPoslist[iPos], nSpace);
- }
- assert( n>0 );
- fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n);
- iPos += n;
- if( (pBuf->n + pPgidx->n)>=pgsz ){
- fts5WriteFlushLeaf(p, &writer);
+ int bDel = 0;
+ int nPos = 0;
+ int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDel);
+ if( bDel && bSecureDelete ){
+ fts5BufferAppendVarint(&p->rc, pBuf, nPos*2);
+ iOff += nCopy;
+ nCopy = nPos;
+ }else{
+ nCopy += nPos;
+ }
+ if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){
+ /* The entire poslist will fit on the current leaf. So copy
+ ** it in one go. */
+ fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy);
+ }else{
+ /* The entire poslist will not fit on this leaf. So it needs
+ ** to be broken into sections. The only qualification being
+ ** that each varint must be stored contiguously. */
+ const u8 *pPoslist = &pDoclist[iOff];
+ int iPos = 0;
+ while( p->rc==SQLITE_OK ){
+ int nSpace = pgsz - pBuf->n - pPgidx->n;
+ int n = 0;
+ if( (nCopy - iPos)<=nSpace ){
+ n = nCopy - iPos;
+ }else{
+ n = fts5PoslistPrefix(&pPoslist[iPos], nSpace);
+ }
+ assert( n>0 );
+ fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n);
+ iPos += n;
+ if( (pBuf->n + pPgidx->n)>=pgsz ){
+ fts5WriteFlushLeaf(p, &writer);
+ }
+ if( iPos>=nCopy ) break;
}
- if( iPos>=nCopy ) break;
}
+ iOff += nCopy;
}
- iOff += nCopy;
}
}
+
+ /* TODO2: Doclist terminator written here. */
+ /* pBuf->p[pBuf->n++] = '\0'; */
+ assert( pBuf->n<=pBuf->nSpace );
+ if( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash);
}
-
- /* TODO2: Doclist terminator written here. */
- /* pBuf->p[pBuf->n++] = '\0'; */
- assert( pBuf->n<=pBuf->nSpace );
- if( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash);
- }
- sqlite3Fts5HashClear(pHash);
- fts5WriteFinish(p, &writer, &pgnoLast);
-
- assert( p->rc!=SQLITE_OK || bSecureDelete || pgnoLast>0 );
- if( pgnoLast>0 ){
- /* Update the Fts5Structure. It is written back to the database by the
- ** fts5StructureRelease() call below. */
- if( pStruct->nLevel==0 ){
- fts5StructureAddLevel(&p->rc, &pStruct);
- }
- fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0);
- if( p->rc==SQLITE_OK ){
- pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ];
- pSeg->iSegid = iSegid;
- pSeg->pgnoFirst = 1;
- pSeg->pgnoLast = pgnoLast;
- pStruct->nSegment++;
+ fts5WriteFinish(p, &writer, &pgnoLast);
+
+ assert( p->rc!=SQLITE_OK || bSecureDelete || pgnoLast>0 );
+ if( pgnoLast>0 ){
+ /* Update the Fts5Structure. It is written back to the database by the
+ ** fts5StructureRelease() call below. */
+ if( pStruct->nLevel==0 ){
+ fts5StructureAddLevel(&p->rc, &pStruct);
+ }
+ fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0);
+ if( p->rc==SQLITE_OK ){
+ pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ];
+ pSeg->iSegid = iSegid;
+ pSeg->pgnoFirst = 1;
+ pSeg->pgnoLast = pgnoLast;
+ if( pStruct->nOriginCntr>0 ){
+ pSeg->iOrigin1 = pStruct->nOriginCntr;
+ pSeg->iOrigin2 = pStruct->nOriginCntr;
+ pSeg->nEntry = p->nPendingRow;
+ pStruct->nOriginCntr++;
+ }
+ pStruct->nSegment++;
+ }
+ fts5StructurePromote(p, 0, pStruct);
}
- fts5StructurePromote(p, 0, pStruct);
}
}
- fts5IndexAutomerge(p, &pStruct, pgnoLast);
+ fts5IndexAutomerge(p, &pStruct, pgnoLast + p->nContentlessDelete);
fts5IndexCrisismerge(p, &pStruct);
fts5StructureWrite(p, pStruct);
fts5StructureRelease(pStruct);
@@ -5165,10 +5570,15 @@
*/
static void fts5IndexFlush(Fts5Index *p){
/* Unless it is empty, flush the hash table to disk */
- if( p->nPendingData ){
+ if( p->nPendingData || p->nContentlessDelete ){
assert( p->pHash );
- p->nPendingData = 0;
fts5FlushOneHash(p);
+ if( p->rc==SQLITE_OK ){
+ sqlite3Fts5HashClear(p->pHash);
+ p->nPendingData = 0;
+ p->nPendingRow = 0;
+ p->nContentlessDelete = 0;
+ }
}
}
@@ -5184,17 +5594,22 @@
/* Figure out if this structure requires optimization. A structure does
** not require optimization if either:
**
- ** + it consists of fewer than two segments, or
- ** + all segments are on the same level, or
- ** + all segments except one are currently inputs to a merge operation.
+ ** 1. it consists of fewer than two segments, or
+ ** 2. all segments are on the same level, or
+ ** 3. all segments except one are currently inputs to a merge operation.
**
- ** In the first case, return NULL. In the second, increment the ref-count
- ** on *pStruct and return a copy of the pointer to it.
+ ** In the first case, if there are no tombstone hash pages, return NULL. In
+ ** the second, increment the ref-count on *pStruct and return a copy of the
+ ** pointer to it.
*/
- if( nSeg<2 ) return 0;
+ if( nSeg==0 ) return 0;
for(i=0; inLevel; i++){
int nThis = pStruct->aLevel[i].nSeg;
- if( nThis==nSeg || (nThis==nSeg-1 && pStruct->aLevel[i].nMerge==nThis) ){
+ int nMerge = pStruct->aLevel[i].nMerge;
+ if( nThis>0 && (nThis==nSeg || (nThis==nSeg-1 && nMerge==nThis)) ){
+ if( nSeg==1 && nThis==1 && pStruct->aLevel[i].aSeg[0].nPgTombstone==0 ){
+ return 0;
+ }
fts5StructureRef(pStruct);
return pStruct;
}
@@ -5210,6 +5625,7 @@
pNew->nLevel = MIN(pStruct->nLevel+1, FTS5_MAX_LEVEL);
pNew->nRef = 1;
pNew->nWriteCounter = pStruct->nWriteCounter;
+ pNew->nOriginCntr = pStruct->nOriginCntr;
pLvl = &pNew->aLevel[pNew->nLevel-1];
pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&p->rc, nByte);
if( pLvl->aSeg ){
@@ -5240,6 +5656,7 @@
assert( p->rc==SQLITE_OK );
fts5IndexFlush(p);
+ assert( p->nContentlessDelete==0 );
pStruct = fts5StructureRead(p);
fts5StructureInvalidate(p);
@@ -5269,7 +5686,10 @@
** INSERT command.
*/
int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){
- Fts5Structure *pStruct = fts5StructureRead(p);
+ Fts5Structure *pStruct = 0;
+
+ fts5IndexFlush(p);
+ pStruct = fts5StructureRead(p);
if( pStruct ){
int nMin = p->pConfig->nUsermerge;
fts5StructureInvalidate(p);
@@ -5277,7 +5697,7 @@
Fts5Structure *pNew = fts5IndexOptimizeStruct(p, pStruct);
fts5StructureRelease(pStruct);
pStruct = pNew;
- nMin = 2;
+ nMin = 1;
nMerge = nMerge*-1;
}
if( pStruct && pStruct->nLevel ){
@@ -5791,6 +6211,9 @@
p->iWriteRowid = iRowid;
p->bDelete = bDelete;
+ if( bDelete==0 ){
+ p->nPendingRow++;
+ }
return fts5IndexReturn(p);
}
@@ -5828,6 +6251,9 @@
fts5StructureInvalidate(p);
fts5IndexDiscardData(p);
memset(&s, 0, sizeof(Fts5Structure));
+ if( p->pConfig->bContentlessDelete ){
+ s.nOriginCntr = 1;
+ }
fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0);
fts5StructureWrite(p, &s);
return fts5IndexReturn(p);
@@ -6219,6 +6645,347 @@
return fts5IndexReturn(p);
}
+/*
+** Retrieve the origin value that will be used for the segment currently
+** being accumulated in the in-memory hash table when it is flushed to
+** disk. If successful, SQLITE_OK is returned and (*piOrigin) set to
+** the queried value. Or, if an error occurs, an error code is returned
+** and the final value of (*piOrigin) is undefined.
+*/
+int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin){
+ Fts5Structure *pStruct;
+ pStruct = fts5StructureRead(p);
+ if( pStruct ){
+ *piOrigin = pStruct->nOriginCntr;
+ fts5StructureRelease(pStruct);
+ }
+ return fts5IndexReturn(p);
+}
+
+/*
+** Buffer pPg contains a page of a tombstone hash table - one of nPg pages
+** associated with the same segment. This function adds rowid iRowid to
+** the hash table. The caller is required to guarantee that there is at
+** least one free slot on the page.
+**
+** If parameter bForce is false and the hash table is deemed to be full
+** (more than half of the slots are occupied), then non-zero is returned
+** and iRowid not inserted. Or, if bForce is true or if the hash table page
+** is not full, iRowid is inserted and zero returned.
+*/
+static int fts5IndexTombstoneAddToPage(
+ Fts5Data *pPg,
+ int bForce,
+ int nPg,
+ u64 iRowid
+){
+ const int szKey = TOMBSTONE_KEYSIZE(pPg);
+ const int nSlot = TOMBSTONE_NSLOT(pPg);
+ const int nElem = fts5GetU32(&pPg->p[4]);
+ int iSlot = (iRowid / nPg) % nSlot;
+ int nCollide = nSlot;
+
+ if( szKey==4 && iRowid>0xFFFFFFFF ) return 2;
+ if( iRowid==0 ){
+ pPg->p[1] = 0x01;
+ return 0;
+ }
+
+ if( bForce==0 && nElem>=(nSlot/2) ){
+ return 1;
+ }
+
+ fts5PutU32(&pPg->p[4], nElem+1);
+ if( szKey==4 ){
+ u32 *aSlot = (u32*)&pPg->p[8];
+ while( aSlot[iSlot] ){
+ iSlot = (iSlot + 1) % nSlot;
+ if( nCollide--==0 ) return 0;
+ }
+ fts5PutU32((u8*)&aSlot[iSlot], (u32)iRowid);
+ }else{
+ u64 *aSlot = (u64*)&pPg->p[8];
+ while( aSlot[iSlot] ){
+ iSlot = (iSlot + 1) % nSlot;
+ if( nCollide--==0 ) return 0;
+ }
+ fts5PutU64((u8*)&aSlot[iSlot], iRowid);
+ }
+
+ return 0;
+}
+
+/*
+** This function attempts to build a new hash containing all the keys
+** currently in the tombstone hash table for segment pSeg. The new
+** hash will be stored in the nOut buffers passed in array apOut[].
+** All pages of the new hash use key-size szKey (4 or 8).
+**
+** Return 0 if the hash is successfully rebuilt into the nOut pages.
+** Or non-zero if it is not (because one page became overfull). In this
+** case the caller should retry with a larger nOut parameter.
+**
+** Parameter pData1 is page iPg1 of the hash table being rebuilt.
+*/
+static int fts5IndexTombstoneRehash(
+ Fts5Index *p,
+ Fts5StructureSegment *pSeg, /* Segment to rebuild hash of */
+ Fts5Data *pData1, /* One page of current hash - or NULL */
+ int iPg1, /* Which page of the current hash is pData1 */
+ int szKey, /* 4 or 8, the keysize */
+ int nOut, /* Number of output pages */
+ Fts5Data **apOut /* Array of output hash pages */
+){
+ int ii;
+ int res = 0;
+
+ /* Initialize the headers of all the output pages */
+ for(ii=0; iip[0] = szKey;
+ fts5PutU32(&apOut[ii]->p[4], 0);
+ }
+
+ /* Loop through the current pages of the hash table. */
+ for(ii=0; res==0 && iinPgTombstone; ii++){
+ Fts5Data *pData = 0; /* Page ii of the current hash table */
+ Fts5Data *pFree = 0; /* Free this at the end of the loop */
+
+ if( iPg1==ii ){
+ pData = pData1;
+ }else{
+ pFree = pData = fts5DataRead(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid, ii));
+ }
+
+ if( pData ){
+ int szKeyIn = TOMBSTONE_KEYSIZE(pData);
+ int nSlotIn = (pData->nn - 8) / szKeyIn;
+ int iIn;
+ for(iIn=0; iInp[8];
+ if( aSlot[iIn] ) iVal = fts5GetU32((u8*)&aSlot[iIn]);
+ }else{
+ u64 *aSlot = (u64*)&pData->p[8];
+ if( aSlot[iIn] ) iVal = fts5GetU64((u8*)&aSlot[iIn]);
+ }
+
+ /* If iVal is not 0 at this point, insert it into the new hash table */
+ if( iVal ){
+ Fts5Data *pPg = apOut[(iVal % nOut)];
+ res = fts5IndexTombstoneAddToPage(pPg, 0, nOut, iVal);
+ if( res ) break;
+ }
+ }
+
+ /* If this is page 0 of the old hash, copy the rowid-0-flag from the
+ ** old hash to the new. */
+ if( ii==0 ){
+ apOut[0]->p[1] = pData->p[1];
+ }
+ }
+ fts5DataRelease(pFree);
+ }
+
+ return res;
+}
+
+/*
+** This is called to rebuild the hash table belonging to segment pSeg.
+** If parameter pData1 is not NULL, then one page of the existing hash table
+** has already been loaded - pData1, which is page iPg1. The key-size for
+** the new hash table is szKey (4 or 8).
+**
+** If successful, the new hash table is not written to disk. Instead,
+** output parameter (*pnOut) is set to the number of pages in the new
+** hash table, and (*papOut) to point to an array of buffers containing
+** the new page data.
+**
+** If an error occurs, an error code is left in the Fts5Index object and
+** both output parameters set to 0 before returning.
+*/
+static void fts5IndexTombstoneRebuild(
+ Fts5Index *p,
+ Fts5StructureSegment *pSeg, /* Segment to rebuild hash of */
+ Fts5Data *pData1, /* One page of current hash - or NULL */
+ int iPg1, /* Which page of the current hash is pData1 */
+ int szKey, /* 4 or 8, the keysize */
+ int *pnOut, /* OUT: Number of output pages */
+ Fts5Data ***papOut /* OUT: Output hash pages */
+){
+ const int MINSLOT = 32;
+ int nSlotPerPage = MAX(MINSLOT, (p->pConfig->pgsz - 8) / szKey);
+ int nSlot = 0; /* Number of slots in each output page */
+ int nOut = 0;
+
+ /* Figure out how many output pages (nOut) and how many slots per
+ ** page (nSlot). There are three possibilities:
+ **
+ ** 1. The hash table does not yet exist. In this case the new hash
+ ** table will consist of a single page with MINSLOT slots.
+ **
+ ** 2. The hash table exists but is currently a single page. In this
+ ** case an attempt is made to grow the page to accommodate the new
+ ** entry. The page is allowed to grow up to nSlotPerPage (see above)
+ ** slots.
+ **
+ ** 3. The hash table already consists of more than one page, or of
+ ** a single page already so large that it cannot be grown. In this
+ ** case the new hash consists of (nPg*2+1) pages of nSlotPerPage
+ ** slots each, where nPg is the current number of pages in the
+ ** hash table.
+ */
+ if( pSeg->nPgTombstone==0 ){
+ /* Case 1. */
+ nOut = 1;
+ nSlot = MINSLOT;
+ }else if( pSeg->nPgTombstone==1 ){
+ /* Case 2. */
+ int nElem = (int)fts5GetU32(&pData1->p[4]);
+ assert( pData1 && iPg1==0 );
+ nOut = 1;
+ nSlot = MAX(nElem*4, MINSLOT);
+ if( nSlot>nSlotPerPage ) nOut = 0;
+ }
+ if( nOut==0 ){
+ /* Case 3. */
+ nOut = (pSeg->nPgTombstone * 2 + 1);
+ nSlot = nSlotPerPage;
+ }
+
+ /* Allocate the required array and output pages */
+ while( 1 ){
+ int res = 0;
+ int ii = 0;
+ int szPage = 0;
+ Fts5Data **apOut = 0;
+
+ /* Allocate space for the new hash table */
+ assert( nSlot>=MINSLOT );
+ apOut = (Fts5Data**)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data*) * nOut);
+ szPage = 8 + nSlot*szKey;
+ for(ii=0; iirc,
+ sizeof(Fts5Data)+szPage
+ );
+ if( pNew ){
+ pNew->nn = szPage;
+ pNew->p = (u8*)&pNew[1];
+ apOut[ii] = pNew;
+ }
+ }
+
+ /* Rebuild the hash table. */
+ if( p->rc==SQLITE_OK ){
+ res = fts5IndexTombstoneRehash(p, pSeg, pData1, iPg1, szKey, nOut, apOut);
+ }
+ if( res==0 ){
+ if( p->rc ){
+ fts5IndexFreeArray(apOut, nOut);
+ apOut = 0;
+ nOut = 0;
+ }
+ *pnOut = nOut;
+ *papOut = apOut;
+ break;
+ }
+
+ /* If control flows to here, it was not possible to rebuild the hash
+ ** table. Free all buffers and then try again with more pages. */
+ assert( p->rc==SQLITE_OK );
+ fts5IndexFreeArray(apOut, nOut);
+ nSlot = nSlotPerPage;
+ nOut = nOut*2 + 1;
+ }
+}
+
+
+/*
+** Add a tombstone for rowid iRowid to segment pSeg.
+*/
+static void fts5IndexTombstoneAdd(
+ Fts5Index *p,
+ Fts5StructureSegment *pSeg,
+ u64 iRowid
+){
+ Fts5Data *pPg = 0;
+ int iPg = -1;
+ int szKey = 0;
+ int nHash = 0;
+ Fts5Data **apHash = 0;
+
+ p->nContentlessDelete++;
+
+ if( pSeg->nPgTombstone>0 ){
+ iPg = iRowid % pSeg->nPgTombstone;
+ pPg = fts5DataRead(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid,iPg));
+ if( pPg==0 ){
+ assert( p->rc!=SQLITE_OK );
+ return;
+ }
+
+ if( 0==fts5IndexTombstoneAddToPage(pPg, 0, pSeg->nPgTombstone, iRowid) ){
+ fts5DataWrite(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid,iPg), pPg->p, pPg->nn);
+ fts5DataRelease(pPg);
+ return;
+ }
+ }
+
+ /* Have to rebuild the hash table. First figure out the key-size (4 or 8). */
+ szKey = pPg ? TOMBSTONE_KEYSIZE(pPg) : 4;
+ if( iRowid>0xFFFFFFFF ) szKey = 8;
+
+ /* Rebuild the hash table */
+ fts5IndexTombstoneRebuild(p, pSeg, pPg, iPg, szKey, &nHash, &apHash);
+ assert( p->rc==SQLITE_OK || (nHash==0 && apHash==0) );
+
+ /* If all has succeeded, write the new rowid into one of the new hash
+ ** table pages, then write them all out to disk. */
+ if( nHash ){
+ int ii = 0;
+ fts5IndexTombstoneAddToPage(apHash[iRowid % nHash], 1, nHash, iRowid);
+ for(ii=0; iiiSegid, ii);
+ fts5DataWrite(p, iTombstoneRowid, apHash[ii]->p, apHash[ii]->nn);
+ }
+ pSeg->nPgTombstone = nHash;
+ fts5StructureWrite(p, p->pStruct);
+ }
+
+ fts5DataRelease(pPg);
+ fts5IndexFreeArray(apHash, nHash);
+}
+
+/*
+** Add iRowid to the tombstone list of the segment or segments that contain
+** rows from origin iOrigin. Return SQLITE_OK if successful, or an SQLite
+** error code otherwise.
+*/
+int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid){
+ Fts5Structure *pStruct;
+ pStruct = fts5StructureRead(p);
+ if( pStruct ){
+ int bFound = 0; /* True after pSeg->nEntryTombstone incr. */
+ int iLvl;
+ for(iLvl=pStruct->nLevel-1; iLvl>=0; iLvl--){
+ int iSeg;
+ for(iSeg=pStruct->aLevel[iLvl].nSeg-1; iSeg>=0; iSeg--){
+ Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg];
+ if( pSeg->iOrigin1<=(u64)iOrigin && pSeg->iOrigin2>=(u64)iOrigin ){
+ if( bFound==0 ){
+ pSeg->nEntryTombstone++;
+ bFound = 1;
+ }
+ fts5IndexTombstoneAdd(p, pSeg, iRowid);
+ }
+ }
+ }
+ fts5StructureRelease(pStruct);
+ }
+ return fts5IndexReturn(p);
+}
/*************************************************************************
**************************************************************************
@@ -6770,13 +7537,14 @@
** function only.
*/
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
/*
** Decode a segment-data rowid from the %_data table. This function is
** the opposite of macro FTS5_SEGMENT_ROWID().
*/
static void fts5DecodeRowid(
i64 iRowid, /* Rowid from %_data table */
+ int *pbTombstone, /* OUT: Tombstone hash flag */
int *piSegid, /* OUT: Segment id */
int *pbDlidx, /* OUT: Dlidx flag */
int *piHeight, /* OUT: Height */
@@ -6792,13 +7560,16 @@
iRowid >>= FTS5_DATA_DLI_B;
*piSegid = (int)(iRowid & (((i64)1 << FTS5_DATA_ID_B) - 1));
+ iRowid >>= FTS5_DATA_ID_B;
+
+ *pbTombstone = (int)(iRowid & 0x0001);
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){
- int iSegid, iHeight, iPgno, bDlidx; /* Rowid compenents */
- fts5DecodeRowid(iKey, &iSegid, &bDlidx, &iHeight, &iPgno);
+ int iSegid, iHeight, iPgno, bDlidx, bTomb; /* Rowid compenents */
+ fts5DecodeRowid(iKey, &bTomb, &iSegid, &bDlidx, &iHeight, &iPgno);
if( iSegid==0 ){
if( iKey==FTS5_AVERAGES_ROWID ){
@@ -6808,14 +7579,16 @@
}
}
else{
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{%ssegid=%d h=%d pgno=%d}",
- bDlidx ? "dlidx " : "", iSegid, iHeight, iPgno
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{%s%ssegid=%d h=%d pgno=%d}",
+ bDlidx ? "dlidx " : "",
+ bTomb ? "tombstone " : "",
+ iSegid, iHeight, iPgno
);
}
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
static void fts5DebugStructure(
int *pRc, /* IN/OUT: error code */
Fts5Buffer *pBuf,
@@ -6830,16 +7603,22 @@
);
for(iSeg=0; iSegnSeg; iSeg++){
Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {id=%d leaves=%d..%d}",
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {id=%d leaves=%d..%d",
pSeg->iSegid, pSeg->pgnoFirst, pSeg->pgnoLast
);
+ if( pSeg->iOrigin1>0 ){
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " origin=%lld..%lld",
+ pSeg->iOrigin1, pSeg->iOrigin2
+ );
+ }
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}");
}
sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}");
}
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
/*
** This is part of the fts5_decode() debugging aid.
**
@@ -6864,9 +7643,9 @@
fts5DebugStructure(pRc, pBuf, p);
fts5StructureRelease(p);
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
/*
** This is part of the fts5_decode() debugging aid.
**
@@ -6889,9 +7668,9 @@
zSpace = " ";
}
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
/*
** Buffer (a/n) is assumed to contain a list of serialized varints. Read
** each varint and append its string representation to buffer pBuf. Return
@@ -6908,9 +7687,9 @@
}
return iOff;
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
/*
** The start of buffer (a/n) contains the start of a doclist. The doclist
** may or may not finish within the buffer. This function appends a text
@@ -6943,9 +7722,9 @@
return iOff;
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
/*
** This function is part of the fts5_decode() debugging function. It is
** only ever used with detail=none tables.
@@ -6986,9 +7765,9 @@
sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %lld%s", iRowid, zApp);
}
}
-#endif /* SQLITE_TEST */
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
/*
** The implementation of user-defined scalar function fts5_decode().
*/
@@ -6999,6 +7778,7 @@
){
i64 iRowid; /* Rowid for record being decoded */
int iSegid,iHeight,iPgno,bDlidx;/* Rowid components */
+ int bTomb;
const u8 *aBlob; int n; /* Record to decode */
u8 *a = 0;
Fts5Buffer s; /* Build up text to return here */
@@ -7021,7 +7801,7 @@
if( a==0 ) goto decode_out;
if( n>0 ) memcpy(a, aBlob, n);
- fts5DecodeRowid(iRowid, &iSegid, &bDlidx, &iHeight, &iPgno);
+ fts5DecodeRowid(iRowid, &bTomb, &iSegid, &bDlidx, &iHeight, &iPgno);
fts5DebugRowid(&rc, &s, iRowid);
if( bDlidx ){
@@ -7040,6 +7820,28 @@
" %d(%lld)", lvl.iLeafPgno, lvl.iRowid
);
}
+ }else if( bTomb ){
+ u32 nElem = fts5GetU32(&a[4]);
+ int szKey = (aBlob[0]==4 || aBlob[0]==8) ? aBlob[0] : 8;
+ int nSlot = (n - 8) / szKey;
+ int ii;
+ sqlite3Fts5BufferAppendPrintf(&rc, &s, " nElem=%d", (int)nElem);
+ if( aBlob[1] ){
+ sqlite3Fts5BufferAppendPrintf(&rc, &s, " 0");
+ }
+ for(ii=0; iiszLeaf ){
+ rc = FTS5_CORRUPT;
+ }else{
+ fts5DecodeRowidList(&rc, &s, &a[iOff], iTermOff-iOff);
+ }
iOff = iTermOff;
if( iOffestimatedCost = (double)100;
+ pIdxInfo->estimatedRows = 100;
+ pIdxInfo->idxNum = 0;
+ for(i=0, p=pIdxInfo->aConstraint; inConstraint; i++, p++){
+ if( p->usable==0 ) continue;
+ if( p->op==SQLITE_INDEX_CONSTRAINT_EQ && p->iColumn==11 ){
+ rc = SQLITE_OK;
+ pIdxInfo->aConstraintUsage[i].omit = 1;
+ pIdxInfo->aConstraintUsage[i].argvIndex = 1;
+ break;
+ }
+ }
+ return rc;
+}
+
+/*
+** This method is the destructor for bytecodevtab objects.
+*/
+static int fts5structDisconnectMethod(sqlite3_vtab *pVtab){
+ Fts5StructVtab *p = (Fts5StructVtab*)pVtab;
+ sqlite3_free(p);
+ return SQLITE_OK;
+}
+
+/*
+** Constructor for a new bytecodevtab_cursor object.
+*/
+static int fts5structOpenMethod(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCsr){
+ int rc = SQLITE_OK;
+ Fts5StructVcsr *pNew = 0;
+
+ pNew = sqlite3Fts5MallocZero(&rc, sizeof(*pNew));
+ *ppCsr = (sqlite3_vtab_cursor*)pNew;
+
+ return SQLITE_OK;
+}
+
+/*
+** Destructor for a bytecodevtab_cursor.
+*/
+static int fts5structCloseMethod(sqlite3_vtab_cursor *cur){
+ Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur;
+ fts5StructureRelease(pCsr->pStruct);
+ sqlite3_free(pCsr);
+ return SQLITE_OK;
+}
+
+
+/*
+** Advance a bytecodevtab_cursor to its next row of output.
+*/
+static int fts5structNextMethod(sqlite3_vtab_cursor *cur){
+ Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur;
+ Fts5Structure *p = pCsr->pStruct;
+
+ assert( pCsr->pStruct );
+ pCsr->iSeg++;
+ pCsr->iRowid++;
+ while( pCsr->iLevelnLevel && pCsr->iSeg>=p->aLevel[pCsr->iLevel].nSeg ){
+ pCsr->iLevel++;
+ pCsr->iSeg = 0;
+ }
+ if( pCsr->iLevel>=p->nLevel ){
+ fts5StructureRelease(pCsr->pStruct);
+ pCsr->pStruct = 0;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Return TRUE if the cursor has been moved off of the last
+** row of output.
+*/
+static int fts5structEofMethod(sqlite3_vtab_cursor *cur){
+ Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur;
+ return pCsr->pStruct==0;
+}
+
+static int fts5structRowidMethod(
+ sqlite3_vtab_cursor *cur,
+ sqlite_int64 *piRowid
+){
+ Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur;
+ *piRowid = pCsr->iRowid;
+ return SQLITE_OK;
+}
+
+/*
+** Return values of columns for the row at which the bytecodevtab_cursor
+** is currently pointing.
+*/
+static int fts5structColumnMethod(
+ sqlite3_vtab_cursor *cur, /* The cursor */
+ sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
+ int i /* Which column to return */
+){
+ Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur;
+ Fts5Structure *p = pCsr->pStruct;
+ Fts5StructureSegment *pSeg = &p->aLevel[pCsr->iLevel].aSeg[pCsr->iSeg];
+
+ switch( i ){
+ case 0: /* level */
+ sqlite3_result_int(ctx, pCsr->iLevel);
+ break;
+ case 1: /* segment */
+ sqlite3_result_int(ctx, pCsr->iSeg);
+ break;
+ case 2: /* merge */
+ sqlite3_result_int(ctx, pCsr->iSeg < p->aLevel[pCsr->iLevel].nMerge);
+ break;
+ case 3: /* segid */
+ sqlite3_result_int(ctx, pSeg->iSegid);
+ break;
+ case 4: /* leaf1 */
+ sqlite3_result_int(ctx, pSeg->pgnoFirst);
+ break;
+ case 5: /* leaf2 */
+ sqlite3_result_int(ctx, pSeg->pgnoLast);
+ break;
+ case 6: /* origin1 */
+ sqlite3_result_int64(ctx, pSeg->iOrigin1);
+ break;
+ case 7: /* origin2 */
+ sqlite3_result_int64(ctx, pSeg->iOrigin2);
+ break;
+ case 8: /* npgtombstone */
+ sqlite3_result_int(ctx, pSeg->nPgTombstone);
+ break;
+ case 9: /* nentrytombstone */
+ sqlite3_result_int64(ctx, pSeg->nEntryTombstone);
+ break;
+ case 10: /* nentry */
+ sqlite3_result_int64(ctx, pSeg->nEntry);
+ break;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Initialize a cursor.
+**
+** idxNum==0 means show all subprograms
+** idxNum==1 means show only the main bytecode and omit subprograms.
+*/
+static int fts5structFilterMethod(
+ sqlite3_vtab_cursor *pVtabCursor,
+ int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv
+){
+ Fts5StructVcsr *pCsr = (Fts5StructVcsr *)pVtabCursor;
+ int rc = SQLITE_OK;
+
+ const u8 *aBlob = 0;
+ int nBlob = 0;
+
+ assert( argc==1 );
+ fts5StructureRelease(pCsr->pStruct);
+ pCsr->pStruct = 0;
+
+ nBlob = sqlite3_value_bytes(argv[0]);
+ aBlob = (const u8*)sqlite3_value_blob(argv[0]);
+ rc = fts5StructureDecode(aBlob, nBlob, 0, &pCsr->pStruct);
+ if( rc==SQLITE_OK ){
+ pCsr->iLevel = 0;
+ pCsr->iRowid = 0;
+ pCsr->iSeg = -1;
+ rc = fts5structNextMethod(pVtabCursor);
+ }
+
+ return rc;
+}
+
+#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */
/*
** This is called as part of registering the FTS5 module with database
@@ -7244,7 +8277,7 @@
** SQLite error code is returned instead.
*/
int sqlite3Fts5IndexInit(sqlite3 *db){
-#ifdef SQLITE_TEST
+#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG)
int rc = sqlite3_create_function(
db, "fts5_decode", 2, SQLITE_UTF8, 0, fts5DecodeFunction, 0, 0
);
@@ -7261,6 +8294,37 @@
db, "fts5_rowid", -1, SQLITE_UTF8, 0, fts5RowidFunction, 0, 0
);
}
+
+ if( rc==SQLITE_OK ){
+ static const sqlite3_module fts5structure_module = {
+ 0, /* iVersion */
+ 0, /* xCreate */
+ fts5structConnectMethod, /* xConnect */
+ fts5structBestIndexMethod, /* xBestIndex */
+ fts5structDisconnectMethod, /* xDisconnect */
+ 0, /* xDestroy */
+ fts5structOpenMethod, /* xOpen */
+ fts5structCloseMethod, /* xClose */
+ fts5structFilterMethod, /* xFilter */
+ fts5structNextMethod, /* xNext */
+ fts5structEofMethod, /* xEof */
+ fts5structColumnMethod, /* xColumn */
+ fts5structRowidMethod, /* xRowid */
+ 0, /* xUpdate */
+ 0, /* xBegin */
+ 0, /* xSync */
+ 0, /* xCommit */
+ 0, /* xRollback */
+ 0, /* xFindFunction */
+ 0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
+ };
+ rc = sqlite3_create_module(db, "fts5_structure", &fts5structure_module, 0);
+ }
return rc;
#else
return SQLITE_OK;
diff -Nru sqlite3-3.42.0/ext/fts5/fts5Int.h sqlite3-3.44.0-0/ext/fts5/fts5Int.h
--- sqlite3-3.42.0/ext/fts5/fts5Int.h 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5Int.h 2023-11-04 14:23:58.000000000 +0000
@@ -154,6 +154,10 @@
** attempt to merge together. A value of 1 sets the object to use the
** compile time default. Zero disables auto-merge altogether.
**
+** bContentlessDelete:
+** True if the contentless_delete option was present in the CREATE
+** VIRTUAL TABLE statement.
+**
** zContent:
**
** zContentRowid:
@@ -188,6 +192,7 @@
int nPrefix; /* Number of prefix indexes */
int *aPrefix; /* Sizes in bytes of nPrefix prefix indexes */
int eContent; /* An FTS5_CONTENT value */
+ int bContentlessDelete; /* "contentless_delete=" option (dflt==0) */
char *zContent; /* content table */
char *zContentRowid; /* "content_rowid=" option value */
int bColumnsize; /* "columnsize=" option value (dflt==1) */
@@ -209,6 +214,7 @@
char *zRank; /* Name of rank function */
char *zRankArgs; /* Arguments to rank function */
int bSecureDelete; /* 'secure-delete' */
+ int nDeleteMerge; /* 'deletemerge' */
/* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */
char **pzErrmsg;
@@ -531,6 +537,9 @@
int sqlite3Fts5IndexLoadConfig(Fts5Index *p);
+int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin);
+int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid);
+
/*
** End of interface to code in fts5_index.c.
**************************************************************************/
@@ -615,6 +624,11 @@
*/
void sqlite3Fts5HashClear(Fts5Hash*);
+/*
+** Return true if the hash is empty, false otherwise.
+*/
+int sqlite3Fts5HashIsEmpty(Fts5Hash*);
+
int sqlite3Fts5HashQuery(
Fts5Hash*, /* Hash table to query */
int nPre,
@@ -636,6 +650,7 @@
);
+
/*
** End of interface to code in fts5_hash.c.
**************************************************************************/
diff -Nru sqlite3-3.42.0/ext/fts5/fts5_main.c sqlite3-3.44.0-0/ext/fts5/fts5_main.c
--- sqlite3-3.42.0/ext/fts5/fts5_main.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_main.c 2023-11-04 14:23:58.000000000 +0000
@@ -117,6 +117,8 @@
Fts5Storage *pStorage; /* Document store */
Fts5Global *pGlobal; /* Global (connection wide) data */
Fts5Cursor *pSortCsr; /* Sort data from this cursor */
+ int iSavepoint; /* Successful xSavepoint()+1 */
+ int bInSavepoint;
#ifdef SQLITE_DEBUG
struct Fts5TransactionState ts;
#endif
@@ -405,6 +407,13 @@
pConfig->pzErrmsg = 0;
}
+ if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){
+ rc = sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, (int)1);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
+ }
+
if( rc!=SQLITE_OK ){
fts5FreeVtab(pTab);
pTab = 0;
@@ -1329,6 +1338,9 @@
pCsr->iFirstRowid = fts5GetRowidLimit(pRowidGe, SMALLEST_INT64);
}
+ rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
+ if( rc!=SQLITE_OK ) goto filter_out;
+
if( pTab->pSortCsr ){
/* If pSortCsr is non-NULL, then this call is being made as part of
** processing for a "... MATCH ORDER BY rank" query (ePlan is
@@ -1351,6 +1363,7 @@
pCsr->pExpr = pTab->pSortCsr->pExpr;
rc = fts5CursorFirst(pTab, pCsr, bDesc);
}else if( pCsr->pExpr ){
+ assert( rc==SQLITE_OK );
rc = fts5CursorParseRank(pConfig, pCsr, pRank);
if( rc==SQLITE_OK ){
if( bOrderByRank ){
@@ -1522,6 +1535,7 @@
Fts5Config *pConfig = pTab->p.pConfig;
int rc = SQLITE_OK;
int bError = 0;
+ int bLoadConfig = 0;
if( 0==sqlite3_stricmp("delete-all", zCmd) ){
if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
@@ -1533,6 +1547,7 @@
}else{
rc = sqlite3Fts5StorageDeleteAll(pTab->pStorage);
}
+ bLoadConfig = 1;
}else if( 0==sqlite3_stricmp("rebuild", zCmd) ){
if( pConfig->eContent==FTS5_CONTENT_NONE ){
fts5SetVtabError(pTab,
@@ -1542,6 +1557,7 @@
}else{
rc = sqlite3Fts5StorageRebuild(pTab->pStorage);
}
+ bLoadConfig = 1;
}else if( 0==sqlite3_stricmp("optimize", zCmd) ){
rc = sqlite3Fts5StorageOptimize(pTab->pStorage);
}else if( 0==sqlite3_stricmp("merge", zCmd) ){
@@ -1554,6 +1570,8 @@
}else if( 0==sqlite3_stricmp("prefix-index", zCmd) ){
pConfig->bPrefixIndex = sqlite3_value_int(pVal);
#endif
+ }else if( 0==sqlite3_stricmp("flush", zCmd) ){
+ rc = sqlite3Fts5FlushToDisk(&pTab->p);
}else{
rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
if( rc==SQLITE_OK ){
@@ -1567,6 +1585,12 @@
}
}
}
+
+ if( rc==SQLITE_OK && bLoadConfig ){
+ pTab->p.pConfig->iCookie--;
+ rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
+ }
+
return rc;
}
@@ -1624,7 +1648,6 @@
int eType0; /* value_type() of apVal[0] */
int rc = SQLITE_OK; /* Return code */
int bUpdateOrDelete = 0;
-
/* A transaction must be open when this is called. */
assert( pTab->ts.eState==1 || pTab->ts.eState==2 );
@@ -1654,7 +1677,14 @@
if( pConfig->eContent!=FTS5_CONTENT_NORMAL
&& 0==sqlite3_stricmp("delete", z)
){
- rc = fts5SpecialDelete(pTab, apVal);
+ if( pConfig->bContentlessDelete ){
+ fts5SetVtabError(pTab,
+ "'delete' may not be used with a contentless_delete=1 table"
+ );
+ rc = SQLITE_ERROR;
+ }else{
+ rc = fts5SpecialDelete(pTab, apVal);
+ }
}else{
rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]);
}
@@ -1671,7 +1701,7 @@
** Cases 3 and 4 may violate the rowid constraint.
*/
int eConflict = SQLITE_ABORT;
- if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
+ if( pConfig->eContent==FTS5_CONTENT_NORMAL || pConfig->bContentlessDelete ){
eConflict = sqlite3_vtab_on_conflict(pConfig->db);
}
@@ -1679,8 +1709,12 @@
assert( nArg!=1 || eType0==SQLITE_INTEGER );
/* Filter out attempts to run UPDATE or DELETE on contentless tables.
- ** This is not suported. */
- if( eType0==SQLITE_INTEGER && fts5IsContentless(pTab) ){
+ ** This is not suported. Except - they are both supported if the CREATE
+ ** VIRTUAL TABLE statement contained "contentless_delete=1". */
+ if( eType0==SQLITE_INTEGER
+ && pConfig->eContent==FTS5_CONTENT_NONE
+ && pConfig->bContentlessDelete==0
+ ){
pTab->p.base.zErrMsg = sqlite3_mprintf(
"cannot %s contentless fts5 table: %s",
(nArg>1 ? "UPDATE" : "DELETE from"), pConfig->zName
@@ -1704,7 +1738,8 @@
}
else if( eType0!=SQLITE_INTEGER ){
- /* If this is a REPLACE, first remove the current entry (if any) */
+ /* An INSERT statement. If the conflict-mode is REPLACE, first remove
+ ** the current entry (if any). */
if( eConflict==SQLITE_REPLACE && eType1==SQLITE_INTEGER ){
i64 iNew = sqlite3_value_int64(apVal[1]); /* Rowid to delete */
rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0);
@@ -1767,8 +1802,7 @@
Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
fts5CheckTransactionState(pTab, FTS5_SYNC, 0);
pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg;
- fts5TripCursors(pTab);
- rc = sqlite3Fts5StorageSync(pTab->pStorage);
+ rc = sqlite3Fts5FlushToDisk(&pTab->p);
pTab->p.pConfig->pzErrmsg = 0;
return rc;
}
@@ -2535,6 +2569,12 @@
sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1));
}
pConfig->pzErrmsg = 0;
+ }else if( pConfig->bContentlessDelete && sqlite3_vtab_nochange(pCtx) ){
+ char *zErr = sqlite3_mprintf("cannot UPDATE a subset of "
+ "columns on fts5 contentless-delete table: %s", pConfig->zName
+ );
+ sqlite3_result_error(pCtx, zErr, -1);
+ sqlite3_free(zErr);
}
return rc;
}
@@ -2573,8 +2613,12 @@
sqlite3_vtab *pVtab, /* Virtual table handle */
const char *zName /* New name of table */
){
+ int rc;
Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
- return sqlite3Fts5StorageRename(pTab->pStorage, zName);
+ pTab->bInSavepoint = 1;
+ rc = sqlite3Fts5StorageRename(pTab->pStorage, zName);
+ pTab->bInSavepoint = 0;
+ return rc;
}
int sqlite3Fts5FlushToDisk(Fts5Table *pTab){
@@ -2588,9 +2632,29 @@
** Flush the contents of the pending-terms table to disk.
*/
static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
- UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */
- fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_SAVEPOINT, iSavepoint);
- return sqlite3Fts5FlushToDisk((Fts5Table*)pVtab);
+ Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
+ int rc = SQLITE_OK;
+ char *zSql = 0;
+ fts5CheckTransactionState(pTab, FTS5_SAVEPOINT, iSavepoint);
+
+ if( pTab->bInSavepoint==0 ){
+ zSql = sqlite3_mprintf("INSERT INTO %Q.%Q(%Q) VALUES('flush')",
+ pTab->p.pConfig->zDb, pTab->p.pConfig->zName, pTab->p.pConfig->zName
+ );
+ if( zSql ){
+ pTab->bInSavepoint = 1;
+ rc = sqlite3_exec(pTab->p.pConfig->db, zSql, 0, 0, 0);
+ pTab->bInSavepoint = 0;
+ sqlite3_free(zSql);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ if( rc==SQLITE_OK ){
+ pTab->iSavepoint = iSavepoint+1;
+ }
+ }
+
+ return rc;
}
/*
@@ -2599,9 +2663,16 @@
** This is a no-op.
*/
static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
- UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */
- fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_RELEASE, iSavepoint);
- return sqlite3Fts5FlushToDisk((Fts5Table*)pVtab);
+ Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
+ int rc = SQLITE_OK;
+ fts5CheckTransactionState(pTab, FTS5_RELEASE, iSavepoint);
+ if( (iSavepoint+1)iSavepoint ){
+ rc = sqlite3Fts5FlushToDisk(&pTab->p);
+ if( rc==SQLITE_OK ){
+ pTab->iSavepoint = iSavepoint;
+ }
+ }
+ return rc;
}
/*
@@ -2611,11 +2682,14 @@
*/
static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
- UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */
+ int rc = SQLITE_OK;
fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint);
fts5TripCursors(pTab);
pTab->p.pConfig->pgsz = 0;
- return sqlite3Fts5StorageRollback(pTab->pStorage);
+ if( (iSavepoint+1)<=pTab->iSavepoint ){
+ rc = sqlite3Fts5StorageRollback(pTab->pStorage);
+ }
+ return rc;
}
/*
@@ -2835,9 +2909,46 @@
return 0;
}
+/*
+** Run an integrity check on the FTS5 data structures. Return a string
+** if anything is found amiss. Return a NULL pointer if everything is
+** OK.
+*/
+static int fts5Integrity(
+ sqlite3_vtab *pVtab, /* the FTS5 virtual table to check */
+ const char *zSchema, /* Name of schema in which this table lives */
+ const char *zTabname, /* Name of the table itself */
+ int isQuick, /* True if this is a quick-check */
+ char **pzErr /* Write error message here */
+){
+ Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
+ Fts5Config *pConfig = pTab->p.pConfig;
+ char *zSql;
+ char *zErr = 0;
+ int rc;
+ assert( pzErr!=0 && *pzErr==0 );
+ UNUSED_PARAM(isQuick);
+ zSql = sqlite3_mprintf(
+ "INSERT INTO \"%w\".\"%w\"(\"%w\") VALUES('integrity-check');",
+ zSchema, zTabname, pConfig->zName);
+ if( zSql==0 ) return SQLITE_NOMEM;
+ rc = sqlite3_exec(pConfig->db, zSql, 0, 0, &zErr);
+ sqlite3_free(zSql);
+ if( (rc&0xff)==SQLITE_CORRUPT ){
+ *pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s",
+ zSchema, zTabname);
+ }else if( rc!=SQLITE_OK ){
+ *pzErr = sqlite3_mprintf("unable to validate the inverted index for"
+ " FTS5 table %s.%s: %s",
+ zSchema, zTabname, zErr);
+ }
+ sqlite3_free(zErr);
+ return SQLITE_OK;
+}
+
static int fts5Init(sqlite3 *db){
static const sqlite3_module fts5Mod = {
- /* iVersion */ 3,
+ /* iVersion */ 4,
/* xCreate */ fts5CreateMethod,
/* xConnect */ fts5ConnectMethod,
/* xBestIndex */ fts5BestIndexMethod,
@@ -2860,7 +2971,8 @@
/* xSavepoint */ fts5SavepointMethod,
/* xRelease */ fts5ReleaseMethod,
/* xRollbackTo */ fts5RollbackToMethod,
- /* xShadowName */ fts5ShadowName
+ /* xShadowName */ fts5ShadowName,
+ /* xIntegrity */ fts5Integrity
};
int rc;
diff -Nru sqlite3-3.42.0/ext/fts5/fts5_storage.c sqlite3-3.44.0-0/ext/fts5/fts5_storage.c
--- sqlite3-3.42.0/ext/fts5/fts5_storage.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_storage.c 2023-11-04 14:23:58.000000000 +0000
@@ -77,10 +77,10 @@
"INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */
"REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */
"DELETE FROM %Q.'%q_content' WHERE id=?", /* DELETE_CONTENT */
- "REPLACE INTO %Q.'%q_docsize' VALUES(?,?)", /* REPLACE_DOCSIZE */
+ "REPLACE INTO %Q.'%q_docsize' VALUES(?,?%s)", /* REPLACE_DOCSIZE */
"DELETE FROM %Q.'%q_docsize' WHERE id=?", /* DELETE_DOCSIZE */
- "SELECT sz FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */
+ "SELECT sz%s FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */
"REPLACE INTO %Q.'%q_config' VALUES(?,?)", /* REPLACE_CONFIG */
"SELECT %s FROM %s AS T", /* SCAN */
@@ -128,6 +128,19 @@
break;
}
+ case FTS5_STMT_REPLACE_DOCSIZE:
+ zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName,
+ (pC->bContentlessDelete ? ",?" : "")
+ );
+ break;
+
+ case FTS5_STMT_LOOKUP_DOCSIZE:
+ zSql = sqlite3_mprintf(azStmt[eStmt],
+ (pC->bContentlessDelete ? ",origin" : ""),
+ pC->zDb, pC->zName
+ );
+ break;
+
default:
zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName);
break;
@@ -317,9 +330,11 @@
}
if( rc==SQLITE_OK && pConfig->bColumnsize ){
- rc = sqlite3Fts5CreateTable(
- pConfig, "docsize", "id INTEGER PRIMARY KEY, sz BLOB", 0, pzErr
- );
+ const char *zCols = "id INTEGER PRIMARY KEY, sz BLOB";
+ if( pConfig->bContentlessDelete ){
+ zCols = "id INTEGER PRIMARY KEY, sz BLOB, origin INTEGER";
+ }
+ rc = sqlite3Fts5CreateTable(pConfig, "docsize", zCols, 0, pzErr);
}
if( rc==SQLITE_OK ){
rc = sqlite3Fts5CreateTable(
@@ -396,7 +411,7 @@
){
Fts5Config *pConfig = p->pConfig;
sqlite3_stmt *pSeek = 0; /* SELECT to read row iDel from %_data */
- int rc; /* Return code */
+ int rc = SQLITE_OK; /* Return code */
int rc2; /* sqlite3_reset() return code */
int iCol;
Fts5InsertCtx ctx;
@@ -412,7 +427,6 @@
ctx.pStorage = p;
ctx.iCol = -1;
- rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel);
for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){
if( pConfig->abUnindexed[iCol-1]==0 ){
const char *zText;
@@ -449,6 +463,37 @@
return rc;
}
+/*
+** This function is called to process a DELETE on a contentless_delete=1
+** table. It adds the tombstone required to delete the entry with rowid
+** iDel. If successful, SQLITE_OK is returned. Or, if an error occurs,
+** an SQLite error code.
+*/
+static int fts5StorageContentlessDelete(Fts5Storage *p, i64 iDel){
+ i64 iOrigin = 0;
+ sqlite3_stmt *pLookup = 0;
+ int rc = SQLITE_OK;
+
+ assert( p->pConfig->bContentlessDelete );
+ assert( p->pConfig->eContent==FTS5_CONTENT_NONE );
+
+ /* Look up the origin of the document in the %_docsize table. Store
+ ** this in stack variable iOrigin. */
+ rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0);
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_int64(pLookup, 1, iDel);
+ if( SQLITE_ROW==sqlite3_step(pLookup) ){
+ iOrigin = sqlite3_column_int64(pLookup, 1);
+ }
+ rc = sqlite3_reset(pLookup);
+ }
+
+ if( rc==SQLITE_OK && iOrigin!=0 ){
+ rc = sqlite3Fts5IndexContentlessDelete(p->pIndex, iOrigin, iDel);
+ }
+
+ return rc;
+}
/*
** Insert a record into the %_docsize table. Specifically, do:
@@ -469,10 +514,17 @@
rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0);
if( rc==SQLITE_OK ){
sqlite3_bind_int64(pReplace, 1, iRowid);
- sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC);
- sqlite3_step(pReplace);
- rc = sqlite3_reset(pReplace);
- sqlite3_bind_null(pReplace, 2);
+ if( p->pConfig->bContentlessDelete ){
+ i64 iOrigin = 0;
+ rc = sqlite3Fts5IndexGetOrigin(p->pIndex, &iOrigin);
+ sqlite3_bind_int64(pReplace, 3, iOrigin);
+ }
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC);
+ sqlite3_step(pReplace);
+ rc = sqlite3_reset(pReplace);
+ sqlite3_bind_null(pReplace, 2);
+ }
}
}
return rc;
@@ -536,7 +588,15 @@
/* Delete the index records */
if( rc==SQLITE_OK ){
- rc = fts5StorageDeleteFromIndex(p, iDel, apVal);
+ rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel);
+ }
+
+ if( rc==SQLITE_OK ){
+ if( p->pConfig->bContentlessDelete ){
+ rc = fts5StorageContentlessDelete(p, iDel);
+ }else{
+ rc = fts5StorageDeleteFromIndex(p, iDel, apVal);
+ }
}
/* Delete the %_docsize record */
@@ -1124,7 +1184,9 @@
i64 iLastRowid = sqlite3_last_insert_rowid(p->pConfig->db);
if( p->bTotalsValid ){
rc = fts5StorageSaveTotals(p);
- p->bTotalsValid = 0;
+ if( rc==SQLITE_OK ){
+ p->bTotalsValid = 0;
+ }
}
if( rc==SQLITE_OK ){
rc = sqlite3Fts5IndexSync(p->pIndex);
diff -Nru sqlite3-3.42.0/ext/fts5/fts5_test_tok.c sqlite3-3.44.0-0/ext/fts5/fts5_test_tok.c
--- sqlite3-3.42.0/ext/fts5/fts5_test_tok.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_test_tok.c 2023-11-04 14:23:58.000000000 +0000
@@ -472,7 +472,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
int rc; /* Return code */
diff -Nru sqlite3-3.42.0/ext/fts5/fts5_vocab.c sqlite3-3.44.0-0/ext/fts5/fts5_vocab.c
--- sqlite3-3.42.0/ext/fts5/fts5_vocab.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/fts5_vocab.c 2023-11-04 14:23:58.000000000 +0000
@@ -783,7 +783,8 @@
/* xSavepoint */ 0,
/* xRelease */ 0,
/* xRollbackTo */ 0,
- /* xShadowName */ 0
+ /* xShadowName */ 0,
+ /* xIntegrity */ 0
};
void *p = (void*)pGlobal;
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5aa.test sqlite3-3.44.0-0/ext/fts5/test/fts5aa.test
--- sqlite3-3.42.0/ext/fts5/test/fts5aa.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5aa.test 2023-11-04 14:23:58.000000000 +0000
@@ -65,7 +65,9 @@
do_execsql_test 2.4 {
INSERT INTO t1(t1) VALUES('integrity-check');
-}
+ PRAGMA integrity_check;
+ PRAGMA integrity_check(t1);
+} {ok ok}
#-------------------------------------------------------------------------
@@ -88,6 +90,7 @@
} {
do_execsql_test 3.$i.1 { INSERT INTO t1 VALUES($x, $y) }
do_execsql_test 3.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+ do_execsql_test 3.$i.3 { PRAGMA integrity_check(t1) } ok
if {[set_test_counter errors]} break
}
@@ -135,7 +138,7 @@
10 {ddd abcde dddd dd c} {dddd c c d abcde}
} {
do_execsql_test 5.$i.1 { INSERT INTO t1 VALUES($x, $y) }
- do_execsql_test 5.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+ do_execsql_test 5.$i.2 { PRAGMA integrity_check(t1) } ok
if {[set_test_counter errors]} break
}
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5aux.test sqlite3-3.44.0-0/ext/fts5/test/fts5aux.test
--- sqlite3-3.42.0/ext/fts5/test/fts5aux.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5aux.test 2023-11-04 14:23:58.000000000 +0000
@@ -307,5 +307,31 @@
SELECT group_concat(firstcol(t1), '.') FROM t1 GROUP BY rowid
} {1 {unable to use function firstcol in the requested context}}
-finish_test
+#-------------------------------------------------------------------------
+# Test that xInstCount() works from within an xPhraseQuery() callback.
+#
+reset_db
+
+proc xCallback {cmd} {
+ incr ::hitcount [$cmd xInstCount]
+ return SQLITE_OK
+}
+proc fts5_hitcount {cmd} {
+ set ::hitcount 0
+ $cmd xQueryPhrase 0 xCallback
+ return $::hitcount
+}
+sqlite3_fts5_create_function db fts5_hitcount fts5_hitcount
+
+do_execsql_test 11.1 {
+ CREATE VIRTUAL TABLE x1 USING fts5(z);
+ INSERT INTO x1 VALUES('one two three');
+ INSERT INTO x1 VALUES('one two one three one');
+ INSERT INTO x1 VALUES('one two three');
+}
+do_execsql_test 11.2 {
+ SELECT fts5_hitcount(x1) FROM x1('one') LIMIT 1;
+} {5}
+
+finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5bigid.test sqlite3-3.44.0-0/ext/fts5/test/fts5bigid.test
--- sqlite3-3.42.0/ext/fts5/test/fts5bigid.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5bigid.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,62 @@
+# 2023 May 28
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5bigid
+
+# If SQLITE_ENABLE_FTS5 is not defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+set nRow 20000
+
+proc do_ascdesc_test {tn query} {
+ set ::lAsc [db eval { SELECT rowid FROM x1($query) }]
+ set ::lDesc [db eval { SELECT rowid FROM x1($query) ORDER BY rowid DESC }]
+ do_test $tn.1 { lsort -integer $::lAsc } $::lAsc
+ do_test $tn.2 { lsort -integer -decr $::lDesc } $::lDesc
+ do_test $tn.3 { lsort -integer $::lDesc } $::lAsc
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE x1 USING fts5(a);
+}
+
+do_test 1.1 {
+ for {set ii 0} {$ii < $nRow} {incr ii} {
+ db eval {
+ REPLACE INTO x1(rowid, a) VALUES(random(), 'movement at the station');
+ }
+ }
+} {}
+
+do_ascdesc_test 1.2 "the"
+
+do_execsql_test 1.3 {
+ DELETE FROM x1
+}
+
+do_test 1.4 {
+ for {set ii 0} {$ii < $nRow} {incr ii} {
+ db eval {
+ INSERT INTO x1(rowid, a) VALUES(
+ $ii + 0x6FFFFFFFFFFFFFFF, 'movement at the station'
+ );
+ }
+ }
+} {}
+
+do_ascdesc_test 1.5 "movement"
+
+finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5conflict.test sqlite3-3.44.0-0/ext/fts5/test/fts5conflict.test
--- sqlite3-3.42.0/ext/fts5/test/fts5conflict.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5conflict.test 2023-11-04 14:23:58.000000000 +0000
@@ -65,4 +65,44 @@
INSERT INTO fts_idx(fts_idx) VALUES('integrity-check');
}
+#-------------------------------------------------------------------------
+# Tests for OR IGNORE conflict handling.
+#
+reset_db
+foreach_detail_mode $::testprefix {
+
+ do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(xyz, detail=%DETAIL%);
+
+ BEGIN;
+ INSERT INTO t1(rowid, xyz) VALUES(13, 'thirteen documents');
+ INSERT INTO t1(rowid, xyz) VALUES(14, 'fourteen documents');
+ INSERT INTO t1(rowid, xyz) VALUES(15, 'fifteen documents');
+ COMMIT;
+ }
+
+ set db_cksum [cksum]
+ foreach {tn sql} {
+ 1 {
+ INSERT OR IGNORE INTO t1(rowid, xyz) VALUES(14, 'new text');
+ }
+ 2 {
+ UPDATE OR IGNORE t1 SET rowid=13 WHERE rowid=15;
+ }
+ 3 {
+ INSERT OR IGNORE INTO t1(rowid, xyz)
+ SELECT 13, 'some text'
+ UNION ALL
+ SELECT 14, 'some text'
+ UNION ALL
+ SELECT 15, 'some text'
+ }
+ } {
+ do_execsql_test 3.1.$tn.1 $sql
+ do_test 3.1.$tn.2 { cksum } $db_cksum
+ }
+
+}
+
+
finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5contentless2.test sqlite3-3.44.0-0/ext/fts5/test/fts5contentless2.test
--- sqlite3-3.42.0/ext/fts5/test/fts5contentless2.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5contentless2.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,207 @@
+# 2023 July 19
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This file contains tests for the content= and content_rowid= options.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5contentless2
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+proc vocab {} {
+ list aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm nnn ooo ppp
+}
+
+proc document {nToken} {
+ set doc [list]
+ set vocab [vocab]
+ for {set ii 0} {$ii < $nToken} {incr ii} {
+ lappend doc [lindex $vocab [expr int(rand()*[llength $vocab])]]
+ }
+ set doc
+}
+db func document document
+
+proc contains {doc token} {
+ expr {[lsearch $doc $token]>=0}
+}
+db func contains contains
+
+proc do_compare_tables_test {tn} {
+ uplevel [list do_test $tn {
+ foreach v [vocab] {
+ set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $v) }]
+ set l2 [execsql { SELECT rowid FROM t2($v) }]
+ if {$l1!=$l2} { error "1: query mismatch ($l1) ($l2)" }
+
+ set w "[string range $v 0 1]*"
+ set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $w) }]
+ set l2 [execsql { SELECT rowid FROM t2($w) }]
+ if {$l1!=$l2} { error "2: query mismatch ($l1) ($l2)" }
+
+ set w "[string range $v 0 0]*"
+ set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $w) }]
+ set l2 [execsql { SELECT rowid FROM t2($w) }]
+ if {$l1!=$l2} { error "2: query mismatch ($l1) ($l2)" }
+
+ set l1 [execsql {
+ SELECT rowid FROM t1 WHERE contains(doc, $v) ORDER BY rowid DESC
+ }]
+ set l2 [execsql { SELECT rowid FROM t2($v) ORDER BY rowid DESC }]
+ if {$l1!=$l2} { error "1: query mismatch ($l1) ($l2)" }
+ }
+ set {} {}
+ } {}]
+}
+
+proc lshuffle {in} {
+ set L [list]
+ set ret [list]
+ foreach elem $in { lappend L [list [expr rand()] $elem] }
+ foreach pair [lsort -index 0 $L] { lappend ret [lindex $pair 1] }
+ set ret
+}
+
+expr srand(0)
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t2 USING fts5(
+ doc, prefix=2, content=, contentless_delete=1
+ );
+
+ CREATE TABLE t1(doc);
+ CREATE TRIGGER tr1 AFTER DELETE ON t1 BEGIN
+ DELETE FROM t2 WHERE rowid = old.rowid;
+ END;
+}
+
+set SMALLEST64 -9223372036854775808
+set LARGEST64 9223372036854775807
+
+foreach {tn r1 r2} {
+ 1 0 50
+ 2 $SMALLEST64 $SMALLEST64+50
+ 3 $LARGEST64-50 $LARGEST64
+ 4 -50 -1
+} {
+ set r1 [expr $r1]
+ set r2 [expr $r2]
+
+ do_test 1.1.$tn {
+ execsql BEGIN
+ for {set ii $r1} {$ii <= $r2} {incr ii} {
+ execsql { INSERT INTO t1(rowid, doc) VALUES ($ii, document(8)); }
+ }
+ execsql COMMIT
+ } {}
+}
+do_test 1.2 {
+ db eval { SELECT rowid, doc FROM t1 } {
+ execsql { INSERT INTO t2(rowid, doc) VALUES($rowid, $doc) }
+ }
+} {}
+
+foreach {tn rowid} {
+ 1 $SMALLEST64
+ 2 0
+ 3 -5
+ 4 -30
+ 5 $LARGEST64
+ 6 $LARGEST64-1
+} {
+ set rowid [expr $rowid]
+ do_execsql_test 1.3.$tn.1 {
+ DELETE FROM t1 WHERE rowid=$rowid
+ }
+ do_compare_tables_test 1.3.$tn.2
+}
+
+set iTest 1
+foreach r [lshuffle [execsql {SELECT rowid FROM t1}]] {
+ if {($iTest % 50)==0} {
+ execsql { INSERT INTO t2(t2) VALUES('optimize') }
+ }
+ if {($iTest % 5)==0} {
+ execsql { INSERT INTO t2(t2, rank) VALUES('merge', 5) }
+ }
+ do_execsql_test 1.4.$iTest.1($r) {
+ DELETE FROM t1 WHERE rowid=$r
+ }
+ do_compare_tables_test 1.4.$iTest.2
+ incr iTest
+}
+
+do_execsql_test 1.5 {
+ SELECT * FROM t1
+} {}
+
+#-------------------------------------------------------------------------
+reset_db
+db func document document
+
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t2 USING fts5(doc, content=, contentless_delete=1);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
+ )
+ INSERT INTO t2(rowid, doc) SELECT i, i || ' ' || i FROM s;
+}
+
+do_execsql_test 2.1 {
+ BEGIN;
+ DELETE FROM t2 WHERE rowid=32;
+ DELETE FROM t2 WHERE rowid=64;
+ DELETE FROM t2 WHERE rowid=96;
+ DELETE FROM t2 WHERE rowid=128;
+ DELETE FROM t2 WHERE rowid=160;
+ DELETE FROM t2 WHERE rowid=192;
+ COMMIT;
+}
+
+do_execsql_test 2.2 {
+ SELECT * FROM t2('128');
+} {}
+
+#-------------------------------------------------------------------------
+
+foreach {tn step} {
+ 1 3
+ 2 7
+ 3 15
+} {
+ set step [expr $step]
+
+ reset_db
+ db func document document
+ do_execsql_test 3.$tn.0 {
+ CREATE VIRTUAL TABLE t2 USING fts5(doc, content=, contentless_delete=1);
+ INSERT INTO t2(t2, rank) VALUES('pgsz', 100);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
+ )
+ INSERT INTO t2(rowid, doc) SELECT i, i || ' ' || i FROM s;
+ }
+ do_execsql_test 3.$tn.1 {
+ DELETE FROM t2 WHERE (rowid % $step)==0
+ }
+ do_execsql_test 3.$tn.2 {
+ SELECT * FROM t2( $step * 5 )
+ } {}
+}
+
+
+
+finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5contentless3.test sqlite3-3.44.0-0/ext/fts5/test/fts5contentless3.test
--- sqlite3-3.42.0/ext/fts5/test/fts5contentless3.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5contentless3.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,195 @@
+# 2023 July 21
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This file contains tests for the content= and content_rowid= options.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5contentless3
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content=, contentless_delete=1);
+ BEGIN;
+ INSERT INTO ft VALUES('one one one');
+ INSERT INTO ft VALUES('two two two');
+ INSERT INTO ft VALUES('three three three');
+ INSERT INTO ft VALUES('four four four');
+ INSERT INTO ft VALUES('five five five');
+ INSERT INTO ft VALUES('six six six');
+ INSERT INTO ft VALUES('seven seven seven');
+ INSERT INTO ft VALUES('eight eight eight');
+ INSERT INTO ft VALUES('nine nine nine');
+ COMMIT;
+
+ DELETE FROM ft WHERE rowid=3;
+}
+
+proc myhex {hex} { binary decode hex $hex }
+db func myhex myhex
+
+do_execsql_test 1.1 {
+ UPDATE ft_data SET block =
+ myhex('04000000 00000001' ||
+ '01020304 01020304 01020304 01020304' ||
+ '01020304 01020304 01020304 01020304'
+ )
+ WHERE id = (SELECT max(id) FROM ft_data);
+}
+
+do_execsql_test 1.2 {
+ DELETE FROM ft WHERE rowid=1
+}
+
+do_execsql_test 1.3 {
+ SELECT rowid FROM ft('two');
+} {2}
+
+do_execsql_test 1.3 {
+ UPDATE ft_data SET block =
+ myhex('08000000 00000001' ||
+ '0000000001020304 0000000001020304 0000000001020304 0000000001020304' ||
+ '0000000001020304 0000000001020304 0000000001020304 0000000001020304'
+ )
+ WHERE id = (SELECT max(id) FROM ft_data);
+}
+
+do_execsql_test 1.4 {
+ SELECT rowid FROM ft('two');
+} {2}
+
+do_execsql_test 1.5 {
+ DELETE FROM ft WHERE rowid=4
+}
+
+do_execsql_test 1.6 {
+ UPDATE ft_data SET block = myhex('04000000 00000000')
+ WHERE id = (SELECT max(id) FROM ft_data);
+}
+do_execsql_test 1.7 {
+ SELECT rowid FROM ft('two');
+} {2}
+
+do_execsql_test 1.8 {
+ UPDATE ft_data SET block = myhex('04000000 00000000')
+ WHERE id = (SELECT max(id) FROM ft_data);
+}
+do_execsql_test 1.9 {
+ DELETE FROM ft WHERE rowid=8
+} {}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content=, contentless_delete=1);
+ INSERT INTO ft VALUES('one one one');
+ INSERT INTO ft VALUES('two two two');
+ INSERT INTO ft VALUES('three three three');
+ INSERT INTO ft VALUES('four four four');
+ INSERT INTO ft VALUES('five five five');
+ INSERT INTO ft VALUES('six six six');
+ INSERT INTO ft VALUES('seven seven seven');
+ INSERT INTO ft VALUES('eight eight eight');
+ INSERT INTO ft VALUES('nine nine nine');
+}
+
+do_execsql_test 2.1 {
+ INSERT INTO ft(ft) VALUES('optimize');
+}
+do_execsql_test 2.2 {
+ SELECT count(*) FROM ft_data
+} {3}
+do_execsql_test 2.3 {
+ DELETE FROM ft WHERE rowid=5
+}
+do_execsql_test 2.4 {
+ SELECT count(*) FROM ft_data
+} {4}
+
+# Check that an 'optimize' works (rewrites the index) if there is a single
+# segment with one or more tombstone hash pages.
+do_execsql_test 2.5 {
+ INSERT INTO ft(ft) VALUES('optimize');
+}
+do_execsql_test 2.6 {
+ SELECT count(*) FROM ft_data
+} {3}
+
+# Check that an 'optimize' is a no-op if there is a single segment
+# and no tombstone hash pages.
+do_execsql_test 2.7 {
+ INSERT INTO ft(ft) VALUES('optimize');
+ SELECT rowid FROM ft_data;
+} [db eval {SELECT rowid FROM ft_data}]
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content=, contentless_delete=1);
+ INSERT INTO ft(ft, rank) VALUES('pgsz', 64);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
+ )
+ INSERT INTO ft(rowid, x) SELECT i, i||' '||i||' '||i||' '||i FROM s;
+ INSERT INTO ft(ft) VALUES('optimize');
+}
+
+do_execsql_test 3.1 {
+ SELECT count(*) FROM ft_data
+} {200}
+
+do_execsql_test 3.2 {
+ DELETE FROM ft WHERE (rowid % 50)==0;
+ SELECT count(*) FROM ft_data;
+} {203}
+
+do_execsql_test 3.3 {
+ INSERT INTO ft(ft, rank) VALUES('merge', 500);
+ SELECT rowid FROM ft_data;
+} [db eval {SELECT rowid FROM ft_data}]
+
+do_execsql_test 3.4 {
+ INSERT INTO ft(ft, rank) VALUES('merge', -1000);
+ SELECT count(*) FROM ft_data;
+} {197}
+
+do_execsql_test 3.5 {
+ DELETE FROM ft WHERE (rowid % 50)==1;
+ SELECT count(*) FROM ft_data;
+} {200}
+
+do_execsql_test 3.6 {
+ SELECT level, segment, npgtombstone FROM fts5_structure(
+ (SELECT block FROM ft_data WHERE id=10)
+ )
+} {1 0 3}
+
+do_test 3.6 {
+ while 1 {
+ set nChange [db total_changes]
+ execsql { INSERT INTO ft(ft, rank) VALUES('merge', -5) }
+ if {([db total_changes] - $nChange)<2} break
+ }
+} {}
+
+do_execsql_test 3.7 {
+ SELECT level, segment, npgtombstone FROM fts5_structure(
+ (SELECT block FROM ft_data WHERE id=10)
+ )
+} {2 0 0}
+
+
+finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5contentless4.test sqlite3-3.44.0-0/ext/fts5/test/fts5contentless4.test
--- sqlite3-3.42.0/ext/fts5/test/fts5contentless4.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5contentless4.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,247 @@
+# 2023 July 21
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This file contains tests for the content= and content_rowid= options.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5contentless4
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+proc document {n} {
+ set vocab [list A B C D E F G H I J K L M N O P Q R S T U V W X Y Z]
+ set ret [list]
+ for {set ii 0} {$ii < $n} {incr ii} {
+ lappend ret [lindex $vocab [expr int(rand()*[llength $vocab])]]
+ }
+ set ret
+}
+db func document document
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+ INSERT INTO ft(ft, rank) VALUES('pgsz', 240);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
+ )
+ INSERT INTO ft SELECT document(12) FROM s;
+}
+
+do_execsql_test 1.1 {
+ INSERT INTO ft(ft) VALUES('optimize');
+}
+
+do_execsql_test 1.2 {
+ SELECT level, segment, nentry, nentrytombstone FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+} {0 0 1000 0}
+
+do_execsql_test 1.3 {
+ DELETE FROM ft WHERE rowid < 50
+}
+
+do_execsql_test 1.4 {
+ SELECT level, segment, nentry, nentrytombstone FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+} {0 0 1000 49}
+
+do_execsql_test 1.5 {
+ DELETE FROM ft WHERE rowid < 1000
+}
+
+do_execsql_test 1.6 {
+ SELECT level, segment, nentry, nentrytombstone FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+} {1 0 1 0}
+
+#--------------------------------------------------------------------------
+reset_db
+db func document document
+
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+}
+
+do_test 2.1 {
+ for {set ii 0} {$ii < 5000} {incr ii} {
+ execsql { INSERT INTO ft VALUES( document(12) ) }
+ }
+} {}
+
+do_execsql_test 2.2 {
+ SELECT sum(nentry) - sum(nentrytombstone) FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+} {5000}
+
+for {set ii 5000} {$ii >= 0} {incr ii -100} {
+ do_execsql_test 2.3.$ii {
+ DELETE FROM ft WHERE rowid > $ii
+ }
+ do_execsql_test 2.3.$ii.2 {
+ SELECT
+ CAST((total(nentry) - total(nentrytombstone)) AS integer)
+ FROM
+ fts5_structure( (SELECT block FROM ft_data WHERE id=10) )
+ } $ii
+}
+
+execsql_pp {
+ SELECT * FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+}
+
+do_test 2.4 {
+ for {set ii 0} {$ii < 5000} {incr ii} {
+ execsql { INSERT INTO ft VALUES( document(12) ) }
+ }
+} {}
+
+for {set ii 1} {$ii <= 5000} {incr ii 10} {
+ do_execsql_test 2.3.$ii {
+ DELETE FROM ft WHERE rowid = $ii;
+ INSERT INTO ft VALUES( document(12) );
+ INSERT INTO ft(ft, rank) VALUES('merge', -10);
+ }
+
+ do_execsql_test 2.3.$ii.2 {
+ SELECT
+ CAST((total(nentry) - total(nentrytombstone)) AS integer)
+ FROM
+ fts5_structure( (SELECT block FROM ft_data WHERE id=10) )
+ } 5000
+}
+
+#-------------------------------------------------------------------------
+reset_db
+db func document document
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100
+ )
+ INSERT INTO ft SELECT document(12) FROM s;
+}
+
+do_catchsql_test 3.1 {
+ INSERT INTO ft(ft, rank) VALUES('deletemerge', 'text');
+} {1 {SQL logic error}}
+do_catchsql_test 3.2 {
+ INSERT INTO ft(ft, rank) VALUES('deletemerge', 50);
+} {0 {}}
+do_execsql_test 3.3 {
+ SELECT * FROM ft_config WHERE k='deletemerge'
+} {deletemerge 50}
+do_catchsql_test 3.4 {
+ INSERT INTO ft(ft, rank) VALUES('deletemerge', 101);
+} {0 {}}
+do_execsql_test 3.5 {
+ SELECT * FROM ft_config WHERE k='deletemerge'
+} {deletemerge 101}
+
+do_execsql_test 3.6 {
+ DELETE FROM ft WHERE rowid<95
+}
+
+do_execsql_test 3.7 {
+ SELECT nentrytombstone, nentry FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+} {94 100}
+
+do_execsql_test 3.8 {
+ DELETE FROM ft WHERE rowid=95
+}
+
+do_execsql_test 3.9 {
+ SELECT nentrytombstone, nentry FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+} {95 100}
+
+do_execsql_test 3.10 {
+ DELETE FROM ft;
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100
+ )
+ INSERT INTO ft SELECT document(12) FROM s;
+ INSERT INTO ft(ft, rank) VALUES('deletemerge', 50);
+}
+
+do_execsql_test 3.11 {
+ DELETE FROM ft WHERE rowid<95
+}
+
+do_execsql_test 3.12 {
+ SELECT nentrytombstone, nentry FROM fts5_structure((
+ SELECT block FROM ft_data WHERE id=10
+ ))
+} {0 6}
+
+#-------------------------------------------------------------------------
+reset_db
+db func document document
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE x1 USING fts5(x, content='', contentless_delete=1);
+ INSERT INTO x1(x1, rank) VALUES('usermerge', 16);
+ INSERT INTO x1(x1, rank) VALUES('deletemerge', 40);
+ INSERT INTO x1 VALUES('one');
+ INSERT INTO x1 VALUES('two');
+ INSERT INTO x1 VALUES('three');
+ INSERT INTO x1 VALUES('four');
+ INSERT INTO x1 VALUES('five');
+ INSERT INTO x1 VALUES('six');
+ INSERT INTO x1 VALUES('seven');
+ INSERT INTO x1 VALUES('eight');
+ INSERT INTO x1 VALUES('nine');
+ INSERT INTO x1 VALUES('ten');
+}
+
+do_execsql_test 4.1 {
+ SELECT level, segment FROM fts5_structure((
+ SELECT block FROM x1_data WHERE id=10
+ ))
+} {
+ 0 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9
+}
+
+for {set ii 1} {$ii < 4} {incr ii} {
+ do_execsql_test 4.2.$ii {
+ DELETE FROM x1 WHERE rowid = $ii;
+ INSERT INTO x1(x1, rank) VALUES('merge', 5);
+ SELECT level, segment FROM fts5_structure((
+ SELECT block FROM x1_data WHERE id=10
+ ))
+ } {
+ 0 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9
+ }
+}
+
+do_execsql_test 4.3 {
+ DELETE FROM x1 WHERE rowid = $ii;
+ INSERT INTO x1(x1, rank) VALUES('merge', 5);
+ SELECT level, segment, nentry FROM fts5_structure((
+ SELECT block FROM x1_data WHERE id=10
+ ))
+} {
+ 1 0 6
+}
+
+finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5contentless5.test sqlite3-3.44.0-0/ext/fts5/test/fts5contentless5.test
--- sqlite3-3.42.0/ext/fts5/test/fts5contentless5.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5contentless5.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,58 @@
+# 2023 August 7
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This file contains tests for the content= and content_rowid= options.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5contentless5
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, b, c, content='', contentless_delete=1);
+ INSERT INTO t1 VALUES('A', 'B', 'C');
+ INSERT INTO t1 VALUES('D', 'E', 'F');
+ INSERT INTO t1 VALUES('G', 'H', 'I');
+}
+
+do_execsql_test 1.01 {
+ CREATE TABLE t2(x, y);
+ INSERT INTO t2 VALUES('x', 'y');
+}
+
+# explain_i "UPDATE t1 SET a='a' WHERE t1.rowid=1"
+breakpoint
+explain_i "UPDATE t1 SET a='a' FROM t2 WHERE t1.rowid=1 AND b IS NULL"
+
+#breakpoint
+#explain_i "UPDATE t1 SET a='a' WHERE b IS NULL AND rowid=?"
+
+foreach {tn up err} {
+ 1 "UPDATE t1 SET a='a', b='b', c='c' WHERE rowid=1" 0
+ 2 "UPDATE t1 SET a='a', b='b' WHERE rowid=1" 1
+ 3 "UPDATE t1 SET b='b', c='c' WHERE rowid=1" 1
+ 4 "UPDATE t1 SET a='a', c='c' WHERE rowid=1" 1
+ 5 "UPDATE t1 SET a='a', c='c' WHERE t1.rowid=1 AND b IS NULL" 1
+ 6 "UPDATE t1 SET a='a' FROM t2 WHERE t1.rowid=1" 1
+ 7 "UPDATE t1 SET a='a', b='b', c='c' FROM t2 WHERE t1.rowid=1" 0
+} {
+
+ set res(0) {0 {}}
+ set res(1) {1 {cannot UPDATE a subset of columns on fts5 contentless-delete table: t1}}
+ do_catchsql_test 1.$tn $up $res($err)
+}
+
+finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5contentless.test sqlite3-3.44.0-0/ext/fts5/test/fts5contentless.test
--- sqlite3-3.42.0/ext/fts5/test/fts5contentless.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5contentless.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,270 @@
+# 2014 Dec 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This file contains tests for the content= and content_rowid= options.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5contentless
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+# Check that it is not possible to specify "contentless_delete=1" for
+# anything other than a contentless table.
+#
+set res(0) {0 {}}
+set res(1) {1 {contentless_delete=1 requires a contentless table}}
+foreach {tn sql bError} {
+ 1 "(a, b, contentless_delete=1)" 1
+ 2 "(a, b, contentless_delete=1, content=abc)" 1
+ 3 "(a, b, contentless_delete=1, content=)" 0
+ 4 "(content=, contentless_delete=1, a)" 0
+ 5 "(content='', contentless_delete=1, hello)" 0
+} {
+ execsql { BEGIN }
+ do_catchsql_test 1.$tn "CREATE VIRTUAL TABLE t1 USING fts5 $sql" $res($bError)
+ execsql { ROLLBACK }
+}
+
+# Check that it is not possible to specify "contentless_delete=1"
+# along with columnsize=1.
+#
+set res(0) {0 {}}
+set res(1) {1 {contentless_delete=1 is incompatible with columnsize=0}}
+foreach {tn sql bError} {
+ 2 "(a, b, content='', contentless_delete=1, columnsize=0)" 1
+} {
+ execsql { BEGIN }
+ do_catchsql_test 1.$tn "CREATE VIRTUAL TABLE t1 USING fts5 $sql" $res($bError)
+ execsql { ROLLBACK }
+}
+
+# Check that if contentless_delete=1 is specified, then the "origin"
+# column is added to the %_docsize table.
+reset_db
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE x1 USING fts5(c, content='');
+ CREATE VIRTUAL TABLE x2 USING fts5(c, content='', contentless_delete=1);
+}
+do_execsql_test 3.1 {
+ SELECT sql FROM sqlite_schema WHERE name IN ('x1_docsize', 'x2_docsize');
+} {
+ {CREATE TABLE 'x1_docsize'(id INTEGER PRIMARY KEY, sz BLOB)}
+ {CREATE TABLE 'x2_docsize'(id INTEGER PRIMARY KEY, sz BLOB, origin INTEGER)}
+}
+
+do_execsql_test 3.2.1 {
+ SELECT hex(block) FROM x1_data WHERE id=10
+} {00000000000000}
+do_execsql_test 3.2.2 {
+ SELECT hex(block) FROM x2_data WHERE id=10
+} {00000000FF000001000000}
+
+do_execsql_test 3.3 {
+ INSERT INTO x2 VALUES('first text');
+ INSERT INTO x2 VALUES('second text');
+}
+do_execsql_test 3.4 {
+ SELECT id, origin FROM x2_docsize
+} {1 1 2 2}
+do_execsql_test 3.5 {
+ SELECT level, segment, loc1, loc2 FROM fts5_structure(
+ (SELECT block FROM x2_data WHERE id=10)
+ )
+} {
+ 0 0 1 1
+ 0 1 2 2
+}
+do_execsql_test 3.6 {
+ INSERT INTO x2(x2) VALUES('optimize');
+}
+do_execsql_test 3.7 {
+ SELECT level, segment, loc1, loc2 FROM fts5_structure(
+ (SELECT block FROM x2_data WHERE id=10)
+ )
+} {
+ 1 0 1 2
+}
+
+do_execsql_test 3.8 {
+ DELETE FROM x2 WHERE rowid=2;
+}
+
+do_execsql_test 3.9 {
+ SELECT rowid FROM x2('text')
+} {1}
+
+#--------------------------------------------------------------------------
+reset_db
+proc document {n} {
+ set vocab [list A B C D E F G H I J K L M N O P Q R S T U V W X Y Z]
+ set ret [list]
+ for {set ii 0} {$ii < $n} {incr ii} {
+ lappend ret [lindex $vocab [expr int(rand()*[llength $vocab])]]
+ }
+ set ret
+}
+
+set nRow 1000
+
+do_execsql_test 4.0 {
+ CREATE TABLE t1(x);
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+ INSERT INTO ft(ft, rank) VALUES('pgsz', 100);
+}
+do_test 4.1 {
+ for {set ii 0} {$ii < $nRow} {incr ii} {
+ set doc [document 6]
+ execsql {
+ INSERT INTO t1 VALUES($doc);
+ INSERT INTO ft VALUES($doc);
+ }
+ }
+} {}
+
+foreach v {A B C D E F G H I J K L M N O P Q R S T U V W X Y Z} {
+ set L1 [execsql {SELECT rowid FROM t1 WHERE x LIKE '%'||$v||'%'}]
+ set L2 [execsql {SELECT rowid FROM ft($v)}]
+ do_test 4.2.$v { set L1 } $L2
+}
+
+do_test 4.3 {
+ for {set ii 1} {$ii < $nRow} {incr ii 2} {
+ execsql {
+ DELETE FROM ft WHERE rowid=$ii;
+ DELETE FROM t1 WHERE rowid=$ii;
+ }
+ }
+} {}
+
+foreach v {A B C D E F G H I J K L M N O P Q R S T U V W X Y Z} {
+ set L1 [execsql {SELECT rowid FROM t1 WHERE x LIKE '%'||$v||'%'}]
+ set L2 [execsql {SELECT rowid FROM ft($v)}]
+ do_test 4.4.$v { set L1 } $L2
+}
+
+do_execsql_test 4.5 {
+ INSERT INTO ft(ft) VALUES('optimize');
+} {}
+
+foreach v {A B C D E F G H I J K L M N O P Q R S T U V W X Y Z} {
+ set L1 [execsql {SELECT rowid FROM t1 WHERE x LIKE '%'||$v||'%'}]
+ set L2 [execsql {SELECT rowid FROM ft($v)}]
+ do_test 4.6.$v { set L1 } $L2
+}
+
+#execsql_pp { SELECT fts5_decode(id, block) FROM ft_data }
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 5.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+ INSERT INTO ft(rowid, x) VALUES(1, 'one two three');
+ INSERT INTO ft(rowid, x) VALUES(2, 'one two four');
+ INSERT INTO ft(rowid, x) VALUES(3, 'one two five');
+ INSERT INTO ft(rowid, x) VALUES(4, 'one two seven');
+ INSERT INTO ft(rowid, x) VALUES(5, 'one two eight');
+}
+
+do_execsql_test 5.1 {
+ DELETE FROM ft WHERE rowid=2
+}
+
+do_execsql_test 5.2 {
+ SELECT rowid FROM ft
+} {1 3 4 5}
+
+do_catchsql_test 5.3 {
+ UPDATE ft SET x='four six' WHERE rowid=3
+} {0 {}}
+
+do_execsql_test 5.4 {
+ SELECT rowid FROM ft('one');
+} {1 4 5}
+
+do_execsql_test 5.5 {
+ REPLACE INTO ft(rowid, x) VALUES(3, 'four six');
+ SELECT rowid FROM ft('one');
+} {1 4 5}
+
+do_execsql_test 5.6 {
+ REPLACE INTO ft(rowid, x) VALUES(6, 'one two eleven');
+ SELECT rowid FROM ft('one');
+} {1 4 5 6}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 6.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+ INSERT INTO ft(rowid, x) VALUES(1, 'one two three');
+ INSERT INTO ft(rowid, x) VALUES(2, 'one two four');
+}
+
+do_test 6.1 {
+ db eval { SELECT rowid FROM ft('one two') } {
+ if {$rowid==1} {
+ db eval { INSERT INTO ft(rowid, x) VALUES(3, 'one two four') }
+ }
+ }
+} {}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 7.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+}
+
+set lRowid [list -450 0 1 2 42]
+
+do_test 7.1 {
+ execsql BEGIN
+ foreach r $lRowid {
+ execsql { INSERT INTO ft(rowid, x) VALUES($r, 'one one one'); }
+ }
+ execsql COMMIT
+} {}
+
+do_test 7.2 {
+ execsql BEGIN
+ foreach r $lRowid {
+ execsql { REPLACE INTO ft(rowid, x) VALUES($r, 'two two two'); }
+ }
+ execsql COMMIT
+} {}
+
+do_execsql_test 7.3 { SELECT rowid FROM ft('one'); } {}
+do_execsql_test 7.4 { SELECT rowid FROM ft('two'); } $lRowid
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 8.0 {
+ CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1);
+ INSERT INTO ft VALUES('hello world');
+ INSERT INTO ft VALUES('one two three');
+}
+
+do_catchsql_test 8.1 {
+ INSERT INTO ft(ft, rowid, x) VALUES('delete', 1, 'hello world');
+} {1 {'delete' may not be used with a contentless_delete=1 table}}
+
+do_execsql_test 8.2 {
+ BEGIN;
+ INSERT INTO ft(rowid, x) VALUES(3, 'four four four');
+ DELETE FROM ft WHERE rowid=3;
+ COMMIT;
+ SELECT rowid FROM ft('four');
+} {}
+
+finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5content.test sqlite3-3.44.0-0/ext/fts5/test/fts5content.test
--- sqlite3-3.42.0/ext/fts5/test/fts5content.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5content.test 2023-11-04 14:23:58.000000000 +0000
@@ -294,4 +294,3 @@
} {1 {recursively defined fts5 content table}}
finish_test
-
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5corrupt2.test sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt2.test
--- sqlite3-3.42.0/ext/fts5/test/fts5corrupt2.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt2.test 2023-11-04 14:23:58.000000000 +0000
@@ -167,6 +167,9 @@
do_test 3.$tn.$tn2.2 {
catchsql { INSERT INTO x3(x3) VALUES('integrity-check') }
} {1 {database disk image is malformed}}
+ do_execsql_test 3.$tn.$tn2.3 {
+ PRAGMA integrity_check(x3);
+ } {{malformed inverted index for FTS5 table main.x3}}
}
execsql ROLLBACK
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5corrupt5.test sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt5.test
--- sqlite3-3.42.0/ext/fts5/test/fts5corrupt5.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt5.test 2023-11-04 14:23:58.000000000 +0000
@@ -15,7 +15,7 @@
#
source [file join [file dirname [info script]] fts5_common.tcl]
-set testprefix fts5corrupt3
+set testprefix fts5corrupt5
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
ifcapable !fts5 {
@@ -793,6 +793,94 @@
REPLACE INTO t1(rowid,a,b,rowid) VALUES(200,1,2,3);
} {1 {database disk image is malformed}}
+#-------------------------------------------------------------------------
+reset_db
+do_test 5.0 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+.open --hexdb
+| size 28672 pagesize 4096 filename crash-0c6d3451d11597.db
+| page 1 offset 0
+| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
+| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 07 .....@ ........
+| 32: 00 00 00 00 00 00 00 00 00 00 00 07 00 00 00 04 ................
+| 96: 00 00 00 00 0d 00 00 00 07 0d d2 00 0f c4 0f 6d ...............m
+| 112: 0f 02 0e ab 0e 4e 0d f6 0d d2 00 00 00 00 00 00 .....N..........
+| 3536: 00 00 22 07 06 17 11 11 01 31 74 61 62 6c 65 74 .........1tablet
+| 3552: 32 74 32 07 43 52 45 41 54 45 20 54 41 42 4c 45 2t2.CREATE TABLE
+| 3568: 20 74 32 28 78 29 56 06 06 17 1f 1f 01 7d 74 61 t2(x)V.......ta
+| 3584: 62 6c 65 74 31 5f 63 6f 6e 66 69 67 74 31 5f 63 blet1_configt1_c
+| 3600: 6f 6e 66 69 67 06 43 52 45 41 54 45 20 54 41 42 onfig.CREATE TAB
+| 3616: 4c 45 20 27 74 31 5f 63 6f 6e 66 69 67 27 28 6b LE 't1_config'(k
+| 3632: 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 PRIMARY KEY, v)
+| 3648: 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 5b 05 WITHOUT ROWID[.
+| 3664: 07 17 21 21 01 81 01 74 61 62 6c 65 74 31 5f 64 ..!!...tablet1_d
+| 3680: 6f 63 73 69 7a 65 74 31 5f 64 6f 63 73 69 7a 65 ocsizet1_docsize
+| 3696: 05 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 .CREATE TABLE 't
+| 3712: 31 5f 64 6f 63 73 69 7a 65 27 28 69 64 20 49 4e 1_docsize'(id IN
+| 3728: 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 TEGER PRIMARY KE
+| 3744: 59 2c 20 73 7a 20 42 4c 4f 42 29 55 04 06 17 21 Y, sz BLOB)U...!
+| 3760: 21 01 77 74 61 62 6c 65 74 31 5f 63 6f 6e 74 65 !.wtablet1_conte
+| 3776: 6e 74 74 31 5f 63 6f 6e 74 65 6e 74 04 43 52 45 ntt1_content.CRE
+| 3792: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 63 6f ATE TABLE 't1_co
+| 3808: 6e 74 65 6e 74 27 28 69 64 20 49 4e 54 45 47 45 ntent'(id INTEGE
+| 3824: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 63 R PRIMARY KEY, c
+| 3840: 30 29 69 03 07 17 19 19 01 81 2d 74 61 62 6c 65 0)i.......-table
+| 3856: 74 31 5f 69 64 78 74 31 5f 69 64 78 03 43 52 45 t1_idxt1_idx.CRE
+| 3872: 41 54 45 20 54 41 42 4c 45 20 27 74 31 5f 69 64 ATE TABLE 't1_id
+| 3888: 78 27 28 73 65 67 69 64 2c 20 74 65 72 6d 2c 20 x'(segid, term,
+| 3904: 70 67 6e 6f 2c 20 50 52 49 4d 41 52 59 20 4b 45 pgno, PRIMARY KE
+| 3920: 59 28 73 65 67 69 64 2c 20 74 65 72 6d 29 29 20 Y(segid, term))
+| 3936: 57 49 54 48 4f 55 54 20 52 4f 57 49 44 55 02 07 WITHOUT ROWIDU..
+| 3952: 17 1b 1b 01 81 01 74 61 62 6c 65 74 31 5f 64 61 ......tablet1_da
+| 3968: 74 61 74 31 5f 64 61 74 61 02 43 52 45 41 54 45 tat1_data.CREATE
+| 3984: 20 54 41 42 4c 45 20 27 74 31 5f 64 61 74 61 27 TABLE 't1_data'
+| 4000: 28 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d (id INTEGER PRIM
+| 4016: 41 52 b9 20 4b 45 59 2c 20 62 6c 6f 63 6b 20 42 AR. KEY, block B
+| 4032: 4c 4f 42 29 3a 01 06 17 11 11 08 63 74 61 62 6c LOB):......ctabl
+| 4048: 65 74 31 74 31 43 52 45 41 54 45 20 56 49 52 54 et1t1CREATE VIRT
+| 4064: 55 41 4c 20 54 41 42 4c 45 20 74 31 20 55 53 49 UAL TABLE t1 USI
+| 4080: 4e 47 20 66 74 73 35 28 63 6f 6e 74 65 6e 74 29 NG fts5(content)
+| page 2 offset 4096
+| 0: 0d 00 00 00 03 0f bd 00 0f e8 0f ef 0f bd 00 00 ................
+| 16: 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 4016: 00 00 00 00 00 00 00 00 00 00 00 00 00 24 84 80 .............$..
+| 4032: 80 80 80 01 03 00 4e 00 00 00 1e 06 30 61 62 61 ......N.....0aba
+| 4048: 63 6b 01 02 02 04 02 66 74 02 02 02 04 04 6e 64 ck.....ft.....nd
+| 4064: 6f 6e 03 02 02 04 0a 07 05 01 03 00 10 03 03 0f on..............
+| 4080: 0a 03 00 24 00 00 00 00 01 01 01 00 01 01 01 11 ...$............
+| page 3 offset 8192
+| 0: 0a 00 00 00 01 0f 00 00 00 00 00 00 00 00 00 00 ................
+| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................
+| page 4 offset 12288
+| 0: 0d 00 00 00 03 0f e0 00 0f f6 0f ec 0f e0 00 00 ................
+| 4064: 0a 03 03 00 1b 61 62 61 6e 64 6f 6e 08 02 03 00 .....abandon....
+| 4080: 17 61 62 61 66 74 08 01 03 00 17 61 62 61 63 6b .abaft.....aback
+| page 5 offset 16384
+| 0: 0d 00 00 00 03 0f ee 00 0f fa 0f f4 0f ee 00 00 ................
+| 16: 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 4064: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 03 ................
+| 4080: 03 00 0e 01 04 02 03 00 0e 01 04 01 03 00 0e 01 ................
+| page 6 offset 20480
+| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................
+| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version.
+| page 7 offset 24576
+| 0: 0d 00 00 10 03 0f d6 00 0f f4 10 e1 0f d6 00 00 ................
+| 16: 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 4048: 00 00 00 00 00 00 09 03 02 1b 72 65 62 75 69 6c ..........rebuil
+| 4064: 64 11 02 02 2b 69 6e 74 65 67 72 69 74 79 2d 63 d...+integrity-c
+| 4080: 68 65 63 6b 0a 01 02 1d 6f 70 74 69 6d 00 00 00 heck....optim...
+| end crash-0c6d3451d11597.db
+}]} {}
+
+do_execsql_test 5.1 {
+ INSERT INTO t1(t1,rank) VALUES('secure-delete',1);
+}
+do_catchsql_test 5.4 {
+ UPDATE t1 SET content=randomblob(500);
+} {1 {database disk image is malformed}}
+
+
sqlite3_fts5_may_be_corrupt 0
finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5corrupt7.test sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt7.test
--- sqlite3-3.42.0/ext/fts5/test/fts5corrupt7.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt7.test 2023-11-04 14:23:58.000000000 +0000
@@ -96,4 +96,33 @@
db close
}
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ BEGIN;
+ INSERT INTO t1 VALUES('abc');
+ INSERT INTO t1 VALUES('b d d d');
+ COMMIT;
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+}
+
+execsql_pp {
+ SELECT id, quote(block) FROM t1_data
+}
+
+do_execsql_test 2.1 {
+ SELECT quote(block) FROM t1_data WHERE id > 10;
+} {X'0000001A04306162630102020101620202020101640206030303040806'}
+
+do_execsql_test 2.2 {
+ UPDATE t1_data SET
+ block=X'0000001A04306162630102025501620202020101640206030303040806'
+ WHERE id>10
+}
+
+do_catchsql_test 2.3 {
+ DELETE FROM t1 WHERE rowid = 1
+} {1 {database disk image is malformed}}
+
finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5corrupt.test sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt.test
--- sqlite3-3.42.0/ext/fts5/test/fts5corrupt.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5corrupt.test 2023-11-04 14:23:58.000000000 +0000
@@ -48,6 +48,10 @@
}
catchsql { INSERT INTO t1(t1) VALUES('integrity-check') }
} {1 {database disk image is malformed}}
+do_execsql_test 1.3b {
+ PRAGMA integrity_check(t1);
+} {{malformed inverted index for FTS5 table main.t1}}
+
do_test 1.4 {
db_restore_and_reopen
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5eb.test sqlite3-3.44.0-0/ext/fts5/test/fts5eb.test
--- sqlite3-3.42.0/ext/fts5/test/fts5eb.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5eb.test 2023-11-04 14:23:58.000000000 +0000
@@ -95,6 +95,9 @@
SELECT rowid, bm25(e1) FROM e1 WHERE e1 MATCH '"/" OR "just"' ORDER BY rank;
} {1 -1e-06}
+do_execsql_test 3.4 "
+ SELECT fts5_expr_tcl('e AND \" \"');
+" {{AND [nearset -- {e}] [{}]}}
finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5faultF.test sqlite3-3.44.0-0/ext/fts5/test/fts5faultF.test
--- sqlite3-3.42.0/ext/fts5/test/fts5faultF.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5faultF.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,111 @@
+# 2023 July 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+#
+# This file is focused on OOM errors. Particularly those that may occur
+# when using contentless_delete=1 databases.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+source $testdir/malloc_common.tcl
+set testprefix fts5faultF
+
+# If SQLITE_ENABLE_FTS5 is not defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+faultsim_save_and_close
+do_faultsim_test 1 -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql {
+ CREATE VIRTUAL TABLE t1 USING fts5(x, y, content=, contentless_delete=1)
+ }
+} -test {
+ faultsim_test_result {0 {}} {1 {vtable constructor failed: t1}}
+}
+
+reset_db
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(doc, content=, contentless_delete=1);
+ BEGIN;
+ INSERT INTO t1(rowid, doc) VALUES(1, 'a b c d');
+ INSERT INTO t1(rowid, doc) VALUES(2, 'a b c d');
+ INSERT INTO t1(rowid, doc) VALUES(3, 'a b c d');
+ INSERT INTO t1(rowid, doc) VALUES(4, 'a b c d');
+ COMMIT;
+ DELETE FROM t1 WHERE rowid IN (2, 4);
+}
+
+do_faultsim_test 2 -prep {
+ sqlite3 db test.db
+ execsql { SELECT rowid FROM t1 }
+} -body {
+ execsql {
+ SELECT rowid FROM t1('b c');
+ }
+} -test {
+ faultsim_test_result {0 {1 3}}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(doc, content=, contentless_delete=1);
+ BEGIN;
+ INSERT INTO t1(rowid, doc) VALUES(1, 'a b c d');
+ INSERT INTO t1(rowid, doc) VALUES(2, 'a b c d');
+ INSERT INTO t1(rowid, doc) VALUES(3, 'a b c d');
+ INSERT INTO t1(rowid, doc) VALUES(4, 'a b c d');
+ COMMIT;
+}
+
+faultsim_save_and_close
+do_faultsim_test 3 -prep {
+ faultsim_restore_and_reopen
+ execsql { SELECT rowid FROM t1 }
+} -body {
+ execsql {
+ INSERT INTO t1(rowid, doc) VALUES(5, 'a b c d');
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(doc, content=, contentless_delete=1);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000
+ )
+ INSERT INTO t1(rowid, doc) SELECT i, 'a b c d' FROM s;
+}
+
+do_execsql_test 4.1 { DELETE FROM t1 WHERE rowid <= 25 }
+
+faultsim_save_and_close
+do_faultsim_test 4 -faults oom-t* -prep {
+ faultsim_restore_and_reopen
+ execsql { SELECT rowid FROM t1 }
+} -body {
+ execsql {
+ DELETE FROM t1 WHERE rowid < 100
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+
+finish_test
+
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5faultG.test sqlite3-3.44.0-0/ext/fts5/test/fts5faultG.test
--- sqlite3-3.42.0/ext/fts5/test/fts5faultG.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5faultG.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,50 @@
+# 2010 June 15
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+source $testdir/malloc_common.tcl
+set testprefix fts5faultG
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+set ::testprefix fts5faultG
+
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a);
+ INSERT INTO t1 VALUES('test renaming the table');
+ INSERT INTO t1 VALUES(' after it has been written');
+ INSERT INTO t1 VALUES(' actually other stuff instead');
+}
+faultsim_save_and_close
+do_faultsim_test 1 -faults oom* -prep {
+ faultsim_restore_and_reopen
+ execsql {
+ BEGIN;
+ DELETE FROM t1 WHERE rowid=2;
+ }
+} -body {
+ execsql {
+ DELETE FROM t1;
+ }
+} -test {
+ catchsql { COMMIT }
+ faultsim_integrity_check
+ faultsim_test_result {0 {}}
+}
+
+
+finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5integrity.test sqlite3-3.44.0-0/ext/fts5/test/fts5integrity.test
--- sqlite3-3.42.0/ext/fts5/test/fts5integrity.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5integrity.test 2023-11-04 14:23:58.000000000 +0000
@@ -77,6 +77,9 @@
UPDATE aa_docsize SET sz = X'44' WHERE rowid = 3;
INSERT INTO aa(aa) VALUES('integrity-check');
} {1 {database disk image is malformed}}
+do_execsql_test 4.2.1 {
+ PRAGMA integrity_check(aa);
+} {{malformed inverted index for FTS5 table main.aa}}
do_catchsql_test 4.3 {
ROLLBACK;
@@ -317,4 +320,39 @@
INSERT INTO vt0(vt0) VALUES('integrity-check');
} {0 {}}
+reset_db
+proc slang {in} {return [string map {th d e eh} $in]}
+db function slang -deterministic -innocuous slang
+do_execsql_test 11.0 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c TEXT AS (slang(b)));
+ INSERT INTO t1(b) VALUES('the quick fox jumps over the lazy brown dog');
+ SELECT c FROM t1;
+} {{deh quick fox jumps ovehr deh lazy brown dog}}
+
+do_execsql_test 11.1 {
+ CREATE VIRTUAL TABLE t2 USING fts5(content="t1", c);
+ INSERT INTO t2(t2) VALUES('rebuild');
+ SELECT rowid FROM t2 WHERE t2 MATCH 'deh';
+} {1}
+
+do_execsql_test 11.2 {
+ PRAGMA integrity_check(t2);
+} {ok}
+db close
+sqlite3 db test.db
+
+# FIX ME?
+#
+# FTS5 integrity-check does not care if the content table is unreadable or
+# does not exist. It only looks for internal inconsistencies in the
+# inverted index.
+#
+do_execsql_test 11.3 {
+ PRAGMA integrity_check(t2);
+} {ok}
+do_execsql_test 11.4 {
+ DROP TABLE t1;
+ PRAGMA integrity_check(t2);
+} {ok}
+
finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5misc.test sqlite3-3.44.0-0/ext/fts5/test/fts5misc.test
--- sqlite3-3.42.0/ext/fts5/test/fts5misc.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5misc.test 2023-11-04 14:23:58.000000000 +0000
@@ -44,12 +44,12 @@
do_catchsql_test 1.3.1 {
SELECT highlight(t1, 4, '', '') FROM t1('*reads');
-} {1 {no such cursor: 1}}
+} {1 {no such cursor: 2}}
do_catchsql_test 1.3.2 {
SELECT a FROM t1
WHERE rank = (SELECT highlight(t1, 4, '', '') FROM t1('*reads'));
-} {1 {no such cursor: 1}}
+} {1 {no such cursor: 2}}
db close
sqlite3 db test.db
@@ -424,10 +424,12 @@
SAVEPOINT one;
} {}
do_execsql_test 15.4 END
-do_test 15.4 {
+do_test 15.5 {
list [catch { db2 eval COMMIT } msg] $msg
} {0 {}}
+db2 close
+
#-------------------------------------------------------------------------
reset_db
forcedelete test.db2
@@ -469,6 +471,8 @@
SELECT * FROM x1
} {abc def}
+db2 close
+
#-------------------------------------------------------------------------
reset_db
do_execsql_test 17.1 {
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5optimize2.test sqlite3-3.44.0-0/ext/fts5/test/fts5optimize2.test
--- sqlite3-3.42.0/ext/fts5/test/fts5optimize2.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5optimize2.test 2023-11-04 14:23:58.000000000 +0000
@@ -9,7 +9,7 @@
#
#***********************************************************************
#
-# TESTRUNNER: slow
+# TESTRUNNER: superslow
#
source [file join [file dirname [info script]] fts5_common.tcl]
@@ -42,23 +42,4 @@
SELECT count(*) FROM t1('mno')
} $nLoop
-do_execsql_test 2.0 {
- CREATE VIRTUAL TABLE t2 USING fts5(x);
- INSERT INTO t2(t2, rank) VALUES('pgsz', 32);
-}
-
-do_test 2.1 {
- for {set ii 0} {$ii < $nLoop} {incr ii} {
- execsql {
- INSERT INTO t2 VALUES('abc def ghi');
- INSERT INTO t2 VALUES('jkl mno pqr');
- INSERT INTO t2(t2, rank) VALUES('merge', -1);
- }
- }
-} {}
-
-do_execsql_test 2.2 {
- SELECT count(*) FROM t2('mno')
-} $nLoop
-
finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5optimize3.test sqlite3-3.44.0-0/ext/fts5/test/fts5optimize3.test
--- sqlite3-3.42.0/ext/fts5/test/fts5optimize3.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5optimize3.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,45 @@
+# 2023 Aug 27
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# TESTRUNNER: superslow
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5optimize2
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+set nLoop 2500
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t2 USING fts5(x);
+ INSERT INTO t2(t2, rank) VALUES('pgsz', 32);
+}
+
+do_test 1.1 {
+ for {set ii 0} {$ii < $nLoop} {incr ii} {
+ execsql {
+ INSERT INTO t2 VALUES('abc def ghi');
+ INSERT INTO t2 VALUES('jkl mno pqr');
+ INSERT INTO t2(t2, rank) VALUES('merge', -1);
+ }
+ }
+} {}
+
+do_execsql_test 1.2 {
+ SELECT count(*) FROM t2('mno')
+} $nLoop
+
+finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5rank.test sqlite3-3.44.0-0/ext/fts5/test/fts5rank.test
--- sqlite3-3.42.0/ext/fts5/test/fts5rank.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5rank.test 2023-11-04 14:23:58.000000000 +0000
@@ -180,4 +180,28 @@
{table table table} {the table names.} {rank on an fts5 table}
}
+
+#-------------------------------------------------------------------------
+# forum post: https://sqlite.org/forum/forumpost/a2dd636330
+#
+reset_db
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t USING fts5 (a, b);
+ INSERT INTO t (a, b) VALUES ('data1', 'sentence1'), ('data2', 'sentence2');
+ INSERT INTO t(t, rank) VALUES ('rank', 'bm25(10.0,1.0)');
+}
+
+sqlite3 db2 test.db
+do_execsql_test -db db2 1.1 {
+ SELECT *, rank<0.0 FROM t('data*') ORDER BY RANK;
+} {data1 sentence1 1 data2 sentence2 1}
+
+do_execsql_test 1.2 {
+ INSERT INTO t(t, rank) VALUES ('rank', 'bm25(10.0,1.0)');
+}
+do_execsql_test -db db2 1.3 {
+ SELECT *, rank<0.0 FROM t('data*') ORDER BY RANK;
+} {data1 sentence1 1 data2 sentence2 1}
+db2 close
+
finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5savepoint.test sqlite3-3.44.0-0/ext/fts5/test/fts5savepoint.test
--- sqlite3-3.42.0/ext/fts5/test/fts5savepoint.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5savepoint.test 2023-11-04 14:23:58.000000000 +0000
@@ -71,7 +71,7 @@
do_catchsql_test 3.2 {
DROP TABLE vt1;
- } {1 {SQL logic error}}
+ } {0 {}}
do_execsql_test 3.3 {
SAVEPOINT x;
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5secure6.test sqlite3-3.44.0-0/ext/fts5/test/fts5secure6.test
--- sqlite3-3.42.0/ext/fts5/test/fts5secure6.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5secure6.test 2023-11-04 14:23:58.000000000 +0000
@@ -18,7 +18,7 @@
set ::PHC 0
proc progress_handler {args} {
incr ::PHC
- if {($::PHC % 100000)==0} breakpoint
+ # if {($::PHC % 100000)==0} breakpoint
return 0
}
@@ -50,6 +50,92 @@
expr $phc(1)*5 < $phc(2)
} {1}
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd)
+}
+
+do_execsql_test 2.1 {
+ BEGIN;
+ INSERT INTO t1(rowid, x) VALUES(-100000, 'abc def ghi');
+ INSERT INTO t1(rowid, x) VALUES(-99999, 'abc def ghi');
+ INSERT INTO t1(rowid, x) VALUES(9223372036854775800, 'abc def ghi');
+ COMMIT;
+}
+
+do_execsql_test 2.2 {
+ SELECT rowid FROM t1('def')
+} {-100000 -99999 9223372036854775800}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', $sd)
+}
+
+do_execsql_test 3.1 {
+ BEGIN;
+ INSERT INTO t1(rowid, x)
+ VALUES(51869, 'when whenever where weress what turn'),
+ (51871, 'to were');
+ COMMIT;
+}
+
+do_execsql_test 3.2 {
+ DELETE FROM t1 WHERE rowid=51871;
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1(rowid, x) VALUES(10, 'one two');
+}
+do_execsql_test 4.1 {
+ UPDATE t1 SET x = 'one three' WHERE rowid=10;
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+}
+do_execsql_test 4.2 {
+ DELETE FROM t1 WHERE rowid=10;
+}
+do_execsql_test 4.3 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 5.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(content);
+
+ INSERT INTO t1(t1,rank) VALUES('secure-delete',1);
+ INSERT INTO t1 VALUES('active'),('boomer'),('atom'),('atomic'),
+ ('alpha channel backup abandon test aback boomer atom alpha active');
+ DELETE FROM t1 WHERE t1 MATCH 'abandon';
+}
+
+do_execsql_test 5.1 {
+ INSERT INTO t1(t1) VALUES('rebuild');
+}
+
+do_execsql_test 5.2 {
+ DELETE FROM t1 WHERE rowid NOTNULL<5;
+}
+
+db close
+sqlite3 db test.db
+
+do_execsql_test 5.3 {
+ PRAGMA integrity_check;
+} {ok}
+
finish_test
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5secure7.test sqlite3-3.44.0-0/ext/fts5/test/fts5secure7.test
--- sqlite3-3.42.0/ext/fts5/test/fts5secure7.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5secure7.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,116 @@
+# 2023 Feb 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+#
+# TESTRUNNER: slow
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+ifcapable !fts5 { finish_test ; return }
+set ::testprefix fts5secure7
+
+
+set NVOCAB 500
+set NDOC [expr 1000]
+
+set NREP 100
+set nDeletePerRep [expr 5]
+
+set VOCAB [list]
+
+proc select_one {list} {
+ set n [llength $list]
+ lindex $list [expr {abs(int(rand()*$n))}]
+}
+
+proc init_vocab {} {
+ set L [split "abcdefghijklmnopqrstuvwxyz" {}]
+ set nL [llength $L]
+ for {set i 0} {$i < $::NVOCAB} {incr i} {
+ set n [expr {6 + int(rand()*8)}]
+ set word ""
+ for {set j 0} {$j < $n} {incr j} {
+ append word [select_one $L]
+ }
+ lappend ::VOCAB $word
+ }
+}
+
+proc get_word {} {
+ select_one $::VOCAB
+}
+
+proc get_document {nWord} {
+ set ret [list]
+ for {set i 0} {$i < $nWord} {incr i} {
+ lappend ret [get_word]
+ }
+ return $ret
+}
+
+init_vocab
+
+db func document [list get_document 12]
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(body);
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+}
+do_execsql_test 1.1 {
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$NDOC
+ )
+ INSERT INTO t1 SELECT document() FROM s;
+}
+
+for {set iRep 0} {$iRep < $NREP} {incr iRep} {
+ set lRowid [db eval {SELECT rowid FROM t1}]
+ for {set iDel 0} {$iDel < $nDeletePerRep} {incr iDel} {
+ set idx [select_one $lRowid]
+ db eval {
+ DELETE FROM t1 WHERE rowid=$idx
+ }
+ }
+ db eval {
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$nDeletePerRep
+ )
+ INSERT INTO t1 SELECT document() FROM s;
+ }
+ do_execsql_test 1.2.$iRep {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+ }
+}
+
+reset_db
+db func document [list get_document 12]
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(body);
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 128);
+}
+do_execsql_test 2.1 {
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<$NDOC
+ )
+ INSERT INTO t1 SELECT document() FROM s;
+}
+for {set ii 0} {$ii < $NDOC} {incr ii} {
+ set lRowid [db eval {SELECT rowid FROM t1}]
+ set idx [select_one $lRowid]
+ db eval { DELETE FROM t1 WHERE rowid=$idx }
+ do_execsql_test 2.2.$ii {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+ }
+}
+
+finish_test
+
+
diff -Nru sqlite3-3.42.0/ext/fts5/test/fts5secure.test sqlite3-3.44.0-0/ext/fts5/test/fts5secure.test
--- sqlite3-3.42.0/ext/fts5/test/fts5secure.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/fts5/test/fts5secure.test 2023-11-04 14:23:58.000000000 +0000
@@ -273,6 +273,76 @@
do_execsql_test 5.4 { SELECT rowid FROM t1('abc'); } 2
do_execsql_test 5.5 { SELECT rowid FROM t1('aa'); } 2
+#-------------------------------------------------------------------------
+# Tests for the bug fixed by https://sqlite.org/src/info/4b60a1c3
+#
+reset_db
+do_execsql_test 6.0 {
+ CREATE VIRTUAL TABLE fts USING fts5(content);
+ INSERT INTO fts(fts, rank) VALUES ('secure-delete', 1);
+ INSERT INTO fts(rowid, content) VALUES
+ (3407, 'profile profile profile profile profile profile profile profile pull pulling pulling really');
+ DELETE FROM fts WHERE rowid IS 3407;
+ INSERT INTO fts(fts) VALUES ('integrity-check');
+}
+
+foreach {tn detail} {
+ 1 full
+ 2 column
+ 3 none
+} {
+ do_execsql_test 6.1.$detail "
+ DROP TABLE IF EXISTS t1;
+ CREATE VIRTUAL TABLE t1 USING fts5(x, detail=$detail);
+ "
+
+ do_execsql_test 6.2.$detail {
+ INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
+ }
+
+ for {set ii 1} {$ii < 100} {incr ii} {
+ do_execsql_test 6.3.$detail.$ii.1 {
+ BEGIN;
+ INSERT INTO t1(rowid, x) VALUES(10, 'word1');
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i/dev/null))
+# SQLITE_C_IS_SEE := 0
+#else
+# SQLITE_C_IS_SEE := 1
+# $(info This is an SEE build.)
+#endif
+
+.NOTPARALLEL: $(sqlite3.h)
+$(sqlite3.h):
+ $(MAKE) -C $(dir.top) sqlite3.c
+$(sqlite3.c): $(sqlite3.h)
+
+opt.threadsafe ?= 1
+opt.fatal-oom ?= 1
+opt.debug ?= 1
+opt.metrics ?= 1
+SQLITE_OPT = \
+ -DSQLITE_THREADSAFE=$(opt.threadsafe) \
+ -DSQLITE_TEMP_STORE=2 \
+ -DSQLITE_USE_URI=1 \
+ -DSQLITE_OMIT_LOAD_EXTENSION \
+ -DSQLITE_OMIT_DEPRECATED \
+ -DSQLITE_OMIT_SHARED_CACHE \
+ -DSQLITE_C=$(sqlite3.c) \
+ -DSQLITE_JNI_FATAL_OOM=$(opt.fatal-oom) \
+ -DSQLITE_JNI_ENABLE_METRICS=$(opt.metrics)
+
+opt.extras ?= 1
+ifeq (1,$(opt.extras))
+SQLITE_OPT += -DSQLITE_ENABLE_RTREE \
+ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
+ -DSQLITE_ENABLE_STMTVTAB \
+ -DSQLITE_ENABLE_DBPAGE_VTAB \
+ -DSQLITE_ENABLE_DBSTAT_VTAB \
+ -DSQLITE_ENABLE_BYTECODE_VTAB \
+ -DSQLITE_ENABLE_OFFSET_SQL_FUNC \
+ -DSQLITE_ENABLE_PREUPDATE_HOOK \
+ -DSQLITE_ENABLE_NORMALIZE \
+ -DSQLITE_ENABLE_SQLLOG
+endif
+
+ifeq (1,$(opt.debug))
+ SQLITE_OPT += -DSQLITE_DEBUG -g -DDEBUG -UNDEBUG
+else
+ SQLITE_OPT += -Os
+endif
+
+ifeq (1,$(enable.fts5))
+ SQLITE_OPT += -DSQLITE_ENABLE_FTS5
+endif
+
+sqlite3-jni.c := $(dir.src.c)/sqlite3-jni.c
+sqlite3-jni.o := $(dir.bld.c)/sqlite3-jni.o
+sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h
+package.dll := $(dir.bld.c)/libsqlite3-jni.so
+# All javac-generated .h files must be listed in $(sqlite3-jni.h.in):
+sqlite3-jni.h.in :=
+# $(java.with.jni) lists all Java files which contain JNI decls:
+java.with.jni :=
+define ADD_JNI_H
+sqlite3-jni.h.in += $$(dir.bld.c)/org_sqlite_jni$(3)_$(2).h
+java.with.jni += $(1)/$(2).java
+$$(dir.bld.c)/org_sqlite_jni$(3)_$(2).h: $(1)/$(2).java
+endef
+# Invoke ADD_JNI_H once for each Java file which includes JNI
+# declarations:
+$(eval $(call ADD_JNI_H,$(dir.src.capi),CApi,_capi))
+$(eval $(call ADD_JNI_H,$(dir.src.capi),SQLTester,_capi))
+ifeq (1,$(enable.fts5))
+ $(eval $(call ADD_JNI_H,$(dir.src.fts5),Fts5ExtensionApi,_fts5))
+ $(eval $(call ADD_JNI_H,$(dir.src.fts5),fts5_api,_fts5))
+ $(eval $(call ADD_JNI_H,$(dir.src.fts5),fts5_tokenizer,_fts5))
+endif
+$(sqlite3-jni.h.in): $(dir.bld.c)
+
+#package.dll.cfiles :=
+package.dll.cflags = \
+ -std=c99 \
+ -fPIC \
+ -I. \
+ -I$(dir $(sqlite3.h)) \
+ -I$(dir.src.c) \
+ -I$(JDK_HOME)/include \
+ $(patsubst %,-I%,$(patsubst %.h,,$(wildcard $(JDK_HOME)/include/*))) \
+ -Wall
+# The gross $(patsubst...) above is to include the platform-specific
+# subdir which lives under $(JDK_HOME)/include and is a required
+# include path for client-level code.
+#
+# Using (-Wall -Wextra) triggers an untennable number of
+# gcc warnings from sqlite3.c for mundane things like
+# unused parameters.
+########################################################################
+ifeq (1,$(enable.tester))
+ package.dll.cflags += -DSQLITE_JNI_ENABLE_SQLTester
+endif
+
+$(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE)
+ @cat $(sqlite3-jni.h.in) > $@.tmp
+ @if cmp $@ $@.tmp >/dev/null; then \
+ rm -f $@.tmp; \
+ echo "$@ not modified"; \
+ else \
+ mv $@.tmp $@; \
+ echo "Updated $@"; \
+ fi
+ @if [ x1 != x$(enable.fts5) ]; then \
+ echo "*** REMINDER:"; \
+ echo "*** enable.fts5=0, so please do not check in changes to $@."; \
+ fi
+
+$(package.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h)
+$(package.dll): $(sqlite3-jni.c) $(MAKEFILE)
+ $(CC) $(package.dll.cflags) $(SQLITE_OPT) \
+ $(sqlite3-jni.c) -shared -o $@
+all: $(package.dll)
+
+.PHONY: test test-one
+Tester1.flags ?=
+Tester2.flags ?=
+test.flags.jvm = -ea -Djava.library.path=$(dir.bld.c) \
+ $(java.flags) -cp $(classpath)
+test.deps := $(CLASS_FILES) $(package.dll)
+test-one: $(test.deps)
+ $(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 $(Tester1.flags)
+ $(bin.java) $(test.flags.jvm) org.sqlite.jni.wrapper1.Tester2 $(Tester2.flags)
+test-sqllog: $(test.deps)
+ @echo "Testing with -sqllog..."
+ $(bin.java) $(test.flags.jvm) -sqllog
+test-mt: $(test.deps)
+ @echo "Testing in multi-threaded mode:";
+ $(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 \
+ -t 7 -r 50 -shuffle $(Tester1.flags)
+ $(bin.java) $(test.flags.jvm) org.sqlite.jni.wrapper1.Tester2 \
+ -t 7 -r 50 -shuffle $(Tester2.flags)
+
+test: test-one test-mt
+tests: test test-sqllog
+
+tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test))
+tester.flags ?= # --verbose
+.PHONY: tester tester-local tester-ext
+ifeq (1,$(enable.tester))
+tester-local: $(CLASS_FILES.tester) $(package.dll)
+ $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
+ $(java.flags) -cp $(classpath) \
+ org.sqlite.jni.capi.SQLTester $(tester.flags) $(tester.scripts)
+tester: tester-local
+else
+tester:
+ @echo "SQLTester support is disabled."
+endif
+
+tester.extdir.default := $(dir.tests)/ext
+tester.extdir ?= $(tester.extdir.default)
+tester.extern-scripts := $(wildcard $(tester.extdir)/*.test)
+ifneq (,$(tester.extern-scripts))
+tester-ext:
+ $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
+ $(java.flags) -cp $(classpath) \
+ org.sqlite.jni.capi.SQLTester $(tester.flags) $(tester.extern-scripts)
+else
+tester-ext:
+ @echo "******************************************************"; \
+ echo "*** Include the out-of-tree test suite in the 'tester'"; \
+ echo "*** target by either symlinking its directory to"; \
+ echo "*** $(tester.extdir.default) or passing it to make"; \
+ echo "*** as tester.extdir=/path/to/that/dir."; \
+ echo "******************************************************";
+endif
+
+tester-ext: tester-local
+tester: tester-ext
+tests: tester
+########################################################################
+# Build each SQLITE_THREADMODE variant and run all tests against them.
+multitest: clean
+define MULTIOPT
+multitest: multitest-$(1)
+multitest-$(1):
+ $$(MAKE) opt.debug=$$(opt.debug) $(patsubst %,opt.%,$(2)) \
+ tests clean enable.fts5=1
+endef
+
+$(eval $(call MULTIOPT,01,threadsafe=0 oom=1))
+$(eval $(call MULTIOPT,00,threadsafe=0 oom=0))
+$(eval $(call MULTIOPT,11,threadsafe=1 oom=1))
+$(eval $(call MULTIOPT,10,threadsafe=1 oom=0))
+$(eval $(call MULTIOPT,21,threadsafe=2 oom=1))
+$(eval $(call MULTIOPT,20,threadsafe=2 oom=0))
+
+
+########################################################################
+# jar bundle...
+package.jar.in := $(abspath $(dir.src)/jar.in)
+CLEAN_FILES += $(package.jar.in)
+JAVA_FILES.jar := $(JAVA_FILES.main) $(JAVA_FILES.unittest) $(JAVA_FILES.package.info)
+CLASS_FILES.jar := $(filter-out %/package-info.class,$(JAVA_FILES.jar:.java=.class))
+$(package.jar.in): $(package.dll) $(MAKEFILE)
+ ls -1 \
+ $(dir.src.jni)/*/*.java $(dir.src.jni)/*/*.class \
+ | sed -e 's,^$(dir.src)/,,' | sort > $@
+
+$(package.jar): $(CLASS_FILES.jar) $(MAKEFILE) $(package.jar.in)
+ @rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~
+ cd $(dir.src); $(bin.jar) -cfe ../$@ org.sqlite.jni.capi.Tester1 @$(package.jar.in)
+ @ls -la $@
+ @echo "To use this jar you will need the -Djava.library.path=DIR/CONTAINING/libsqlite3-jni.so flag."
+ @echo "e.g. java -Djava.library.path=bld -jar $@"
+
+jar: $(package.jar)
+run-jar: $(package.jar) $(package.dll)
+ $(bin.java) -Djava.library.path=$(dir.bld) -jar $(package.jar) $(run-jar.flags)
+
+########################################################################
+# javadoc...
+dir.doc := $(dir.jni)/javadoc
+doc.index := $(dir.doc)/index.html
+javadoc.exclude := -exclude org.sqlite.jni.fts5
+# ^^^^ 2023-09-13: elide the fts5 parts from the public docs for
+# the time being, as it's not clear where the Java bindings for
+# those bits are going.
+# javadoc.exclude += -exclude org.sqlite.jni.capi
+# ^^^^ exclude the capi API only for certain builds (TBD)
+$(doc.index): $(JAVA_FILES.main) $(MAKEFILE)
+ @if [ -d $(dir.doc) ]; then rm -fr $(dir.doc)/*; fi
+ $(bin.javadoc) -cp $(classpath) -d $(dir.doc) -quiet \
+ -subpackages org.sqlite.jni $(javadoc.exclude)
+ @echo "javadoc output is in $@"
+
+.PHONY: doc javadoc docserve
+.FORCE: doc
+doc: $(doc.index)
+javadoc: $(doc.index)
+# Force rebild of docs
+redoc:
+ @rm -f $(doc.index)
+ @$(MAKE) doc
+docserve: $(doc.index)
+ cd $(dir.doc) && althttpd -max-age 1 -page index.html
+########################################################################
+# Clean up...
+CLEAN_FILES += $(dir.bld.c)/* \
+ $(dir.src.jni)/*.class \
+ $(dir.src.jni)/*/*.class \
+ $(package.dll) \
+ hs_err_pid*.log
+
+.PHONY: clean distclean
+clean:
+ -rm -f $(CLEAN_FILES)
+distclean: clean
+ -rm -f $(DISTCLEAN_FILES)
+ -rm -fr $(dir.bld.c) $(dir.doc)
+
+########################################################################
+# disttribution bundle rules...
+
+ifeq (,$(filter snapshot,$(MAKECMDGOALS)))
+dist-name-prefix := sqlite-jni
+else
+dist-name-prefix := sqlite-jni-snapshot-$(shell /usr/bin/date +%Y%m%d)
+endif
+dist-name := $(dist-name-prefix)-TEMP
+
+
+dist-dir.top := $(dist-name)
+dist-dir.src := $(dist-dir.top)/src
+dist.top.extras := \
+ README.md
+
+.PHONY: dist snapshot
+
+dist: \
+ $(bin.version-info) $(sqlite3.canonical.c) \
+ $(package.jar) $(MAKEFILE)
+ @echo "Making end-user deliverables..."
+ @echo "****************************************************************************"; \
+ echo "*** WARNING: be sure to build this with JDK8 (javac 1.8) for compatibility."; \
+ echo "*** reasons!"; $$($(bin.javac) -version); \
+ echo "****************************************************************************"
+ @rm -fr $(dist-dir.top)
+ @mkdir -p $(dist-dir.src)
+ @cp -p $(dist.top.extras) $(dist-dir.top)/.
+ @cp -p jar-dist.make $(dist-dir.top)/Makefile
+ @cp -p $(dir.src.c)/*.[ch] $(dist-dir.src)/.
+ @cp -p $(sqlite3.canonical.c) $(sqlite3.canonical.h) $(dist-dir.src)/.
+ @set -e; \
+ vnum=$$($(bin.version-info) --download-version); \
+ vjar=$$($(bin.version-info) --version); \
+ vdir=$(dist-name-prefix)-$$vnum; \
+ arczip=$$vdir.zip; \
+ cp -p $(package.jar) $(dist-dir.top)/sqlite3-jni-$${vjar}.jar; \
+ echo "Making $$arczip ..."; \
+ rm -fr $$arczip $$vdir; \
+ mv $(dist-dir.top) $$vdir; \
+ zip -qr $$arczip $$vdir; \
+ rm -fr $$vdir; \
+ ls -la $$arczip; \
+ set +e; \
+ unzip -lv $$arczip || echo "Missing unzip app? Not fatal."
+
+snapshot: dist
+
+.PHONY: dist-clean
+clean: dist-clean
+dist-clean:
+ rm -fr $(dist-name) $(wildcard sqlite-jni-*.zip)
diff -Nru sqlite3-3.42.0/ext/jni/jar-dist.make sqlite3-3.44.0-0/ext/jni/jar-dist.make
--- sqlite3-3.42.0/ext/jni/jar-dist.make 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/jar-dist.make 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,60 @@
+#!/this/is/make
+#^^^^ help emacs out
+#
+# This is a POSIX-make-compatible makefile for building the sqlite3
+# JNI library from "dist" zip file. It must be edited to set the
+# proper top-level JDK directory and, depending on the platform, add a
+# platform-specific -I directory. It should build as-is with any
+# 2020s-era version of gcc or clang. It requires JDK version 8 or
+# higher and that JAVA_HOME points to the top-most installation
+# directory of that JDK. On Ubuntu-style systems the JDK is typically
+# installed under /usr/lib/jvm/java-VERSION-PLATFORM.
+
+default: all
+
+JAVA_HOME = /usr/lib/jvm/java-1.8.0-openjdk-amd64
+CFLAGS = \
+ -fPIC \
+ -Isrc \
+ -I$(JAVA_HOME)/include \
+ -I$(JAVA_HOME)/include/linux \
+ -I$(JAVA_HOME)/include/apple \
+ -I$(JAVA_HOME)/include/bsd \
+ -Wall
+
+SQLITE_OPT = \
+ -DSQLITE_ENABLE_RTREE \
+ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
+ -DSQLITE_ENABLE_STMTVTAB \
+ -DSQLITE_ENABLE_DBPAGE_VTAB \
+ -DSQLITE_ENABLE_DBSTAT_VTAB \
+ -DSQLITE_ENABLE_BYTECODE_VTAB \
+ -DSQLITE_ENABLE_OFFSET_SQL_FUNC \
+ -DSQLITE_OMIT_LOAD_EXTENSION \
+ -DSQLITE_OMIT_DEPRECATED \
+ -DSQLITE_OMIT_SHARED_CACHE \
+ -DSQLITE_THREADSAFE=1 \
+ -DSQLITE_TEMP_STORE=2 \
+ -DSQLITE_USE_URI=1 \
+ -DSQLITE_ENABLE_FTS5 \
+ -DSQLITE_DEBUG
+
+sqlite3-jni.dll = libsqlite3-jni.so
+$(sqlite3-jni.dll):
+ @echo "************************************************************************"; \
+ echo "*** If this fails to build, be sure to edit this makefile ***"; \
+ echo "*** to configure it for your system. ***"; \
+ echo "************************************************************************"
+ $(CC) $(CFLAGS) $(SQLITE_OPT) \
+ src/sqlite3-jni.c -shared -o $@
+ @echo "Now try running it with: make test"
+
+test.flags = -Djava.library.path=. sqlite3-jni-*.jar
+test: $(sqlite3-jni.dll)
+ java -jar $(test.flags)
+ java -jar $(test.flags) -t 7 -r 10 -shuffle
+
+clean:
+ -rm -f $(sqlite3-jni.dll)
+
+all: $(sqlite3-jni.dll)
diff -Nru sqlite3-3.42.0/ext/jni/README.md sqlite3-3.44.0-0/ext/jni/README.md
--- sqlite3-3.42.0/ext/jni/README.md 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/README.md 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,313 @@
+SQLite3 via JNI
+========================================================================
+
+This directory houses a Java Native Interface (JNI) binding for the
+sqlite3 API. If you are reading this from the distribution ZIP file,
+links to resources in the canonical source tree will note work. The
+canonical copy of this file can be browsed at:
+
+
+
+Technical support is available in the forum:
+
+
+
+
+> **FOREWARNING:** this subproject is very much in development and
+ subject to any number of changes. Please do not rely on any
+ information about its API until this disclaimer is removed. The JNI
+ bindings released with version 3.43 are a "tech preview" and 3.44
+ will be "final," at which point strong backward compatibility
+ guarantees will apply.
+
+Project goals/requirements:
+
+- A [1-to-1(-ish) mapping of the C API](#1to1ish) to Java via JNI,
+ insofar as cross-language semantics allow for. A closely-related
+ goal is that [the C documentation](https://sqlite.org/c3ref/intro.html)
+ should be usable as-is, insofar as possible, for the JNI binding.
+
+- Support Java as far back as version 8 (2014).
+
+- Environment-independent. Should work everywhere both Java
+ and SQLite3 do.
+
+- No 3rd-party dependencies beyond the JDK. That includes no
+ build-level dependencies for specific IDEs and toolchains. We
+ welcome the addition of build files for arbitrary environments
+ insofar as they neither interfere with each other nor become
+ a maintenance burden for the sqlite developers.
+
+Non-goals:
+
+- Creation of high-level OO wrapper APIs. Clients are free to create
+ them off of the C-style API.
+
+- Support for mixed-mode operation, where client code accesses SQLite
+ both via the Java-side API and the C API via their own native
+ code. In such cases, proxy functionalities (primarily callback
+ handler wrappers of all sorts) may fail because the C-side use of
+ the SQLite APIs will bypass those proxies.
+
+
+Hello World
+-----------------------------------------------------------------------
+
+```java
+import org.sqlite.jni.*;
+import static org.sqlite.jni.CApi.*;
+
+...
+
+final sqlite3 db = sqlite3_open(":memory:");
+try {
+ final int rc = sqlite3_errcode(db);
+ if( 0 != rc ){
+ if( null != db ){
+ System.out.print("Error opening db: "+sqlite3_errmsg(db));
+ }else{
+ System.out.print("Error opening db: rc="+rc);
+ }
+ ... handle error ...
+ }
+ // ... else use the db ...
+}finally{
+ // ALWAYS close databases using sqlite3_close() or sqlite3_close_v2()
+ // when done with them. All of their active statement handles must
+ // first have been passed to sqlite3_finalize().
+ sqlite3_close_v2(db);
+}
+```
+
+
+Building
+========================================================================
+
+The canonical builds assumes a Linux-like environment and requires:
+
+- GNU Make
+- A JDK supporting Java 8 or higher
+- A modern C compiler. gcc and clang should both work.
+
+Put simply:
+
+```console
+$ export JAVA_HOME=/path/to/jdk/root
+$ make
+$ make test
+$ make clean
+```
+
+The jar distribution can be created with `make jar`, but note that it
+does not contain the binary DLL file. A different DLL is needed for
+each target platform.
+
+
+
+One-to-One(-ish) Mapping to C
+========================================================================
+
+This JNI binding aims to provide as close to a 1-to-1 experience with
+the C API as cross-language semantics allow. Interface changes are
+necessarily made where cross-language semantics do not allow a 1-to-1,
+and judiciously made where a 1-to-1 mapping would be unduly cumbersome
+to use in Java. In all cases, this binding makes every effort to
+provide semantics compatible with the C API documentation even if the
+interface to those semantics is slightly different. Any cases which
+deviate from those semantics (either removing or adding semantics) are
+clearly documented.
+
+Where it makes sense to do so for usability, Java-side overloads are
+provided which accept or return data in alternative forms or provide
+sensible default argument values. In all such cases they are thin
+proxies around the corresponding C APIs and do not introduce new
+semantics.
+
+In some very few cases, Java-specific capabilities have been added in
+new APIs, all of which have "_java" somewhere in their names.
+Examples include:
+
+- `sqlite3_result_java_object()`
+- `sqlite3_column_java_object()`
+- `sqlite3_column_java_casted()`
+- `sqlite3_value_java_object()`
+- `sqlite3_value_java_casted()`
+
+which, as one might surmise, collectively enable the passing of
+arbitrary Java objects from user-defined SQL functions through to the
+caller.
+
+
+Golden Rule: Garbage Collection Cannot Free SQLite Resources
+------------------------------------------------------------------------
+
+It is important that all databases and prepared statement handles get
+cleaned up by client code. A database cannot be closed if it has open
+statement handles. `sqlite3_close()` fails if the db cannot be closed
+whereas `sqlite3_close_v2()` recognizes that case and marks the db as
+a "zombie," pending finalization when the library detects that all
+pending statements have been closed. Be aware that Java garbage
+collection _cannot_ close a database or finalize a prepared statement.
+Those things require explicit API calls.
+
+
+Golden Rule #2: _Never_ Throw from Callbacks (Unless...)
+------------------------------------------------------------------------
+
+All routines in this API, barring explicitly documented exceptions,
+retain C-like semantics. For example, they are not permitted to throw
+or propagate exceptions and must return error information (if any) via
+result codes or `null`. The only cases where the C-style APIs may
+throw is through client-side misuse, e.g. passing in a null where it
+shouldn't be used. The APIs clearly mark function parameters which
+should not be null, but does not actively defend itself against such
+misuse. Some C-style APIs explicitly accept `null` as a no-op for
+usability's sake, and some of the JNI APIs deliberately return an
+error code, instead of segfaulting, when passed a `null`.
+
+Client-defined callbacks _must never throw exceptions_ unless _very
+explicitly documented_ as being throw-safe. Exceptions are generally
+reserved for higher-level bindings which are constructed to
+specifically deal with them and ensure that they do not leak C-level
+resources. In some cases, callback handlers are permitted to throw, in
+which cases they get translated to C-level result codes and/or
+messages. If a callback which is not permitted to throw throws, its
+exception may trigger debug output but will otherwise be suppressed.
+
+The reason some callbacks are permitted to throw and others not is
+because all such callbacks act as proxies for C function callback
+interfaces and some of those interfaces have no error-reporting
+mechanism. Those which are capable of propagating errors back through
+the library convert exceptions from callbacks into corresponding
+C-level error information. Those which cannot propagate errors
+necessarily suppress any exceptions in order to maintain the C-style
+semantics of the APIs.
+
+
+Unwieldy Constructs are Re-mapped
+------------------------------------------------------------------------
+
+Some constructs, when modelled 1-to-1 from C to Java, are unduly
+clumsy to work with in Java because they try to shoehorn C's way of
+doing certain things into Java's wildly different ways. The following
+subsections cover those, starting with a verbose explanation and
+demonstration of where such changes are "really necessary"...
+
+### Custom Collations
+
+A prime example of where interface changes for Java are necessary for
+usability is [registration of a custom
+collation](https://sqlite.org/c3ref/create_collation.html):
+
+```c
+// C:
+int sqlite3_create_collation(sqlite3 * db, const char * name, int eTextRep,
+ void *pUserData,
+ int (*xCompare)(void*,int,void const *,int,void const *));
+
+int sqlite3_create_collation_v2(sqlite3 * db, const char * name, int eTextRep,
+ void *pUserData,
+ int (*xCompare)(void*,int,void const *,int,void const *),
+ void (*xDestroy)(void*));
+```
+
+The `pUserData` object is optional client-defined state for the
+`xCompare()` and/or `xDestroy()` callback functions, both of which are
+passed that object as their first argument. That data is passed around
+"externally" in C because that's how C models the world. If we were to
+bind that part as-is to Java, the result would be awkward to use (^Yes,
+we tried this.):
+
+```java
+// Java:
+int sqlite3_create_collation(sqlite3 db, String name, int eTextRep,
+ Object pUserData, xCompareType xCompare);
+
+int sqlite3_create_collation_v2(sqlite3 db, String name, int eTextRep,
+ Object pUserData,
+ xCompareType xCompare, xDestroyType xDestroy);
+```
+
+The awkwardness comes from (A) having two distinctly different objects
+for callbacks and (B) having their internal state provided separately,
+which is ill-fitting in Java. For the sake of usability, C APIs which
+follow that pattern use a slightly different Java interface:
+
+```java
+int sqlite3_create_collation(sqlite3 db, String name, int eTextRep,
+ SomeCallbackType collation);
+```
+
+Where the `Collation` class has an abstract `call()` method and
+no-op `xDestroy()` method which can be overridden if needed, leading to
+a much more Java-esque usage:
+
+```java
+int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new SomeCallbackType(){
+
+ // Required comparison function:
+ @Override public int call(byte[] lhs, byte[] rhs){ ... }
+
+ // Optional finalizer function:
+ @Override public void xDestroy(){ ... }
+
+ // Optional local state:
+ private String localState1 =
+ "This is local state. There are many like it, but this one is mine.";
+ private MyStateType localState2 = new MyStateType();
+ ...
+});
+```
+
+Noting that:
+
+- It is possible to bind in call-scope-local state via closures, if
+ desired, as opposed to packing it into the Collation object.
+
+- No capabilities of the C API are lost or unduly obscured via the
+ above API reshaping, so power users need not make any compromises.
+
+- In the specific example above, `sqlite3_create_collation_v2()`
+ becomes superfluous because the provided interface effectively
+ provides both the v1 and v2 interfaces, the difference being that
+ overriding the `xDestroy()` method effectively gives it v2
+ semantics.
+
+
+### User-defined SQL Functions (a.k.a. UDFs)
+
+The [`sqlite3_create_function()`](https://sqlite.org/c3ref/create_function.html)
+family of APIs make heavy use of function pointers to provide
+client-defined callbacks, necessitating interface changes in the JNI
+binding. The Java API has only one core function-registration function:
+
+```java
+int sqlite3_create_function(sqlite3 db, String funcName, int nArgs,
+ int encoding, SQLFunction func);
+```
+
+> Design question: does the encoding argument serve any purpose in
+ Java? That's as-yet undetermined. If not, it will be removed.
+
+`SQLFunction` is not used directly, but is instead instantiated via
+one of its three subclasses:
+
+- `SQLFunction.Scalar` implements simple scalar functions using but a
+ single callback.
+- `SQLFunction.Aggregate` implements aggregate functions using two
+ callbacks.
+- `SQLFunction.Window` implements window functions using four
+ callbacks.
+
+Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/Tester1.java) for
+`SQLFunction` for how it's used.
+
+Reminder: see the disclaimer at the top of this document regarding the
+in-flux nature of this API.
+
+### And so on...
+
+Various APIs which accept callbacks, e.g. `sqlite3_trace_v2()` and
+`sqlite3_update_hook()`, use interfaces similar to those shown above.
+Despite the changes in signature, the JNI layer makes every effort to
+provide the same semantics as the C API documentation suggests.
diff -Nru sqlite3-3.42.0/ext/jni/src/c/sqlite3-jni.c sqlite3-3.44.0-0/ext/jni/src/c/sqlite3-jni.c
--- sqlite3-3.42.0/ext/jni/src/c/sqlite3-jni.c 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/c/sqlite3-jni.c 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,5899 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file implements the JNI bindings declared in
+** org.sqlite.jni.capi.CApi (from which sqlite3-jni.h is generated).
+*/
+
+/*
+** If you found this comment by searching the code for
+** CallStaticObjectMethod then you're the victim of an OpenJDK bug:
+**
+** https://bugs.openjdk.org/browse/JDK-8130659
+**
+** It's known to happen with OpenJDK v8 but not with v19.
+**
+** This code does not use JNI's CallStaticObjectMethod().
+*/
+
+/*
+** Define any SQLITE_... config defaults we want if they aren't
+** overridden by the builder. Please keep these alphabetized.
+*/
+
+/**********************************************************************/
+/* SQLITE_D... */
+#ifndef SQLITE_DEFAULT_CACHE_SIZE
+# define SQLITE_DEFAULT_CACHE_SIZE -16384
+#endif
+#if !defined(SQLITE_DEFAULT_PAGE_SIZE)
+# define SQLITE_DEFAULT_PAGE_SIZE 8192
+#endif
+#ifndef SQLITE_DQS
+# define SQLITE_DQS 0
+#endif
+
+/**********************************************************************/
+/* SQLITE_ENABLE_... */
+/*
+** Unconditionally enable API_ARMOR in the JNI build. It ensures that
+** public APIs behave predictable in the face of passing illegal NULLs
+** or ranges which might otherwise invoke undefined behavior.
+*/
+#undef SQLITE_ENABLE_API_ARMOR
+#define SQLITE_ENABLE_API_ARMOR 1
+
+#ifndef SQLITE_ENABLE_BYTECODE_VTAB
+# define SQLITE_ENABLE_BYTECODE_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_DBPAGE_VTAB
+# define SQLITE_ENABLE_DBPAGE_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_DBSTAT_VTAB
+# define SQLITE_ENABLE_DBSTAT_VTAB 1
+#endif
+#ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS
+# define SQLITE_ENABLE_EXPLAIN_COMMENTS 1
+#endif
+#ifndef SQLITE_ENABLE_MATH_FUNCTIONS
+# define SQLITE_ENABLE_MATH_FUNCTIONS 1
+#endif
+#ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC
+# define SQLITE_ENABLE_OFFSET_SQL_FUNC 1
+#endif
+#ifndef SQLITE_ENABLE_RTREE
+# define SQLITE_ENABLE_RTREE 1
+#endif
+//#ifndef SQLITE_ENABLE_SESSION
+//# define SQLITE_ENABLE_SESSION 1
+//#endif
+#ifndef SQLITE_ENABLE_STMTVTAB
+# define SQLITE_ENABLE_STMTVTAB 1
+#endif
+//#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+//# define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+//#endif
+
+/**********************************************************************/
+/* SQLITE_J... */
+#ifdef SQLITE_JNI_FATAL_OOM
+#if !SQLITE_JNI_FATAL_OOM
+#undef SQLITE_JNI_FATAL_OOM
+#endif
+#endif
+
+/**********************************************************************/
+/* SQLITE_M... */
+#ifndef SQLITE_MAX_ALLOCATION_SIZE
+# define SQLITE_MAX_ALLOCATION_SIZE 0x1fffffff
+#endif
+
+/**********************************************************************/
+/* SQLITE_O... */
+#ifndef SQLITE_OMIT_DEPRECATED
+# define SQLITE_OMIT_DEPRECATED 1
+#endif
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+# define SQLITE_OMIT_LOAD_EXTENSION 1
+#endif
+#ifndef SQLITE_OMIT_SHARED_CACHE
+# define SQLITE_OMIT_SHARED_CACHE 1
+#endif
+#ifdef SQLITE_OMIT_UTF16
+/* UTF16 is required for java */
+# undef SQLITE_OMIT_UTF16 1
+#endif
+
+/**********************************************************************/
+/* SQLITE_T... */
+#ifndef SQLITE_TEMP_STORE
+# define SQLITE_TEMP_STORE 2
+#endif
+#ifndef SQLITE_THREADSAFE
+# define SQLITE_THREADSAFE 1
+#endif
+
+/**********************************************************************/
+/* SQLITE_USE_... */
+#ifndef SQLITE_USE_URI
+# define SQLITE_USE_URI 1
+#endif
+
+
+/*
+** Which sqlite3.c we're using needs to be configurable to enable
+** building against a custom copy, e.g. the SEE variant. We have to
+** include sqlite3.c, as opposed to sqlite3.h, in order to get access
+** to some interal details like SQLITE_MAX_... and friends. This
+** increases the rebuild time considerably but we need this in order
+** to access some internal functionality and keep the to-Java-exported
+** values of SQLITE_MAX_... and SQLITE_LIMIT_... in sync with the C
+** build.
+*/
+#ifndef SQLITE_C
+# define SQLITE_C sqlite3.c
+#endif
+#define INC__STRINGIFY_(f) #f
+#define INC__STRINGIFY(f) INC__STRINGIFY_(f)
+#include INC__STRINGIFY(SQLITE_C)
+#undef INC__STRINGIFY_
+#undef INC__STRINGIFY
+#undef SQLITE_C
+
+/*
+** End of the sqlite3 lib setup. What follows is JNI-specific.
+*/
+
+#include "sqlite3-jni.h"
+#include
+#include /* only for testing/debugging */
+#include /* intptr_t for 32-bit builds */
+
+/* Only for debugging */
+#define MARKER(pfexp) \
+ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
+ printf pfexp; \
+ } while(0)
+
+/*
+** Creates a verbose JNI function name. Suffix must be
+** the JNI-mangled form of the function's name, minus the
+** prefix seen in this macro.
+*/
+#define JniFuncName(Suffix) \
+ Java_org_sqlite_jni_capi_CApi_sqlite3_ ## Suffix
+
+/* Prologue for JNI function declarations and definitions. */
+#define JniDecl(ReturnType,Suffix) \
+ JNIEXPORT ReturnType JNICALL JniFuncName(Suffix)
+
+/*
+** S3JniApi's intent is that CFunc be the C API func(s) the
+** being-declared JNI function is wrapping, making it easier to find
+** that function's JNI-side entry point. The other args are for JniDecl.
+** See the many examples in this file.
+*/
+#define S3JniApi(CFunc,ReturnType,Suffix) JniDecl(ReturnType,Suffix)
+
+/*
+** S3JniCast_L2P and P2L cast jlong (64-bit) to/from pointers. This is
+** required for casting warning-free on 32-bit builds, where we
+** otherwise get complaints that we're casting between different-sized
+** int types.
+**
+** This use of intptr_t is the _only_ reason we require
+** which, in turn, requires building with -std=c99 (or later).
+*/
+#define S3JniCast_L2P(JLongAsPtr) (void*)((intptr_t)(JLongAsPtr))
+#define S3JniCast_P2L(PTR) (jlong)((intptr_t)(PTR))
+
+/*
+** Shortcuts for the first 2 parameters to all JNI bindings.
+**
+** The type of the jSelf arg differs, but no docs seem to mention
+** this: for static methods it's of type jclass and for non-static
+** it's jobject. jobject actually works for all funcs, in the sense
+** that it compiles and runs so long as we don't use jSelf (which is
+** only rarely needed in this code), but to be pedantically correct we
+** need the proper type in the signature.
+**
+** https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers
+*/
+#define JniArgsEnvObj JNIEnv * const env, jobject jSelf
+#define JniArgsEnvClass JNIEnv * const env, jclass jKlazz
+/*
+** Helpers to account for -Xcheck:jni warnings about not having
+** checked for exceptions.
+*/
+#define S3JniIfThrew if( (*env)->ExceptionCheck(env) )
+#define S3JniExceptionClear (*env)->ExceptionClear(env)
+#define S3JniExceptionReport (*env)->ExceptionDescribe(env)
+#define S3JniExceptionIgnore S3JniIfThrew S3JniExceptionClear
+#define S3JniExceptionWarnIgnore \
+ S3JniIfThrew {S3JniExceptionReport; S3JniExceptionClear;}(void)0
+#define S3JniExceptionWarnCallbackThrew(STR) \
+ MARKER(("WARNING: " STR " MUST NOT THROW.\n")); \
+ (*env)->ExceptionDescribe(env)
+
+/** To be used for cases where we're _really_ not expecting an
+ exception, e.g. looking up well-defined Java class members. */
+#define S3JniExceptionIsFatal(MSG) S3JniIfThrew {\
+ S3JniExceptionReport; S3JniExceptionClear; \
+ (*env)->FatalError(env, MSG); \
+ }
+
+/*
+** Declares local var env = s3jni_env(). All JNI calls involve a
+** JNIEnv somewhere, always named env, and many of our macros assume
+** env is in scope. Where it's not, but should be, use this to make it
+** so.
+*/
+#define S3JniDeclLocal_env JNIEnv * const env = s3jni_env()
+
+/* Fail fatally with an OOM message. */
+static inline void s3jni_oom(JNIEnv * const env){
+ (*env)->FatalError(env, "SQLite3 JNI is out of memory.") /* does not return */;
+}
+
+/*
+** sqlite3_malloc() proxy which fails fatally on OOM. This should
+** only be used for routines which manage global state and have no
+** recovery strategy for OOM. For sqlite3 API which can reasonably
+** return SQLITE_NOMEM, s3jni_malloc() should be used instead.
+*/
+static void * s3jni_malloc_or_die(JNIEnv * const env, size_t n){
+ void * const rv = sqlite3_malloc(n);
+ if( n && !rv ) s3jni_oom(env);
+ return rv;
+}
+
+/*
+** Works like sqlite3_malloc() unless built with SQLITE_JNI_FATAL_OOM,
+** in which case it calls s3jni_oom() on OOM.
+*/
+#ifdef SQLITE_JNI_FATAL_OOM
+#define s3jni_malloc(SIZE) s3jni_malloc_or_die(env, SIZE)
+#else
+#define s3jni_malloc(SIZE) sqlite3_malloc(((void)env,(SIZE)))
+/* the ((void)env) trickery here is to avoid ^^^^^^ an otherwise
+ unused arg in at least one place. */
+#endif
+
+/*
+** Works like sqlite3_realloc() unless built with SQLITE_JNI_FATAL_OOM,
+** in which case it calls s3jni_oom() on OOM.
+*/
+#ifdef SQLITE_JNI_FATAL_OOM
+static void * s3jni_realloc_or_die(JNIEnv * const env, void * p, size_t n){
+ void * const rv = sqlite3_realloc(p, (int)n);
+ if( n && !rv ) s3jni_oom(env);
+ return rv;
+}
+#define s3jni_realloc(MEM,SIZE) s3jni_realloc_or_die(env, (MEM), (SIZE))
+#else
+#define s3jni_realloc(MEM,SIZE) sqlite3_realloc((MEM), ((void)env, (SIZE)))
+#endif
+
+/* Fail fatally if !EXPR. */
+#define s3jni_oom_fatal(EXPR) if( !(EXPR) ) s3jni_oom(env)
+/* Maybe fail fatally if !EXPR. */
+#ifdef SQLITE_JNI_FATAL_OOM
+#define s3jni_oom_check s3jni_oom_fatal
+#else
+#define s3jni_oom_check(EXPR)
+#endif
+//#define S3JniDb_oom(pDb,EXPR) ((EXPR) ? sqlite3OomFault(pDb) : 0)
+
+#define s3jni_db_oom(pDb) (void)((pDb) ? ((pDb)->mallocFailed=1) : 0)
+
+/* Helpers for Java value reference management. */
+static jobject s3jni_ref_global(JNIEnv * const env, jobject const v){
+ jobject const rv = v ? (*env)->NewGlobalRef(env, v) : NULL;
+ s3jni_oom_fatal( v ? !!rv : 1 );
+ return rv;
+}
+static jobject s3jni_ref_local(JNIEnv * const env, jobject const v){
+ jobject const rv = v ? (*env)->NewLocalRef(env, v) : NULL;
+ s3jni_oom_fatal( v ? !!rv : 1 );
+ return rv;
+}
+static inline void s3jni_unref_global(JNIEnv * const env, jobject const v){
+ if( v ) (*env)->DeleteGlobalRef(env, v);
+}
+static inline void s3jni_unref_local(JNIEnv * const env, jobject const v){
+ if( v ) (*env)->DeleteLocalRef(env, v);
+}
+#define S3JniRefGlobal(VAR) s3jni_ref_global(env, (VAR))
+#define S3JniRefLocal(VAR) s3jni_ref_local(env, (VAR))
+#define S3JniUnrefGlobal(VAR) s3jni_unref_global(env, (VAR))
+#define S3JniUnrefLocal(VAR) s3jni_unref_local(env, (VAR))
+
+/*
+** Lookup key type for use with s3jni_nphop() and a cache of a
+** frequently-needed Java-side class reference and one or two Java
+** class member IDs.
+*/
+typedef struct S3JniNphOp S3JniNphOp;
+struct S3JniNphOp {
+ const int index /* index into S3JniGlobal.nph[] */;
+ const char * const zName /* Full Java name of the class */;
+ const char * const zMember /* Name of member property */;
+ const char * const zTypeSig /* JNI type signature of zMember */;
+ /*
+ ** klazz is a global ref to the class represented by pRef.
+ **
+ ** According to:
+ **
+ ** https://developer.ibm.com/articles/j-jni/
+ **
+ ** > ... the IDs returned for a given class don't change for the
+ ** lifetime of the JVM process. But the call to get the field or
+ ** method can require significant work in the JVM, because fields
+ ** and methods might have been inherited from superclasses, making
+ ** the JVM walk up the class hierarchy to find them. Because the
+ ** IDs are the same for a given class, you should look them up
+ ** once and then reuse them. Similarly, looking up class objects
+ ** can be expensive, so they should be cached as well.
+ */
+ jclass klazz;
+ volatile jfieldID fidValue /* NativePointerHolder.nativePointer or
+ ** OutputPointer.T.value */;
+ volatile jmethodID midCtor /* klazz's no-arg constructor. Used by
+ ** NativePointerHolder_new(). */;
+};
+
+/*
+** Cache keys for each concrete NativePointerHolder subclasses and
+** OutputPointer.T types. The members are to be used with s3jni_nphop()
+** and friends, and each one's member->index corresponds to its index
+** in the S3JniGlobal.nph[] array.
+*/
+static const struct {
+ const S3JniNphOp sqlite3;
+ const S3JniNphOp sqlite3_backup;
+ const S3JniNphOp sqlite3_blob;
+ const S3JniNphOp sqlite3_context;
+ const S3JniNphOp sqlite3_stmt;
+ const S3JniNphOp sqlite3_value;
+ const S3JniNphOp OutputPointer_Bool;
+ const S3JniNphOp OutputPointer_Int32;
+ const S3JniNphOp OutputPointer_Int64;
+ const S3JniNphOp OutputPointer_sqlite3;
+ const S3JniNphOp OutputPointer_sqlite3_blob;
+ const S3JniNphOp OutputPointer_sqlite3_stmt;
+ const S3JniNphOp OutputPointer_sqlite3_value;
+ const S3JniNphOp OutputPointer_String;
+#ifdef SQLITE_ENABLE_FTS5
+ const S3JniNphOp OutputPointer_ByteArray;
+ const S3JniNphOp Fts5Context;
+ const S3JniNphOp Fts5ExtensionApi;
+ const S3JniNphOp fts5_api;
+ const S3JniNphOp fts5_tokenizer;
+ const S3JniNphOp Fts5Tokenizer;
+#endif
+} S3JniNphOps = {
+#define MkRef(INDEX, KLAZZ, MEMBER, SIG) \
+ { INDEX, "org/sqlite/jni/" KLAZZ, MEMBER, SIG }
+/* NativePointerHolder ref */
+#define RefN(INDEX, KLAZZ) MkRef(INDEX, KLAZZ, "nativePointer", "J")
+/* OutputPointer.T ref */
+#define RefO(INDEX, KLAZZ, SIG) MkRef(INDEX, KLAZZ, "value", SIG)
+ RefN(0, "capi/sqlite3"),
+ RefN(1, "capi/sqlite3_backup"),
+ RefN(2, "capi/sqlite3_blob"),
+ RefN(3, "capi/sqlite3_context"),
+ RefN(4, "capi/sqlite3_stmt"),
+ RefN(5, "capi/sqlite3_value"),
+ RefO(6, "capi/OutputPointer$Bool", "Z"),
+ RefO(7, "capi/OutputPointer$Int32", "I"),
+ RefO(8, "capi/OutputPointer$Int64", "J"),
+ RefO(9, "capi/OutputPointer$sqlite3",
+ "Lorg/sqlite/jni/capi/sqlite3;"),
+ RefO(10, "capi/OutputPointer$sqlite3_blob",
+ "Lorg/sqlite/jni/capi/sqlite3_blob;"),
+ RefO(11, "capi/OutputPointer$sqlite3_stmt",
+ "Lorg/sqlite/jni/capi/sqlite3_stmt;"),
+ RefO(12, "capi/OutputPointer$sqlite3_value",
+ "Lorg/sqlite/jni/capi/sqlite3_value;"),
+ RefO(13, "capi/OutputPointer$String", "Ljava/lang/String;"),
+#ifdef SQLITE_ENABLE_FTS5
+ RefO(14, "capi/OutputPointer$ByteArray", "[B"),
+ RefN(15, "fts5/Fts5Context"),
+ RefN(16, "fts5/Fts5ExtensionApi"),
+ RefN(17, "fts5/fts5_api"),
+ RefN(18, "fts5/fts5_tokenizer"),
+ RefN(19, "fts5/Fts5Tokenizer")
+#endif
+#undef MkRef
+#undef RefN
+#undef RefO
+};
+
+#define S3JniNph(T) &S3JniNphOps.T
+
+enum {
+ /*
+ ** Size of the NativePointerHolder cache. Need enough space for
+ ** (only) the library's NativePointerHolder and OutputPointer types,
+ ** a fixed count known at build-time. This value needs to be
+ ** exactly the number of S3JniNphOp entries in the S3JniNphOps
+ ** object.
+ */
+ S3Jni_NphCache_size = sizeof(S3JniNphOps) / sizeof(S3JniNphOp)
+};
+
+/*
+** State for binding C callbacks to Java methods.
+*/
+typedef struct S3JniHook S3JniHook;
+struct S3JniHook{
+ jobject jObj /* global ref to Java instance */;
+ jmethodID midCallback /* callback method. Signature depends on
+ ** jObj's type */;
+ /* We lookup the jObj.xDestroy() method as-needed for contexts which
+ ** support custom finalizers. Fundamentally we can support them for
+ ** any Java type, but we only want to expose support for them where
+ ** the C API does. */
+ jobject jExtra /* Global ref to a per-hook-type value */;
+ int doXDestroy /* If true then S3JniHook_unref() will call
+ jObj->xDestroy() if it's available. */;
+ S3JniHook * pNext /* Next entry in S3Global.hooks.aFree */;
+};
+/* For clean bitwise-copy init of local instances. */
+static const S3JniHook S3JniHook_empty = {0,0,0,0,0};
+
+/*
+** Per-(sqlite3*) state for various JNI bindings. This state is
+** allocated as needed, cleaned up in sqlite3_close(_v2)(), and
+** recycled when possible.
+**
+** Trivia: vars and parameters of this type are often named "ps"
+** because this class used to have a name for which that abbreviation
+** made sense.
+*/
+typedef struct S3JniDb S3JniDb;
+struct S3JniDb {
+ sqlite3 *pDb /* The associated db handle */;
+ jobject jDb /* A global ref of the output object which gets
+ returned from sqlite3_open(_v2)(). We need this in
+ order to have an object to pass to routines like
+ sqlite3_collation_needed()'s callback, or else we
+ have to dynamically create one for that purpose,
+ which would be fine except that it would be a
+ different instance (and maybe even a different
+ class) than the one the user may expect to
+ receive. */;
+ char * zMainDbName /* Holds the string allocated on behalf of
+ SQLITE_DBCONFIG_MAINDBNAME. */;
+ struct {
+ S3JniHook busyHandler;
+ S3JniHook collationNeeded;
+ S3JniHook commit;
+ S3JniHook progress;
+ S3JniHook rollback;
+ S3JniHook trace;
+ S3JniHook update;
+ S3JniHook auth;
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ S3JniHook preUpdate;
+#endif
+ } hooks;
+#ifdef SQLITE_ENABLE_FTS5
+ /* FTS5-specific state */
+ struct {
+ jobject jApi /* global ref to s3jni_fts5_api_from_db() */;
+ } fts;
+#endif
+ S3JniDb * pNext /* Next entry in SJG.perDb.aFree */;
+};
+
+static const char * const S3JniDb_clientdata_key = "S3JniDb";
+#define S3JniDb_from_clientdata(pDb) \
+ (pDb ? sqlite3_get_clientdata(pDb, S3JniDb_clientdata_key) : 0)
+
+/*
+** Cache for per-JNIEnv (i.e. per-thread) data.
+**
+** Trivia: vars and parameters of this type are often named "jc"
+** because this class used to have a name for which that abbreviation
+** made sense.
+*/
+typedef struct S3JniEnv S3JniEnv;
+struct S3JniEnv {
+ JNIEnv *env /* JNIEnv in which this cache entry was created */;
+ /*
+ ** pdbOpening is used to coordinate the Java/DB connection of a
+ ** being-open()'d db in the face of auto-extensions.
+ ** Auto-extensions run before we can bind the C db to its Java
+ ** representation, but auto-extensions require that binding to pass
+ ** on to their Java-side callbacks. We handle this as follows:
+ **
+ ** - In the JNI side of sqlite3_open(), allocate the Java side of
+ ** that connection and set pdbOpening to point to that
+ ** object.
+ **
+ ** - Call sqlite3_open(), which triggers the auto-extension
+ ** handler. That handler uses pdbOpening to connect the native
+ ** db handle which it receives with pdbOpening.
+ **
+ ** - When sqlite3_open() returns, check whether pdbOpening->pDb is
+ ** NULL. If it isn't, auto-extension handling set it up. If it
+ ** is, complete the Java/C binding unless sqlite3_open() returns
+ ** a NULL db, in which case free pdbOpening.
+ */
+ S3JniDb * pdbOpening;
+ S3JniEnv * pNext /* Next entry in SJG.envCache.aHead or
+ SJG.envCache.aFree */;
+};
+
+/*
+** State for proxying sqlite3_auto_extension() in Java. This was
+** initially a separate class from S3JniHook and now the older name is
+** retained for readability in the APIs which use this, as well as for
+** its better code-searchability.
+*/
+typedef S3JniHook S3JniAutoExtension;
+
+/*
+** Type IDs for SQL function categories.
+*/
+enum UDFType {
+ UDF_UNKNOWN_TYPE = 0/*for error propagation*/,
+ UDF_SCALAR,
+ UDF_AGGREGATE,
+ UDF_WINDOW
+};
+
+/*
+** State for binding Java-side UDFs.
+*/
+typedef struct S3JniUdf S3JniUdf;
+struct S3JniUdf {
+ jobject jObj /* SQLFunction instance */;
+ char * zFuncName /* Only for error reporting and debug logging */;
+ enum UDFType type /* UDF type */;
+ /** Method IDs for the various UDF methods. */
+ jmethodID jmidxFunc /* xFunc method (scalar) */;
+ jmethodID jmidxStep /* xStep method (aggregate/window) */;
+ jmethodID jmidxFinal /* xFinal method (aggregate/window) */;
+ jmethodID jmidxValue /* xValue method (window) */;
+ jmethodID jmidxInverse /* xInverse method (window) */;
+ S3JniUdf * pNext /* Next entry in SJG.udf.aFree. */;
+};
+
+#if defined(SQLITE_JNI_ENABLE_METRICS) && 0==SQLITE_JNI_ENABLE_METRICS
+# undef SQLITE_JNI_ENABLE_METRICS
+#endif
+
+/*
+** If true, modifying S3JniGlobal.metrics is protected by a mutex,
+** else it isn't.
+*/
+#ifdef SQLITE_DEBUG
+# define S3JNI_METRICS_MUTEX SQLITE_THREADSAFE
+#else
+# define S3JNI_METRICS_MUTEX 0
+#endif
+#ifndef SQLITE_JNI_ENABLE_METRICS
+# undef S3JNI_METRICS_MUTEX
+# define S3JNI_METRICS_MUTEX 0
+#endif
+
+/*
+** Global state, e.g. caches and metrics.
+*/
+typedef struct S3JniGlobalType S3JniGlobalType;
+struct S3JniGlobalType {
+ /*
+ ** According to: https://developer.ibm.com/articles/j-jni/
+ **
+ ** > A thread can get a JNIEnv by calling GetEnv() using the JNI
+ ** invocation interface through a JavaVM object. The JavaVM object
+ ** itself can be obtained by calling the JNI GetJavaVM() method
+ ** using a JNIEnv object and can be cached and shared across
+ ** threads. Caching a copy of the JavaVM object enables any thread
+ ** with access to the cached object to get access to its own
+ ** JNIEnv when necessary.
+ */
+ JavaVM * jvm;
+ /*
+ ** Global mutex. It must not be used for anything which might call
+ ** back into the JNI layer.
+ */
+ sqlite3_mutex * mutex;
+ /*
+ ** Cache of references to Java classes and method IDs for
+ ** NativePointerHolder subclasses and OutputPointer.T types.
+ */
+ struct {
+ S3JniNphOp list[S3Jni_NphCache_size];
+ sqlite3_mutex * mutex; /* mutex for this->list */
+ volatile void const * locker; /* sanity-checking-only context object
+ for this->mutex */
+ } nph;
+ /*
+ ** Cache of per-thread state.
+ */
+ struct {
+ S3JniEnv * aHead /* Linked list of in-use instances */;
+ S3JniEnv * aFree /* Linked list of free instances */;
+ sqlite3_mutex * mutex /* mutex for aHead and aFree. */;
+ volatile void const * locker /* env mutex is held on this
+ object's behalf. Used only for
+ sanity checking. */;
+ } envCache;
+ /*
+ ** Per-db state. This can move into the core library once we can tie
+ ** client-defined state to db handles there.
+ */
+ struct {
+ S3JniDb * aFree /* Linked list of free instances */;
+ sqlite3_mutex * mutex /* mutex for aHead and aFree */;
+ volatile void const * locker
+ /* perDb mutex is held on this object's behalf. Used only for
+ sanity checking. Note that the mutex is at the class level, not
+ instance level. */;
+ } perDb;
+ struct {
+ S3JniUdf * aFree /* Head of the free-item list. Guarded by global
+ mutex. */;
+ } udf;
+ /*
+ ** Refs to global classes and methods. Obtained during static init
+ ** and never released.
+ */
+ struct {
+ jclass cLong /* global ref to java.lang.Long */;
+ jclass cString /* global ref to java.lang.String */;
+ jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */;
+ jmethodID ctorLong1 /* the Long(long) constructor */;
+ jmethodID ctorStringBA /* the String(byte[],Charset) constructor */;
+ jmethodID stringGetBytes /* the String.getBytes(Charset) method */;
+ } g;
+ /*
+ ** The list of Java-side auto-extensions
+ ** (org.sqlite.jni.capi.AutoExtensionCallback objects).
+ */
+ struct {
+ S3JniAutoExtension *aExt /* The auto-extension list. It is
+ maintained such that all active
+ entries are in the first contiguous
+ nExt array elements. */;
+ int nAlloc /* number of entries allocated for aExt,
+ as distinct from the number of active
+ entries. */;
+ int nExt /* number of active entries in aExt, all in the
+ first nExt'th array elements. */;
+ sqlite3_mutex * mutex /* mutex for manipulation/traversal of aExt */;
+ volatile const void * locker /* object on whose behalf the mutex
+ is held. Only for sanity checking
+ in debug builds. */;
+ } autoExt;
+#ifdef SQLITE_ENABLE_FTS5
+ struct {
+ volatile jobject jExt /* Global ref to Java singleton for the
+ Fts5ExtensionApi instance. */;
+ struct {
+ jfieldID fidA /* Fts5Phrase::a member */;
+ jfieldID fidB /* Fts5Phrase::b member */;
+ } jPhraseIter;
+ } fts5;
+#endif
+ struct {
+#ifdef SQLITE_ENABLE_SQLLOG
+ S3JniHook sqllog /* sqlite3_config(SQLITE_CONFIG_SQLLOG) callback */;
+#endif
+ S3JniHook configlog /* sqlite3_config(SQLITE_CONFIG_LOG) callback */;
+ S3JniHook * aFree /* free-item list, for recycling. */;
+ sqlite3_mutex * mutex /* mutex for aFree */;
+ volatile const void * locker /* object on whose behalf the mutex
+ is held. Only for sanity checking
+ in debug builds. */;
+ } hook;
+#ifdef SQLITE_JNI_ENABLE_METRICS
+ /* Internal metrics. */
+ struct {
+ volatile unsigned nEnvHit;
+ volatile unsigned nEnvMiss;
+ volatile unsigned nEnvAlloc;
+ volatile unsigned nMutexEnv /* number of times envCache.mutex was entered for
+ a S3JniEnv operation. */;
+ volatile unsigned nMutexNph /* number of times SJG.mutex was entered */;
+ volatile unsigned nMutexHook /* number of times SJG.mutex hooks.was entered */;
+ volatile unsigned nMutexPerDb /* number of times perDb.mutex was entered */;
+ volatile unsigned nMutexAutoExt /* number of times autoExt.mutex was entered */;
+ volatile unsigned nMutexGlobal /* number of times global mutex was entered. */;
+ volatile unsigned nMutexUdf /* number of times global mutex was entered
+ for UDFs. */;
+ volatile unsigned nDestroy /* xDestroy() calls across all types */;
+ volatile unsigned nPdbAlloc /* Number of S3JniDb alloced. */;
+ volatile unsigned nPdbRecycled /* Number of S3JniDb reused. */;
+ volatile unsigned nUdfAlloc /* Number of S3JniUdf alloced. */;
+ volatile unsigned nUdfRecycled /* Number of S3JniUdf reused. */;
+ volatile unsigned nHookAlloc /* Number of S3JniHook alloced. */;
+ volatile unsigned nHookRecycled /* Number of S3JniHook reused. */;
+ struct {
+ /* Number of calls for each type of UDF callback. */
+ volatile unsigned nFunc;
+ volatile unsigned nStep;
+ volatile unsigned nFinal;
+ volatile unsigned nValue;
+ volatile unsigned nInverse;
+ } udf;
+ unsigned nMetrics /* Total number of mutex-locked
+ metrics increments. */;
+#if S3JNI_METRICS_MUTEX
+ sqlite3_mutex * mutex;
+#endif
+ } metrics;
+#endif /* SQLITE_JNI_ENABLE_METRICS */
+};
+static S3JniGlobalType S3JniGlobal = {};
+#define SJG S3JniGlobal
+
+/* Increments *p, possibly protected by a mutex. */
+#ifndef SQLITE_JNI_ENABLE_METRICS
+#define s3jni_incr(PTR)
+#elif S3JNI_METRICS_MUTEX
+static void s3jni_incr( volatile unsigned int * const p ){
+ sqlite3_mutex_enter(SJG.metrics.mutex);
+ ++SJG.metrics.nMetrics;
+ ++(*p);
+ sqlite3_mutex_leave(SJG.metrics.mutex);
+}
+#else
+#define s3jni_incr(PTR) ++(*(PTR))
+#endif
+
+/* Helpers for working with specific mutexes. */
+#if SQLITE_THREADSAFE
+#define s3jni_mutex_enter2(M, Metric) \
+ sqlite3_mutex_enter( M ); \
+ s3jni_incr( &SJG.metrics.Metric )
+#define s3jni_mutex_leave2(M) \
+ sqlite3_mutex_leave( M )
+
+#define s3jni_mutex_enter(M, L, Metric) \
+ assert( (void*)env != (void*)L && "Invalid use of " #L); \
+ s3jni_mutex_enter2( M, Metric ); \
+ L = env
+#define s3jni_mutex_leave(M, L) \
+ assert( (void*)env == (void*)L && "Invalid use of " #L); \
+ L = 0; \
+ s3jni_mutex_leave2( M )
+
+#define S3JniEnv_mutex_assertLocked \
+ assert( 0 != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
+#define S3JniEnv_mutex_assertLocker \
+ assert( (env) == SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
+#define S3JniEnv_mutex_assertNotLocker \
+ assert( (env) != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
+
+#define S3JniEnv_mutex_enter \
+ s3jni_mutex_enter( SJG.envCache.mutex, SJG.envCache.locker, nMutexEnv )
+#define S3JniEnv_mutex_leave \
+ s3jni_mutex_leave( SJG.envCache.mutex, SJG.envCache.locker )
+
+#define S3JniAutoExt_mutex_enter \
+ s3jni_mutex_enter( SJG.autoExt.mutex, SJG.autoExt.locker, nMutexAutoExt )
+#define S3JniAutoExt_mutex_leave \
+ s3jni_mutex_leave( SJG.autoExt.mutex, SJG.autoExt.locker )
+#define S3JniAutoExt_mutex_assertLocker \
+ assert( env == SJG.autoExt.locker && "Misuse of S3JniGlobal.autoExt.mutex" )
+
+#define S3JniGlobal_mutex_enter \
+ s3jni_mutex_enter2( SJG.mutex, nMutexGlobal )
+#define S3JniGlobal_mutex_leave \
+ s3jni_mutex_leave2( SJG.mutex )
+
+#define S3JniHook_mutex_enter \
+ s3jni_mutex_enter( SJG.hook.mutex, SJG.hook.locker, nMutexHook )
+#define S3JniHook_mutex_leave \
+ s3jni_mutex_leave( SJG.hook.mutex, SJG.hook.locker )
+
+#define S3JniNph_mutex_enter \
+ s3jni_mutex_enter( SJG.nph.mutex, SJG.nph.locker, nMutexNph )
+#define S3JniNph_mutex_leave \
+ s3jni_mutex_leave( SJG.nph.mutex, SJG.nph.locker )
+
+#define S3JniDb_mutex_assertLocker \
+ assert( (env) == SJG.perDb.locker && "Misuse of S3JniGlobal.perDb.mutex" )
+#define S3JniDb_mutex_enter \
+ s3jni_mutex_enter( SJG.perDb.mutex, SJG.perDb.locker, nMutexPerDb )
+#define S3JniDb_mutex_leave \
+ s3jni_mutex_leave( SJG.perDb.mutex, SJG.perDb.locker )
+
+#else /* SQLITE_THREADSAFE==0 */
+#define S3JniAutoExt_mutex_assertLocker
+#define S3JniAutoExt_mutex_enter
+#define S3JniAutoExt_mutex_leave
+#define S3JniDb_mutex_assertLocker
+#define S3JniDb_mutex_enter
+#define S3JniDb_mutex_leave
+#define S3JniEnv_mutex_assertLocked
+#define S3JniEnv_mutex_assertLocker
+#define S3JniEnv_mutex_assertNotLocker
+#define S3JniEnv_mutex_enter
+#define S3JniEnv_mutex_leave
+#define S3JniGlobal_mutex_enter
+#define S3JniGlobal_mutex_leave
+#define S3JniHook_mutex_enter
+#define S3JniHook_mutex_leave
+#define S3JniNph_mutex_enter
+#define S3JniNph_mutex_leave
+#endif
+
+/* Helpers for jstring and jbyteArray. */
+static const char * s3jni__jstring_to_mutf8(JNIEnv * const env, jstring v ){
+ const char *z = v ? (*env)->GetStringUTFChars(env, v, NULL) : 0;
+ s3jni_oom_check( v ? !!z : !z );
+ return z;
+}
+
+#define s3jni_jstring_to_mutf8(ARG) s3jni__jstring_to_mutf8(env, (ARG))
+#define s3jni_mutf8_release(ARG,VAR) if( VAR ) (*env)->ReleaseStringUTFChars(env, ARG, VAR)
+
+/*
+** If jBA is not NULL then its GetByteArrayElements() value is
+** returned. If jBA is not NULL and nBA is not NULL then *nBA is set
+** to the GetArrayLength() of jBA. If GetByteArrayElements() requires
+** an allocation and that allocation fails then this function either
+** fails fatally or returns 0, depending on build-time options.
+ */
+static jbyte * s3jni__jbyteArray_bytes2(JNIEnv * const env, jbyteArray jBA, jsize * nBA ){
+ jbyte * const rv = jBA ? (*env)->GetByteArrayElements(env, jBA, NULL) : 0;
+ s3jni_oom_check( jBA ? !!rv : 1 );
+ if( jBA && nBA ) *nBA = (*env)->GetArrayLength(env, jBA);
+ return rv;
+}
+
+#define s3jni_jbyteArray_bytes2(jByteArray,ptrToSz) \
+ s3jni__jbyteArray_bytes2(env, (jByteArray), (ptrToSz))
+#define s3jni_jbyteArray_bytes(jByteArray) s3jni__jbyteArray_bytes2(env, (jByteArray), 0)
+#define s3jni_jbyteArray_release(jByteArray,jBytes) \
+ if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_ABORT)
+#define s3jni_jbyteArray_commit(jByteArray,jBytes) \
+ if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_COMMIT)
+
+/*
+** Returns the current JNIEnv object. Fails fatally if it cannot find
+** the object.
+*/
+static JNIEnv * s3jni_env(void){
+ JNIEnv * env = 0;
+ if( (*SJG.jvm)->GetEnv(SJG.jvm, (void **)&env,
+ JNI_VERSION_1_8) ){
+ fprintf(stderr, "Fatal error: cannot get current JNIEnv.\n");
+ abort();
+ }
+ return env;
+}
+
+/*
+** Fetches the S3JniGlobal.envCache row for the given env, allocing a
+** row if needed. When a row is allocated, its state is initialized
+** insofar as possible. Calls (*env)->FatalError() if allocation of an
+** entry fails. That's hypothetically possible but "shouldn't happen."
+*/
+static S3JniEnv * S3JniEnv__get(JNIEnv * const env){
+ struct S3JniEnv * row;
+ S3JniEnv_mutex_enter;
+ row = SJG.envCache.aHead;
+ for( ; row; row = row->pNext ){
+ if( row->env == env ){
+ s3jni_incr( &SJG.metrics.nEnvHit );
+ S3JniEnv_mutex_leave;
+ return row;
+ }
+ }
+ s3jni_incr( &SJG.metrics.nEnvMiss );
+ row = SJG.envCache.aFree;
+ if( row ){
+ SJG.envCache.aFree = row->pNext;
+ }else{
+ row = s3jni_malloc_or_die(env, sizeof(*row));
+ s3jni_incr( &SJG.metrics.nEnvAlloc );
+ }
+ memset(row, 0, sizeof(*row));
+ row->pNext = SJG.envCache.aHead;
+ SJG.envCache.aHead = row;
+ row->env = env;
+
+ S3JniEnv_mutex_leave;
+ return row;
+}
+
+#define S3JniEnv_get() S3JniEnv__get(env)
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own Java/JNI bindings.
+**
+** For purposes of certain hand-crafted JNI function bindings, we
+** need a way of reporting errors which is consistent with the rest of
+** the C API, as opposed to throwing Java exceptions. To that end, this
+** internal-use-only function is a thin proxy around
+** sqlite3ErrorWithMessage(). The intent is that it only be used from
+** JNI bindings such as sqlite3_prepare_v2/v3(), and definitely not
+** from client code.
+**
+** Returns err_code.
+*/
+static int s3jni_db_error(sqlite3* const db, int err_code,
+ const char * const zMsg){
+ if( db!=0 ){
+ if( 0==zMsg ){
+ sqlite3Error(db, err_code);
+ }else{
+ const int nMsg = sqlite3Strlen30(zMsg);
+ sqlite3_mutex_enter(sqlite3_db_mutex(db));
+ sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg);
+ sqlite3_mutex_leave(sqlite3_db_mutex(db));
+ }
+ }
+ return err_code;
+}
+
+/*
+** Creates a new jByteArray of length nP, copies p's contents into it,
+** and returns that byte array (NULL on OOM unless fail-fast alloc
+** errors are enabled). p may be NULL, in which case the array is
+** created but no bytes are filled.
+*/
+static jbyteArray s3jni__new_jbyteArray(JNIEnv * const env,
+ const void * const p, int nP){
+ jbyteArray jba = (*env)->NewByteArray(env, (jint)nP);
+
+ s3jni_oom_check( jba );
+ if( jba && p ){
+ (*env)->SetByteArrayRegion(env, jba, 0, (jint)nP, (const jbyte*)p);
+ }
+ return jba;
+}
+
+#define s3jni_new_jbyteArray(P,n) s3jni__new_jbyteArray(env, P, n)
+
+
+/*
+** Uses the java.lang.String(byte[],Charset) constructor to create a
+** new String from UTF-8 string z. n is the number of bytes to
+** copy. If n<0 then sqlite3Strlen30() is used to calculate it.
+**
+** Returns NULL if z is NULL or on OOM, else returns a new jstring
+** owned by the caller.
+**
+** Sidebar: this is a painfully inefficient way to convert from
+** standard UTF-8 to a Java string, but JNI offers only algorithms for
+** working with MUTF-8, not UTF-8.
+*/
+static jstring s3jni__utf8_to_jstring(JNIEnv * const env,
+ const char * const z, int n){
+ jstring rv = NULL;
+ if( 0==n || (n<0 && z && !z[0]) ){
+ /* Fast-track the empty-string case via the MUTF-8 API. We could
+ hypothetically do this for any strings where n<4 and z is
+ NUL-terminated and none of z[0..3] are NUL bytes. */
+ rv = (*env)->NewStringUTF(env, "");
+ s3jni_oom_check( rv );
+ }else if( z ){
+ jbyteArray jba;
+ if( n<0 ) n = sqlite3Strlen30(z);
+ jba = s3jni_new_jbyteArray((unsigned const char *)z, n);
+ if( jba ){
+ rv = (*env)->NewObject(env, SJG.g.cString, SJG.g.ctorStringBA,
+ jba, SJG.g.oCharsetUtf8);
+ S3JniIfThrew{
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ }
+ S3JniUnrefLocal(jba);
+ }
+ s3jni_oom_check( rv );
+ }
+ return rv;
+}
+#define s3jni_utf8_to_jstring(CStr,n) s3jni__utf8_to_jstring(env, CStr, n)
+
+/*
+** Converts the given java.lang.String object into a NUL-terminated
+** UTF-8 C-string by calling jstr.getBytes(StandardCharset.UTF_8).
+** Returns NULL if jstr is NULL or on allocation error. If jstr is not
+** NULL and nLen is not NULL then nLen is set to the length of the
+** returned string, not including the terminating NUL. If jstr is not
+** NULL and it returns NULL, this indicates an allocation error. In
+** that case, if nLen is not NULL then it is either set to 0 (if
+** fetching of jstr's bytes fails to allocate) or set to what would
+** have been the length of the string had C-string allocation
+** succeeded.
+**
+** The returned memory is allocated from sqlite3_malloc() and
+** ownership is transferred to the caller.
+*/
+static char * s3jni__jstring_to_utf8(JNIEnv * const env,
+ jstring jstr, int *nLen){
+ jbyteArray jba;
+ jsize nBA;
+ char *rv;
+
+ if( !jstr ) return 0;
+ jba = (*env)->CallObjectMethod(env, jstr, SJG.g.stringGetBytes,
+ SJG.g.oCharsetUtf8);
+
+ if( (*env)->ExceptionCheck(env) || !jba
+ /* order of these checks is significant for -Xlint:jni */ ) {
+ S3JniExceptionReport;
+ s3jni_oom_check( jba );
+ if( nLen ) *nLen = 0;
+ return 0;
+ }
+ nBA = (*env)->GetArrayLength(env, jba);
+ if( nLen ) *nLen = (int)nBA;
+ rv = s3jni_malloc( nBA + 1 );
+ if( rv ){
+ (*env)->GetByteArrayRegion(env, jba, 0, nBA, (jbyte*)rv);
+ rv[nBA] = 0;
+ }
+ S3JniUnrefLocal(jba);
+ return rv;
+}
+#define s3jni_jstring_to_utf8(JStr,n) s3jni__jstring_to_utf8(env, JStr, n)
+
+/*
+** Expects to be passed a pointer from sqlite3_column_text16() or
+** sqlite3_value_text16() and a byte-length value from
+** sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a
+** Java String of exactly half that character length, returning NULL
+** if !p or (*env)->NewString() fails.
+*/
+static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, int nP){
+ jstring const rv = p
+ ? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2))
+ : NULL;
+ s3jni_oom_check( p ? !!rv : 1 );
+ return rv;
+}
+
+/*
+** Requires jx to be a Throwable. Calls its toString() method and
+** returns its value converted to a UTF-8 string. The caller owns the
+** returned string and must eventually sqlite3_free() it. Returns 0
+** if there is a problem fetching the info or on OOM.
+**
+** Design note: we use toString() instead of getMessage() because the
+** former includes the exception type's name:
+**
+** Exception e = new RuntimeException("Hi");
+** System.out.println(e.toString()); // java.lang.RuntimeException: Hi
+** System.out.println(e.getMessage()); // Hi
+*/
+static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx){
+ jmethodID mid;
+ jstring msg;
+ char * zMsg;
+ jclass const klazz = (*env)->GetObjectClass(env, jx);
+ mid = (*env)->GetMethodID(env, klazz, "toString", "()Ljava/lang/String;");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew{
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ return 0;
+ }
+ msg = (*env)->CallObjectMethod(env, jx, mid);
+ S3JniIfThrew{
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ return 0;
+ }
+ zMsg = s3jni_jstring_to_utf8( msg, 0);
+ S3JniUnrefLocal(msg);
+ return zMsg;
+}
+
+/*
+** Extracts env's current exception, sets ps->pDb's error message to
+** its message string, and clears the exception. If errCode is non-0,
+** it is used as-is, else SQLITE_ERROR is assumed. If there's a
+** problem extracting the exception's message, it's treated as
+** non-fatal and zDfltMsg is used in its place.
+**
+** Locks the global S3JniDb mutex.
+**
+** This must only be called if a JNI exception is pending.
+**
+** Returns errCode unless it is 0, in which case SQLITE_ERROR is
+** returned.
+*/
+static int s3jni__db_exception(JNIEnv * const env, sqlite3 * const pDb,
+ int errCode, const char *zDfltMsg){
+ jthrowable const ex = (*env)->ExceptionOccurred(env);
+
+ if( 0==errCode ) errCode = SQLITE_ERROR;
+ if( ex ){
+ char * zMsg;
+ S3JniExceptionClear;
+ zMsg = s3jni_exception_error_msg(env, ex);
+ s3jni_db_error(pDb, errCode, zMsg ? zMsg : zDfltMsg);
+ sqlite3_free(zMsg);
+ S3JniUnrefLocal(ex);
+ }else if( zDfltMsg ){
+ s3jni_db_error(pDb, errCode, zDfltMsg);
+ }
+ return errCode;
+}
+#define s3jni_db_exception(pDb,ERRCODE,DFLTMSG) \
+ s3jni__db_exception(env, (pDb), (ERRCODE), (DFLTMSG) )
+
+/*
+** Extracts the (void xDestroy()) method from jObj and applies it to
+** jObj. If jObj is NULL, this is a no-op. The lack of an xDestroy()
+** method is silently ignored. Any exceptions thrown by xDestroy()
+** trigger a warning to stdout or stderr and then the exception is
+** suppressed.
+*/
+static void s3jni__call_xDestroy(JNIEnv * const env, jobject jObj){
+ if( jObj ){
+ jclass const klazz = (*env)->GetObjectClass(env, jObj);
+ jmethodID method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V");
+
+ S3JniUnrefLocal(klazz);
+ if( method ){
+ s3jni_incr( &SJG.metrics.nDestroy );
+ (*env)->CallVoidMethod(env, jObj, method);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("xDestroy() callback");
+ S3JniExceptionClear;
+ }
+ }else{
+ /* Non-fatal. */
+ S3JniExceptionClear;
+ }
+ }
+}
+#define s3jni_call_xDestroy(JOBJ) s3jni__call_xDestroy(env, (JOBJ))
+
+/*
+** Internal helper for many hook callback impls. Locks the S3JniDb
+** mutex, makes a copy of src into dest, with a some differences: (1)
+** if src->jObj or src->jExtra are not NULL then dest will be a new
+** LOCAL ref to it instead of a copy of the prior GLOBAL ref. (2)
+** dest->doXDestroy is always false.
+**
+** If dest->jObj is not NULL when this returns then the caller is
+** obligated to eventually free the new ref by passing *dest to
+** S3JniHook_localundup(). The dest pointer must NOT be passed to
+** S3JniHook_unref(), as that routine assumes that dest->jObj/jExtra
+** are GLOBAL refs (it's illegal to try to unref the wrong ref type).
+**
+** Background: when running a hook we need a call-local copy lest
+** another thread modify the hook while we're running it. That copy
+** has to have its own Java reference, but it need only be call-local.
+*/
+static void S3JniHook__localdup( JNIEnv * const env, S3JniHook const * const src,
+ S3JniHook * const dest ){
+ S3JniHook_mutex_enter;
+ *dest = *src;
+ if(src->jObj) dest->jObj = S3JniRefLocal(src->jObj);
+ if(src->jExtra) dest->jExtra = S3JniRefLocal(src->jExtra);
+ dest->doXDestroy = 0;
+ S3JniHook_mutex_leave;
+}
+#define S3JniHook_localdup(src,dest) S3JniHook__localdup(env,src,dest)
+
+static void S3JniHook__localundup( JNIEnv * const env, S3JniHook * const h ){
+ S3JniUnrefLocal(h->jObj);
+ S3JniUnrefLocal(h->jExtra);
+ *h = S3JniHook_empty;
+}
+#define S3JniHook_localundup(HOOK) S3JniHook__localundup(env, &(HOOK))
+
+/*
+** Removes any Java references from s and clears its state. If
+** doXDestroy is true and s->jObj is not NULL, s->jObj
+** is passed to s3jni_call_xDestroy() before any references are
+** cleared. It is legal to call this when the object has no Java
+** references. s must not be NULL.
+*/
+static void S3JniHook__unref(JNIEnv * const env, S3JniHook * const s){
+ if( s->jObj ){
+ if( s->doXDestroy ){
+ s3jni_call_xDestroy(s->jObj);
+ }
+ S3JniUnrefGlobal(s->jObj);
+ S3JniUnrefGlobal(s->jExtra);
+ }else{
+ assert( !s->jExtra );
+ }
+ *s = S3JniHook_empty;
+}
+#define S3JniHook_unref(hook) S3JniHook__unref(env, (hook))
+
+/*
+** Allocates one blank S3JniHook object from the recycling bin, if
+** available, else from the heap. Returns NULL or dies on OOM,
+** depending on build options. Locks on SJG.hooks.mutex.
+*/
+static S3JniHook *S3JniHook__alloc(JNIEnv * const env){
+ S3JniHook * p = 0;
+ S3JniHook_mutex_enter;
+ if( SJG.hook.aFree ){
+ p = SJG.hook.aFree;
+ SJG.hook.aFree = p->pNext;
+ p->pNext = 0;
+ s3jni_incr(&SJG.metrics.nHookRecycled);
+ }
+ S3JniHook_mutex_leave;
+ if( 0==p ){
+ p = s3jni_malloc(sizeof(S3JniHook));
+ if( p ){
+ s3jni_incr(&SJG.metrics.nHookAlloc);
+ }
+ }
+ if( p ){
+ *p = S3JniHook_empty;
+ }
+ return p;
+}
+#define S3JniHook_alloc() S3JniHook__alloc(env)
+
+/*
+** The rightful fate of all results from S3JniHook_alloc(). Locks on
+** SJG.hook.mutex.
+*/
+static void S3JniHook__free(JNIEnv * const env, S3JniHook * const p){
+ if(p){
+ assert( !p->pNext );
+ S3JniHook_unref(p);
+ S3JniHook_mutex_enter;
+ p->pNext = SJG.hook.aFree;
+ SJG.hook.aFree = p;
+ S3JniHook_mutex_leave;
+ }
+}
+#define S3JniHook_free(hook) S3JniHook__free(env, hook)
+
+#if 0
+/* S3JniHook__free() without the lock: caller must hold the global mutex */
+static void S3JniHook__free_unlocked(JNIEnv * const env, S3JniHook * const p){
+ if(p){
+ assert( !p->pNext );
+ assert( p->pNext != SJG.hook.aFree );
+ S3JniHook_unref(p);
+ p->pNext = SJG.hook.aFree;
+ SJG.hook.aFree = p;
+ }
+}
+#define S3JniHook_free_unlocked(hook) S3JniHook__free_unlocked(env, hook)
+#endif
+
+/*
+** Clears all of s's state. Requires that that the caller has locked
+** S3JniGlobal.perDb.mutex. Make sure to do anything needed with
+** s->pNext and s->pPrev before calling this, as this clears them.
+*/
+static void S3JniDb_clear(JNIEnv * const env, S3JniDb * const s){
+ S3JniDb_mutex_assertLocker;
+ sqlite3_free( s->zMainDbName );
+#define UNHOOK(MEMBER) \
+ S3JniHook_unref(&s->hooks.MEMBER)
+ UNHOOK(auth);
+ UNHOOK(busyHandler);
+ UNHOOK(collationNeeded);
+ UNHOOK(commit);
+ UNHOOK(progress);
+ UNHOOK(rollback);
+ UNHOOK(trace);
+ UNHOOK(update);
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ UNHOOK(preUpdate);
+#endif
+#undef UNHOOK
+ S3JniUnrefGlobal(s->jDb);
+ memset(s, 0, sizeof(S3JniDb));
+}
+
+/*
+** Clears s's state and moves it to the free-list. Requires that
+** S3JniGlobal.perDb.mutex is locked.
+*/
+static void S3JniDb__set_aside_unlocked(JNIEnv * const env, S3JniDb * const s){
+ assert( s );
+ S3JniDb_mutex_assertLocker;
+ if( s ){
+ S3JniDb_clear(env, s);
+ s->pNext = SJG.perDb.aFree;
+ SJG.perDb.aFree = s;
+ }
+}
+#define S3JniDb_set_aside_unlocked(JniDb) S3JniDb__set_aside_unlocked(env, JniDb)
+
+static void S3JniDb__set_aside(JNIEnv * const env, S3JniDb * const s){
+ S3JniDb_mutex_enter;
+ S3JniDb_set_aside_unlocked(s);
+ S3JniDb_mutex_leave;
+}
+#define S3JniDb_set_aside(JNIDB) S3JniDb__set_aside(env, JNIDB)
+
+/*
+** Uncache any state for the given JNIEnv, clearing all Java
+** references the cache owns. Returns true if env was cached and false
+** if it was not found in the cache. Ownership of the S3JniEnv object
+** associated with the given argument is transferred to this function,
+** which makes it free for re-use.
+**
+** Requires that the env mutex be locked.
+*/
+static int S3JniEnv_uncache(JNIEnv * const env){
+ struct S3JniEnv * row;
+ struct S3JniEnv * pPrev = 0;
+
+ S3JniEnv_mutex_assertLocked;
+ row = SJG.envCache.aHead;
+ for( ; row; pPrev = row, row = row->pNext ){
+ if( row->env == env ){
+ break;
+ }
+ }
+ if( !row ){
+ return 0;
+ }
+ if( pPrev) pPrev->pNext = row->pNext;
+ else{
+ assert( SJG.envCache.aHead == row );
+ SJG.envCache.aHead = row->pNext;
+ }
+ memset(row, 0, sizeof(S3JniEnv));
+ row->pNext = SJG.envCache.aFree;
+ SJG.envCache.aFree = row;
+ return 1;
+}
+
+/*
+** Fetches the given nph-ref from cache the cache and returns the
+** object with its klazz member set. This is an O(1) operation except
+** on the first call for a given pRef, during which pRef->klazz and
+** pRef->pRef are initialized thread-safely. In the latter case it's
+** still effectively O(1), but with a much longer 1.
+**
+** It is up to the caller to populate the other members of the
+** returned object if needed, taking care to lock the modification
+** with S3JniNph_mutex_enter/leave.
+**
+** This simple cache catches >99% of searches in the current
+** (2023-07-31) tests.
+*/
+static S3JniNphOp * s3jni__nphop(JNIEnv * const env, S3JniNphOp const* pRef){
+ S3JniNphOp * const pNC = &SJG.nph.list[pRef->index];
+
+ assert( (void*)pRef>=(void*)&S3JniNphOps && (void*)pRef<(void*)(&S3JniNphOps + 1)
+ && "pRef is out of range" );
+ assert( pRef->index>=0
+ && (pRef->index < (sizeof(S3JniNphOps) / sizeof(S3JniNphOp)))
+ && "pRef->index is out of range" );
+ if( !pNC->klazz ){
+ S3JniNph_mutex_enter;
+ if( !pNC->klazz ){
+ jclass const klazz = (*env)->FindClass(env, pRef->zName);
+ //printf("FindClass %s\n", pRef->zName);
+ S3JniExceptionIsFatal("FindClass() unexpectedly threw");
+ pNC->klazz = S3JniRefGlobal(klazz);
+ }
+ S3JniNph_mutex_leave;
+ }
+ assert( pNC->klazz );
+ return pNC;
+}
+
+#define s3jni_nphop(PRef) s3jni__nphop(env, PRef)
+
+/*
+** Common code for accessor functions for NativePointerHolder and
+** OutputPointer types. pRef must be a pointer from S3JniNphOps. jOut
+** must be an instance of that class (Java's type safety takes care of
+** that requirement). If necessary, this fetches the jfieldID for
+** jOut's pRef->zMember, which must be of the type represented by the
+** JNI type signature pRef->zTypeSig, and stores it in
+** S3JniGlobal.nph.list[pRef->index]. Fails fatally if the pRef->zMember
+** property is not found, as that presents a serious internal misuse.
+**
+** Property lookups are cached on a per-pRef basis.
+*/
+static jfieldID s3jni_nphop_field(JNIEnv * const env, S3JniNphOp const* pRef){
+ S3JniNphOp * const pNC = s3jni_nphop(pRef);
+
+ if( !pNC->fidValue ){
+ S3JniNph_mutex_enter;
+ if( !pNC->fidValue ){
+ pNC->fidValue = (*env)->GetFieldID(env, pNC->klazz,
+ pRef->zMember, pRef->zTypeSig);
+ S3JniExceptionIsFatal("Code maintenance required: missing "
+ "required S3JniNphOp::fidValue.");
+ }
+ S3JniNph_mutex_leave;
+ }
+ assert( pNC->fidValue );
+ return pNC->fidValue;
+}
+
+/*
+** Sets a native ptr value in NativePointerHolder object jNph,
+** which must be of the native type described by pRef. jNph
+** may not be NULL.
+*/
+static void NativePointerHolder__set(JNIEnv * const env, S3JniNphOp const* pRef,
+ jobject jNph, const void * p){
+ assert( jNph );
+ (*env)->SetLongField(env, jNph, s3jni_nphop_field(env, pRef),
+ S3JniCast_P2L(p));
+ S3JniExceptionIsFatal("Could not set NativePointerHolder.nativePointer.");
+}
+
+#define NativePointerHolder_set(PREF,JNPH,P) \
+ NativePointerHolder__set(env, PREF, JNPH, P)
+
+/*
+** Fetches a native ptr value from NativePointerHolder object jNph,
+** which must be of the native type described by pRef. This is a
+** no-op if jNph is NULL.
+*/
+static void * NativePointerHolder__get(JNIEnv * env, jobject jNph,
+ S3JniNphOp const* pRef){
+ void * rv = 0;
+ if( jNph ){
+ rv = S3JniCast_L2P(
+ (*env)->GetLongField(env, jNph, s3jni_nphop_field(env, pRef))
+ );
+ S3JniExceptionIsFatal("Cannot fetch NativePointerHolder.nativePointer.");
+ }
+ return rv;
+}
+
+#define NativePointerHolder_get(JOBJ,NPHREF) \
+ NativePointerHolder__get(env, (JOBJ), (NPHREF))
+
+/*
+** Helpers for extracting pointers from jobjects, noting that we rely
+** on the corresponding Java interfaces having already done the
+** type-checking. OBJ must be a jobject referring to a
+** NativePointerHolder, where T matches PtrGet_T. Don't use these
+** in contexts where that's not the case. Note that these aren't
+** type-safe in the strictest sense:
+**
+** sqlite3 * s = PtrGet_sqlite3_stmt(...)
+**
+** will work, despite the incorrect macro name, so long as the
+** argument is a Java sqlite3 object, as this operation only has void
+** pointers to work with.
+*/
+#define PtrGet_T(T,OBJ) (T*)NativePointerHolder_get(OBJ, S3JniNph(T))
+#define PtrGet_sqlite3(OBJ) PtrGet_T(sqlite3, OBJ)
+#define PtrGet_sqlite3_backup(OBJ) PtrGet_T(sqlite3_backup, OBJ)
+#define PtrGet_sqlite3_blob(OBJ) PtrGet_T(sqlite3_blob, OBJ)
+#define PtrGet_sqlite3_context(OBJ) PtrGet_T(sqlite3_context, OBJ)
+#define PtrGet_sqlite3_stmt(OBJ) PtrGet_T(sqlite3_stmt, OBJ)
+#define PtrGet_sqlite3_value(OBJ) PtrGet_T(sqlite3_value, OBJ)
+/*
+** S3JniLongPtr_T(X,Y) expects X to be an unqualified sqlite3 struct
+** type name and Y to be a native pointer to such an object in the
+** form of a jlong value. The jlong is simply cast to (X*). This
+** approach is, as of 2023-09-27, supplanting the former approach. We
+** now do the native pointer extraction in the Java side, rather than
+** the C side, because it's reportedly significantly faster. The
+** intptr_t part here is necessary for compatibility with (at least)
+** ARM32.
+*/
+#define S3JniLongPtr_T(T,JLongAsPtr) (T*)((intptr_t)(JLongAsPtr))
+#define S3JniLongPtr_sqlite3(JLongAsPtr) S3JniLongPtr_T(sqlite3,JLongAsPtr)
+#define S3JniLongPtr_sqlite3_backup(JLongAsPtr) S3JniLongPtr_T(sqlite3_backup,JLongAsPtr)
+#define S3JniLongPtr_sqlite3_blob(JLongAsPtr) S3JniLongPtr_T(sqlite3_blob,JLongAsPtr)
+#define S3JniLongPtr_sqlite3_stmt(JLongAsPtr) S3JniLongPtr_T(sqlite3_stmt,JLongAsPtr)
+#define S3JniLongPtr_sqlite3_value(JLongAsPtr) S3JniLongPtr_T(sqlite3_value,JLongAsPtr)
+/*
+** Extracts the new S3JniDb instance from the free-list, or allocates
+** one if needed, associates it with pDb, and returns. Returns NULL
+** on OOM. The returned object MUST, on success of the calling
+** operation, subsequently be associated with jDb via
+** NativePointerHolder_set() or freed using S3JniDb_set_aside().
+*/
+static S3JniDb * S3JniDb_alloc(JNIEnv * const env, jobject jDb){
+ S3JniDb * rv = 0;
+ S3JniDb_mutex_enter;
+ if( SJG.perDb.aFree ){
+ rv = SJG.perDb.aFree;
+ SJG.perDb.aFree = rv->pNext;
+ rv->pNext = 0;
+ s3jni_incr( &SJG.metrics.nPdbRecycled );
+ }
+ S3JniDb_mutex_leave;
+ if( 0==rv ){
+ rv = s3jni_malloc(sizeof(S3JniDb));
+ if( rv ){
+ s3jni_incr( &SJG.metrics.nPdbAlloc );
+ }
+ }
+ if( rv ){
+ memset(rv, 0, sizeof(S3JniDb));
+ rv->jDb = S3JniRefGlobal(jDb);
+ }
+ return rv;
+}
+
+/*
+** Returns the S3JniDb object for the given org.sqlite.jni.capi.sqlite3
+** object, or NULL if jDb is NULL, no pointer can be extracted
+** from it, or no matching entry can be found.
+*/
+static S3JniDb * S3JniDb__from_java(JNIEnv * const env, jobject jDb){
+ sqlite3 * const pDb = jDb ? PtrGet_sqlite3(jDb) : 0;
+ return pDb ? S3JniDb_from_clientdata(pDb) : 0;
+}
+#define S3JniDb_from_java(jObject) S3JniDb__from_java(env,(jObject))
+
+/*
+** S3JniDb finalizer for use with sqlite3_set_clientdata().
+*/
+static void S3JniDb_xDestroy(void *p){
+ S3JniDeclLocal_env;
+ S3JniDb * const ps = p;
+ assert( !ps->pNext && "Else ps is already in the free-list.");
+ S3JniDb_set_aside(ps);
+}
+
+/*
+** Evaluates to the S3JniDb object for the given sqlite3 object, or
+** NULL if pDb is NULL or was not initialized via the JNI interfaces.
+*/
+#define S3JniDb_from_c(sqlite3Ptr) \
+ ((sqlite3Ptr) ? S3JniDb_from_clientdata(sqlite3Ptr) : 0)
+#define S3JniDb_from_jlong(sqlite3PtrAsLong) \
+ S3JniDb_from_c(S3JniLongPtr_T(sqlite3,sqlite3PtrAsLong))
+
+/*
+** Unref any Java-side state in (S3JniAutoExtension*) AX and zero out
+** AX.
+*/
+#define S3JniAutoExtension_clear(AX) S3JniHook_unref(AX);
+
+/*
+** Initializes a pre-allocated S3JniAutoExtension object. Returns
+** non-0 if there is an error collecting the required state from
+** jAutoExt (which must be an AutoExtensionCallback object). On error,
+** it passes ax to S3JniAutoExtension_clear().
+*/
+static int S3JniAutoExtension_init(JNIEnv *const env,
+ S3JniAutoExtension * const ax,
+ jobject const jAutoExt){
+ jclass const klazz = (*env)->GetObjectClass(env, jAutoExt);
+
+ S3JniAutoExt_mutex_assertLocker;
+ *ax = S3JniHook_empty;
+ ax->midCallback = (*env)->GetMethodID(env, klazz, "call",
+ "(Lorg/sqlite/jni/capi/sqlite3;)I");
+ S3JniUnrefLocal(klazz);
+ S3JniExceptionWarnIgnore;
+ if( !ax->midCallback ){
+ S3JniAutoExtension_clear(ax);
+ return SQLITE_ERROR;
+ }
+ ax->jObj = S3JniRefGlobal(jAutoExt);
+ return 0;
+}
+
+/*
+** Sets the value property of the OutputPointer.Bool jOut object to
+** v.
+*/
+static void OutputPointer_set_Bool(JNIEnv * const env, jobject const jOut,
+ int v){
+ (*env)->SetBooleanField(env, jOut, s3jni_nphop_field(
+ env, S3JniNph(OutputPointer_Bool)
+ ), v ? JNI_TRUE : JNI_FALSE );
+ S3JniExceptionIsFatal("Cannot set OutputPointer.Bool.value");
+}
+
+/*
+** Sets the value property of the OutputPointer.Int32 jOut object to
+** v.
+*/
+static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut,
+ int v){
+ (*env)->SetIntField(env, jOut, s3jni_nphop_field(
+ env, S3JniNph(OutputPointer_Int32)
+ ), (jint)v);
+ S3JniExceptionIsFatal("Cannot set OutputPointer.Int32.value");
+}
+
+/*
+** Sets the value property of the OutputPointer.Int64 jOut object to
+** v.
+*/
+static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut,
+ jlong v){
+ (*env)->SetLongField(env, jOut, s3jni_nphop_field(
+ env, S3JniNph(OutputPointer_Int64)
+ ), v);
+ S3JniExceptionIsFatal("Cannot set OutputPointer.Int64.value");
+}
+
+/*
+** Internal helper for OutputPointer_set_TYPE() where TYPE is an
+** Object type.
+*/
+static void OutputPointer_set_obj(JNIEnv * const env,
+ S3JniNphOp const * const pRef,
+ jobject const jOut,
+ jobject v){
+ (*env)->SetObjectField(env, jOut, s3jni_nphop_field(env, pRef), v);
+ S3JniExceptionIsFatal("Cannot set OutputPointer.T.value");
+}
+
+#ifdef SQLITE_ENABLE_FTS5
+#if 0
+/*
+** Sets the value property of the OutputPointer.ByteArray jOut object
+** to v.
+*/
+static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut,
+ jbyteArray const v){
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_ByteArray), jOut, v);
+}
+#endif
+#endif /* SQLITE_ENABLE_FTS5 */
+
+/*
+** Sets the value property of the OutputPointer.String jOut object to
+** v.
+*/
+static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut,
+ jstring const v){
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_String), jOut, v);
+}
+
+/*
+** Returns true if eTextRep is a valid sqlite3 encoding constant, else
+** returns false.
+*/
+static int encodingTypeIsValid(int eTextRep){
+ switch( eTextRep ){
+ case SQLITE_UTF8: case SQLITE_UTF16:
+ case SQLITE_UTF16LE: case SQLITE_UTF16BE:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/* For use with sqlite3_result/value_pointer() */
+static const char * const ResultJavaValuePtrStr = "org.sqlite.jni.capi.ResultJavaVal";
+
+/*
+** If v is not NULL, it must be a jobject global reference. Its
+** reference is relinquished.
+*/
+static void S3Jni_jobject_finalizer(void *v){
+ if( v ){
+ S3JniDeclLocal_env;
+ S3JniUnrefGlobal((jobject)v);
+ }
+}
+
+/*
+** Returns a new Java instance of the class referred to by pRef, which
+** MUST be interface-compatible with NativePointerHolder and MUST have
+** a no-arg constructor. The NativePointerHolder_set() method is
+** passed the new Java object (which must not be NULL) and pNative
+** (which may be NULL). Hypothetically returns NULL if Java fails to
+** allocate, but the JNI docs are not entirely clear on that detail.
+**
+** Always use a static pointer from the S3JniNphOps struct for the
+** 2nd argument.
+*/
+static jobject NativePointerHolder_new(JNIEnv * const env,
+ S3JniNphOp const * pRef,
+ const void * pNative){
+ jobject rv = 0;
+ S3JniNphOp * const pNC = s3jni_nphop(pRef);
+ if( !pNC->midCtor ){
+ S3JniNph_mutex_enter;
+ if( !pNC->midCtor ){
+ pNC->midCtor = (*env)->GetMethodID(env, pNC->klazz, "", "()V");
+ S3JniExceptionIsFatal("Cannot find constructor for class.");
+ }
+ S3JniNph_mutex_leave;
+ }
+ rv = (*env)->NewObject(env, pNC->klazz, pNC->midCtor);
+ S3JniExceptionIsFatal("No-arg constructor threw.");
+ s3jni_oom_check(rv);
+ if( rv ) NativePointerHolder_set(pRef, rv, pNative);
+ return rv;
+}
+
+static inline jobject new_java_sqlite3(JNIEnv * const env, sqlite3 *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3), sv);
+}
+static inline jobject new_java_sqlite3_backup(JNIEnv * const env, sqlite3_backup *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3_backup), sv);
+}
+static inline jobject new_java_sqlite3_blob(JNIEnv * const env, sqlite3_blob *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3_blob), sv);
+}
+static inline jobject new_java_sqlite3_context(JNIEnv * const env, sqlite3_context *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3_context), sv);
+}
+static inline jobject new_java_sqlite3_stmt(JNIEnv * const env, sqlite3_stmt *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3_stmt), sv);
+}
+static inline jobject new_java_sqlite3_value(JNIEnv * const env, sqlite3_value *sv){
+ return NativePointerHolder_new(env, S3JniNph(sqlite3_value), sv);
+}
+
+/* Helper typedefs for UDF callback types. */
+typedef void (*udf_xFunc_f)(sqlite3_context*,int,sqlite3_value**);
+typedef void (*udf_xStep_f)(sqlite3_context*,int,sqlite3_value**);
+typedef void (*udf_xFinal_f)(sqlite3_context*);
+/*typedef void (*udf_xValue_f)(sqlite3_context*);*/
+/*typedef void (*udf_xInverse_f)(sqlite3_context*,int,sqlite3_value**);*/
+
+/*
+** Allocate a new S3JniUdf (User-defined Function) and associate it
+** with the SQLFunction-type jObj. Returns NULL on OOM. If the
+** returned object's type==UDF_UNKNOWN_TYPE then the type of UDF was
+** not unambiguously detected based on which callback members it has,
+** which falls into the category of user error.
+**
+** The caller must arrange for the returned object to eventually be
+** passed to S3JniUdf_free().
+*/
+static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){
+ S3JniUdf * s = 0;
+
+ S3JniGlobal_mutex_enter;
+ s3jni_incr(&SJG.metrics.nMutexUdf);
+ if( SJG.udf.aFree ){
+ s = SJG.udf.aFree;
+ SJG.udf.aFree = s->pNext;
+ s->pNext = 0;
+ s3jni_incr(&SJG.metrics.nUdfRecycled);
+ }
+ S3JniGlobal_mutex_leave;
+ if( !s ){
+ s = s3jni_malloc( sizeof(*s));
+ s3jni_incr(&SJG.metrics.nUdfAlloc);
+ }
+ if( s ){
+ const char * zFSI = /* signature for xFunc, xStep, xInverse */
+ "(Lorg/sqlite/jni/capi/sqlite3_context;[Lorg/sqlite/jni/capi/sqlite3_value;)V";
+ const char * zFV = /* signature for xFinal, xValue */
+ "(Lorg/sqlite/jni/capi/sqlite3_context;)V";
+ jclass const klazz = (*env)->GetObjectClass(env, jObj);
+
+ memset(s, 0, sizeof(*s));
+ s->jObj = S3JniRefGlobal(jObj);
+
+#define FGET(FuncName,FuncSig,Field) \
+ s->Field = (*env)->GetMethodID(env, klazz, FuncName, FuncSig); \
+ if( !s->Field ) (*env)->ExceptionClear(env)
+
+ FGET("xFunc", zFSI, jmidxFunc);
+ FGET("xStep", zFSI, jmidxStep);
+ FGET("xFinal", zFV, jmidxFinal);
+ FGET("xValue", zFV, jmidxValue);
+ FGET("xInverse", zFSI, jmidxInverse);
+#undef FGET
+
+ S3JniUnrefLocal(klazz);
+ if( s->jmidxFunc ) s->type = UDF_SCALAR;
+ else if( s->jmidxStep && s->jmidxFinal ){
+ s->type = (s->jmidxValue && s->jmidxInverse)
+ ? UDF_WINDOW : UDF_AGGREGATE;
+ }else{
+ s->type = UDF_UNKNOWN_TYPE;
+ }
+ }
+ return s;
+}
+
+/*
+** Frees up all resources owned by s, clears its state, then either
+** caches it for reuse (if cacheIt is true) or frees it. The former
+** requires locking the global mutex, so it must not be held when this
+** is called.
+*/
+static void S3JniUdf_free(JNIEnv * const env, S3JniUdf * const s,
+ int cacheIt){
+ assert( !s->pNext );
+ if( s->jObj ){
+ s3jni_call_xDestroy(s->jObj);
+ S3JniUnrefGlobal(s->jObj);
+ sqlite3_free(s->zFuncName);
+ assert( !s->pNext );
+ memset(s, 0, sizeof(*s));
+ }
+ if( cacheIt ){
+ S3JniGlobal_mutex_enter;
+ s->pNext = S3JniGlobal.udf.aFree;
+ S3JniGlobal.udf.aFree = s;
+ S3JniGlobal_mutex_leave;
+ }else{
+ sqlite3_free( s );
+ }
+}
+
+/* Finalizer for sqlite3_create_function() and friends. */
+static void S3JniUdf_finalizer(void * s){
+ S3JniUdf_free(s3jni_env(), (S3JniUdf*)s, 1);
+}
+
+/*
+** Helper for processing args to UDF handlers with signature
+** (sqlite3_context*,int,sqlite3_value**).
+*/
+typedef struct {
+ jobject jcx /* sqlite3_context */;
+ jobjectArray jargv /* sqlite3_value[] */;
+} udf_jargs;
+
+/*
+** Converts the given (cx, argc, argv) into arguments for the given
+** UDF, writing the result (Java wrappers for cx and argv) in the
+** final 2 arguments. Returns 0 on success, SQLITE_NOMEM on allocation
+** error. On error *jCx and *jArgv will be set to 0. The output
+** objects are of type org.sqlite.jni.capi.sqlite3_context and
+** array-of-org.sqlite.jni.capi.sqlite3_value, respectively.
+*/
+static int udf_args(JNIEnv *env,
+ sqlite3_context * const cx,
+ int argc, sqlite3_value**argv,
+ jobject * jCx, jobjectArray *jArgv){
+ jobjectArray ja = 0;
+ jobject jcx = new_java_sqlite3_context(env, cx);
+ jint i;
+ *jCx = 0;
+ *jArgv = 0;
+ if( !jcx ) goto error_oom;
+ ja = (*env)->NewObjectArray(
+ env, argc, s3jni_nphop(S3JniNph(sqlite3_value))->klazz,
+ NULL);
+ s3jni_oom_check( ja );
+ if( !ja ) goto error_oom;
+ for(i = 0; i < argc; ++i){
+ jobject jsv = new_java_sqlite3_value(env, argv[i]);
+ if( !jsv ) goto error_oom;
+ (*env)->SetObjectArrayElement(env, ja, i, jsv);
+ S3JniUnrefLocal(jsv)/*ja has a ref*/;
+ }
+ *jCx = jcx;
+ *jArgv = ja;
+ return 0;
+error_oom:
+ S3JniUnrefLocal(jcx);
+ S3JniUnrefLocal(ja);
+ return SQLITE_NOMEM;
+}
+
+/*
+** Requires that jCx and jArgv are sqlite3_context
+** resp. array-of-sqlite3_value values initialized by udf_args(). This
+** function zeroes out the nativePointer member of jCx and each entry
+** in jArgv. This is a safety-net precaution to avoid undefined
+** behavior if a Java-side UDF holds a reference to one of its
+** arguments. This MUST be called from any function which successfully
+** calls udf_args(), after calling the corresponding UDF and checking
+** its exception status. It MUST NOT be called in any other case.
+*/
+static void udf_unargs(JNIEnv *env, jobject jCx, int argc, jobjectArray jArgv){
+ int i = 0;
+ assert(jCx);
+ NativePointerHolder_set(S3JniNph(sqlite3_context), jCx, 0);
+ for( ; i < argc; ++i ){
+ jobject jsv = (*env)->GetObjectArrayElement(env, jArgv, i);
+ assert(jsv);
+ NativePointerHolder_set(S3JniNph(sqlite3_value), jsv, 0);
+ }
+}
+
+
+/*
+** Must be called immediately after a Java-side UDF callback throws.
+** If translateToErr is true then it sets the exception's message in
+** the result error using sqlite3_result_error(). If translateToErr is
+** false then it emits a warning that the function threw but should
+** not do so. In either case, it clears the exception state.
+**
+** Returns SQLITE_NOMEM if an allocation fails, else SQLITE_ERROR. In
+** the former case it calls sqlite3_result_error_nomem().
+*/
+static int udf_report_exception(JNIEnv * const env, int translateToErr,
+ sqlite3_context * cx,
+ const char *zFuncName, const char *zFuncType ){
+ jthrowable const ex = (*env)->ExceptionOccurred(env);
+ int rc = SQLITE_ERROR;
+
+ assert(ex && "This must only be called when a Java exception is pending.");
+ if( translateToErr ){
+ char * zMsg;
+ char * z;
+
+ S3JniExceptionClear;
+ zMsg = s3jni_exception_error_msg(env, ex);
+ z = sqlite3_mprintf("Client-defined SQL function %s.%s() threw: %s",
+ zFuncName ? zFuncName : "", zFuncType,
+ zMsg ? zMsg : "Unknown exception" );
+ sqlite3_free(zMsg);
+ if( z ){
+ sqlite3_result_error(cx, z, -1);
+ sqlite3_free(z);
+ }else{
+ sqlite3_result_error_nomem(cx);
+ rc = SQLITE_NOMEM;
+ }
+ }else{
+ S3JniExceptionWarnCallbackThrew("client-defined SQL function");
+ S3JniExceptionClear;
+ }
+ S3JniUnrefLocal(ex);
+ return rc;
+}
+
+/*
+** Sets up the state for calling a Java-side xFunc/xStep/xInverse()
+** UDF, calls it, and returns 0 on success.
+*/
+static int udf_xFSI(sqlite3_context* const pCx, int argc,
+ sqlite3_value** const argv, S3JniUdf * const s,
+ jmethodID xMethodID, const char * const zFuncType){
+ S3JniDeclLocal_env;
+ udf_jargs args = {0,0};
+ int rc = udf_args(env, pCx, argc, argv, &args.jcx, &args.jargv);
+
+ if( 0 == rc ){
+ (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv);
+ S3JniIfThrew{
+ rc = udf_report_exception(env, 'F'==zFuncType[1]/*xFunc*/, pCx,
+ s->zFuncName, zFuncType);
+ }
+ udf_unargs(env, args.jcx, argc, args.jargv);
+ }
+ S3JniUnrefLocal(args.jcx);
+ S3JniUnrefLocal(args.jargv);
+ return rc;
+}
+
+/*
+** Sets up the state for calling a Java-side xFinal/xValue() UDF,
+** calls it, and returns 0 on success.
+*/
+static int udf_xFV(sqlite3_context* cx, S3JniUdf * s,
+ jmethodID xMethodID,
+ const char *zFuncType){
+ S3JniDeclLocal_env;
+ jobject jcx = new_java_sqlite3_context(env, cx);
+ int rc = 0;
+ int const isFinal = 'F'==zFuncType[1]/*xFinal*/;
+
+ if( jcx ){
+ (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx);
+ S3JniIfThrew{
+ rc = udf_report_exception(env, isFinal, cx, s->zFuncName,
+ zFuncType);
+ }
+ S3JniUnrefLocal(jcx);
+ }else{
+ if( isFinal ) sqlite3_result_error_nomem(cx);
+ rc = SQLITE_NOMEM;
+ }
+ return rc;
+}
+
+/* Proxy for C-to-Java xFunc. */
+static void udf_xFunc(sqlite3_context* cx, int argc,
+ sqlite3_value** argv){
+ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
+ s3jni_incr( &SJG.metrics.udf.nFunc );
+ udf_xFSI(cx, argc, argv, s, s->jmidxFunc, "xFunc");
+}
+/* Proxy for C-to-Java xStep. */
+static void udf_xStep(sqlite3_context* cx, int argc,
+ sqlite3_value** argv){
+ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
+ s3jni_incr( &SJG.metrics.udf.nStep );
+ udf_xFSI(cx, argc, argv, s, s->jmidxStep, "xStep");
+}
+/* Proxy for C-to-Java xFinal. */
+static void udf_xFinal(sqlite3_context* cx){
+ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
+ s3jni_incr( &SJG.metrics.udf.nFinal );
+ udf_xFV(cx, s, s->jmidxFinal, "xFinal");
+}
+/* Proxy for C-to-Java xValue. */
+static void udf_xValue(sqlite3_context* cx){
+ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
+ s3jni_incr( &SJG.metrics.udf.nValue );
+ udf_xFV(cx, s, s->jmidxValue, "xValue");
+}
+/* Proxy for C-to-Java xInverse. */
+static void udf_xInverse(sqlite3_context* cx, int argc,
+ sqlite3_value** argv){
+ S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
+ s3jni_incr( &SJG.metrics.udf.nInverse );
+ udf_xFSI(cx, argc, argv, s, s->jmidxInverse, "xInverse");
+}
+
+
+////////////////////////////////////////////////////////////////////////
+// What follows is the JNI/C bindings. They are in alphabetical order
+// except for this macro-generated subset which are kept together
+// (alphabetized) here at the front...
+////////////////////////////////////////////////////////////////////////
+
+/** Create a trivial JNI wrapper for (int CName(void)). */
+#define WRAP_INT_VOID(JniNameSuffix,CName) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass){ \
+ return (jint)CName(); \
+ }
+/** Create a trivial JNI wrapper for (int CName(int)). */
+#define WRAP_INT_INT(JniNameSuffix,CName) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jint arg){ \
+ return (jint)CName((int)arg); \
+ }
+/*
+** Create a trivial JNI wrapper for (const mutf8_string *
+** CName(void)). This is only valid for functions which are known to
+** return ASCII or text which is equivalent in UTF-8 and MUTF-8.
+*/
+#define WRAP_MUTF8_VOID(JniNameSuffix,CName) \
+ JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass){ \
+ jstring const rv = (*env)->NewStringUTF( env, CName() ); \
+ s3jni_oom_check(rv); \
+ return rv; \
+ }
+/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */
+#define WRAP_INT_STMT(JniNameSuffix,CName) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt){ \
+ return (jint)CName(S3JniLongPtr_sqlite3_stmt(jpStmt)); \
+ }
+/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */
+#define WRAP_INT_STMT_INT(JniNameSuffix,CName) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint n){ \
+ return (jint)CName(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)n); \
+ }
+/** Create a trivial JNI wrapper for (boolean CName(sqlite3_stmt*)). */
+#define WRAP_BOOL_STMT(JniNameSuffix,CName) \
+ JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jobject jStmt){ \
+ return CName(PtrGet_sqlite3_stmt(jStmt)) ? JNI_TRUE : JNI_FALSE; \
+ }
+/** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */
+#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \
+ JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpStmt, jint ndx){ \
+ return s3jni_utf8_to_jstring( \
+ CName(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx), \
+ -1); \
+ }
+/** Create a trivial JNI wrapper for (boolean CName(sqlite3*)). */
+#define WRAP_BOOL_DB(JniNameSuffix,CName) \
+ JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \
+ return CName(S3JniLongPtr_sqlite3(jpDb)) ? JNI_TRUE : JNI_FALSE; \
+ }
+/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */
+#define WRAP_INT_DB(JniNameSuffix,CName) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \
+ return (jint)CName(S3JniLongPtr_sqlite3(jpDb)); \
+ }
+/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */
+#define WRAP_INT64_DB(JniNameSuffix,CName) \
+ JniDecl(jlong,JniNameSuffix)(JniArgsEnvClass, jlong jpDb){ \
+ return (jlong)CName(S3JniLongPtr_sqlite3(jpDb)); \
+ }
+/** Create a trivial JNI wrapper for (jstring CName(sqlite3*,int)). */
+#define WRAP_STR_DB_INT(JniNameSuffix,CName) \
+ JniDecl(jstring,JniNameSuffix)(JniArgsEnvClass, jlong jpDb, jint ndx){ \
+ return s3jni_utf8_to_jstring( \
+ CName(S3JniLongPtr_sqlite3(jpDb), (int)ndx), \
+ -1); \
+ }
+/** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */
+#define WRAP_INT_SVALUE(JniNameSuffix,CName,DfltOnNull) \
+ JniDecl(jint,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSValue); \
+ return (jint)(sv ? CName(sv): DfltOnNull); \
+ }
+/** Create a trivial JNI wrapper for (boolean CName(sqlite3_value*)). */
+#define WRAP_BOOL_SVALUE(JniNameSuffix,CName,DfltOnNull) \
+ JniDecl(jboolean,JniNameSuffix)(JniArgsEnvClass, jlong jpSValue){ \
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSValue); \
+ return (jint)(sv ? CName(sv) : DfltOnNull) \
+ ? JNI_TRUE : JNI_FALSE; \
+ }
+
+WRAP_INT_DB(1changes, sqlite3_changes)
+WRAP_INT64_DB(1changes64, sqlite3_changes64)
+WRAP_INT_STMT(1clear_1bindings, sqlite3_clear_bindings)
+WRAP_INT_STMT_INT(1column_1bytes, sqlite3_column_bytes)
+WRAP_INT_STMT_INT(1column_1bytes16, sqlite3_column_bytes16)
+WRAP_INT_STMT(1column_1count, sqlite3_column_count)
+WRAP_STR_STMT_INT(1column_1decltype, sqlite3_column_decltype)
+WRAP_STR_STMT_INT(1column_1name, sqlite3_column_name)
+WRAP_STR_STMT_INT(1column_1database_1name, sqlite3_column_database_name)
+WRAP_STR_STMT_INT(1column_1origin_1name, sqlite3_column_origin_name)
+WRAP_STR_STMT_INT(1column_1table_1name, sqlite3_column_table_name)
+WRAP_INT_STMT_INT(1column_1type, sqlite3_column_type)
+WRAP_INT_STMT(1data_1count, sqlite3_data_count)
+WRAP_STR_DB_INT(1db_1name, sqlite3_db_name)
+WRAP_INT_DB(1error_1offset, sqlite3_error_offset)
+WRAP_INT_DB(1extended_1errcode, sqlite3_extended_errcode)
+WRAP_BOOL_DB(1get_1autocommit, sqlite3_get_autocommit)
+WRAP_MUTF8_VOID(1libversion, sqlite3_libversion)
+WRAP_INT_VOID(1libversion_1number, sqlite3_libversion_number)
+WRAP_INT_VOID(1keyword_1count, sqlite3_keyword_count)
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+WRAP_INT_DB(1preupdate_1blobwrite, sqlite3_preupdate_blobwrite)
+WRAP_INT_DB(1preupdate_1count, sqlite3_preupdate_count)
+WRAP_INT_DB(1preupdate_1depth, sqlite3_preupdate_depth)
+#endif
+WRAP_INT_INT(1release_1memory, sqlite3_release_memory)
+WRAP_INT_INT(1sleep, sqlite3_sleep)
+WRAP_MUTF8_VOID(1sourceid, sqlite3_sourceid)
+WRAP_BOOL_STMT(1stmt_1busy, sqlite3_stmt_busy)
+WRAP_INT_STMT_INT(1stmt_1explain, sqlite3_stmt_explain)
+WRAP_INT_STMT(1stmt_1isexplain, sqlite3_stmt_isexplain)
+WRAP_BOOL_STMT(1stmt_1readonly, sqlite3_stmt_readonly)
+WRAP_INT_DB(1system_1errno, sqlite3_system_errno)
+WRAP_INT_VOID(1threadsafe, sqlite3_threadsafe)
+WRAP_INT_DB(1total_1changes, sqlite3_total_changes)
+WRAP_INT64_DB(1total_1changes64, sqlite3_total_changes64)
+WRAP_INT_SVALUE(1value_1encoding, sqlite3_value_encoding,SQLITE_UTF8)
+WRAP_BOOL_SVALUE(1value_1frombind, sqlite3_value_frombind,0)
+WRAP_INT_SVALUE(1value_1nochange, sqlite3_value_nochange,0)
+WRAP_INT_SVALUE(1value_1numeric_1type, sqlite3_value_numeric_type,SQLITE_NULL)
+WRAP_INT_SVALUE(1value_1subtype, sqlite3_value_subtype,0)
+WRAP_INT_SVALUE(1value_1type, sqlite3_value_type,SQLITE_NULL)
+
+#undef WRAP_BOOL_DB
+#undef WRAP_BOOL_STMT
+#undef WRAP_BOOL_SVALUE
+#undef WRAP_INT64_DB
+#undef WRAP_INT_DB
+#undef WRAP_INT_INT
+#undef WRAP_INT_STMT
+#undef WRAP_INT_STMT_INT
+#undef WRAP_INT_SVALUE
+#undef WRAP_INT_VOID
+#undef WRAP_MUTF8_VOID
+#undef WRAP_STR_STMT_INT
+#undef WRAP_STR_DB_INT
+
+S3JniApi(sqlite3_aggregate_context(),jlong,1aggregate_1context)(
+ JniArgsEnvClass, jobject jCx, jboolean initialize
+){
+ sqlite3_context * const pCx = PtrGet_sqlite3_context(jCx);
+ void * const p = pCx
+ ? sqlite3_aggregate_context(pCx, (int)(initialize
+ ? (int)sizeof(void*)
+ : 0))
+ : 0;
+ return S3JniCast_P2L(p);
+}
+
+/* Central auto-extension handler. */
+static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr,
+ const struct sqlite3_api_routines *ignored){
+ int rc = 0;
+ unsigned i, go = 1;
+ JNIEnv * env = 0;
+ S3JniDb * ps;
+ S3JniEnv * jc;
+
+ if( 0==SJG.autoExt.nExt ) return 0;
+ env = s3jni_env();
+ jc = S3JniEnv_get();
+ S3JniDb_mutex_enter;
+ ps = jc->pdbOpening ? jc->pdbOpening : S3JniDb_from_c(pDb);
+ if( !ps ){
+ *pzErr = sqlite3_mprintf("Unexpected arrival of null S3JniDb in "
+ "auto-extension runner.");
+ S3JniDb_mutex_leave;
+ return SQLITE_ERROR;
+ }
+ assert( ps->jDb );
+ if( !ps->pDb ){
+ assert( jc->pdbOpening == ps );
+ rc = sqlite3_set_clientdata(pDb, S3JniDb_clientdata_key,
+ ps, 0/* we'll re-set this after open()
+ completes. */);
+ if( rc ){
+ S3JniDb_mutex_leave;
+ return rc;
+ }
+ }
+ else{
+ assert( ps == jc->pdbOpening );
+ jc->pdbOpening = 0;
+ }
+ S3JniDb_mutex_leave;
+ NativePointerHolder_set(S3JniNph(sqlite3), ps->jDb, pDb)
+ /* As of here, the Java/C connection is complete except for the
+ (temporary) lack of finalizer for the ps object. */;
+ ps->pDb = pDb;
+ for( i = 0; go && 0==rc; ++i ){
+ S3JniAutoExtension ax = S3JniHook_empty
+ /* We need a copy of the auto-extension object, with our own
+ ** local reference to it, to avoid a race condition with another
+ ** thread manipulating the list during the call and invaliding
+ ** what ax references. */;
+ S3JniAutoExt_mutex_enter;
+ if( i >= SJG.autoExt.nExt ){
+ go = 0;
+ }else{
+ S3JniHook_localdup(&SJG.autoExt.aExt[i], &ax);
+ }
+ S3JniAutoExt_mutex_leave;
+ if( ax.jObj ){
+ rc = (*env)->CallIntMethod(env, ax.jObj, ax.midCallback, ps->jDb);
+ S3JniHook_localundup(ax);
+ S3JniIfThrew {
+ jthrowable const ex = (*env)->ExceptionOccurred(env);
+ char * zMsg;
+ S3JniExceptionClear;
+ zMsg = s3jni_exception_error_msg(env, ex);
+ S3JniUnrefLocal(ex);
+ *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg);
+ sqlite3_free(zMsg);
+ rc = SQLITE_ERROR;
+ }
+ }
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_auto_extension(),jint,1auto_1extension)(
+ JniArgsEnvClass, jobject jAutoExt
+){
+ int i;
+ S3JniAutoExtension * ax = 0;
+ int rc = 0;
+
+ if( !jAutoExt ) return SQLITE_MISUSE;
+ S3JniAutoExt_mutex_enter;
+ for( i = 0; i < SJG.autoExt.nExt; ++i ){
+ /* Look for a match. */
+ ax = &SJG.autoExt.aExt[i];
+ if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
+ /* same object, so this is a no-op. */
+ S3JniAutoExt_mutex_leave;
+ return 0;
+ }
+ }
+ if( i == SJG.autoExt.nExt ){
+ assert( SJG.autoExt.nExt <= SJG.autoExt.nAlloc );
+ if( SJG.autoExt.nExt == SJG.autoExt.nAlloc ){
+ /* Allocate another slot. */
+ unsigned n = 1 + SJG.autoExt.nAlloc;
+ S3JniAutoExtension * const aNew =
+ s3jni_realloc( SJG.autoExt.aExt, n * sizeof(*ax) );
+ if( !aNew ){
+ rc = SQLITE_NOMEM;
+ }else{
+ SJG.autoExt.aExt = aNew;
+ ++SJG.autoExt.nAlloc;
+ }
+ }
+ if( 0==rc ){
+ ax = &SJG.autoExt.aExt[SJG.autoExt.nExt];
+ rc = S3JniAutoExtension_init(env, ax, jAutoExt);
+ assert( rc ? (0==ax->jObj && 0==ax->midCallback)
+ : (0!=ax->jObj && 0!=ax->midCallback) );
+ }
+ }
+ if( 0==rc ){
+ static int once = 0;
+ if( 0==once && ++once ){
+ rc = sqlite3_auto_extension(
+ (void(*)(void))s3jni_run_java_auto_extensions
+ /* Reminder: the JNI binding of sqlite3_reset_auto_extension()
+ ** does not call the core-lib impl. It only clears Java-side
+ ** auto-extensions. */
+ );
+ if( rc ){
+ assert( ax );
+ S3JniAutoExtension_clear(ax);
+ }
+ }
+ if( 0==rc ){
+ ++SJG.autoExt.nExt;
+ }
+ }
+ S3JniAutoExt_mutex_leave;
+ return rc;
+}
+
+S3JniApi(sqlite3_backup_finish(),jint,1backup_1finish)(
+ JniArgsEnvClass, jlong jpBack
+){
+ int rc = 0;
+ if( jpBack!=0 ){
+ rc = sqlite3_backup_finish( S3JniLongPtr_sqlite3_backup(jpBack) );
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_backup_init(),jobject,1backup_1init)(
+ JniArgsEnvClass, jlong jpDbDest, jstring jTDest,
+ jlong jpDbSrc, jstring jTSrc
+){
+ sqlite3 * const pDest = S3JniLongPtr_sqlite3(jpDbDest);
+ sqlite3 * const pSrc = S3JniLongPtr_sqlite3(jpDbSrc);
+ char * const zDest = s3jni_jstring_to_utf8(jTDest, 0);
+ char * const zSrc = s3jni_jstring_to_utf8(jTSrc, 0);
+ jobject rv = 0;
+
+ if( pDest && pSrc && zDest && zSrc ){
+ sqlite3_backup * const pB =
+ sqlite3_backup_init(pDest, zDest, pSrc, zSrc);
+ if( pB ){
+ rv = new_java_sqlite3_backup(env, pB);
+ if( !rv ){
+ sqlite3_backup_finish( pB );
+ }
+ }
+ }
+ sqlite3_free(zDest);
+ sqlite3_free(zSrc);
+ return rv;
+}
+
+S3JniApi(sqlite3_backup_pagecount(),jint,1backup_1pagecount)(
+ JniArgsEnvClass, jlong jpBack
+){
+ return sqlite3_backup_pagecount(S3JniLongPtr_sqlite3_backup(jpBack));
+}
+
+S3JniApi(sqlite3_backup_remaining(),jint,1backup_1remaining)(
+ JniArgsEnvClass, jlong jpBack
+){
+ return sqlite3_backup_remaining(S3JniLongPtr_sqlite3_backup(jpBack));
+}
+
+S3JniApi(sqlite3_backup_step(),jint,1backup_1step)(
+ JniArgsEnvClass, jlong jpBack, jint nPage
+){
+ return sqlite3_backup_step(S3JniLongPtr_sqlite3_backup(jpBack), (int)nPage);
+}
+
+S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jbyteArray baData, jint nMax
+){
+ jsize nBA = 0;
+ jbyte * const pBuf = baData ? s3jni_jbyteArray_bytes2(baData, &nBA) : 0;
+ int rc;
+ if( pBuf ){
+ if( nMax>nBA ){
+ nMax = nBA;
+ }
+ rc = sqlite3_bind_blob(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx,
+ pBuf, (int)nMax, SQLITE_TRANSIENT);
+ s3jni_jbyteArray_release(baData, pBuf);
+ }else{
+ rc = baData
+ ? SQLITE_NOMEM
+ : sqlite3_bind_null( S3JniLongPtr_sqlite3_stmt(jpStmt), ndx );
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_bind_double(),jint,1bind_1double)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jdouble val
+){
+ return (jint)sqlite3_bind_double(S3JniLongPtr_sqlite3_stmt(jpStmt),
+ (int)ndx, (double)val);
+}
+
+S3JniApi(sqlite3_bind_int(),jint,1bind_1int)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jint val
+){
+ return (jint)sqlite3_bind_int(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, (int)val);
+}
+
+S3JniApi(sqlite3_bind_int64(),jint,1bind_1int64)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jlong val
+){
+ return (jint)sqlite3_bind_int64(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val);
+}
+
+/*
+** Bind a new global ref to Object `val` using sqlite3_bind_pointer().
+*/
+S3JniApi(sqlite3_bind_java_object(),jint,1bind_1java_1object)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jobject val
+){
+ sqlite3_stmt * const pStmt = S3JniLongPtr_sqlite3_stmt(jpStmt);
+ int rc = SQLITE_MISUSE;
+
+ if(pStmt){
+ jobject const rv = S3JniRefGlobal(val);
+ if( rv ){
+ rc = sqlite3_bind_pointer(pStmt, ndx, rv, ResultJavaValuePtrStr,
+ S3Jni_jobject_finalizer);
+ }else if(val){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_bind_null(pStmt, ndx);
+ }
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_bind_null(),jint,1bind_1null)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx
+){
+ return (jint)sqlite3_bind_null(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx);
+}
+
+S3JniApi(sqlite3_bind_parameter_count(),jint,1bind_1parameter_1count)(
+ JniArgsEnvClass, jlong jpStmt
+){
+ return (jint)sqlite3_bind_parameter_count(S3JniLongPtr_sqlite3_stmt(jpStmt));
+}
+
+S3JniApi(sqlite3_bind_parameter_index(),jint,1bind_1parameter_1index)(
+ JniArgsEnvClass, jlong jpStmt, jbyteArray jName
+){
+ int rc = 0;
+ jbyte * const pBuf = s3jni_jbyteArray_bytes(jName);
+ if( pBuf ){
+ rc = sqlite3_bind_parameter_index(S3JniLongPtr_sqlite3_stmt(jpStmt),
+ (const char *)pBuf);
+ s3jni_jbyteArray_release(jName, pBuf);
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_bind_parameter_name(),jstring,1bind_1parameter_1name)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx
+){
+ const char *z =
+ sqlite3_bind_parameter_name(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx);
+ return z ? s3jni_utf8_to_jstring(z, -1) : 0;
+}
+
+/*
+** Impl of sqlite3_bind_text/text16().
+*/
+static int s3jni__bind_text(int is16, JNIEnv *env, jlong jpStmt, jint ndx,
+ jbyteArray baData, jint nMax){
+ jsize nBA = 0;
+ jbyte * const pBuf =
+ baData ? s3jni_jbyteArray_bytes2(baData, &nBA) : 0;
+ int rc;
+ if( pBuf ){
+ if( nMax>nBA ){
+ nMax = nBA;
+ }
+ /* Note that we rely on the Java layer having assured that baData
+ is NUL-terminated if nMax is negative. In order to avoid UB for
+ such cases, we do not expose the byte-limit arguments in the
+ public API. */
+ rc = is16
+ ? sqlite3_bind_text16(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx,
+ pBuf, (int)nMax, SQLITE_TRANSIENT)
+ : sqlite3_bind_text(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx,
+ (const char *)pBuf,
+ (int)nMax, SQLITE_TRANSIENT);
+ }else{
+ rc = baData
+ ? sqlite3_bind_null(S3JniLongPtr_sqlite3_stmt(jpStmt), (int)ndx)
+ : SQLITE_NOMEM;
+ }
+ s3jni_jbyteArray_release(baData, pBuf);
+ return (jint)rc;
+
+}
+
+S3JniApi(sqlite3_bind_text(),jint,1bind_1text)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jbyteArray baData, jint nMax
+){
+ return s3jni__bind_text(0, env, jpStmt, ndx, baData, nMax);
+}
+
+S3JniApi(sqlite3_bind_text16(),jint,1bind_1text16)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jbyteArray baData, jint nMax
+){
+ return s3jni__bind_text(1, env, jpStmt, ndx, baData, nMax);
+}
+
+S3JniApi(sqlite3_bind_value(),jint,1bind_1value)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jlong jpValue
+){
+ int rc = 0;
+ sqlite3_stmt * pStmt = S3JniLongPtr_sqlite3_stmt(jpStmt);
+ if( pStmt ){
+ sqlite3_value *v = S3JniLongPtr_sqlite3_value(jpValue);
+ if( v ){
+ rc = sqlite3_bind_value(pStmt, (int)ndx, v);
+ }else{
+ rc = sqlite3_bind_null(pStmt, (int)ndx);
+ }
+ }else{
+ rc = SQLITE_MISUSE;
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_bind_zeroblob(),jint,1bind_1zeroblob)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jint n
+){
+ return (jint)sqlite3_bind_zeroblob(S3JniLongPtr_sqlite3_stmt(jpStmt),
+ (int)ndx, (int)n);
+}
+
+S3JniApi(sqlite3_bind_zeroblob64(),jint,1bind_1zeroblob64)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx, jlong n
+){
+ return (jint)sqlite3_bind_zeroblob64(S3JniLongPtr_sqlite3_stmt(jpStmt),
+ (int)ndx, (sqlite3_uint64)n);
+}
+
+S3JniApi(sqlite3_blob_bytes(),jint,1blob_1bytes)(
+ JniArgsEnvClass, jlong jpBlob
+){
+ return sqlite3_blob_bytes(S3JniLongPtr_sqlite3_blob(jpBlob));
+}
+
+S3JniApi(sqlite3_blob_close(),jint,1blob_1close)(
+ JniArgsEnvClass, jlong jpBlob
+){
+ sqlite3_blob * const b = S3JniLongPtr_sqlite3_blob(jpBlob);
+ return b ? (jint)sqlite3_blob_close(b) : SQLITE_MISUSE;
+}
+
+S3JniApi(sqlite3_blob_open(),jint,1blob_1open)(
+ JniArgsEnvClass, jlong jpDb, jstring jDbName, jstring jTbl, jstring jCol,
+ jlong jRowId, jint flags, jobject jOut
+){
+ sqlite3 * const db = S3JniLongPtr_sqlite3(jpDb);
+ sqlite3_blob * pBlob = 0;
+ char * zDbName = 0, * zTableName = 0, * zColumnName = 0;
+ int rc;
+
+ if( !db || !jDbName || !jTbl || !jCol ) return SQLITE_MISUSE;
+ zDbName = s3jni_jstring_to_utf8(jDbName,0);
+ zTableName = zDbName ? s3jni_jstring_to_utf8(jTbl,0) : 0;
+ zColumnName = zTableName ? s3jni_jstring_to_utf8(jCol,0) : 0;
+ rc = zColumnName
+ ? sqlite3_blob_open(db, zDbName, zTableName, zColumnName,
+ (sqlite3_int64)jRowId, (int)flags, &pBlob)
+ : SQLITE_NOMEM;
+ if( 0==rc ){
+ jobject rv = new_java_sqlite3_blob(env, pBlob);
+ if( !rv ){
+ sqlite3_blob_close(pBlob);
+ rc = SQLITE_NOMEM;
+ }
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_blob), jOut, rv);
+ }
+ sqlite3_free(zDbName);
+ sqlite3_free(zTableName);
+ sqlite3_free(zColumnName);
+ return rc;
+}
+
+S3JniApi(sqlite3_blob_read(),jint,1blob_1read)(
+ JniArgsEnvClass, jlong jpBlob, jbyteArray jTgt, jint iOffset
+){
+ jbyte * const pBa = s3jni_jbyteArray_bytes(jTgt);
+ int rc = jTgt ? (pBa ? SQLITE_MISUSE : SQLITE_NOMEM) : SQLITE_MISUSE;
+ if( pBa ){
+ jsize const nTgt = (*env)->GetArrayLength(env, jTgt);
+ rc = sqlite3_blob_read(S3JniLongPtr_sqlite3_blob(jpBlob), pBa,
+ (int)nTgt, (int)iOffset);
+ if( 0==rc ){
+ s3jni_jbyteArray_commit(jTgt, pBa);
+ }else{
+ s3jni_jbyteArray_release(jTgt, pBa);
+ }
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_blob_reopen(),jint,1blob_1reopen)(
+ JniArgsEnvClass, jlong jpBlob, jlong iNewRowId
+){
+ return (jint)sqlite3_blob_reopen(S3JniLongPtr_sqlite3_blob(jpBlob),
+ (sqlite3_int64)iNewRowId);
+}
+
+S3JniApi(sqlite3_blob_write(),jint,1blob_1write)(
+ JniArgsEnvClass, jlong jpBlob, jbyteArray jBa, jint iOffset
+){
+ sqlite3_blob * const b = S3JniLongPtr_sqlite3_blob(jpBlob);
+ jbyte * const pBuf = b ? s3jni_jbyteArray_bytes(jBa) : 0;
+ const jsize nBA = pBuf ? (*env)->GetArrayLength(env, jBa) : 0;
+ int rc = SQLITE_MISUSE;
+ if(b && pBuf){
+ rc = sqlite3_blob_write( b, pBuf, (int)nBA, (int)iOffset );
+ }
+ s3jni_jbyteArray_release(jBa, pBuf);
+ return (jint)rc;
+}
+
+/* Central C-to-Java busy handler proxy. */
+static int s3jni_busy_handler(void* pState, int n){
+ S3JniDb * const ps = (S3JniDb *)pState;
+ int rc = 0;
+ S3JniDeclLocal_env;
+ S3JniHook hook;
+
+ S3JniHook_localdup(&ps->hooks.busyHandler, &hook);
+ if( hook.jObj ){
+ rc = (*env)->CallIntMethod(env, hook.jObj,
+ hook.midCallback, (jint)n);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("sqlite3_busy_handler() callback");
+ rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR,
+ "sqlite3_busy_handler() callback threw.");
+ }
+ S3JniHook_localundup(hook);
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_busy_handler(),jint,1busy_1handler)(
+ JniArgsEnvClass, jlong jpDb, jobject jBusy
+){
+ S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+ S3JniHook * const pHook = ps ? &ps->hooks.busyHandler : 0;
+ S3JniHook hook = S3JniHook_empty;
+ int rc = 0;
+
+ if( !ps ) return (jint)SQLITE_MISUSE;
+ S3JniDb_mutex_enter;
+ if( jBusy ){
+ if( pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy) ){
+ /* Same object - this is a no-op. */
+ }else{
+ jclass const klazz = (*env)->GetObjectClass(env, jBusy);
+ hook.jObj = S3JniRefGlobal(jBusy);
+ hook.midCallback = (*env)->GetMethodID(env, klazz, "call", "(I)I");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ rc = SQLITE_ERROR;
+ }
+ }
+ }
+ if( 0==rc ){
+ if( jBusy ){
+ if( hook.jObj ){ /* Replace handler */
+ rc = sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps);
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ *pHook = hook /* transfer Java ref ownership */;
+ hook = S3JniHook_empty;
+ }
+ }/* else no-op */
+ }else{ /* Clear handler */
+ rc = sqlite3_busy_handler(ps->pDb, 0, 0);
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ }
+ }
+ }
+ S3JniHook_unref(&hook);
+ S3JniDb_mutex_leave;
+ return rc;
+}
+
+S3JniApi(sqlite3_busy_timeout(),jint,1busy_1timeout)(
+ JniArgsEnvClass, jlong jpDb, jint ms
+){
+ S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+ int rc = SQLITE_MISUSE;
+ if( ps ){
+ S3JniDb_mutex_enter;
+ S3JniHook_unref(&ps->hooks.busyHandler);
+ rc = sqlite3_busy_timeout(ps->pDb, (int)ms);
+ S3JniDb_mutex_leave;
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_cancel_auto_extension(),jboolean,1cancel_1auto_1extension)(
+ JniArgsEnvClass, jobject jAutoExt
+){
+ S3JniAutoExtension * ax;
+ jboolean rc = JNI_FALSE;
+ int i;
+
+ if( !jAutoExt ){
+ return rc;
+ }
+ S3JniAutoExt_mutex_enter;
+ /* This algo corresponds to the one in the core. */
+ for( i = SJG.autoExt.nExt-1; i >= 0; --i ){
+ ax = &SJG.autoExt.aExt[i];
+ if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
+ S3JniAutoExtension_clear(ax);
+ /* Move final entry into this slot. */
+ --SJG.autoExt.nExt;
+ *ax = SJG.autoExt.aExt[SJG.autoExt.nExt];
+ SJG.autoExt.aExt[SJG.autoExt.nExt] = S3JniHook_empty;
+ assert( !SJG.autoExt.aExt[SJG.autoExt.nExt].jObj );
+ rc = JNI_TRUE;
+ break;
+ }
+ }
+ S3JniAutoExt_mutex_leave;
+ return rc;
+}
+
+/* Wrapper for sqlite3_close(_v2)(). */
+static jint s3jni_close_db(JNIEnv * const env, jlong jpDb, int version){
+ int rc = 0;
+ S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+
+ assert(version == 1 || version == 2);
+ if( ps ){
+ rc = 1==version
+ ? (jint)sqlite3_close(ps->pDb)
+ : (jint)sqlite3_close_v2(ps->pDb);
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_close(),jint,1close)(JniArgsEnvClass, jlong pDb){
+ return s3jni_close_db(env, pDb, 1);
+}
+
+S3JniApi(sqlite3_close_v2(),jint,1close_1v2)(JniArgsEnvClass, jlong pDb){
+ return s3jni_close_db(env, pDb, 2);
+}
+
+/*
+** Assumes z is an array of unsigned short and returns the index in
+** that array of the first element with the value 0.
+*/
+static unsigned int s3jni_utf16_strlen(void const * z){
+ unsigned int i = 0;
+ const unsigned short * p = z;
+ while( p[i] ) ++i;
+ return i;
+}
+
+/* Descriptive alias for use with sqlite3_collation_needed(). */
+typedef S3JniHook S3JniCollationNeeded;
+
+/* Central C-to-Java sqlite3_collation_needed16() hook impl. */
+static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb,
+ int eTextRep, const void * z16Name){
+ S3JniCollationNeeded * const pHook = pState;
+ S3JniDeclLocal_env;
+ S3JniHook hook;
+
+ S3JniHook_localdup(pHook, &hook);
+ if( hook.jObj ){
+ unsigned int const nName = s3jni_utf16_strlen(z16Name);
+ jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName);
+
+ s3jni_oom_check( jName );
+ assert( hook.jExtra );
+ S3JniIfThrew{
+ S3JniExceptionClear;
+ }else if( hook.jExtra ){
+ (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback,
+ hook.jExtra, (jint)eTextRep, jName);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("sqlite3_collation_needed() callback");
+ }
+ }
+ S3JniUnrefLocal(jName);
+ S3JniHook_localundup(hook);
+ }
+}
+
+S3JniApi(sqlite3_collation_needed(),jint,1collation_1needed)(
+ JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+ S3JniDb * ps;
+ S3JniCollationNeeded * pHook;
+ int rc = 0;
+
+ S3JniDb_mutex_enter;
+ ps = S3JniDb_from_jlong(jpDb);
+ if( !ps ){
+ S3JniDb_mutex_leave;
+ return SQLITE_MISUSE;
+ }
+ pHook = &ps->hooks.collationNeeded;
+ if( pHook->jObj && jHook &&
+ (*env)->IsSameObject(env, pHook->jObj, jHook) ){
+ /* no-op */
+ }else if( !jHook ){
+ rc = sqlite3_collation_needed(ps->pDb, 0, 0);
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ }
+ }else{
+ jclass const klazz = (*env)->GetObjectClass(env, jHook);
+ jmethodID const xCallback = (*env)->GetMethodID(
+ env, klazz, "call", "(Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I"
+ );
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ rc = s3jni_db_exception(ps->pDb, SQLITE_MISUSE,
+ "Cannot not find matching call() in "
+ "CollationNeededCallback object.");
+ }else{
+ rc = sqlite3_collation_needed16(ps->pDb, pHook,
+ s3jni_collation_needed_impl16);
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ pHook->midCallback = xCallback;
+ pHook->jObj = S3JniRefGlobal(jHook);
+ pHook->jExtra = S3JniRefGlobal(ps->jDb);
+ }
+ }
+ }
+ S3JniDb_mutex_leave;
+ return rc;
+}
+
+S3JniApi(sqlite3_column_blob(),jbyteArray,1column_1blob)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+ void const * const p = sqlite3_column_blob(pStmt, (int)ndx);
+ int const n = p ? sqlite3_column_bytes(pStmt, (int)ndx) : 0;
+
+ return p ? s3jni_new_jbyteArray(p, n) : 0;
+}
+
+S3JniApi(sqlite3_column_double(),jdouble,1column_1double)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ return (jdouble)sqlite3_column_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
+}
+
+S3JniApi(sqlite3_column_int(),jint,1column_1int)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ return (jint)sqlite3_column_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
+}
+
+S3JniApi(sqlite3_column_int64(),jlong,1column_1int64)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
+}
+
+S3JniApi(sqlite3_column_text(),jbyteArray,1column_1text)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
+ const unsigned char * const p = stmt ? sqlite3_column_text(stmt, (int)ndx) : 0;
+ const int n = p ? sqlite3_column_bytes(stmt, (int)ndx) : 0;
+ return p ? s3jni_new_jbyteArray(p, n) : NULL;
+}
+
+#if 0
+// this impl might prove useful.
+S3JniApi(sqlite3_column_text(),jstring,1column_1text)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
+ const unsigned char * const p = stmt ? sqlite3_column_text(stmt, (int)ndx) : 0;
+ const int n = p ? sqlite3_column_bytes(stmt, (int)ndx) : 0;
+ return p ? s3jni_utf8_to_jstring( (const char *)p, n) : 0;
+}
+#endif
+
+S3JniApi(sqlite3_column_text16(),jstring,1column_1text16)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
+ const void * const p = stmt ? sqlite3_column_text16(stmt, (int)ndx) : 0;
+ const int n = p ? sqlite3_column_bytes16(stmt, (int)ndx) : 0;
+ return s3jni_text16_to_jstring(env, p, n);
+}
+
+S3JniApi(sqlite3_column_value(),jobject,1column_1value)(
+ JniArgsEnvClass, jobject jpStmt, jint ndx
+){
+ sqlite3_value * const sv =
+ sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx)
+ /* reminder: returns an SQL NULL if jpStmt==NULL */;
+ return new_java_sqlite3_value(env, sv);
+}
+
+/*
+** Impl for commit hooks (if isCommit is true) or rollback hooks.
+*/
+static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){
+ S3JniDeclLocal_env;
+ int rc = 0;
+ S3JniHook hook;
+
+ S3JniHook_localdup(isCommit
+ ? &ps->hooks.commit : &ps->hooks.rollback,
+ &hook);
+ if( hook.jObj ){
+ rc = isCommit
+ ? (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback)
+ : (int)((*env)->CallVoidMethod(env, hook.jObj, hook.midCallback), 0);
+ S3JniIfThrew{
+ rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR, "hook callback threw");
+ }
+ S3JniHook_localundup(hook);
+ }
+ return rc;
+}
+
+/* C-to-Java commit hook wrapper. */
+static int s3jni_commit_hook_impl(void *pP){
+ return s3jni_commit_rollback_hook_impl(1, pP);
+}
+
+/* C-to-Java rollback hook wrapper. */
+static void s3jni_rollback_hook_impl(void *pP){
+ (void)s3jni_commit_rollback_hook_impl(0, pP);
+}
+
+/*
+** Proxy for sqlite3_commit_hook() (if isCommit is true) or
+** sqlite3_rollback_hook().
+*/
+static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,
+ jlong jpDb, jobject jHook){
+ S3JniDb * ps;
+ jobject pOld = 0; /* previous hoook */
+ S3JniHook * pHook; /* ps->hooks.commit|rollback */
+
+ S3JniDb_mutex_enter;
+ ps = S3JniDb_from_jlong(jpDb);
+ if( !ps ){
+ s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0);
+ S3JniDb_mutex_leave;
+ return 0;
+ }
+ pHook = isCommit ? &ps->hooks.commit : &ps->hooks.rollback;
+ pOld = pHook->jObj;
+ if( pOld && jHook &&
+ (*env)->IsSameObject(env, pOld, jHook) ){
+ /* No-op. */
+ }else if( !jHook ){
+ if( pOld ){
+ jobject tmp = S3JniRefLocal(pOld);
+ S3JniUnrefGlobal(pOld);
+ pOld = tmp;
+ }
+ *pHook = S3JniHook_empty;
+ if( isCommit ) sqlite3_commit_hook(ps->pDb, 0, 0);
+ else sqlite3_rollback_hook(ps->pDb, 0, 0);
+ }else{
+ jclass const klazz = (*env)->GetObjectClass(env, jHook);
+ jmethodID const xCallback = (*env)->GetMethodID(env, klazz, "call",
+ isCommit ? "()I" : "()V");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Cannot not find matching call() method in"
+ "hook object.");
+ }else{
+ pHook->midCallback = xCallback;
+ pHook->jObj = S3JniRefGlobal(jHook);
+ if( isCommit ) sqlite3_commit_hook(ps->pDb, s3jni_commit_hook_impl, ps);
+ else sqlite3_rollback_hook(ps->pDb, s3jni_rollback_hook_impl, ps);
+ if( pOld ){
+ jobject tmp = S3JniRefLocal(pOld);
+ S3JniUnrefGlobal(pOld);
+ pOld = tmp;
+ }
+ }
+ }
+ S3JniDb_mutex_leave;
+ return pOld;
+}
+
+S3JniApi(sqlite3_commit_hook(),jobject,1commit_1hook)(
+ JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+ return s3jni_commit_rollback_hook(1, env, jpDb, jHook);
+}
+
+S3JniApi(sqlite3_compileoption_get(),jstring,1compileoption_1get)(
+ JniArgsEnvClass, jint n
+){
+ const char * z = sqlite3_compileoption_get(n);
+ jstring const rv = z ? (*env)->NewStringUTF( env, z ) : 0;
+ /* We know these to be ASCII, so MUTF-8 is fine. */;
+ s3jni_oom_check(z ? !!rv : 1);
+ return rv;
+}
+
+S3JniApi(sqlite3_compileoption_used(),jboolean,1compileoption_1used)(
+ JniArgsEnvClass, jstring name
+){
+ const char *zUtf8 = s3jni_jstring_to_mutf8(name)
+ /* We know these to be ASCII, so MUTF-8 is fine (and
+ hypothetically faster to convert). */;
+ const jboolean rc =
+ 0==sqlite3_compileoption_used(zUtf8) ? JNI_FALSE : JNI_TRUE;
+ s3jni_mutf8_release(name, zUtf8);
+ return rc;
+}
+
+S3JniApi(sqlite3_complete(),int,1complete)(
+ JniArgsEnvClass, jbyteArray jSql
+){
+ jbyte * const pBuf = s3jni_jbyteArray_bytes(jSql);
+ const jsize nBA = pBuf ? (*env)->GetArrayLength(env, jSql) : 0;
+ int rc;
+
+ assert( (nBA>0 ? 0==pBuf[nBA-1] : (pBuf ? 0==*pBuf : 1))
+ && "Byte array is not NUL-terminated." );
+ rc = (pBuf && 0==pBuf[(nBA ? nBA-1 : 0)])
+ ? sqlite3_complete( (const char *)pBuf )
+ : (jSql ? SQLITE_NOMEM : SQLITE_MISUSE);
+ s3jni_jbyteArray_release(jSql, pBuf);
+ return rc;
+}
+
+S3JniApi(sqlite3_config() /*for a small subset of options.*/,
+ jint,1config__I)(JniArgsEnvClass, jint n){
+ switch( n ){
+ case SQLITE_CONFIG_SINGLETHREAD:
+ case SQLITE_CONFIG_MULTITHREAD:
+ case SQLITE_CONFIG_SERIALIZED:
+ return sqlite3_config( n );
+ default:
+ return SQLITE_MISUSE;
+ }
+}
+/* C-to-Java SQLITE_CONFIG_LOG wrapper. */
+static void s3jni_config_log(void *ignored, int errCode, const char *z){
+ S3JniDeclLocal_env;
+ S3JniHook hook = S3JniHook_empty;
+
+ S3JniHook_localdup(&SJG.hook.configlog, &hook);
+ if( hook.jObj ){
+ jstring const jArg1 = z ? s3jni_utf8_to_jstring(z, -1) : 0;
+ if( z ? !!jArg1 : 1 ){
+ (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback, errCode, jArg1);
+ }
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("SQLITE_CONFIG_LOG callback");
+ S3JniExceptionClear;
+ }
+ S3JniHook_localundup(hook);
+ S3JniUnrefLocal(jArg1);
+ }
+}
+
+S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_LOG */,
+ jint, 1config__Lorg_sqlite_jni_ConfigLogCallback_2
+)(JniArgsEnvClass, jobject jLog){
+ S3JniHook * const pHook = &SJG.hook.configlog;
+ int rc = 0;
+
+ S3JniGlobal_mutex_enter;
+ if( !jLog ){
+ rc = sqlite3_config( SQLITE_CONFIG_LOG, NULL, NULL );
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ }
+ }else if( pHook->jObj && (*env)->IsSameObject(env, jLog, pHook->jObj) ){
+ /* No-op */
+ }else {
+ jclass const klazz = (*env)->GetObjectClass(env, jLog);
+ jmethodID const midCallback = (*env)->GetMethodID(env, klazz, "call",
+ "(ILjava/lang/String;)V");
+ S3JniUnrefLocal(klazz);
+ if( midCallback ){
+ rc = sqlite3_config( SQLITE_CONFIG_LOG, s3jni_config_log, NULL );
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ pHook->midCallback = midCallback;
+ pHook->jObj = S3JniRefGlobal(jLog);
+ }
+ }else{
+ S3JniExceptionWarnIgnore;
+ rc = SQLITE_ERROR;
+ }
+ }
+ S3JniGlobal_mutex_leave;
+ return rc;
+}
+
+#ifdef SQLITE_ENABLE_SQLLOG
+/* C-to-Java SQLITE_CONFIG_SQLLOG wrapper. */
+static void s3jni_config_sqllog(void *ignored, sqlite3 *pDb, const char *z, int op){
+ jobject jArg0 = 0;
+ jstring jArg1 = 0;
+ S3JniDeclLocal_env;
+ S3JniDb * const ps = S3JniDb_from_c(pDb);
+ S3JniHook hook = S3JniHook_empty;
+
+ if( ps ){
+ S3JniHook_localdup(&SJG.hook.sqllog, &hook);
+ }
+ if( !hook.jObj ) return;
+ jArg0 = S3JniRefLocal(ps->jDb);
+ switch( op ){
+ case 0: /* db opened */
+ case 1: /* SQL executed */
+ jArg1 = s3jni_utf8_to_jstring( z, -1);
+ break;
+ case 2: /* db closed */
+ break;
+ default:
+ (*env)->FatalError(env, "Unhandled 4th arg to SQLITE_CONFIG_SQLLOG.");
+ break;
+ }
+ (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback, jArg0, jArg1, op);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("SQLITE_CONFIG_SQLLOG callback");
+ S3JniExceptionClear;
+ }
+ S3JniHook_localundup(hook);
+ S3JniUnrefLocal(jArg0);
+ S3JniUnrefLocal(jArg1);
+}
+//! Requirement of SQLITE_CONFIG_SQLLOG.
+void sqlite3_init_sqllog(void){
+ sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, 0 );
+}
+#endif
+
+S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_SQLLOG */,
+ jint, 1config__Lorg_sqlite_jni_ConfigSqllogCallback_2)(
+ JniArgsEnvClass, jobject jLog){
+#ifndef SQLITE_ENABLE_SQLLOG
+ return SQLITE_MISUSE;
+#else
+ S3JniHook * const pHook = &SJG.hook.sqllog;
+ int rc = 0;
+
+ S3JniGlobal_mutex_enter;
+ if( !jLog ){
+ rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, NULL );
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ }
+ }else if( pHook->jObj && (*env)->IsSameObject(env, jLog, pHook->jObj) ){
+ /* No-op */
+ }else {
+ jclass const klazz = (*env)->GetObjectClass(env, jLog);
+ jmethodID const midCallback = (*env)->GetMethodID(env, klazz, "call",
+ "(Lorg/sqlite/jni/capi/sqlite3;"
+ "Ljava/lang/String;"
+ "I)V");
+ S3JniUnrefLocal(klazz);
+ if( midCallback ){
+ rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, NULL );
+ if( 0==rc ){
+ S3JniHook_unref(pHook);
+ pHook->midCallback = midCallback;
+ pHook->jObj = S3JniRefGlobal(jLog);
+ }
+ }else{
+ S3JniExceptionWarnIgnore;
+ rc = SQLITE_ERROR;
+ }
+ }
+ S3JniGlobal_mutex_leave;
+ return rc;
+#endif
+}
+
+S3JniApi(sqlite3_context_db_handle(),jobject,1context_1db_1handle)(
+ JniArgsEnvClass, jobject jpCx
+){
+ sqlite3_context * const pCx = PtrGet_sqlite3_context(jpCx);
+ sqlite3 * const pDb = pCx ? sqlite3_context_db_handle(pCx) : 0;
+ S3JniDb * const ps = pDb ? S3JniDb_from_c(pDb) : 0;
+ return ps ? ps->jDb : 0;
+}
+
+/*
+** State for CollationCallbacks. This used to be its own separate
+** type, but has since been consolidated with S3JniHook. It retains
+** its own typedef for code legibility and searchability reasons.
+*/
+typedef S3JniHook S3JniCollationCallback;
+
+/*
+** Proxy for Java-side CollationCallback.xCompare() callbacks.
+*/
+static int CollationCallback_xCompare(void *pArg, int nLhs, const void *lhs,
+ int nRhs, const void *rhs){
+ S3JniCollationCallback * const pCC = pArg;
+ S3JniDeclLocal_env;
+ jint rc = 0;
+ if( pCC->jObj ){
+ jbyteArray jbaLhs = s3jni_new_jbyteArray(lhs, (jint)nLhs);
+ jbyteArray jbaRhs = jbaLhs
+ ? s3jni_new_jbyteArray(rhs, (jint)nRhs) : 0;
+ if( !jbaRhs ){
+ S3JniUnrefLocal(jbaLhs);
+ /* We have no recovery strategy here. */
+ s3jni_oom_check( jbaRhs );
+ return 0;
+ }
+ rc = (*env)->CallIntMethod(env, pCC->jObj, pCC->midCallback,
+ jbaLhs, jbaRhs);
+ S3JniExceptionIgnore;
+ S3JniUnrefLocal(jbaLhs);
+ S3JniUnrefLocal(jbaRhs);
+ }
+ return (int)rc;
+}
+
+/* CollationCallback finalizer for use by the sqlite3 internals. */
+static void CollationCallback_xDestroy(void *pArg){
+ S3JniCollationCallback * const pCC = pArg;
+ S3JniDeclLocal_env;
+ S3JniHook_free(pCC);
+}
+
+S3JniApi(sqlite3_create_collation() sqlite3_create_collation_v2(),
+ jint,1create_1collation
+)(JniArgsEnvClass, jobject jDb, jstring name, jint eTextRep,
+ jobject oCollation){
+ int rc;
+ S3JniDb * ps;
+
+ if( !jDb || !name || !encodingTypeIsValid(eTextRep) ){
+ return (jint)SQLITE_MISUSE;
+ }
+ S3JniDb_mutex_enter;
+ ps = S3JniDb_from_java(jDb);
+ jclass const klazz = (*env)->GetObjectClass(env, oCollation);
+ jmethodID const midCallback =
+ (*env)->GetMethodID(env, klazz, "call", "([B[B)I");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew{
+ rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Could not get call() method from "
+ "CollationCallback object.");
+ }else{
+ char * const zName = s3jni_jstring_to_utf8(name, 0);
+ S3JniCollationCallback * const pCC =
+ zName ? S3JniHook_alloc() : 0;
+ if( pCC ){
+ rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep,
+ pCC, CollationCallback_xCompare,
+ CollationCallback_xDestroy);
+ if( 0==rc ){
+ pCC->midCallback = midCallback;
+ pCC->jObj = S3JniRefGlobal(oCollation);
+ pCC->doXDestroy = 1;
+ }else{
+ CollationCallback_xDestroy(pCC);
+ }
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ sqlite3_free(zName);
+ }
+ S3JniDb_mutex_leave;
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_create_function() sqlite3_create_function_v2()
+ sqlite3_create_window_function(),
+ jint,1create_1function
+)(JniArgsEnvClass, jobject jDb, jstring jFuncName, jint nArg,
+ jint eTextRep, jobject jFunctor){
+ S3JniUdf * s = 0;
+ int rc;
+ sqlite3 * const pDb = PtrGet_sqlite3(jDb);
+ char * zFuncName = 0;
+
+ if( !pDb || !jFuncName ){
+ return SQLITE_MISUSE;
+ }else if( !encodingTypeIsValid(eTextRep) ){
+ return s3jni_db_error(pDb, SQLITE_FORMAT,
+ "Invalid function encoding option.");
+ }
+ s = S3JniUdf_alloc(env, jFunctor);
+ if( !s ) return SQLITE_NOMEM;
+
+ if( UDF_UNKNOWN_TYPE==s->type ){
+ rc = s3jni_db_error(pDb, SQLITE_MISUSE,
+ "Cannot unambiguously determine function type.");
+ S3JniUdf_free(env, s, 1);
+ goto error_cleanup;
+ }
+ zFuncName = s3jni_jstring_to_utf8(jFuncName,0);
+ if( !zFuncName ){
+ rc = SQLITE_NOMEM;
+ S3JniUdf_free(env, s, 1);
+ goto error_cleanup;
+ }
+ s->zFuncName = zFuncName /* pass on ownership */;
+ if( UDF_WINDOW == s->type ){
+ rc = sqlite3_create_window_function(pDb, zFuncName, nArg, eTextRep, s,
+ udf_xStep, udf_xFinal, udf_xValue,
+ udf_xInverse, S3JniUdf_finalizer);
+ }else{
+ udf_xFunc_f xFunc = 0;
+ udf_xStep_f xStep = 0;
+ udf_xFinal_f xFinal = 0;
+ if( UDF_SCALAR == s->type ){
+ xFunc = udf_xFunc;
+ }else{
+ assert( UDF_AGGREGATE == s->type );
+ xStep = udf_xStep;
+ xFinal = udf_xFinal;
+ }
+ rc = sqlite3_create_function_v2(pDb, zFuncName, nArg, eTextRep, s,
+ xFunc, xStep, xFinal, S3JniUdf_finalizer);
+ }
+error_cleanup:
+ /* Reminder: on sqlite3_create_function() error, s will be
+ ** destroyed via create_function(). */
+ return (jint)rc;
+}
+
+
+S3JniApi(sqlite3_db_config() /*for MAINDBNAME*/,
+ jint,1db_1config__Lorg_sqlite_jni_capi_sqlite3_2ILjava_lang_String_2
+)(JniArgsEnvClass, jobject jDb, jint op, jstring jStr){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ int rc;
+ char *zStr;
+
+ switch( (ps && jStr) ? op : 0 ){
+ case SQLITE_DBCONFIG_MAINDBNAME:
+ S3JniDb_mutex_enter
+ /* Protect against a race in modifying/freeing
+ ps->zMainDbName. */;
+ zStr = s3jni_jstring_to_utf8( jStr, 0);
+ if( zStr ){
+ rc = sqlite3_db_config(ps->pDb, (int)op, zStr);
+ if( rc ){
+ sqlite3_free( zStr );
+ }else{
+ sqlite3_free( ps->zMainDbName );
+ ps->zMainDbName = zStr;
+ }
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ S3JniDb_mutex_leave;
+ break;
+ case 0:
+ default:
+ rc = SQLITE_MISUSE;
+ }
+ return rc;
+}
+
+S3JniApi(
+ sqlite3_db_config(),
+ /* WARNING: openjdk v19 creates a different mangled name for this
+ ** function than openjdk v8 does. We account for that by exporting
+ ** both versions of the name. */
+ jint,1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_Int32_2
+)(
+ JniArgsEnvClass, jobject jDb, jint op, jint onOff, jobject jOut
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ int rc;
+ switch( ps ? op : 0 ){
+ case SQLITE_DBCONFIG_ENABLE_FKEY:
+ case SQLITE_DBCONFIG_ENABLE_TRIGGER:
+ case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER:
+ case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION:
+ case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE:
+ case SQLITE_DBCONFIG_ENABLE_QPSG:
+ case SQLITE_DBCONFIG_TRIGGER_EQP:
+ case SQLITE_DBCONFIG_RESET_DATABASE:
+ case SQLITE_DBCONFIG_DEFENSIVE:
+ case SQLITE_DBCONFIG_WRITABLE_SCHEMA:
+ case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE:
+ case SQLITE_DBCONFIG_DQS_DML:
+ case SQLITE_DBCONFIG_DQS_DDL:
+ case SQLITE_DBCONFIG_ENABLE_VIEW:
+ case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT:
+ case SQLITE_DBCONFIG_TRUSTED_SCHEMA:
+ case SQLITE_DBCONFIG_STMT_SCANSTATUS:
+ case SQLITE_DBCONFIG_REVERSE_SCANORDER: {
+ int pOut = 0;
+ rc = sqlite3_db_config( ps->pDb, (int)op, onOff, &pOut );
+ if( 0==rc && jOut ){
+ OutputPointer_set_Int32(env, jOut, pOut);
+ }
+ break;
+ }
+ case 0:
+ default:
+ rc = SQLITE_MISUSE;
+ }
+ return (jint)rc;
+}
+
+/*
+** This is a workaround for openjdk v19 (and possibly others) encoding
+** this function's name differently than JDK v8 does. If we do not
+** install both names for this function then Java will not be able to
+** find the function in both environments.
+*/
+JniDecl(jint,1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_00024Int32_2)(
+ JniArgsEnvClass, jobject jDb, jint op, jint onOff, jobject jOut
+){
+ return JniFuncName(1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_Int32_2)(
+ env, jKlazz, jDb, op, onOff, jOut
+ );
+}
+
+S3JniApi(sqlite3_db_filename(),jstring,1db_1filename)(
+ JniArgsEnvClass, jobject jDb, jstring jDbName
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ char *zDbName;
+ jstring jRv = 0;
+ int nStr = 0;
+
+ if( !ps || !jDbName ){
+ return 0;
+ }
+ zDbName = s3jni_jstring_to_utf8( jDbName, &nStr);
+ if( zDbName ){
+ char const * zRv = sqlite3_db_filename(ps->pDb, zDbName);
+ sqlite3_free(zDbName);
+ if( zRv ){
+ jRv = s3jni_utf8_to_jstring( zRv, -1);
+ }
+ }
+ return jRv;
+}
+
+S3JniApi(sqlite3_db_handle(),jobject,1db_1handle)(
+ JniArgsEnvClass, jobject jpStmt
+){
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+ sqlite3 * const pDb = pStmt ? sqlite3_db_handle(pStmt) : 0;
+ S3JniDb * const ps = pDb ? S3JniDb_from_c(pDb) : 0;
+ return ps ? ps->jDb : 0;
+}
+
+S3JniApi(sqlite3_db_readonly(),jint,1db_1readonly)(
+ JniArgsEnvClass, jobject jDb, jstring jDbName
+){
+ int rc = 0;
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ char *zDbName = jDbName ? s3jni_jstring_to_utf8( jDbName, 0 ) : 0;
+ rc = sqlite3_db_readonly(ps ? ps->pDb : 0, zDbName);
+ sqlite3_free(zDbName);
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_db_release_memory(),int,1db_1release_1memory)(
+ JniArgsEnvClass, jobject jDb
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jDb);
+ return pDb ? sqlite3_db_release_memory(pDb) : SQLITE_MISUSE;
+}
+
+S3JniApi(sqlite3_db_status(),jint,1db_1status)(
+ JniArgsEnvClass, jobject jDb, jint op, jobject jOutCurrent,
+ jobject jOutHigh, jboolean reset
+){
+ int iCur = 0, iHigh = 0;
+ sqlite3 * const pDb = PtrGet_sqlite3(jDb);
+ int rc = sqlite3_db_status( pDb, op, &iCur, &iHigh, reset );
+ if( 0==rc ){
+ OutputPointer_set_Int32(env, jOutCurrent, iCur);
+ OutputPointer_set_Int32(env, jOutHigh, iHigh);
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_errcode(),jint,1errcode)(
+ JniArgsEnvClass, jobject jpDb
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ return pDb ? sqlite3_errcode(pDb) : SQLITE_MISUSE;
+}
+
+S3JniApi(sqlite3_errmsg(),jstring,1errmsg)(
+ JniArgsEnvClass, jobject jpDb
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ return pDb ? s3jni_utf8_to_jstring( sqlite3_errmsg(pDb), -1) : 0
+ /* We don't use errmsg16() directly only because it would cause an
+ additional level of internal encoding in sqlite3. The end
+ effect should be identical to using errmsg16(), however. */;
+}
+
+S3JniApi(sqlite3_errstr(),jstring,1errstr)(
+ JniArgsEnvClass, jint rcCode
+){
+ jstring const rv = (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode))
+ /* We know these values to be plain ASCII, so pose no MUTF-8
+ ** incompatibility */;
+ s3jni_oom_check( rv );
+ return rv;
+}
+
+#ifndef SQLITE_ENABLE_NORMALIZE
+/* Dummy stub for sqlite3_normalized_sql(). Never called. */
+static const char * sqlite3_normalized_sql(sqlite3_stmt *s){
+ S3JniDeclLocal_env;
+ (*env)->FatalError(env, "dummy sqlite3_normalized_sql() was "
+ "impossibly called.") /* does not return */;
+ return 0;
+}
+#endif
+
+/*
+** Impl for sqlite3_expanded_sql() (if isExpanded is true) and
+** sqlite3_normalized_sql().
+*/
+static jstring s3jni_xn_sql(int isExpanded, JNIEnv *env, jobject jpStmt){
+ jstring rv = 0;
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+
+ if( pStmt ){
+ char * zSql = isExpanded
+ ? sqlite3_expanded_sql(pStmt)
+ : (char*)sqlite3_normalized_sql(pStmt);
+ s3jni_oom_fatal(zSql);
+ if( zSql ){
+ rv = s3jni_utf8_to_jstring(zSql, -1);
+ if( isExpanded ) sqlite3_free(zSql);
+ }
+ }
+ return rv;
+}
+
+S3JniApi(sqlite3_expanded_sql(),jstring,1expanded_1sql)(
+ JniArgsEnvClass, jobject jpStmt
+){
+ return s3jni_xn_sql(1, env, jpStmt);
+}
+
+S3JniApi(sqlite3_normalized_sql(),jstring,1normalized_1sql)(
+ JniArgsEnvClass, jobject jpStmt
+){
+#ifdef SQLITE_ENABLE_NORMALIZE
+ return s3jni_xn_sql(0, env, jpStmt);
+#else
+ return 0;
+#endif
+}
+
+S3JniApi(sqlite3_extended_result_codes(),jboolean,1extended_1result_1codes)(
+ JniArgsEnvClass, jobject jpDb, jboolean onoff
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ int const rc = pDb ? sqlite3_extended_result_codes(pDb, onoff ? 1 : 0) : 0;
+ return rc ? JNI_TRUE : JNI_FALSE;
+}
+
+S3JniApi(sqlite3_finalize(),jint,1finalize)(
+ JniArgsEnvClass, jlong jpStmt
+){
+ return jpStmt
+ ? sqlite3_finalize(S3JniLongPtr_sqlite3_stmt(jpStmt))
+ : 0;
+}
+
+S3JniApi(sqlite3_get_auxdata(),jobject,1get_1auxdata)(
+ JniArgsEnvClass, jobject jCx, jint n
+){
+ return sqlite3_get_auxdata(PtrGet_sqlite3_context(jCx), (int)n);
+}
+
+S3JniApi(sqlite3_initialize(),jint,1initialize)(
+ JniArgsEnvClass
+){
+ return sqlite3_initialize();
+}
+
+S3JniApi(sqlite3_interrupt(),void,1interrupt)(
+ JniArgsEnvClass, jobject jpDb
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ if( pDb ){
+ sqlite3_interrupt(pDb);
+ }
+}
+
+S3JniApi(sqlite3_is_interrupted(),jboolean,1is_1interrupted)(
+ JniArgsEnvClass, jobject jpDb
+){
+ int rc = 0;
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ if( pDb ){
+ rc = sqlite3_is_interrupted(pDb);
+ }
+ return rc ? JNI_TRUE : JNI_FALSE;
+}
+
+/*
+** Uncaches the current JNIEnv from the S3JniGlobal state, clearing
+** any resources owned by that cache entry and making that slot
+** available for re-use.
+*/
+JniDecl(jboolean,1java_1uncache_1thread)(JniArgsEnvClass){
+ int rc;
+ S3JniEnv_mutex_enter;
+ rc = S3JniEnv_uncache(env);
+ S3JniEnv_mutex_leave;
+ return rc ? JNI_TRUE : JNI_FALSE;
+}
+
+S3JniApi(sqlite3_keyword_check(),jboolean,1keyword_1check)(
+ JniArgsEnvClass, jstring jWord
+){
+ int nWord = 0;
+ char * zWord = s3jni_jstring_to_utf8(jWord, &nWord);
+ int rc = 0;
+
+ s3jni_oom_check(jWord ? !!zWord : 1);
+ if( zWord && nWord ){
+ rc = sqlite3_keyword_check(zWord, nWord);
+ }
+ sqlite3_free(zWord);
+ return rc ? JNI_TRUE : JNI_FALSE;
+}
+
+S3JniApi(sqlite3_keyword_name(),jstring,1keyword_1name)(
+ JniArgsEnvClass, jint ndx
+){
+ const char * zWord = 0;
+ int n = 0;
+ jstring rv = 0;
+
+ if( 0==sqlite3_keyword_name(ndx, &zWord, &n) ){
+ rv = s3jni_utf8_to_jstring(zWord, n);
+ }
+ return rv;
+}
+
+
+S3JniApi(sqlite3_last_insert_rowid(),jlong,1last_1insert_1rowid)(
+ JniArgsEnvClass, jobject jpDb
+){
+ return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb));
+}
+
+S3JniApi(sqlite3_limit(),jint,1limit)(
+ JniArgsEnvClass, jobject jpDb, jint id, jint newVal
+){
+ jint rc = 0;
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ if( pDb ){
+ rc = sqlite3_limit( pDb, (int)id, (int)newVal );
+ }
+ return rc;
+}
+
+/* Pre-open() code common to sqlite3_open[_v2](). */
+static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc,
+ jstring jDbName, char **zDbName,
+ S3JniDb ** ps){
+ int rc = 0;
+ jobject jDb = 0;
+
+ *jc = S3JniEnv_get();
+ if( !*jc ){
+ rc = SQLITE_NOMEM;
+ goto end;
+ }
+ *zDbName = jDbName ? s3jni_jstring_to_utf8( jDbName, 0) : 0;
+ if( jDbName && !*zDbName ){
+ rc = SQLITE_NOMEM;
+ goto end;
+ }
+ jDb = new_java_sqlite3(env, 0);
+ if( !jDb ){
+ sqlite3_free(*zDbName);
+ *zDbName = 0;
+ rc = SQLITE_NOMEM;
+ goto end;
+ }
+ *ps = S3JniDb_alloc(env, jDb);
+ if( *ps ){
+ (*jc)->pdbOpening = *ps;
+ }else{
+ S3JniUnrefLocal(jDb);
+ rc = SQLITE_NOMEM;
+ }
+end:
+ return rc;
+}
+
+/*
+** Post-open() code common to both the sqlite3_open() and
+** sqlite3_open_v2() bindings. ps->jDb must be the
+** org.sqlite.jni.capi.sqlite3 object which will hold the db's native
+** pointer. theRc must be the result code of the open() op. If
+** *ppDb is NULL then ps is set aside and its state cleared,
+** else ps is associated with *ppDb. If *ppDb is not NULL then
+** ps->jDb is stored in jOut (an OutputPointer.sqlite3 instance).
+**
+** Must be called if s3jni_open_pre() succeeds and must not be called
+** if it doesn't.
+**
+** Returns theRc.
+*/
+static int s3jni_open_post(JNIEnv * const env, S3JniEnv * const jc,
+ S3JniDb * ps, sqlite3 **ppDb,
+ jobject jOut, int theRc){
+ int rc = 0;
+ jc->pdbOpening = 0;
+ if( *ppDb ){
+ assert(ps->jDb);
+ if( 0==ps->pDb ){
+ ps->pDb = *ppDb;
+ NativePointerHolder_set(S3JniNph(sqlite3), ps->jDb, *ppDb);
+ }else{
+ assert( ps->pDb==*ppDb
+ && "Set up via s3jni_run_java_auto_extensions()" );
+ }
+ rc = sqlite3_set_clientdata(ps->pDb, S3JniDb_clientdata_key,
+ ps, S3JniDb_xDestroy)
+ /* As of here, the Java/C connection is complete */;
+ }else{
+ S3JniDb_set_aside(ps);
+ ps = 0;
+ }
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3),
+ jOut, ps ? ps->jDb : 0);
+ return theRc ? theRc : rc;
+}
+
+S3JniApi(sqlite3_open(),jint,1open)(
+ JniArgsEnvClass, jstring strName, jobject jOut
+){
+ sqlite3 * pOut = 0;
+ char *zName = 0;
+ S3JniDb * ps = 0;
+ S3JniEnv * jc = 0;
+ int rc;
+
+ if( 0==jOut ) return SQLITE_MISUSE;
+ rc = s3jni_open_pre(env, &jc, strName, &zName, &ps);
+ if( 0==rc ){
+ rc = s3jni_open_post(env, jc, ps, &pOut, jOut,
+ sqlite3_open(zName, &pOut));
+ assert(rc==0 ? pOut!=0 : 1);
+ sqlite3_free(zName);
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_open_v2(),jint,1open_1v2)(
+ JniArgsEnvClass, jstring strName,
+ jobject jOut, jint flags, jstring strVfs
+){
+ sqlite3 * pOut = 0;
+ char *zName = 0;
+ S3JniDb * ps = 0;
+ S3JniEnv * jc = 0;
+ char *zVfs = 0;
+ int rc;
+
+ if( 0==jOut ) return SQLITE_MISUSE;
+ rc = s3jni_open_pre(env, &jc, strName, &zName, &ps);
+ if( 0==rc ){
+ if( strVfs ){
+ zVfs = s3jni_jstring_to_utf8( strVfs, 0);
+ if( !zVfs ){
+ rc = SQLITE_NOMEM;
+ }
+ }
+ if( 0==rc ){
+ rc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs);
+ }
+ rc = s3jni_open_post(env, jc, ps, &pOut, jOut, rc);
+ }
+ assert(rc==0 ? pOut!=0 : 1);
+ sqlite3_free(zName);
+ sqlite3_free(zVfs);
+ return (jint)rc;
+}
+
+/* Proxy for the sqlite3_prepare[_v2/3]() family. */
+jint sqlite3_jni_prepare_v123( int prepVersion, JNIEnv * const env, jclass self,
+ jlong jpDb, jbyteArray baSql,
+ jint nMax, jint prepFlags,
+ jobject jOutStmt, jobject outTail){
+ sqlite3_stmt * pStmt = 0;
+ jobject jStmt = 0;
+ const char * zTail = 0;
+ sqlite3 * const pDb = S3JniLongPtr_sqlite3(jpDb);
+ jbyte * const pBuf = pDb ? s3jni_jbyteArray_bytes(baSql) : 0;
+ int rc = SQLITE_ERROR;
+
+ assert(prepVersion==1 || prepVersion==2 || prepVersion==3);
+ if( !pDb || !jOutStmt ){
+ rc = SQLITE_MISUSE;
+ goto end;
+ }else if( !pBuf ){
+ rc = baSql ? SQLITE_NOMEM : SQLITE_MISUSE;
+ goto end;
+ }
+ jStmt = new_java_sqlite3_stmt(env, 0);
+ if( !jStmt ){
+ rc = SQLITE_NOMEM;
+ goto end;
+ }
+ switch( prepVersion ){
+ case 1: rc = sqlite3_prepare(pDb, (const char *)pBuf,
+ (int)nMax, &pStmt, &zTail);
+ break;
+ case 2: rc = sqlite3_prepare_v2(pDb, (const char *)pBuf,
+ (int)nMax, &pStmt, &zTail);
+ break;
+ case 3: rc = sqlite3_prepare_v3(pDb, (const char *)pBuf,
+ (int)nMax, (unsigned int)prepFlags,
+ &pStmt, &zTail);
+ break;
+ default:
+ assert(!"Invalid prepare() version");
+ }
+end:
+ s3jni_jbyteArray_release(baSql,pBuf);
+ if( 0==rc ){
+ if( 0!=outTail ){
+ /* Noting that pBuf is deallocated now but its address is all we need for
+ ** what follows... */
+ assert(zTail ? ((void*)zTail>=(void*)pBuf) : 1);
+ assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1);
+ OutputPointer_set_Int32(
+ env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)
+ );
+ }
+ if( pStmt ){
+ NativePointerHolder_set(S3JniNph(sqlite3_stmt), jStmt, pStmt);
+ }else{
+ /* Happens for comments and whitespace. */
+ S3JniUnrefLocal(jStmt);
+ jStmt = 0;
+ }
+ }else{
+ S3JniUnrefLocal(jStmt);
+ jStmt = 0;
+ }
+ if( jOutStmt ){
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_stmt),
+ jOutStmt, jStmt);
+ }
+ return (jint)rc;
+}
+S3JniApi(sqlite3_prepare(),jint,1prepare)(
+ JNIEnv * const env, jclass self, jlong jpDb, jbyteArray baSql,
+ jint nMax, jobject jOutStmt, jobject outTail
+){
+ return sqlite3_jni_prepare_v123(1, env, self, jpDb, baSql, nMax, 0,
+ jOutStmt, outTail);
+}
+S3JniApi(sqlite3_prepare_v2(),jint,1prepare_1v2)(
+ JNIEnv * const env, jclass self, jlong jpDb, jbyteArray baSql,
+ jint nMax, jobject jOutStmt, jobject outTail
+){
+ return sqlite3_jni_prepare_v123(2, env, self, jpDb, baSql, nMax, 0,
+ jOutStmt, outTail);
+}
+S3JniApi(sqlite3_prepare_v3(),jint,1prepare_1v3)(
+ JNIEnv * const env, jclass self, jlong jpDb, jbyteArray baSql,
+ jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail
+){
+ return sqlite3_jni_prepare_v123(3, env, self, jpDb, baSql, nMax,
+ prepFlags, jOutStmt, outTail);
+}
+
+/*
+** Impl for C-to-Java of the callbacks for both sqlite3_update_hook()
+** and sqlite3_preupdate_hook(). The differences are that for
+** update_hook():
+**
+** - pDb is NULL
+** - iKey1 is the row ID
+** - iKey2 is unused
+*/
+static void s3jni_updatepre_hook_impl(void * pState, sqlite3 *pDb, int opId,
+ const char *zDb, const char *zTable,
+ sqlite3_int64 iKey1, sqlite3_int64 iKey2){
+ S3JniDb * const ps = pState;
+ S3JniDeclLocal_env;
+ jstring jDbName;
+ jstring jTable;
+ const int isPre = 0!=pDb;
+ S3JniHook hook;
+
+ S3JniHook_localdup(isPre ?
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ &ps->hooks.preUpdate
+#else
+ &S3JniHook_empty
+#endif
+ : &ps->hooks.update, &hook);
+ if( !hook.jObj ){
+ return;
+ }
+ jDbName = s3jni_utf8_to_jstring( zDb, -1);
+ jTable = jDbName ? s3jni_utf8_to_jstring( zTable, -1) : 0;
+ S3JniIfThrew {
+ S3JniExceptionClear;
+ s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
+ }else{
+ assert( hook.jObj );
+ assert( hook.midCallback );
+ assert( ps->jDb );
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ if( isPre ) (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback,
+ ps->jDb, (jint)opId, jDbName, jTable,
+ (jlong)iKey1, (jlong)iKey2);
+ else
+#endif
+ (*env)->CallVoidMethod(env, hook.jObj, hook.midCallback,
+ (jint)opId, jDbName, jTable, (jlong)iKey1);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("sqlite3_(pre)update_hook() callback");
+ s3jni_db_exception(ps->pDb, 0,
+ "sqlite3_(pre)update_hook() callback threw");
+ }
+ }
+ S3JniUnrefLocal(jDbName);
+ S3JniUnrefLocal(jTable);
+ S3JniHook_localundup(hook);
+}
+
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+static void s3jni_preupdate_hook_impl(void * pState, sqlite3 *pDb, int opId,
+ const char *zDb, const char *zTable,
+ sqlite3_int64 iKey1, sqlite3_int64 iKey2){
+ return s3jni_updatepre_hook_impl(pState, pDb, opId, zDb, zTable,
+ iKey1, iKey2);
+}
+#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
+
+static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb,
+ const char *zTable, sqlite3_int64 nRowid){
+ return s3jni_updatepre_hook_impl(pState, NULL, opId, zDb, zTable, nRowid, 0);
+}
+
+#if !defined(SQLITE_ENABLE_PREUPDATE_HOOK)
+/* We need no-op impls for preupdate_{count,depth,blobwrite}() */
+S3JniApi(sqlite3_preupdate_blobwrite(),int,1preupdate_1blobwrite)(
+ JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; }
+S3JniApi(sqlite3_preupdate_count(),int,1preupdate_1count)(
+ JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; }
+S3JniApi(sqlite3_preupdate_depth(),int,1preupdate_1depth)(
+ JniArgsEnvClass, jlong jDb){ return SQLITE_MISUSE; }
+#endif /* !SQLITE_ENABLE_PREUPDATE_HOOK */
+
+/*
+** JNI wrapper for both sqlite3_update_hook() and
+** sqlite3_preupdate_hook() (if isPre is true).
+*/
+static jobject s3jni_updatepre_hook(JNIEnv * env, int isPre, jlong jpDb, jobject jHook){
+ S3JniDb * const ps = S3JniDb_from_jlong(jpDb);
+ jclass klazz;
+ jobject pOld = 0;
+ jmethodID xCallback;
+ S3JniHook * pHook;
+
+ if( !ps ) return 0;
+ S3JniDb_mutex_enter;
+ pHook = isPre ?
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ &ps->hooks.preUpdate
+#else
+ 0
+#endif
+ : &ps->hooks.update;
+ if( !pHook ){
+ goto end;
+ }
+ pOld = pHook->jObj;
+ if( pOld && jHook && (*env)->IsSameObject(env, pOld, jHook) ){
+ goto end;
+ }
+ if( !jHook ){
+ if( pOld ){
+ jobject tmp = S3JniRefLocal(pOld);
+ S3JniUnrefGlobal(pOld);
+ pOld = tmp;
+ }
+ *pHook = S3JniHook_empty;
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ if( isPre ) sqlite3_preupdate_hook(ps->pDb, 0, 0);
+ else
+#endif
+ sqlite3_update_hook(ps->pDb, 0, 0);
+ goto end;
+ }
+ klazz = (*env)->GetObjectClass(env, jHook);
+ xCallback = isPre
+ ? (*env)->GetMethodID(env, klazz, "call",
+ "(Lorg/sqlite/jni/capi/sqlite3;"
+ "I"
+ "Ljava/lang/String;"
+ "Ljava/lang/String;"
+ "JJ)V")
+ : (*env)->GetMethodID(env, klazz, "call",
+ "(ILjava/lang/String;Ljava/lang/String;J)V");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ S3JniExceptionClear;
+ s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Cannot not find matching callback on "
+ "(pre)update hook object.");
+ }else{
+ pHook->midCallback = xCallback;
+ pHook->jObj = S3JniRefGlobal(jHook);
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ if( isPre ) sqlite3_preupdate_hook(ps->pDb, s3jni_preupdate_hook_impl, ps);
+ else
+#endif
+ sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps);
+ if( pOld ){
+ jobject tmp = S3JniRefLocal(pOld);
+ S3JniUnrefGlobal(pOld);
+ pOld = tmp;
+ }
+ }
+end:
+ S3JniDb_mutex_leave;
+ return pOld;
+}
+
+
+S3JniApi(sqlite3_preupdate_hook(),jobject,1preupdate_1hook)(
+ JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ return s3jni_updatepre_hook(env, 1, jpDb, jHook);
+#else
+ return NULL;
+#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
+}
+
+/* Impl for sqlite3_preupdate_{new,old}(). */
+static int s3jni_preupdate_newold(JNIEnv * const env, int isNew, jlong jpDb,
+ jint iCol, jobject jOut){
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ sqlite3 * const pDb = S3JniLongPtr_sqlite3(jpDb);
+ int rc = SQLITE_MISUSE;
+ if( pDb ){
+ sqlite3_value * pOut = 0;
+ int (*fOrig)(sqlite3*,int,sqlite3_value**) =
+ isNew ? sqlite3_preupdate_new : sqlite3_preupdate_old;
+ rc = fOrig(pDb, (int)iCol, &pOut);
+ if( 0==rc ){
+ jobject pWrap = new_java_sqlite3_value(env, pOut);
+ if( !pWrap ){
+ rc = SQLITE_NOMEM;
+ }
+ OutputPointer_set_obj(env, S3JniNph(OutputPointer_sqlite3_value),
+ jOut, pWrap);
+ S3JniUnrefLocal(pWrap);
+ }
+ }
+ return rc;
+#else
+ return SQLITE_MISUSE;
+#endif
+}
+
+S3JniApi(sqlite3_preupdate_new(),jint,1preupdate_1new)(
+ JniArgsEnvClass, jlong jpDb, jint iCol, jobject jOut
+){
+ return s3jni_preupdate_newold(env, 1, jpDb, iCol, jOut);
+}
+
+S3JniApi(sqlite3_preupdate_old(),jint,1preupdate_1old)(
+ JniArgsEnvClass, jlong jpDb, jint iCol, jobject jOut
+){
+ return s3jni_preupdate_newold(env, 0, jpDb, iCol, jOut);
+}
+
+
+/* Central C-to-Java sqlite3_progress_handler() proxy. */
+static int s3jni_progress_handler_impl(void *pP){
+ S3JniDb * const ps = (S3JniDb *)pP;
+ int rc = 0;
+ S3JniDeclLocal_env;
+ S3JniHook hook;
+
+ S3JniHook_localdup(&ps->hooks.progress, &hook);
+ if( hook.jObj ){
+ rc = (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback);
+ S3JniIfThrew{
+ rc = s3jni_db_exception(ps->pDb, rc,
+ "sqlite3_progress_handler() callback threw");
+ }
+ S3JniHook_localundup(hook);
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_progress_handler(),void,1progress_1handler)(
+ JniArgsEnvClass,jobject jDb, jint n, jobject jProgress
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ S3JniHook * const pHook = ps ? &ps->hooks.progress : 0;
+
+ if( !ps ) return;
+ S3JniDb_mutex_enter;
+ if( n<1 || !jProgress ){
+ S3JniHook_unref(pHook);
+ sqlite3_progress_handler(ps->pDb, 0, 0, 0);
+ }else{
+ jclass const klazz = (*env)->GetObjectClass(env, jProgress);
+ jmethodID const xCallback = (*env)->GetMethodID(env, klazz, "call", "()I");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ S3JniExceptionClear;
+ s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Cannot not find matching xCallback() on "
+ "ProgressHandler object.");
+ }else{
+ S3JniUnrefGlobal(pHook->jObj);
+ pHook->midCallback = xCallback;
+ pHook->jObj = S3JniRefGlobal(jProgress);
+ sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps);
+ }
+ }
+ S3JniDb_mutex_leave;
+}
+
+S3JniApi(sqlite3_randomness(),void,1randomness)(
+ JniArgsEnvClass, jbyteArray jTgt
+){
+ jbyte * const jba = s3jni_jbyteArray_bytes(jTgt);
+ if( jba ){
+ jsize const nTgt = (*env)->GetArrayLength(env, jTgt);
+ sqlite3_randomness( (int)nTgt, jba );
+ s3jni_jbyteArray_commit(jTgt, jba);
+ }
+}
+
+
+S3JniApi(sqlite3_reset(),jint,1reset)(
+ JniArgsEnvClass, jobject jpStmt
+){
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+ return pStmt ? sqlite3_reset(pStmt) : SQLITE_MISUSE;
+}
+
+/* Clears all entries from S3JniGlobal.autoExt. */
+static void s3jni_reset_auto_extension(JNIEnv *env){
+ int i;
+ S3JniAutoExt_mutex_enter;
+ for( i = 0; i < SJG.autoExt.nExt; ++i ){
+ S3JniAutoExtension_clear( &SJG.autoExt.aExt[i] );
+ }
+ SJG.autoExt.nExt = 0;
+ S3JniAutoExt_mutex_leave;
+}
+
+S3JniApi(sqlite3_reset_auto_extension(),void,1reset_1auto_1extension)(
+ JniArgsEnvClass
+){
+ s3jni_reset_auto_extension(env);
+}
+
+/* Impl for sqlite3_result_text/blob() and friends. */
+static void result_blob_text(int as64 /* true for text64/blob64() mode */,
+ int eTextRep /* 0 for blobs, else SQLITE_UTF... */,
+ JNIEnv * const env, sqlite3_context *pCx,
+ jbyteArray jBa, jlong nMax){
+ int const asBlob = 0==eTextRep;
+ if( !pCx ){
+ /* We should arguably emit a warning here. But where to log it? */
+ return;
+ }else if( jBa ){
+ jbyte * const pBuf = s3jni_jbyteArray_bytes(jBa);
+ jsize nBA = (*env)->GetArrayLength(env, jBa);
+ if( nMax>=0 && nBA>(jsize)nMax ){
+ nBA = (jsize)nMax;
+ /**
+ From the sqlite docs:
+
+ > If the 3rd parameter to any of the sqlite3_result_text*
+ interfaces other than sqlite3_result_text64() is negative,
+ then SQLite computes the string length itself by searching
+ the 2nd parameter for the first zero character.
+
+ Note that the text64() interfaces take an unsigned value for
+ the length, which Java does not support. This binding takes
+ the approach of passing on negative values to the C API,
+ which will in turn fail with SQLITE_TOOBIG at some later
+ point (recall that the sqlite3_result_xyz() family do not
+ have result values).
+ */
+ }
+ if( as64 ){ /* 64-bit... */
+ static const jsize nLimit64 =
+ SQLITE_MAX_ALLOCATION_SIZE/*only _kinda_ arbitrary*/;
+ if( nBA > nLimit64 ){
+ sqlite3_result_error_toobig(pCx);
+ }else if( asBlob ){
+ sqlite3_result_blob64(pCx, pBuf, (sqlite3_uint64)nBA,
+ SQLITE_TRANSIENT);
+ }else{ /* text64... */
+ if( encodingTypeIsValid(eTextRep) ){
+ sqlite3_result_text64(pCx, (const char *)pBuf,
+ (sqlite3_uint64)nBA,
+ SQLITE_TRANSIENT, eTextRep);
+ }else{
+ sqlite3_result_error_code(pCx, SQLITE_FORMAT);
+ }
+ }
+ }else{ /* 32-bit... */
+ static const jsize nLimit = SQLITE_MAX_ALLOCATION_SIZE;
+ if( nBA > nLimit ){
+ sqlite3_result_error_toobig(pCx);
+ }else if( asBlob ){
+ sqlite3_result_blob(pCx, pBuf, (int)nBA,
+ SQLITE_TRANSIENT);
+ }else{
+ switch( eTextRep ){
+ case SQLITE_UTF8:
+ sqlite3_result_text(pCx, (const char *)pBuf, (int)nBA,
+ SQLITE_TRANSIENT);
+ break;
+ case SQLITE_UTF16:
+ sqlite3_result_text16(pCx, (const char *)pBuf, (int)nBA,
+ SQLITE_TRANSIENT);
+ break;
+ case SQLITE_UTF16LE:
+ sqlite3_result_text16le(pCx, (const char *)pBuf, (int)nBA,
+ SQLITE_TRANSIENT);
+ break;
+ case SQLITE_UTF16BE:
+ sqlite3_result_text16be(pCx, (const char *)pBuf, (int)nBA,
+ SQLITE_TRANSIENT);
+ break;
+ }
+ }
+ s3jni_jbyteArray_release(jBa, pBuf);
+ }
+ }else{
+ sqlite3_result_null(pCx);
+ }
+}
+
+S3JniApi(sqlite3_result_blob(),void,1result_1blob)(
+ JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jint nMax
+){
+ return result_blob_text(0, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax);
+}
+
+S3JniApi(sqlite3_result_blob64(),void,1result_1blob64)(
+ JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jlong nMax
+){
+ return result_blob_text(1, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax);
+}
+
+S3JniApi(sqlite3_result_double(),void,1result_1double)(
+ JniArgsEnvClass, jobject jpCx, jdouble v
+){
+ sqlite3_result_double(PtrGet_sqlite3_context(jpCx), v);
+}
+
+S3JniApi(sqlite3_result_error(),void,1result_1error)(
+ JniArgsEnvClass, jobject jpCx, jbyteArray baMsg, int eTextRep
+){
+ const char * zUnspecified = "Unspecified error.";
+ jsize const baLen = (*env)->GetArrayLength(env, baMsg);
+ jbyte * const pjBuf = baMsg ? s3jni_jbyteArray_bytes(baMsg) : NULL;
+ switch( pjBuf ? eTextRep : SQLITE_UTF8 ){
+ case SQLITE_UTF8: {
+ const char *zMsg = pjBuf ? (const char *)pjBuf : zUnspecified;
+ int const n = pjBuf ? (int)baLen : (int)sqlite3Strlen30(zMsg);
+ sqlite3_result_error(PtrGet_sqlite3_context(jpCx), zMsg, n);
+ break;
+ }
+ case SQLITE_UTF16: {
+ const void *zMsg = pjBuf;
+ sqlite3_result_error16(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen);
+ break;
+ }
+ default:
+ sqlite3_result_error(PtrGet_sqlite3_context(jpCx),
+ "Invalid encoding argument passed "
+ "to sqlite3_result_error().", -1);
+ break;
+ }
+ s3jni_jbyteArray_release(baMsg,pjBuf);
+}
+
+S3JniApi(sqlite3_result_error_code(),void,1result_1error_1code)(
+ JniArgsEnvClass, jobject jpCx, jint v
+){
+ sqlite3_result_error_code(PtrGet_sqlite3_context(jpCx), (int)v);
+}
+
+S3JniApi(sqlite3_result_error_nomem(),void,1result_1error_1nomem)(
+ JniArgsEnvClass, jobject jpCx
+){
+ sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx));
+}
+
+S3JniApi(sqlite3_result_error_toobig(),void,1result_1error_1toobig)(
+ JniArgsEnvClass, jobject jpCx
+){
+ sqlite3_result_error_toobig(PtrGet_sqlite3_context(jpCx));
+}
+
+S3JniApi(sqlite3_result_int(),void,1result_1int)(
+ JniArgsEnvClass, jobject jpCx, jint v
+){
+ sqlite3_result_int(PtrGet_sqlite3_context(jpCx), (int)v);
+}
+
+S3JniApi(sqlite3_result_int64(),void,1result_1int64)(
+ JniArgsEnvClass, jobject jpCx, jlong v
+){
+ sqlite3_result_int64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v);
+}
+
+S3JniApi(sqlite3_result_java_object(),void,1result_1java_1object)(
+ JniArgsEnvClass, jobject jpCx, jobject v
+){
+ sqlite3_context * pCx = PtrGet_sqlite3_context(jpCx);
+ if( !pCx ) return;
+ else if( v ){
+ jobject const rjv = S3JniRefGlobal(v);
+ if( rjv ){
+ sqlite3_result_pointer(pCx, rjv,
+ ResultJavaValuePtrStr, S3Jni_jobject_finalizer);
+ }else{
+ sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx));
+ }
+ }else{
+ sqlite3_result_null(PtrGet_sqlite3_context(jpCx));
+ }
+}
+
+S3JniApi(sqlite3_result_null(),void,1result_1null)(
+ JniArgsEnvClass, jobject jpCx
+){
+ sqlite3_result_null(PtrGet_sqlite3_context(jpCx));
+}
+
+S3JniApi(sqlite3_result_text(),void,1result_1text)(
+ JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jint nMax
+){
+ return result_blob_text(0, SQLITE_UTF8, env,
+ PtrGet_sqlite3_context(jpCx), jBa, nMax);
+}
+
+S3JniApi(sqlite3_result_text64(),void,1result_1text64)(
+ JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jlong nMax,
+ jint eTextRep
+){
+ return result_blob_text(1, eTextRep, env,
+ PtrGet_sqlite3_context(jpCx), jBa, nMax);
+}
+
+S3JniApi(sqlite3_result_value(),void,1result_1value)(
+ JniArgsEnvClass, jobject jpCx, jobject jpSVal
+){
+ sqlite3_result_value(PtrGet_sqlite3_context(jpCx),
+ PtrGet_sqlite3_value(jpSVal));
+}
+
+S3JniApi(sqlite3_result_zeroblob(),void,1result_1zeroblob)(
+ JniArgsEnvClass, jobject jpCx, jint v
+){
+ sqlite3_result_zeroblob(PtrGet_sqlite3_context(jpCx), (int)v);
+}
+
+S3JniApi(sqlite3_result_zeroblob64(),jint,1result_1zeroblob64)(
+ JniArgsEnvClass, jobject jpCx, jlong v
+){
+ return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx),
+ (sqlite3_int64)v);
+}
+
+S3JniApi(sqlite3_rollback_hook(),jobject,1rollback_1hook)(
+ JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+ return s3jni_commit_rollback_hook(0, env, jpDb, jHook);
+}
+
+/* Callback for sqlite3_set_authorizer(). */
+int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1,
+ const char*z2,const char*z3){
+ S3JniDb * const ps = pState;
+ S3JniDeclLocal_env;
+ S3JniHook hook;
+ int rc = 0;
+
+ S3JniHook_localdup(&ps->hooks.auth, &hook );
+ if( hook.jObj ){
+ jstring const s0 = z0 ? s3jni_utf8_to_jstring( z0, -1) : 0;
+ jstring const s1 = z1 ? s3jni_utf8_to_jstring( z1, -1) : 0;
+ jstring const s2 = z2 ? s3jni_utf8_to_jstring( z2, -1) : 0;
+ jstring const s3 = z3 ? s3jni_utf8_to_jstring( z3, -1) : 0;
+
+ rc = (*env)->CallIntMethod(env, hook.jObj, hook.midCallback, (jint)op,
+ s0, s1, s3, s3);
+ S3JniIfThrew{
+ rc = s3jni_db_exception(ps->pDb, rc, "sqlite3_set_authorizer() callback");
+ }
+ S3JniUnrefLocal(s0);
+ S3JniUnrefLocal(s1);
+ S3JniUnrefLocal(s2);
+ S3JniUnrefLocal(s3);
+ S3JniHook_localundup(hook);
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_set_authorizer(),jint,1set_1authorizer)(
+ JniArgsEnvClass,jobject jDb, jobject jHook
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ S3JniHook * const pHook = ps ? &ps->hooks.auth : 0;
+ int rc = 0;
+
+ if( !ps ) return SQLITE_MISUSE;
+ S3JniDb_mutex_enter;
+ if( !jHook ){
+ S3JniHook_unref(pHook);
+ rc = sqlite3_set_authorizer( ps->pDb, 0, 0 );
+ }else{
+ jclass klazz;
+ if( pHook->jObj ){
+ if( (*env)->IsSameObject(env, pHook->jObj, jHook) ){
+ /* Same object - this is a no-op. */
+ S3JniDb_mutex_leave;
+ return 0;
+ }
+ S3JniHook_unref(pHook);
+ }
+ pHook->jObj = S3JniRefGlobal(jHook);
+ klazz = (*env)->GetObjectClass(env, jHook);
+ pHook->midCallback = (*env)->GetMethodID(env, klazz,
+ "call",
+ "(I"
+ "Ljava/lang/String;"
+ "Ljava/lang/String;"
+ "Ljava/lang/String;"
+ "Ljava/lang/String;"
+ ")I");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Error setting up Java parts of authorizer hook.");
+ }else{
+ rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps);
+ }
+ if( rc ) S3JniHook_unref(pHook);
+ }
+ S3JniDb_mutex_leave;
+ return rc;
+}
+
+S3JniApi(sqlite3_set_auxdata(),void,1set_1auxdata)(
+ JniArgsEnvClass, jobject jCx, jint n, jobject jAux
+){
+ sqlite3_set_auxdata(PtrGet_sqlite3_context(jCx), (int)n,
+ S3JniRefGlobal(jAux), S3Jni_jobject_finalizer);
+}
+
+S3JniApi(sqlite3_set_last_insert_rowid(),void,1set_1last_1insert_1rowid)(
+ JniArgsEnvClass, jobject jpDb, jlong rowId
+){
+ sqlite3_set_last_insert_rowid(PtrGet_sqlite3(jpDb),
+ (sqlite3_int64)rowId);
+}
+
+S3JniApi(sqlite3_shutdown(),jint,1shutdown)(
+ JniArgsEnvClass
+){
+ s3jni_reset_auto_extension(env);
+#ifdef SQLITE_ENABLE_SQLLOG
+ S3JniHook_unref(&SJG.hook.sqllog);
+#endif
+ S3JniHook_unref(&SJG.hook.configlog);
+ /* Free up S3JniDb recycling bin. */
+ S3JniDb_mutex_enter; {
+ while( S3JniGlobal.perDb.aFree ){
+ S3JniDb * const d = S3JniGlobal.perDb.aFree;
+ S3JniGlobal.perDb.aFree = d->pNext;
+ S3JniDb_clear(env, d);
+ sqlite3_free(d);
+ }
+ } S3JniDb_mutex_leave;
+ S3JniGlobal_mutex_enter; {
+ /* Free up S3JniUdf recycling bin. */
+ while( S3JniGlobal.udf.aFree ){
+ S3JniUdf * const u = S3JniGlobal.udf.aFree;
+ S3JniGlobal.udf.aFree = u->pNext;
+ u->pNext = 0;
+ S3JniUdf_free(env, u, 0);
+ }
+ } S3JniGlobal_mutex_leave;
+ S3JniHook_mutex_enter; {
+ /* Free up S3JniHook recycling bin. */
+ while( S3JniGlobal.hook.aFree ){
+ S3JniHook * const u = S3JniGlobal.hook.aFree;
+ S3JniGlobal.hook.aFree = u->pNext;
+ u->pNext = 0;
+ assert( !u->doXDestroy );
+ assert( !u->jObj );
+ assert( !u->jExtra );
+ sqlite3_free( u );
+ }
+ } S3JniHook_mutex_leave;
+ /* Free up env cache. */
+ S3JniEnv_mutex_enter; {
+ while( SJG.envCache.aHead ){
+ S3JniEnv_uncache( SJG.envCache.aHead->env );
+ }
+ } S3JniEnv_mutex_leave;
+#if 0
+ /*
+ ** Is automatically closing any still-open dbs a good idea? We will
+ ** get rid of the perDb list once sqlite3 gets a per-db client
+ ** state, at which point we won't have a central list of databases
+ ** to close.
+ */
+ S3JniDb_mutex_enter;
+ while( SJG.perDb.pHead ){
+ s3jni_close_db(env, SJG.perDb.pHead->jDb, 2);
+ }
+ S3JniDb_mutex_leave;
+#endif
+ /* Do not clear S3JniGlobal.jvm: it's legal to restart the lib. */
+ return sqlite3_shutdown();
+}
+
+S3JniApi(sqlite3_status(),jint,1status)(
+ JniArgsEnvClass, jint op, jobject jOutCurrent, jobject jOutHigh,
+ jboolean reset
+){
+ int iCur = 0, iHigh = 0;
+ int rc = sqlite3_status( op, &iCur, &iHigh, reset );
+ if( 0==rc ){
+ OutputPointer_set_Int32(env, jOutCurrent, iCur);
+ OutputPointer_set_Int32(env, jOutHigh, iHigh);
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_status64(),jint,1status64)(
+ JniArgsEnvClass, jint op, jobject jOutCurrent, jobject jOutHigh,
+ jboolean reset
+){
+ sqlite3_int64 iCur = 0, iHigh = 0;
+ int rc = sqlite3_status64( op, &iCur, &iHigh, reset );
+ if( 0==rc ){
+ OutputPointer_set_Int64(env, jOutCurrent, iCur);
+ OutputPointer_set_Int64(env, jOutHigh, iHigh);
+ }
+ return (jint)rc;
+}
+
+S3JniApi(sqlite3_stmt_status(),jint,1stmt_1status)(
+ JniArgsEnvClass, jobject jStmt, jint op, jboolean reset
+){
+ return sqlite3_stmt_status(PtrGet_sqlite3_stmt(jStmt),
+ (int)op, reset ? 1 : 0);
+}
+
+
+static int s3jni_strlike_glob(int isLike, JNIEnv *const env,
+ jbyteArray baG, jbyteArray baT, jint escLike){
+ int rc = 0;
+ jbyte * const pG = s3jni_jbyteArray_bytes(baG);
+ jbyte * const pT = pG ? s3jni_jbyteArray_bytes(baT) : 0;
+
+ /* Note that we're relying on the byte arrays having been
+ NUL-terminated on the Java side. */
+ rc = isLike
+ ? sqlite3_strlike((const char *)pG, (const char *)pT,
+ (unsigned int)escLike)
+ : sqlite3_strglob((const char *)pG, (const char *)pT);
+ s3jni_jbyteArray_release(baG, pG);
+ s3jni_jbyteArray_release(baT, pT);
+ return rc;
+}
+
+S3JniApi(sqlite3_strglob(),jint,1strglob)(
+ JniArgsEnvClass, jbyteArray baG, jbyteArray baT
+){
+ return s3jni_strlike_glob(0, env, baG, baT, 0);
+}
+
+S3JniApi(sqlite3_strlike(),jint,1strlike)(
+ JniArgsEnvClass, jbyteArray baG, jbyteArray baT, jint escChar
+){
+ return s3jni_strlike_glob(1, env, baG, baT, escChar);
+}
+
+S3JniApi(sqlite3_sql(),jstring,1sql)(
+ JniArgsEnvClass, jobject jpStmt
+){
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
+ jstring rv = 0;
+ if( pStmt ){
+ const char * zSql = 0;
+ zSql = sqlite3_sql(pStmt);
+ rv = s3jni_utf8_to_jstring( zSql, -1);
+ }
+ return rv;
+}
+
+S3JniApi(sqlite3_step(),jint,1step)(
+ JniArgsEnvClass,jobject jStmt
+){
+ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt);
+ return pStmt ? (jint)sqlite3_step(pStmt) : (jint)SQLITE_MISUSE;
+}
+
+S3JniApi(sqlite3_table_column_metadata(),int,1table_1column_1metadata)(
+ JniArgsEnvClass, jobject jDb, jstring jDbName, jstring jTableName,
+ jstring jColumnName, jobject jDataType, jobject jCollSeq, jobject jNotNull,
+ jobject jPrimaryKey, jobject jAutoinc
+){
+ sqlite3 * const db = PtrGet_sqlite3(jDb);
+ char * zDbName = 0, * zTableName = 0, * zColumnName = 0;
+ const char * pzCollSeq = 0;
+ const char * pzDataType = 0;
+ int pNotNull = 0, pPrimaryKey = 0, pAutoinc = 0;
+ int rc;
+
+ if( !db || !jDbName || !jTableName ) return SQLITE_MISUSE;
+ zDbName = s3jni_jstring_to_utf8(jDbName,0);
+ zTableName = zDbName ? s3jni_jstring_to_utf8(jTableName,0) : 0;
+ zColumnName = (zTableName && jColumnName)
+ ? s3jni_jstring_to_utf8(jColumnName,0) : 0;
+ rc = zTableName
+ ? sqlite3_table_column_metadata(db, zDbName, zTableName,
+ zColumnName, &pzDataType, &pzCollSeq,
+ &pNotNull, &pPrimaryKey, &pAutoinc)
+ : SQLITE_NOMEM;
+ if( 0==rc ){
+ jstring jseq = jCollSeq
+ ? (pzCollSeq ? s3jni_utf8_to_jstring(pzCollSeq, -1) : 0)
+ : 0;
+ jstring jdtype = jDataType
+ ? (pzDataType ? s3jni_utf8_to_jstring(pzDataType, -1) : 0)
+ : 0;
+ if( (jCollSeq && pzCollSeq && !jseq)
+ || (jDataType && pzDataType && !jdtype) ){
+ rc = SQLITE_NOMEM;
+ }else{
+ if( jNotNull ) OutputPointer_set_Bool(env, jNotNull, pNotNull);
+ if( jPrimaryKey ) OutputPointer_set_Bool(env, jPrimaryKey, pPrimaryKey);
+ if( jAutoinc ) OutputPointer_set_Bool(env, jAutoinc, pAutoinc);
+ if( jCollSeq ) OutputPointer_set_String(env, jCollSeq, jseq);
+ if( jDataType ) OutputPointer_set_String(env, jDataType, jdtype);
+ }
+ S3JniUnrefLocal(jseq);
+ S3JniUnrefLocal(jdtype);
+ }
+ sqlite3_free(zDbName);
+ sqlite3_free(zTableName);
+ sqlite3_free(zColumnName);
+ return rc;
+}
+
+static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){
+ S3JniDb * const ps = (S3JniDb *)pC;
+ S3JniDeclLocal_env;
+ jobject jX = NULL /* the tracer's X arg */;
+ jobject jP = NULL /* the tracer's P arg */;
+ jobject jPUnref = NULL /* potentially a local ref to jP */;
+ int rc = 0;
+ S3JniHook hook;
+
+ S3JniHook_localdup(&ps->hooks.trace, &hook );
+ if( !hook.jObj ){
+ return 0;
+ }
+ switch( traceflag ){
+ case SQLITE_TRACE_STMT:
+ jX = s3jni_utf8_to_jstring( (const char *)pX, -1);
+ if( !jX ) rc = SQLITE_NOMEM;
+ break;
+ case SQLITE_TRACE_PROFILE:
+ jX = (*env)->NewObject(env, SJG.g.cLong, SJG.g.ctorLong1,
+ (jlong)*((sqlite3_int64*)pX));
+ // hmm. ^^^ (*pX) really is zero.
+ // MARKER(("profile time = %llu\n", *((sqlite3_int64*)pX)));
+ s3jni_oom_check( jX );
+ if( !jX ) rc = SQLITE_NOMEM;
+ break;
+ case SQLITE_TRACE_ROW:
+ break;
+ case SQLITE_TRACE_CLOSE:
+ jP = jPUnref = S3JniRefLocal(ps->jDb);
+ break;
+ default:
+ assert(!"cannot happen - unknown trace flag");
+ rc = SQLITE_ERROR;
+ }
+ if( 0==rc ){
+ if( !jP ){
+ /* Create a new temporary sqlite3_stmt wrapper */
+ jP = jPUnref = new_java_sqlite3_stmt(env, pP);
+ if( !jP ){
+ rc = SQLITE_NOMEM;
+ }
+ }
+ if( 0==rc ){
+ assert(jP);
+ rc = (int)(*env)->CallIntMethod(env, hook.jObj, hook.midCallback,
+ (jint)traceflag, jP, jX);
+ S3JniIfThrew{
+ rc = s3jni_db_exception(ps->pDb, SQLITE_ERROR,
+ "sqlite3_trace_v2() callback threw.");
+ }
+ }
+ }
+ S3JniUnrefLocal(jPUnref);
+ S3JniUnrefLocal(jX);
+ S3JniHook_localundup(hook);
+ return rc;
+}
+
+S3JniApi(sqlite3_trace_v2(),jint,1trace_1v2)(
+ JniArgsEnvClass,jobject jDb, jint traceMask, jobject jTracer
+){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+ int rc;
+
+ if( !ps ) return SQLITE_MISUSE;
+ if( !traceMask || !jTracer ){
+ S3JniDb_mutex_enter;
+ rc = (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0);
+ S3JniHook_unref(&ps->hooks.trace);
+ S3JniDb_mutex_leave;
+ }else{
+ jclass const klazz = (*env)->GetObjectClass(env, jTracer);
+ S3JniHook hook = S3JniHook_empty;
+ hook.midCallback = (*env)->GetMethodID(
+ env, klazz, "call", "(ILjava/lang/Object;Ljava/lang/Object;)I"
+ );
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ S3JniExceptionClear;
+ rc = s3jni_db_error(ps->pDb, SQLITE_ERROR,
+ "Cannot not find matching call() on "
+ "TracerCallback object.");
+ }else{
+ hook.jObj = S3JniRefGlobal(jTracer);
+ S3JniDb_mutex_enter;
+ rc = sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps);
+ if( 0==rc ){
+ S3JniHook_unref(&ps->hooks.trace);
+ ps->hooks.trace = hook /* transfer ownership of reference */;
+ }else{
+ S3JniHook_unref(&hook);
+ }
+ S3JniDb_mutex_leave;
+ }
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_txn_state(),jint,1txn_1state)(
+ JniArgsEnvClass,jobject jDb, jstring jSchema
+){
+ sqlite3 * const pDb = PtrGet_sqlite3(jDb);
+ int rc = SQLITE_MISUSE;
+ if( pDb ){
+ char * zSchema = jSchema
+ ? s3jni_jstring_to_utf8(jSchema, 0)
+ : 0;
+ if( !jSchema || (zSchema && jSchema) ){
+ rc = sqlite3_txn_state(pDb, zSchema);
+ sqlite3_free(zSchema);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ }
+ return rc;
+}
+
+S3JniApi(sqlite3_update_hook(),jobject,1update_1hook)(
+ JniArgsEnvClass, jlong jpDb, jobject jHook
+){
+ return s3jni_updatepre_hook(env, 0, jpDb, jHook);
+}
+
+
+S3JniApi(sqlite3_value_blob(),jbyteArray,1value_1blob)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ const jbyte * pBytes = sv ? sqlite3_value_blob(sv) : 0;
+ int const nLen = pBytes ? sqlite3_value_bytes(sv) : 0;
+
+ s3jni_oom_check( nLen ? !!pBytes : 1 );
+ return pBytes
+ ? s3jni_new_jbyteArray(pBytes, nLen)
+ : NULL;
+}
+
+S3JniApi(sqlite3_value_bytes(),int,1value_1bytes)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ return sv ? sqlite3_value_bytes(sv) : 0;
+}
+
+S3JniApi(sqlite3_value_bytes16(),int,1value_1bytes16)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ return sv ? sqlite3_value_bytes16(sv) : 0;
+}
+
+
+S3JniApi(sqlite3_value_double(),jdouble,1value_1double)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ return (jdouble) (sv ? sqlite3_value_double(sv) : 0.0);
+}
+
+
+S3JniApi(sqlite3_value_dup(),jobject,1value_1dup)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ sqlite3_value * const sd = sv ? sqlite3_value_dup(sv) : 0;
+ jobject rv = sd ? new_java_sqlite3_value(env, sd) : 0;
+ if( sd && !rv ) {
+ /* OOM */
+ sqlite3_value_free(sd);
+ }
+ return rv;
+}
+
+S3JniApi(sqlite3_value_free(),void,1value_1free)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ if( sv ){
+ sqlite3_value_free(sv);
+ }
+}
+
+S3JniApi(sqlite3_value_int(),jint,1value_1int)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ return (jint) (sv ? sqlite3_value_int(sv) : 0);
+}
+
+S3JniApi(sqlite3_value_int64(),jlong,1value_1int64)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ return (jlong) (sv ? sqlite3_value_int64(sv) : 0LL);
+}
+
+S3JniApi(sqlite3_value_java_object(),jobject,1value_1java_1object)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ return sv
+ ? sqlite3_value_pointer(sv, ResultJavaValuePtrStr)
+ : 0;
+}
+
+S3JniApi(sqlite3_value_text(),jbyteArray,1value_1text)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0;
+ int const n = p ? sqlite3_value_bytes(sv) : 0;
+ return p ? s3jni_new_jbyteArray(p, n) : 0;
+}
+
+#if 0
+// this impl might prove useful.
+S3JniApi(sqlite3_value_text(),jstring,1value_1text)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ const unsigned char * const p = sv ? sqlite3_value_text(sv) : 0;
+ int const n = p ? sqlite3_value_bytes(sv) : 0;
+ return p ? s3jni_utf8_to_jstring( (const char *)p, n) : 0;
+}
+#endif
+
+S3JniApi(sqlite3_value_text16(),jstring,1value_1text16)(
+ JniArgsEnvClass, jlong jpSVal
+){
+ sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
+ const int n = sv ? sqlite3_value_bytes16(sv) : 0;
+ const void * const p = sv ? sqlite3_value_text16(sv) : 0;
+ return p ? s3jni_text16_to_jstring(env, p, n) : 0;
+}
+
+JniDecl(void,1jni_1internal_1details)(JniArgsEnvClass){
+ MARKER(("\nVarious bits of internal info:\n"));
+ puts("FTS5 is "
+#ifdef SQLITE_ENABLE_FTS5
+ "available"
+#else
+ "unavailable"
+#endif
+ "."
+ );
+ puts("sizeofs:");
+#define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T))
+ SO(void*);
+ SO(jmethodID);
+ SO(jfieldID);
+ SO(S3JniEnv);
+ SO(S3JniHook);
+ SO(S3JniDb);
+ SO(S3JniNphOps);
+ printf("\t(^^^ %u NativePointerHolder/OutputPointer.T types)\n",
+ (unsigned)S3Jni_NphCache_size);
+ SO(S3JniGlobal);
+ SO(S3JniGlobal.nph);
+ SO(S3JniGlobal.metrics);
+ SO(S3JniAutoExtension);
+ SO(S3JniUdf);
+#undef SO
+#ifdef SQLITE_JNI_ENABLE_METRICS
+ printf("Cache info:\n");
+ printf("\tJNIEnv cache: %u allocs, %u misses, %u hits\n",
+ SJG.metrics.nEnvAlloc, SJG.metrics.nEnvMiss,
+ SJG.metrics.nEnvHit);
+ printf("Mutex entry:"
+ "\n\tglobal = %u"
+ "\n\tenv = %u"
+ "\n\tnph = %u for S3JniNphOp init"
+ "\n\thook = %u"
+ "\n\tperDb = %u"
+ "\n\tautoExt list = %u"
+ "\n\tS3JniUdf = %u (free-list)"
+ "\n\tmetrics = %u\n",
+ SJG.metrics.nMutexGlobal, SJG.metrics.nMutexEnv,
+ SJG.metrics.nMutexNph, SJG.metrics.nMutexHook,
+ SJG.metrics.nMutexPerDb, SJG.metrics.nMutexAutoExt,
+ SJG.metrics.nMutexUdf, SJG.metrics.nMetrics);
+ puts("Allocs:");
+ printf("\tS3JniDb: %u alloced (*%u = %u bytes), %u recycled\n",
+ SJG.metrics.nPdbAlloc, (unsigned) sizeof(S3JniDb),
+ (unsigned)(SJG.metrics.nPdbAlloc * sizeof(S3JniDb)),
+ SJG.metrics.nPdbRecycled);
+ printf("\tS3JniUdf: %u alloced (*%u = %u bytes), %u recycled\n",
+ SJG.metrics.nUdfAlloc, (unsigned) sizeof(S3JniUdf),
+ (unsigned)(SJG.metrics.nUdfAlloc * sizeof(S3JniUdf)),
+ SJG.metrics.nUdfRecycled);
+ printf("\tS3JniHook: %u alloced (*%u = %u bytes), %u recycled\n",
+ SJG.metrics.nHookAlloc, (unsigned) sizeof(S3JniHook),
+ (unsigned)(SJG.metrics.nHookAlloc * sizeof(S3JniHook)),
+ SJG.metrics.nHookRecycled);
+ printf("\tS3JniEnv: %u alloced (*%u = %u bytes)\n",
+ SJG.metrics.nEnvAlloc, (unsigned) sizeof(S3JniEnv),
+ (unsigned)(SJG.metrics.nEnvAlloc * sizeof(S3JniEnv)));
+ puts("Java-side UDF calls:");
+#define UDF(T) printf("\t%-8s = %u\n", "x" #T, SJG.metrics.udf.n##T)
+ UDF(Func); UDF(Step); UDF(Final); UDF(Value); UDF(Inverse);
+#undef UDF
+ printf("xDestroy calls across all callback types: %u\n",
+ SJG.metrics.nDestroy);
+#else
+ puts("Built without SQLITE_JNI_ENABLE_METRICS.");
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////
+// End of the sqlite3_... API bindings. Next up, FTS5...
+////////////////////////////////////////////////////////////////////////
+#ifdef SQLITE_ENABLE_FTS5
+
+/* Creates a verbose JNI Fts5 function name. */
+#define JniFuncNameFtsXA(Suffix) \
+ Java_org_sqlite_jni_fts5_Fts5ExtensionApi_ ## Suffix
+#define JniFuncNameFtsApi(Suffix) \
+ Java_org_sqlite_jni_fts5_fts5_1api_ ## Suffix
+#define JniFuncNameFtsTok(Suffix) \
+ Java_org_sqlite_jni_fts5_fts5_tokenizer_ ## Suffix
+
+#define JniDeclFtsXA(ReturnType,Suffix) \
+ JNIEXPORT ReturnType JNICALL \
+ JniFuncNameFtsXA(Suffix)
+#define JniDeclFtsApi(ReturnType,Suffix) \
+ JNIEXPORT ReturnType JNICALL \
+ JniFuncNameFtsApi(Suffix)
+#define JniDeclFtsTok(ReturnType,Suffix) \
+ JNIEXPORT ReturnType JNICALL \
+ JniFuncNameFtsTok(Suffix)
+
+#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(OBJ,S3JniNph(fts5_api))
+#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(OBJ,S3JniNph(fts5_tokenizer))
+#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(OBJ,S3JniNph(Fts5Context))
+#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(OBJ,S3JniNph(Fts5Tokenizer))
+#define s3jni_ftsext() &sFts5Api/*singleton from sqlite3.c*/
+#define Fts5ExtDecl Fts5ExtensionApi const * const ext = s3jni_ftsext()
+
+/**
+ State for binding Java-side FTS5 auxiliary functions.
+*/
+typedef struct {
+ jobject jObj /* functor instance */;
+ jobject jUserData /* 2nd arg to JNI binding of
+ xCreateFunction(), ostensibly the 3rd arg
+ to the lib-level xCreateFunction(), except
+ that we necessarily use that slot for a
+ Fts5JniAux instance. */;
+ char * zFuncName /* Only for error reporting and debug logging */;
+ jmethodID jmid /* callback member's method ID */;
+} Fts5JniAux;
+
+static void Fts5JniAux_free(Fts5JniAux * const s){
+ S3JniDeclLocal_env;
+ if( env ){
+ /*MARKER(("FTS5 aux function cleanup: %s\n", s->zFuncName));*/
+ s3jni_call_xDestroy(s->jObj);
+ S3JniUnrefGlobal(s->jObj);
+ S3JniUnrefGlobal(s->jUserData);
+ }
+ sqlite3_free(s->zFuncName);
+ sqlite3_free(s);
+}
+
+static void Fts5JniAux_xDestroy(void *p){
+ if( p ) Fts5JniAux_free(p);
+}
+
+static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){
+ Fts5JniAux * s = s3jni_malloc( sizeof(Fts5JniAux));
+
+ if( s ){
+ jclass klazz;
+ memset(s, 0, sizeof(Fts5JniAux));
+ s->jObj = S3JniRefGlobal(jObj);
+ klazz = (*env)->GetObjectClass(env, jObj);
+ s->jmid = (*env)->GetMethodID(env, klazz, "call",
+ "(Lorg/sqlite/jni/fts5/Fts5ExtensionApi;"
+ "Lorg/sqlite/jni/fts5/Fts5Context;"
+ "Lorg/sqlite/jni/capi/sqlite3_context;"
+ "[Lorg/sqlite/jni/capi/sqlite3_value;)V");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew{
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ Fts5JniAux_free(s);
+ s = 0;
+ }
+ }
+ return s;
+}
+
+static inline jobject new_java_Fts5Context(JNIEnv * const env, Fts5Context *sv){
+ return NativePointerHolder_new(env, S3JniNph(Fts5Context), sv);
+}
+static inline jobject new_java_fts5_api(JNIEnv * const env, fts5_api *sv){
+ return NativePointerHolder_new(env, S3JniNph(fts5_api), sv);
+}
+
+/*
+** Returns a per-JNIEnv global ref to the Fts5ExtensionApi singleton
+** instance, or NULL on OOM.
+*/
+static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){
+ if( !SJG.fts5.jExt ){
+ S3JniGlobal_mutex_enter;
+ if( !SJG.fts5.jExt ){
+ jobject const pNPH = NativePointerHolder_new(
+ env, S3JniNph(Fts5ExtensionApi), s3jni_ftsext()
+ );
+ if( pNPH ){
+ SJG.fts5.jExt = S3JniRefGlobal(pNPH);
+ S3JniUnrefLocal(pNPH);
+ }
+ }
+ S3JniGlobal_mutex_leave;
+ }
+ return SJG.fts5.jExt;
+}
+
+/*
+** Returns a pointer to the fts5_api instance for database connection
+** db. If an error occurs, returns NULL and leaves an error in the
+** database handle (accessible using sqlite3_errcode()/errmsg()).
+*/
+static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){
+ fts5_api *pRet = 0;
+ sqlite3_stmt *pStmt = 0;
+ if( SQLITE_OK==sqlite3_prepare(db, "SELECT fts5(?1)", -1, &pStmt, 0) ){
+ sqlite3_bind_pointer(pStmt, 1, (void*)&pRet, "fts5_api_ptr", NULL);
+ sqlite3_step(pStmt);
+ }
+ sqlite3_finalize(pStmt);
+ return pRet;
+}
+
+JniDeclFtsApi(jobject,getInstanceForDb)(JniArgsEnvClass,jobject jDb){
+ S3JniDb * const ps = S3JniDb_from_java(jDb);
+#if 0
+ jobject rv = 0;
+ if( !ps ) return 0;
+ else if( ps->fts.jApi ){
+ rv = ps->fts.jApi;
+ }else{
+ fts5_api * const pApi = s3jni_fts5_api_from_db(ps->pDb);
+ if( pApi ){
+ rv = new_java_fts5_api(env, pApi);
+ ps->fts.jApi = rv ? S3JniRefGlobal(rv) : 0;
+ }
+ }
+ return rv;
+#else
+ if( ps && !ps->fts.jApi ){
+ S3JniDb_mutex_enter;
+ if( !ps->fts.jApi ){
+ fts5_api * const pApi = s3jni_fts5_api_from_db(ps->pDb);
+ if( pApi ){
+ jobject const rv = new_java_fts5_api(env, pApi);
+ ps->fts.jApi = rv ? S3JniRefGlobal(rv) : 0;
+ }
+ }
+ S3JniDb_mutex_leave;
+ }
+ return ps ? ps->fts.jApi : 0;
+#endif
+}
+
+
+JniDeclFtsXA(jobject,getInstance)(JniArgsEnvClass){
+ return s3jni_getFts5ExensionApi(env);
+}
+
+JniDeclFtsXA(jint,xColumnCount)(JniArgsEnvObj,jobject jCtx){
+ Fts5ExtDecl;
+ return (jint)ext->xColumnCount(PtrGet_Fts5Context(jCtx));
+}
+
+JniDeclFtsXA(jint,xColumnSize)(JniArgsEnvObj,jobject jCtx, jint iIdx, jobject jOut32){
+ Fts5ExtDecl;
+ int n1 = 0;
+ int const rc = ext->xColumnSize(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1);
+ if( 0==rc ) OutputPointer_set_Int32(env, jOut32, n1);
+ return rc;
+}
+
+JniDeclFtsXA(jint,xColumnText)(JniArgsEnvObj,jobject jCtx, jint iCol,
+ jobject jOut){
+ Fts5ExtDecl;
+ const char *pz = 0;
+ int pn = 0;
+ int rc = ext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol,
+ &pz, &pn);
+ if( 0==rc ){
+ jstring jstr = pz ? s3jni_utf8_to_jstring( pz, pn) : 0;
+ if( pz ){
+ if( jstr ){
+ OutputPointer_set_String(env, jOut, jstr);
+ S3JniUnrefLocal(jstr)/*jOut has a reference*/;
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ }
+ }
+ return (jint)rc;
+}
+
+JniDeclFtsXA(jint,xColumnTotalSize)(JniArgsEnvObj,jobject jCtx, jint iCol, jobject jOut64){
+ Fts5ExtDecl;
+ sqlite3_int64 nOut = 0;
+ int const rc = ext->xColumnTotalSize(PtrGet_Fts5Context(jCtx), (int)iCol, &nOut);
+ if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut);
+ return (jint)rc;
+}
+
+/*
+** Proxy for fts5_extension_function instances plugged in via
+** fts5_api::xCreateFunction().
+*/
+static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi,
+ Fts5Context *pFts,
+ sqlite3_context *pCx,
+ int argc,
+ sqlite3_value **argv){
+ Fts5JniAux * const pAux = pApi->xUserData(pFts);
+ jobject jpCx = 0;
+ jobjectArray jArgv = 0;
+ jobject jpFts = 0;
+ jobject jFXA;
+ int rc;
+ S3JniDeclLocal_env;
+
+ assert(pAux);
+ jFXA = s3jni_getFts5ExensionApi(env);
+ if( !jFXA ) goto error_oom;
+ jpFts = new_java_Fts5Context(env, pFts);
+ if( !jpFts ) goto error_oom;
+ rc = udf_args(env, pCx, argc, argv, &jpCx, &jArgv);
+ if( rc ) goto error_oom;
+ (*env)->CallVoidMethod(env, pAux->jObj, pAux->jmid,
+ jFXA, jpFts, jpCx, jArgv);
+ S3JniIfThrew{
+ udf_report_exception(env, 1, pCx, pAux->zFuncName, "call");
+ }
+ udf_unargs(env, jpCx, argc, jArgv);
+ S3JniUnrefLocal(jpFts);
+ S3JniUnrefLocal(jpCx);
+ S3JniUnrefLocal(jArgv);
+ return;
+error_oom:
+ s3jni_db_oom( sqlite3_context_db_handle(pCx) );
+ assert( !jArgv );
+ assert( !jpCx );
+ S3JniUnrefLocal(jpFts);
+ sqlite3_result_error_nomem(pCx);
+ return;
+}
+
+JniDeclFtsApi(jint,xCreateFunction)(JniArgsEnvObj, jstring jName,
+ jobject jUserData, jobject jFunc){
+ fts5_api * const pApi = PtrGet_fts5_api(jSelf);
+ int rc;
+ char * zName;
+ Fts5JniAux * pAux;
+
+ assert(pApi);
+ zName = s3jni_jstring_to_utf8( jName, 0);
+ if(!zName) return SQLITE_NOMEM;
+ pAux = Fts5JniAux_alloc(env, jFunc);
+ if( pAux ){
+ rc = pApi->xCreateFunction(pApi, zName, pAux,
+ s3jni_fts5_extension_function,
+ Fts5JniAux_xDestroy);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ if( 0==rc ){
+ pAux->jUserData = jUserData ? S3JniRefGlobal(jUserData) : 0;
+ pAux->zFuncName = zName;
+ }else{
+ sqlite3_free(zName);
+ }
+ return (jint)rc;
+}
+
+
+typedef struct S3JniFts5AuxData S3JniFts5AuxData;
+/*
+** TODO: this middle-man struct is no longer necessary. Conider
+** removing it and passing around jObj itself instead.
+*/
+struct S3JniFts5AuxData {
+ jobject jObj;
+};
+
+static void S3JniFts5AuxData_xDestroy(void *x){
+ if( x ){
+ S3JniFts5AuxData * const p = x;
+ if( p->jObj ){
+ S3JniDeclLocal_env;
+ s3jni_call_xDestroy(p->jObj);
+ S3JniUnrefGlobal(p->jObj);
+ }
+ sqlite3_free(x);
+ }
+}
+
+JniDeclFtsXA(jobject,xGetAuxdata)(JniArgsEnvObj,jobject jCtx, jboolean bClear){
+ Fts5ExtDecl;
+ jobject rv = 0;
+ S3JniFts5AuxData * const pAux = ext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear);
+ if( pAux ){
+ if( bClear ){
+ if( pAux->jObj ){
+ rv = S3JniRefLocal(pAux->jObj);
+ S3JniUnrefGlobal(pAux->jObj);
+ }
+ /* Note that we do not call xDestroy() in this case. */
+ sqlite3_free(pAux);
+ }else{
+ rv = pAux->jObj;
+ }
+ }
+ return rv;
+}
+
+JniDeclFtsXA(jint,xInst)(JniArgsEnvObj,jobject jCtx, jint iIdx, jobject jOutPhrase,
+ jobject jOutCol, jobject jOutOff){
+ Fts5ExtDecl;
+ int n1 = 0, n2 = 2, n3 = 0;
+ int const rc = ext->xInst(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1, &n2, &n3);
+ if( 0==rc ){
+ OutputPointer_set_Int32(env, jOutPhrase, n1);
+ OutputPointer_set_Int32(env, jOutCol, n2);
+ OutputPointer_set_Int32(env, jOutOff, n3);
+ }
+ return rc;
+}
+
+JniDeclFtsXA(jint,xInstCount)(JniArgsEnvObj,jobject jCtx, jobject jOut32){
+ Fts5ExtDecl;
+ int nOut = 0;
+ int const rc = ext->xInstCount(PtrGet_Fts5Context(jCtx), &nOut);
+ if( 0==rc && jOut32 ) OutputPointer_set_Int32(env, jOut32, nOut);
+ return (jint)rc;
+}
+
+JniDeclFtsXA(jint,xPhraseCount)(JniArgsEnvObj,jobject jCtx){
+ Fts5ExtDecl;
+ return (jint)ext->xPhraseCount(PtrGet_Fts5Context(jCtx));
+}
+
+/* Copy the 'a' and 'b' fields from pSrc to Fts5PhraseIter object jIter. */
+static void s3jni_phraseIter_NToJ(JNIEnv *const env,
+ Fts5PhraseIter const * const pSrc,
+ jobject jIter){
+ S3JniGlobalType * const g = &S3JniGlobal;
+ assert(g->fts5.jPhraseIter.fidA);
+ (*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidA,
+ S3JniCast_P2L(pSrc->a));
+ S3JniExceptionIsFatal("Cannot set Fts5PhraseIter.a field.");
+ (*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidB,
+ S3JniCast_P2L(pSrc->b));
+ S3JniExceptionIsFatal("Cannot set Fts5PhraseIter.b field.");
+}
+
+/* Copy the 'a' and 'b' fields from Fts5PhraseIter object jIter to pDest. */
+static void s3jni_phraseIter_JToN(JNIEnv *const env, jobject jIter,
+ Fts5PhraseIter * const pDest){
+ S3JniGlobalType * const g = &S3JniGlobal;
+ assert(g->fts5.jPhraseIter.fidA);
+ pDest->a = S3JniCast_L2P(
+ (*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidA)
+ );
+ S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.a field.");
+ pDest->b = S3JniCast_L2P(
+ (*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidB)
+ );
+ S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.b field.");
+}
+
+JniDeclFtsXA(jint,xPhraseFirst)(JniArgsEnvObj,jobject jCtx, jint iPhrase,
+ jobject jIter, jobject jOutCol,
+ jobject jOutOff){
+ Fts5ExtDecl;
+ Fts5PhraseIter iter;
+ int rc, iCol = 0, iOff = 0;
+ rc = ext->xPhraseFirst(PtrGet_Fts5Context(jCtx), (int)iPhrase,
+ &iter, &iCol, &iOff);
+ if( 0==rc ){
+ OutputPointer_set_Int32(env, jOutCol, iCol);
+ OutputPointer_set_Int32(env, jOutOff, iOff);
+ s3jni_phraseIter_NToJ(env, &iter, jIter);
+ }
+ return rc;
+}
+
+JniDeclFtsXA(jint,xPhraseFirstColumn)(JniArgsEnvObj,jobject jCtx, jint iPhrase,
+ jobject jIter, jobject jOutCol){
+ Fts5ExtDecl;
+ Fts5PhraseIter iter;
+ int rc, iCol = 0;
+ rc = ext->xPhraseFirstColumn(PtrGet_Fts5Context(jCtx), (int)iPhrase,
+ &iter, &iCol);
+ if( 0==rc ){
+ OutputPointer_set_Int32(env, jOutCol, iCol);
+ s3jni_phraseIter_NToJ(env, &iter, jIter);
+ }
+ return rc;
+}
+
+JniDeclFtsXA(void,xPhraseNext)(JniArgsEnvObj,jobject jCtx, jobject jIter,
+ jobject jOutCol, jobject jOutOff){
+ Fts5ExtDecl;
+ Fts5PhraseIter iter;
+ int iCol = 0, iOff = 0;
+ s3jni_phraseIter_JToN(env, jIter, &iter);
+ ext->xPhraseNext(PtrGet_Fts5Context(jCtx), &iter, &iCol, &iOff);
+ OutputPointer_set_Int32(env, jOutCol, iCol);
+ OutputPointer_set_Int32(env, jOutOff, iOff);
+ s3jni_phraseIter_NToJ(env, &iter, jIter);
+}
+
+JniDeclFtsXA(void,xPhraseNextColumn)(JniArgsEnvObj,jobject jCtx, jobject jIter,
+ jobject jOutCol){
+ Fts5ExtDecl;
+ Fts5PhraseIter iter;
+ int iCol = 0;
+ s3jni_phraseIter_JToN(env, jIter, &iter);
+ ext->xPhraseNextColumn(PtrGet_Fts5Context(jCtx), &iter, &iCol);
+ OutputPointer_set_Int32(env, jOutCol, iCol);
+ s3jni_phraseIter_NToJ(env, &iter, jIter);
+}
+
+
+JniDeclFtsXA(jint,xPhraseSize)(JniArgsEnvObj,jobject jCtx, jint iPhrase){
+ Fts5ExtDecl;
+ return (jint)ext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase);
+}
+
+/* State for use with xQueryPhrase() and xTokenize(). */
+struct s3jni_xQueryPhraseState {
+ Fts5ExtensionApi const * ext;
+ jmethodID midCallback; /* jCallback->call() method */
+ jobject jCallback; /* Fts5ExtensionApi.XQueryPhraseCallback instance */
+ jobject jFcx; /* (Fts5Context*) for xQueryPhrase()
+ callback. This is NOT the instance that is
+ passed to xQueryPhrase(), it's the one
+ created by xQueryPhrase() for use by its
+ callback. */
+ /* State for xTokenize() */
+ struct {
+ const char * zPrev;
+ int nPrev;
+ jbyteArray jba;
+ } tok;
+};
+
+static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi,
+ Fts5Context * pFcx, void *pData){
+ struct s3jni_xQueryPhraseState * const s = pData;
+ S3JniDeclLocal_env;
+
+ if( !s->jFcx ){
+ s->jFcx = new_java_Fts5Context(env, pFcx);
+ if( !s->jFcx ) return SQLITE_NOMEM;
+ }
+ int rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback,
+ SJG.fts5.jExt, s->jFcx);
+ S3JniIfThrew{
+ S3JniExceptionWarnCallbackThrew("xQueryPhrase() callback");
+ S3JniExceptionClear;
+ rc = SQLITE_ERROR;
+ }
+ return rc;
+}
+
+JniDeclFtsXA(jint,xQueryPhrase)(JniArgsEnvObj,jobject jFcx, jint iPhrase,
+ jobject jCallback){
+ Fts5ExtDecl;
+ int rc;
+ struct s3jni_xQueryPhraseState s;
+ jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL;
+
+ if( !klazz ) return SQLITE_MISUSE;
+ s.jCallback = jCallback;
+ s.jFcx = 0;
+ s.ext = ext;
+ s.midCallback = (*env)->GetMethodID(env, klazz, "call",
+ "(Lorg/sqlite/jni/fts5/Fts5ExtensionApi;"
+ "Lorg/sqlite/jni/fts5/Fts5Context;)I");
+ S3JniUnrefLocal(klazz);
+ S3JniExceptionIsFatal("Could not extract xQueryPhraseCallback.call() method.");
+ rc = ext->xQueryPhrase(PtrGet_Fts5Context(jFcx), iPhrase, &s,
+ s3jni_xQueryPhrase);
+ S3JniUnrefLocal(s.jFcx);
+ return (jint)rc;
+}
+
+
+JniDeclFtsXA(jint,xRowCount)(JniArgsEnvObj,jobject jCtx, jobject jOut64){
+ Fts5ExtDecl;
+ sqlite3_int64 nOut = 0;
+ int const rc = ext->xRowCount(PtrGet_Fts5Context(jCtx), &nOut);
+ if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut);
+ return (jint)rc;
+}
+
+JniDeclFtsXA(jlong,xRowid)(JniArgsEnvObj,jobject jCtx){
+ Fts5ExtDecl;
+ return (jlong)ext->xRowid(PtrGet_Fts5Context(jCtx));
+}
+
+JniDeclFtsXA(int,xSetAuxdata)(JniArgsEnvObj,jobject jCtx, jobject jAux){
+ Fts5ExtDecl;
+ int rc;
+ S3JniFts5AuxData * pAux;
+
+ pAux = s3jni_malloc( sizeof(*pAux));
+ if( !pAux ){
+ if( jAux ){
+ /* Emulate how xSetAuxdata() behaves when it cannot alloc
+ ** its auxdata wrapper. */
+ s3jni_call_xDestroy(jAux);
+ }
+ return SQLITE_NOMEM;
+ }
+ pAux->jObj = S3JniRefGlobal(jAux);
+ rc = ext->xSetAuxdata(PtrGet_Fts5Context(jCtx), pAux,
+ S3JniFts5AuxData_xDestroy);
+ return rc;
+}
+
+/* xToken() impl for xTokenize(). */
+static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z,
+ int nZ, int iStart, int iEnd){
+ int rc;
+ S3JniDeclLocal_env;
+ struct s3jni_xQueryPhraseState * const s = p;
+ jbyteArray jba;
+
+ S3JniUnrefLocal(s->tok.jba);
+ s->tok.zPrev = z;
+ s->tok.nPrev = nZ;
+ s->tok.jba = s3jni_new_jbyteArray(z, nZ);
+ if( !s->tok.jba ) return SQLITE_NOMEM;
+ jba = s->tok.jba;
+ rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback,
+ (jint)tFlags, jba, (jint)iStart,
+ (jint)iEnd);
+ S3JniIfThrew {
+ S3JniExceptionWarnCallbackThrew("xTokenize() callback");
+ rc = SQLITE_ERROR;
+ }
+ return rc;
+}
+
+/*
+** Proxy for Fts5ExtensionApi.xTokenize() and
+** fts5_tokenizer.xTokenize()
+*/
+static jint s3jni_fts5_xTokenize(JniArgsEnvObj, S3JniNphOp const *pRef,
+ jint tokFlags, jobject jFcx,
+ jbyteArray jbaText, jobject jCallback){
+ Fts5ExtDecl;
+ struct s3jni_xQueryPhraseState s;
+ int rc = 0;
+ jbyte * const pText = jCallback ? s3jni_jbyteArray_bytes(jbaText) : 0;
+ jsize nText = pText ? (*env)->GetArrayLength(env, jbaText) : 0;
+ jclass const klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL;
+
+ if( !klazz ) return SQLITE_MISUSE;
+ memset(&s, 0, sizeof(s));
+ s.jCallback = jCallback;
+ s.jFcx = jFcx;
+ s.ext = ext;
+ s.midCallback = (*env)->GetMethodID(env, klazz, "call", "(I[BII)I");
+ S3JniUnrefLocal(klazz);
+ S3JniIfThrew {
+ S3JniExceptionReport;
+ S3JniExceptionClear;
+ s3jni_jbyteArray_release(jbaText, pText);
+ return SQLITE_ERROR;
+ }
+ s.tok.jba = S3JniRefLocal(jbaText);
+ s.tok.zPrev = (const char *)pText;
+ s.tok.nPrev = (int)nText;
+ if( pRef == S3JniNph(Fts5ExtensionApi) ){
+ rc = ext->xTokenize(PtrGet_Fts5Context(jFcx),
+ (const char *)pText, (int)nText,
+ &s, s3jni_xTokenize_xToken);
+ }else if( pRef == S3JniNph(fts5_tokenizer) ){
+ fts5_tokenizer * const pTok = PtrGet_fts5_tokenizer(jSelf);
+ rc = pTok->xTokenize(PtrGet_Fts5Tokenizer(jFcx), &s, tokFlags,
+ (const char *)pText, (int)nText,
+ s3jni_xTokenize_xToken);
+ }else{
+ (*env)->FatalError(env, "This cannot happen. Maintenance required.");
+ }
+ if( s.tok.jba ){
+ assert( s.tok.zPrev );
+ S3JniUnrefLocal(s.tok.jba);
+ }
+ s3jni_jbyteArray_release(jbaText, pText);
+ return (jint)rc;
+}
+
+JniDeclFtsXA(jint,xTokenize)(JniArgsEnvObj,jobject jFcx, jbyteArray jbaText,
+ jobject jCallback){
+ return s3jni_fts5_xTokenize(env, jSelf, S3JniNph(Fts5ExtensionApi),
+ 0, jFcx, jbaText, jCallback);
+}
+
+JniDeclFtsTok(jint,xTokenize)(JniArgsEnvObj,jobject jFcx, jint tokFlags,
+ jbyteArray jbaText, jobject jCallback){
+ return s3jni_fts5_xTokenize(env, jSelf, S3JniNph(Fts5Tokenizer),
+ tokFlags, jFcx, jbaText, jCallback);
+}
+
+
+JniDeclFtsXA(jobject,xUserData)(JniArgsEnvObj,jobject jFcx){
+ Fts5ExtDecl;
+ Fts5JniAux * const pAux = ext->xUserData(PtrGet_Fts5Context(jFcx));
+ return pAux ? pAux->jUserData : 0;
+}
+
+#endif /* SQLITE_ENABLE_FTS5 */
+
+////////////////////////////////////////////////////////////////////////
+// End of the main API bindings. Start of SQLTester bits...
+////////////////////////////////////////////////////////////////////////
+
+#ifdef SQLITE_JNI_ENABLE_SQLTester
+typedef struct SQLTesterJni SQLTesterJni;
+struct SQLTesterJni {
+ sqlite3_int64 nDup;
+};
+static SQLTesterJni SQLTester = {
+ 0
+};
+
+static void SQLTester_dup_destructor(void*pToFree){
+ u64 *p = (u64*)pToFree;
+ assert( p!=0 );
+ p--;
+ assert( p[0]==0x2bbf4b7c );
+ p[0] = 0;
+ p[1] = 0;
+ sqlite3_free(p);
+}
+
+/*
+** Implementation of
+**
+** dup(TEXT)
+**
+** This SQL function simply makes a copy of its text argument. But it
+** returns the result using a custom destructor, in order to provide
+** tests for the use of Mem.xDel() in the SQLite VDBE.
+*/
+static void SQLTester_dup_func(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ u64 *pOut;
+ char *z;
+ int n = sqlite3_value_bytes(argv[0]);
+ SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context);
+ S3JniDeclLocal_env;
+
+ ++p->nDup;
+ if( n>0 && (pOut = s3jni_malloc( (n+16)&~7 ))!=0 ){
+ pOut[0] = 0x2bbf4b7c;
+ z = (char*)&pOut[1];
+ memcpy(z, sqlite3_value_text(argv[0]), n);
+ z[n] = 0;
+ sqlite3_result_text(context, z, n, SQLTester_dup_destructor);
+ }
+ return;
+}
+
+/*
+** Return the number of calls to the dup() SQL function since the
+** SQLTester context was opened or since the last dup_count() call.
+*/
+static void SQLTester_dup_count_func(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context);
+ sqlite3_result_int64(context, p->nDup);
+ p->nDup = 0;
+}
+
+/*
+** Return non-zero if string z matches glob pattern zGlob and zero if the
+** pattern does not match.
+**
+** To repeat:
+**
+** zero == no match
+** non-zero == match
+**
+** Globbing rules:
+**
+** '*' Matches any sequence of zero or more characters.
+**
+** '?' Matches exactly one character.
+**
+** [...] Matches one character from the enclosed list of
+** characters.
+**
+** [^...] Matches one character not in the enclosed list.
+**
+** '#' Matches any sequence of one or more digits with an
+** optional + or - sign in front, or a hexadecimal
+** literal of the form 0x...
+*/
+static int SQLTester_strnotglob(const char *zGlob, const char *z){
+ int c, c2;
+ int invert;
+ int seen;
+
+ while( (c = (*(zGlob++)))!=0 ){
+ if( c=='*' ){
+ while( (c=(*(zGlob++))) == '*' || c=='?' ){
+ if( c=='?' && (*(z++))==0 ) return 0;
+ }
+ if( c==0 ){
+ return 1;
+ }else if( c=='[' ){
+ while( *z && SQLTester_strnotglob(zGlob-1,z)==0 ){
+ z++;
+ }
+ return (*z)!=0;
+ }
+ while( (c2 = (*(z++)))!=0 ){
+ while( c2!=c ){
+ c2 = *(z++);
+ if( c2==0 ) return 0;
+ }
+ if( SQLTester_strnotglob(zGlob,z) ) return 1;
+ }
+ return 0;
+ }else if( c=='?' ){
+ if( (*(z++))==0 ) return 0;
+ }else if( c=='[' ){
+ int prior_c = 0;
+ seen = 0;
+ invert = 0;
+ c = *(z++);
+ if( c==0 ) return 0;
+ c2 = *(zGlob++);
+ if( c2=='^' ){
+ invert = 1;
+ c2 = *(zGlob++);
+ }
+ if( c2==']' ){
+ if( c==']' ) seen = 1;
+ c2 = *(zGlob++);
+ }
+ while( c2 && c2!=']' ){
+ if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
+ c2 = *(zGlob++);
+ if( c>=prior_c && c<=c2 ) seen = 1;
+ prior_c = 0;
+ }else{
+ if( c==c2 ){
+ seen = 1;
+ }
+ prior_c = c2;
+ }
+ c2 = *(zGlob++);
+ }
+ if( c2==0 || (seen ^ invert)==0 ) return 0;
+ }else if( c=='#' ){
+ if( z[0]=='0'
+ && (z[1]=='x' || z[1]=='X')
+ && sqlite3Isxdigit(z[2])
+ ){
+ z += 3;
+ while( sqlite3Isxdigit(z[0]) ){ z++; }
+ }else{
+ if( (z[0]=='-' || z[0]=='+') && sqlite3Isdigit(z[1]) ) z++;
+ if( !sqlite3Isdigit(z[0]) ) return 0;
+ z++;
+ while( sqlite3Isdigit(z[0]) ){ z++; }
+ }
+ }else{
+ if( c!=(*(z++)) ) return 0;
+ }
+ }
+ return *z==0;
+}
+
+JNIEXPORT jint JNICALL
+Java_org_sqlite_jni_capi_SQLTester_strglob(
+ JniArgsEnvClass, jbyteArray baG, jbyteArray baT
+){
+ int rc = 0;
+ jbyte * const pG = s3jni_jbyteArray_bytes(baG);
+ jbyte * const pT = pG ? s3jni_jbyteArray_bytes(baT) : 0;
+
+ s3jni_oom_fatal(pT);
+ /* Note that we're relying on the byte arrays having been
+ NUL-terminated on the Java side. */
+ rc = !SQLTester_strnotglob((const char *)pG, (const char *)pT);
+ s3jni_jbyteArray_release(baG, pG);
+ s3jni_jbyteArray_release(baT, pT);
+ return rc;
+}
+
+
+static int SQLTester_auto_extension(sqlite3 *pDb, const char **pzErr,
+ const struct sqlite3_api_routines *ignored){
+ sqlite3_create_function(pDb, "dup", 1, SQLITE_UTF8, &SQLTester,
+ SQLTester_dup_func, 0, 0);
+ sqlite3_create_function(pDb, "dup_count", 0, SQLITE_UTF8, &SQLTester,
+ SQLTester_dup_count_func, 0, 0);
+ return 0;
+}
+
+JNIEXPORT void JNICALL
+Java_org_sqlite_jni_capi_SQLTester_installCustomExtensions(JniArgsEnvClass){
+ sqlite3_auto_extension( (void(*)(void))SQLTester_auto_extension );
+}
+
+#endif /* SQLITE_JNI_ENABLE_SQLTester */
+////////////////////////////////////////////////////////////////////////
+// End of SQLTester bindings. Start of lower-level bits.
+////////////////////////////////////////////////////////////////////////
+
+/*
+** Called during static init of the CApi class to set up global
+** state.
+*/
+JNIEXPORT void JNICALL
+Java_org_sqlite_jni_capi_CApi_init(JniArgsEnvClass){
+ jclass klazz;
+
+ memset(&S3JniGlobal, 0, sizeof(S3JniGlobal));
+ if( (*env)->GetJavaVM(env, &SJG.jvm) ){
+ (*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible.");
+ return;
+ }
+
+ /* Grab references to various global classes and objects... */
+ SJG.g.cLong = S3JniRefGlobal((*env)->FindClass(env,"java/lang/Long"));
+ S3JniExceptionIsFatal("Error getting reference to Long class.");
+ SJG.g.ctorLong1 = (*env)->GetMethodID(env, SJG.g.cLong,
+ "", "(J)V");
+ S3JniExceptionIsFatal("Error getting reference to Long constructor.");
+
+ SJG.g.cString = S3JniRefGlobal((*env)->FindClass(env,"java/lang/String"));
+ S3JniExceptionIsFatal("Error getting reference to String class.");
+ SJG.g.ctorStringBA =
+ (*env)->GetMethodID(env, SJG.g.cString,
+ "", "([BLjava/nio/charset/Charset;)V");
+ S3JniExceptionIsFatal("Error getting reference to String(byte[],Charset) ctor.");
+ SJG.g.stringGetBytes =
+ (*env)->GetMethodID(env, SJG.g.cString,
+ "getBytes", "(Ljava/nio/charset/Charset;)[B");
+ S3JniExceptionIsFatal("Error getting reference to String.getBytes(Charset).");
+
+ { /* java.nio.charset.StandardCharsets.UTF_8 */
+ jfieldID fUtf8;
+ klazz = (*env)->FindClass(env,"java/nio/charset/StandardCharsets");
+ S3JniExceptionIsFatal("Error getting reference to StandardCharsets class.");
+ fUtf8 = (*env)->GetStaticFieldID(env, klazz, "UTF_8",
+ "Ljava/nio/charset/Charset;");
+ S3JniExceptionIsFatal("Error getting StandardCharsets.UTF_8 field.");
+ SJG.g.oCharsetUtf8 =
+ S3JniRefGlobal((*env)->GetStaticObjectField(env, klazz, fUtf8));
+ S3JniExceptionIsFatal("Error getting reference to StandardCharsets.UTF_8.");
+ S3JniUnrefLocal(klazz);
+ }
+
+#ifdef SQLITE_ENABLE_FTS5
+ klazz = (*env)->FindClass(env, "org/sqlite/jni/fts5/Fts5PhraseIter");
+ S3JniExceptionIsFatal("Error getting reference to org.sqlite.jni.fts5.Fts5PhraseIter.");
+ SJG.fts5.jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J");
+ S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.a field.");
+ SJG.fts5.jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "b", "J");
+ S3JniExceptionIsFatal("Cannot get Fts5PhraseIter.b field.");
+ S3JniUnrefLocal(klazz);
+#endif
+
+ SJG.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.mutex );
+ SJG.hook.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.hook.mutex );
+ SJG.nph.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.nph.mutex );
+ SJG.envCache.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.envCache.mutex );
+ SJG.perDb.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.perDb.mutex );
+ SJG.autoExt.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.autoExt.mutex );
+
+#if S3JNI_METRICS_MUTEX
+ SJG.metrics.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ s3jni_oom_fatal( SJG.metrics.mutex );
+#endif
+
+ sqlite3_shutdown()
+ /* So that it becomes legal for Java-level code to call
+ ** sqlite3_config(). */;
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/c/sqlite3-jni.h sqlite3-3.44.0-0/ext/jni/src/c/sqlite3-jni.h
--- sqlite3-3.42.0/ext/jni/src/c/sqlite3-jni.h 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/c/sqlite3-jni.h 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,2379 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_sqlite_jni_capi_CApi */
+
+#ifndef _Included_org_sqlite_jni_capi_CApi
+#define _Included_org_sqlite_jni_capi_CApi
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef org_sqlite_jni_capi_CApi_SQLITE_ACCESS_EXISTS
+#define org_sqlite_jni_capi_CApi_SQLITE_ACCESS_EXISTS 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READWRITE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_ACCESS_READ 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DENY
+#define org_sqlite_jni_capi_CApi_SQLITE_DENY 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IGNORE
+#define org_sqlite_jni_capi_CApi_SQLITE_IGNORE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_INDEX 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TABLE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_INDEX 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TABLE 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_TRIGGER 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TEMP_VIEW 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_TRIGGER 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_VIEW 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DELETE
+#define org_sqlite_jni_capi_CApi_SQLITE_DELETE 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_INDEX 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TABLE 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_INDEX 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TABLE 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_TRIGGER 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TEMP_VIEW 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_TRIGGER 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_VIEW 17L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INSERT
+#define org_sqlite_jni_capi_CApi_SQLITE_INSERT 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PRAGMA
+#define org_sqlite_jni_capi_CApi_SQLITE_PRAGMA 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_READ 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SELECT
+#define org_sqlite_jni_capi_CApi_SQLITE_SELECT 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRANSACTION
+#define org_sqlite_jni_capi_CApi_SQLITE_TRANSACTION 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UPDATE
+#define org_sqlite_jni_capi_CApi_SQLITE_UPDATE 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ATTACH
+#define org_sqlite_jni_capi_CApi_SQLITE_ATTACH 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DETACH
+#define org_sqlite_jni_capi_CApi_SQLITE_DETACH 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ALTER_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_ALTER_TABLE 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_REINDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_REINDEX 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ANALYZE
+#define org_sqlite_jni_capi_CApi_SQLITE_ANALYZE 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CREATE_VTABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_CREATE_VTABLE 29L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DROP_VTABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DROP_VTABLE 30L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FUNCTION
+#define org_sqlite_jni_capi_CApi_SQLITE_FUNCTION 31L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SAVEPOINT
+#define org_sqlite_jni_capi_CApi_SQLITE_SAVEPOINT 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_RECURSIVE
+#define org_sqlite_jni_capi_CApi_SQLITE_RECURSIVE 33L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATIC
+#define org_sqlite_jni_capi_CApi_SQLITE_STATIC 0LL
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRANSIENT
+#define org_sqlite_jni_capi_CApi_SQLITE_TRANSIENT -1LL
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETSTART_INVERT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETSTART_INVERT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_NOSAVEPOINT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_NOSAVEPOINT 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_INVERT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_INVERT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_IGNORENOOP
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESETAPPLY_IGNORENOOP 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_DATA
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_DATA 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_NOTFOUND
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_NOTFOUND 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONFLICT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONFLICT 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONSTRAINT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_CONSTRAINT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_FOREIGN_KEY
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_FOREIGN_KEY 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_OMIT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_OMIT 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_REPLACE
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_REPLACE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_ABORT
+#define org_sqlite_jni_capi_CApi_SQLITE_CHANGESET_ABORT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SINGLETHREAD
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SINGLETHREAD 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MULTITHREAD
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MULTITHREAD 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SERIALIZED
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SERIALIZED 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MALLOC
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MALLOC 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMALLOC
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMALLOC 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SCRATCH
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SCRATCH 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PAGECACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PAGECACHE 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_HEAP
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_HEAP 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMSTATUS
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMSTATUS 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MUTEX 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETMUTEX 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOOKASIDE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOOKASIDE 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOG
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_LOG 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_URI
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_URI 17L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE2
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE2 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE2
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_GETPCACHE2 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_COVERING_INDEX_SCAN
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_COVERING_INDEX_SCAN 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SQLLOG
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SQLLOG 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MMAP_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MMAP_SIZE 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_WIN32_HEAPSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_WIN32_HEAPSIZE 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE_HDRSZ
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PCACHE_HDRSZ 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PMASZ
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_PMASZ 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_STMTJRNL_SPILL
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_STMTJRNL_SPILL 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SMALL_MALLOC
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SMALL_MALLOC 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SORTERREF_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_SORTERREF_SIZE 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMDB_MAXSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONFIG_MEMDB_MAXSIZE 29L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INTEGER
+#define org_sqlite_jni_capi_CApi_SQLITE_INTEGER 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FLOAT
+#define org_sqlite_jni_capi_CApi_SQLITE_FLOAT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TEXT
+#define org_sqlite_jni_capi_CApi_SQLITE_TEXT 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BLOB
+#define org_sqlite_jni_capi_CApi_SQLITE_BLOB 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NULL
+#define org_sqlite_jni_capi_CApi_SQLITE_NULL 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAINDBNAME
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAINDBNAME 1000L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LOOKASIDE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LOOKASIDE 1001L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FKEY
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FKEY 1002L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_TRIGGER 1003L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_QPSG
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_QPSG 1007L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRIGGER_EQP
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRIGGER_EQP 1008L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_RESET_DATABASE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_RESET_DATABASE 1009L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DEFENSIVE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DEFENSIVE 1010L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_WRITABLE_SCHEMA
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DML
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DML 1013L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DDL
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_DQS_DDL 1014L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_VIEW
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_ENABLE_VIEW 1015L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRUSTED_SCHEMA
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_STMT_SCANSTATUS
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_STMT_SCANSTATUS 1018L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_REVERSE_SCANORDER
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_REVERSE_SCANORDER 1019L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAX
+#define org_sqlite_jni_capi_CApi_SQLITE_DBCONFIG_MAX 1019L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_USED 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_SCHEMA_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_SCHEMA_USED 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_STMT_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_STMT_USED 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_HIT
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_HIT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_HIT
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_HIT 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_MISS
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_MISS 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_WRITE 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_DEFERRED_FKS
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_DEFERRED_FKS 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED_SHARED
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_USED_SHARED 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_SPILL
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_CACHE_SPILL 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_MAX
+#define org_sqlite_jni_capi_CApi_SQLITE_DBSTATUS_MAX 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF8
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF8 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16LE
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16LE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16BE
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16BE 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_UTF16_ALIGNED
+#define org_sqlite_jni_capi_CApi_SQLITE_UTF16_ALIGNED 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCKSTATE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCKSTATE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_GET_LOCKPROXYFILE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_GET_LOCKPROXYFILE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SET_LOCKPROXYFILE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SET_LOCKPROXYFILE 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LAST_ERRNO
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LAST_ERRNO 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_HINT
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_HINT 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CHUNK_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CHUNK_SIZE 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_FILE_POINTER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_FILE_POINTER 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC_OMITTED
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC_OMITTED 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_AV_RETRY
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_AV_RETRY 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PERSIST_WAL
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PERSIST_WAL 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_OVERWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_OVERWRITE 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFSNAME
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFSNAME 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_POWERSAFE_OVERWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_POWERSAFE_OVERWRITE 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PRAGMA
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PRAGMA 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BUSYHANDLER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BUSYHANDLER 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TEMPFILENAME
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TEMPFILENAME 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_MMAP_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_MMAP_SIZE 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TRACE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_TRACE 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_HAS_MOVED
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_HAS_MOVED 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SYNC 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_PHASETWO
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_PHASETWO 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_SET_HANDLE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_SET_HANDLE 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WAL_BLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WAL_BLOCK 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ZIPVFS
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ZIPVFS 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RBU
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RBU 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFS_POINTER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_VFS_POINTER 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_JOURNAL_POINTER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_JOURNAL_POINTER 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_GET_HANDLE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_WIN32_GET_HANDLE 29L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PDB
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_PDB 30L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCK_TIMEOUT
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_LOCK_TIMEOUT 34L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_DATA_VERSION
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_DATA_VERSION 35L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_LIMIT
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_SIZE_LIMIT 36L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_DONE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_DONE 37L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESERVE_BYTES
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESERVE_BYTES 38L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_START
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKPT_START 39L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_EXTERNAL_READER
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_EXTERNAL_READER 40L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKSM_FILE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_CKSM_FILE 41L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESET_CACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_FCNTL_RESET_CACHE 42L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_NONE
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_NONE 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_SHARED
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_SHARED 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_RESERVED
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_RESERVED 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_PENDING
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_PENDING 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCK_EXCLUSIVE
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCK_EXCLUSIVE 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC512
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC512 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC1K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC1K 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC2K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC2K 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC4K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC4K 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC8K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC8K 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC16K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC16K 64L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC32K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC32K 128L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC64K
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_ATOMIC64K 256L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SAFE_APPEND
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SAFE_APPEND 512L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SEQUENTIAL
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_SEQUENTIAL 1024L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 2048L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_POWERSAFE_OVERWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_POWERSAFE_OVERWRITE 4096L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_IMMUTABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_IMMUTABLE 8192L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOCAP_BATCH_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOCAP_BATCH_ATOMIC 16384L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LENGTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LENGTH 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_SQL_LENGTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_SQL_LENGTH 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COLUMN
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COLUMN 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_EXPR_DEPTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_EXPR_DEPTH 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COMPOUND_SELECT
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_COMPOUND_SELECT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VDBE_OP
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VDBE_OP 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_FUNCTION_ARG
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_FUNCTION_ARG 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_ATTACHED
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_ATTACHED 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LIKE_PATTERN_LENGTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VARIABLE_NUMBER
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_VARIABLE_NUMBER 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_TRIGGER_DEPTH
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_TRIGGER_DEPTH 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LIMIT_WORKER_THREADS
+#define org_sqlite_jni_capi_CApi_SQLITE_LIMIT_WORKER_THREADS 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_READONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_READONLY 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_READWRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_READWRITE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_CREATE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_CREATE 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_URI
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_URI 64L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_MEMORY
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_MEMORY 128L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOMUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOMUTEX 32768L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_FULLMUTEX
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_FULLMUTEX 65536L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_SHAREDCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_SHAREDCACHE 131072L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_PRIVATECACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_PRIVATECACHE 262144L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOFOLLOW
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_NOFOLLOW 16777216L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OPEN_EXRESCODE
+#define org_sqlite_jni_capi_CApi_SQLITE_OPEN_EXRESCODE 33554432L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT
+#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_PERSISTENT 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NORMALIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NORMALIZE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB
+#define org_sqlite_jni_capi_CApi_SQLITE_PREPARE_NO_VTAB 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OK
+#define org_sqlite_jni_capi_CApi_SQLITE_OK 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INTERNAL
+#define org_sqlite_jni_capi_CApi_SQLITE_INTERNAL 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PERM
+#define org_sqlite_jni_capi_CApi_SQLITE_PERM 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ABORT
+#define org_sqlite_jni_capi_CApi_SQLITE_ABORT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCKED
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCKED 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOMEM
+#define org_sqlite_jni_capi_CApi_SQLITE_NOMEM 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INTERRUPT
+#define org_sqlite_jni_capi_CApi_SQLITE_INTERRUPT 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR 10L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT 11L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTFOUND
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTFOUND 12L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FULL
+#define org_sqlite_jni_capi_CApi_SQLITE_FULL 13L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN 14L
+#undef org_sqlite_jni_capi_CApi_SQLITE_PROTOCOL
+#define org_sqlite_jni_capi_CApi_SQLITE_PROTOCOL 15L
+#undef org_sqlite_jni_capi_CApi_SQLITE_EMPTY
+#define org_sqlite_jni_capi_CApi_SQLITE_EMPTY 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SCHEMA
+#define org_sqlite_jni_capi_CApi_SQLITE_SCHEMA 17L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TOOBIG
+#define org_sqlite_jni_capi_CApi_SQLITE_TOOBIG 18L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT 19L
+#undef org_sqlite_jni_capi_CApi_SQLITE_MISMATCH
+#define org_sqlite_jni_capi_CApi_SQLITE_MISMATCH 20L
+#undef org_sqlite_jni_capi_CApi_SQLITE_MISUSE
+#define org_sqlite_jni_capi_CApi_SQLITE_MISUSE 21L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOLFS
+#define org_sqlite_jni_capi_CApi_SQLITE_NOLFS 22L
+#undef org_sqlite_jni_capi_CApi_SQLITE_AUTH
+#define org_sqlite_jni_capi_CApi_SQLITE_AUTH 23L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FORMAT
+#define org_sqlite_jni_capi_CApi_SQLITE_FORMAT 24L
+#undef org_sqlite_jni_capi_CApi_SQLITE_RANGE
+#define org_sqlite_jni_capi_CApi_SQLITE_RANGE 25L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTADB
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTADB 26L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTICE
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTICE 27L
+#undef org_sqlite_jni_capi_CApi_SQLITE_WARNING
+#define org_sqlite_jni_capi_CApi_SQLITE_WARNING 28L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ROW
+#define org_sqlite_jni_capi_CApi_SQLITE_ROW 100L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DONE
+#define org_sqlite_jni_capi_CApi_SQLITE_DONE 101L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR_MISSING_COLLSEQ
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR_MISSING_COLLSEQ 257L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR_RETRY
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR_RETRY 513L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ERROR_SNAPSHOT
+#define org_sqlite_jni_capi_CApi_SQLITE_ERROR_SNAPSHOT 769L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_READ 266L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHORT_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHORT_READ 522L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_WRITE 778L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSYNC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSYNC 1034L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_FSYNC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_FSYNC 1290L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_TRUNCATE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_TRUNCATE 1546L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSTAT
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_FSTAT 1802L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_UNLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_UNLOCK 2058L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_RDLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_RDLOCK 2314L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE 2570L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_BLOCKED
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_BLOCKED 2826L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_NOMEM
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_NOMEM 3082L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_ACCESS
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_ACCESS 3338L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CHECKRESERVEDLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CHECKRESERVEDLOCK 3594L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_LOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_LOCK 3850L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CLOSE 4106L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DIR_CLOSE 4362L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMOPEN
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMOPEN 4618L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMSIZE 4874L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMLOCK 5130L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMMAP
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SHMMAP 5386L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_SEEK
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_SEEK 5642L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE_NOENT
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DELETE_NOENT 5898L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_MMAP
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_MMAP 6154L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_GETTEMPPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_GETTEMPPATH 6410L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CONVPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CONVPATH 6666L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_VNODE
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_VNODE 6922L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_AUTH
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_AUTH 7178L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_BEGIN_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_BEGIN_ATOMIC 7434L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_COMMIT_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_COMMIT_ATOMIC 7690L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_ROLLBACK_ATOMIC
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_ROLLBACK_ATOMIC 7946L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_DATA
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_DATA 8202L
+#undef org_sqlite_jni_capi_CApi_SQLITE_IOERR_CORRUPTFS
+#define org_sqlite_jni_capi_CApi_SQLITE_IOERR_CORRUPTFS 8458L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCKED_SHAREDCACHE
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCKED_SHAREDCACHE 262L
+#undef org_sqlite_jni_capi_CApi_SQLITE_LOCKED_VTAB
+#define org_sqlite_jni_capi_CApi_SQLITE_LOCKED_VTAB 518L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY_RECOVERY
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY_RECOVERY 261L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY_SNAPSHOT
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY_SNAPSHOT 517L
+#undef org_sqlite_jni_capi_CApi_SQLITE_BUSY_TIMEOUT
+#define org_sqlite_jni_capi_CApi_SQLITE_BUSY_TIMEOUT 773L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_NOTEMPDIR
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_NOTEMPDIR 270L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_ISDIR
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_ISDIR 526L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_FULLPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_FULLPATH 782L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_CONVPATH
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_CONVPATH 1038L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_SYMLINK
+#define org_sqlite_jni_capi_CApi_SQLITE_CANTOPEN_SYMLINK 1550L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_VTAB
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_VTAB 267L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_SEQUENCE
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_SEQUENCE 523L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_INDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_CORRUPT_INDEX 779L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_RECOVERY
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_RECOVERY 264L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTLOCK
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTLOCK 520L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_ROLLBACK 776L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_DBMOVED
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_DBMOVED 1032L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTINIT
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_CANTINIT 1288L
+#undef org_sqlite_jni_capi_CApi_SQLITE_READONLY_DIRECTORY
+#define org_sqlite_jni_capi_CApi_SQLITE_READONLY_DIRECTORY 1544L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ABORT_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_ABORT_ROLLBACK 516L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_CHECK
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_CHECK 275L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_COMMITHOOK
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_COMMITHOOK 531L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FOREIGNKEY
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FOREIGNKEY 787L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FUNCTION
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_FUNCTION 1043L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_NOTNULL
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_NOTNULL 1299L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PRIMARYKEY
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PRIMARYKEY 1555L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_TRIGGER
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_TRIGGER 1811L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_UNIQUE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_UNIQUE 2067L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_VTAB
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_VTAB 2323L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_ROWID
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_ROWID 2579L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PINNED
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_PINNED 2835L
+#undef org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_DATATYPE
+#define org_sqlite_jni_capi_CApi_SQLITE_CONSTRAINT_DATATYPE 3091L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_WAL
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_WAL 283L
+#undef org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_NOTICE_RECOVER_ROLLBACK 539L
+#undef org_sqlite_jni_capi_CApi_SQLITE_WARNING_AUTOINDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_WARNING_AUTOINDEX 284L
+#undef org_sqlite_jni_capi_CApi_SQLITE_AUTH_USER
+#define org_sqlite_jni_capi_CApi_SQLITE_AUTH_USER 279L
+#undef org_sqlite_jni_capi_CApi_SQLITE_OK_LOAD_PERMANENTLY
+#define org_sqlite_jni_capi_CApi_SQLITE_OK_LOAD_PERMANENTLY 256L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SERIALIZE_NOCOPY
+#define org_sqlite_jni_capi_CApi_SQLITE_SERIALIZE_NOCOPY 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_FREEONCLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_FREEONCLOSE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_READONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_READONLY 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_RESIZEABLE
+#define org_sqlite_jni_capi_CApi_SQLITE_DESERIALIZE_RESIZEABLE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SESSION_CONFIG_STRMSIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_SESSION_CONFIG_STRMSIZE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SESSION_OBJCONFIG_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_SESSION_OBJCONFIG_SIZE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_MEMORY_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_MEMORY_USED 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_USED
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_USED 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_OVERFLOW
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_OVERFLOW 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_SIZE 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PARSER_STACK
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PARSER_STACK 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_SIZE
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_PAGECACHE_SIZE 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_COUNT
+#define org_sqlite_jni_capi_CApi_SQLITE_STATUS_MALLOC_COUNT 9L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FULLSCAN_STEP
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FULLSCAN_STEP 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_SORT
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_SORT 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_AUTOINDEX
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_AUTOINDEX 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_VM_STEP
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_VM_STEP 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_REPREPARE
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_REPREPARE 5L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_RUN
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_RUN 6L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_MISS
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_MISS 7L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_HIT
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_FILTER_HIT 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_MEMUSED
+#define org_sqlite_jni_capi_CApi_SQLITE_STMTSTATUS_MEMUSED 99L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SYNC_NORMAL
+#define org_sqlite_jni_capi_CApi_SQLITE_SYNC_NORMAL 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SYNC_FULL
+#define org_sqlite_jni_capi_CApi_SQLITE_SYNC_FULL 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_SYNC_DATAONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_SYNC_DATAONLY 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_STMT
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_STMT 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_PROFILE
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_PROFILE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_ROW
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_ROW 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TRACE_CLOSE
+#define org_sqlite_jni_capi_CApi_SQLITE_TRACE_CLOSE 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TXN_NONE
+#define org_sqlite_jni_capi_CApi_SQLITE_TXN_NONE 0L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TXN_READ
+#define org_sqlite_jni_capi_CApi_SQLITE_TXN_READ 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_TXN_WRITE
+#define org_sqlite_jni_capi_CApi_SQLITE_TXN_WRITE 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DETERMINISTIC
+#define org_sqlite_jni_capi_CApi_SQLITE_DETERMINISTIC 2048L
+#undef org_sqlite_jni_capi_CApi_SQLITE_DIRECTONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_DIRECTONLY 524288L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS
+#define org_sqlite_jni_capi_CApi_SQLITE_INNOCUOUS 2097152L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_SCAN_UNIQUE 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_EQ
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_EQ 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GT 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LE 8L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LT 16L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GE 32L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_MATCH
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_MATCH 64L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIKE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIKE 65L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GLOB
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_GLOB 66L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_REGEXP
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_REGEXP 67L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_NE
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_NE 68L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOT 69L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOTNULL
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNULL
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_ISNULL 71L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_IS
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_IS 72L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIMIT
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_LIMIT 73L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_OFFSET
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_OFFSET 74L
+#undef org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_FUNCTION
+#define org_sqlite_jni_capi_CApi_SQLITE_INDEX_CONSTRAINT_FUNCTION 150L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_CONSTRAINT_SUPPORT
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_CONSTRAINT_SUPPORT 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_INNOCUOUS
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_INNOCUOUS 2L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_DIRECTONLY
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_DIRECTONLY 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_VTAB_USES_ALL_SCHEMAS
+#define org_sqlite_jni_capi_CApi_SQLITE_VTAB_USES_ALL_SCHEMAS 4L
+#undef org_sqlite_jni_capi_CApi_SQLITE_ROLLBACK
+#define org_sqlite_jni_capi_CApi_SQLITE_ROLLBACK 1L
+#undef org_sqlite_jni_capi_CApi_SQLITE_FAIL
+#define org_sqlite_jni_capi_CApi_SQLITE_FAIL 3L
+#undef org_sqlite_jni_capi_CApi_SQLITE_REPLACE
+#define org_sqlite_jni_capi_CApi_SQLITE_REPLACE 5L
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: init
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_init
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_java_uncache_thread
+ * Signature: ()Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1java_1uncache_1thread
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_aggregate_context
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Z)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1aggregate_1context
+ (JNIEnv *, jclass, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_auto_extension
+ * Signature: (Lorg/sqlite/jni/capi/AutoExtensionCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1auto_1extension
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_backup_finish
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1finish
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_backup_init
+ * Signature: (JLjava/lang/String;JLjava/lang/String;)Lorg/sqlite/jni/capi/sqlite3_backup;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1init
+ (JNIEnv *, jclass, jlong, jstring, jlong, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_backup_pagecount
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1pagecount
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_backup_remaining
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1remaining
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_backup_step
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1backup_1step
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_blob
+ * Signature: (JI[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1blob
+ (JNIEnv *, jclass, jlong, jint, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_double
+ * Signature: (JID)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1double
+ (JNIEnv *, jclass, jlong, jint, jdouble);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_int
+ * Signature: (JII)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1int
+ (JNIEnv *, jclass, jlong, jint, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_int64
+ * Signature: (JIJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1int64
+ (JNIEnv *, jclass, jlong, jint, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_java_object
+ * Signature: (JILjava/lang/Object;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1java_1object
+ (JNIEnv *, jclass, jlong, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_null
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1null
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_parameter_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1parameter_1count
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_parameter_index
+ * Signature: (J[B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1parameter_1index
+ (JNIEnv *, jclass, jlong, jbyteArray);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_parameter_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1parameter_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_text
+ * Signature: (JI[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1text
+ (JNIEnv *, jclass, jlong, jint, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_text16
+ * Signature: (JI[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1text16
+ (JNIEnv *, jclass, jlong, jint, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_value
+ * Signature: (JIJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1value
+ (JNIEnv *, jclass, jlong, jint, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_zeroblob
+ * Signature: (JII)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1zeroblob
+ (JNIEnv *, jclass, jlong, jint, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_bind_zeroblob64
+ * Signature: (JIJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1zeroblob64
+ (JNIEnv *, jclass, jlong, jint, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_bytes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1bytes
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_close
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1close
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_open
+ * Signature: (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;JILorg/sqlite/jni/capi/OutputPointer/sqlite3_blob;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1open
+ (JNIEnv *, jclass, jlong, jstring, jstring, jstring, jlong, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_read
+ * Signature: (J[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1read
+ (JNIEnv *, jclass, jlong, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_reopen
+ * Signature: (JJ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1reopen
+ (JNIEnv *, jclass, jlong, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_blob_write
+ * Signature: (J[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1write
+ (JNIEnv *, jclass, jlong, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_busy_handler
+ * Signature: (JLorg/sqlite/jni/capi/BusyHandlerCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1busy_1handler
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_busy_timeout
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1busy_1timeout
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_cancel_auto_extension
+ * Signature: (Lorg/sqlite/jni/capi/AutoExtensionCallback;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1cancel_1auto_1extension
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_changes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1changes
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_changes64
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1changes64
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_clear_bindings
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1clear_1bindings
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_close
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1close
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_close_v2
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1close_1v2
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_blob
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1blob
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_bytes
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1bytes
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_bytes16
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1bytes16
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1count
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_decltype
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1decltype
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_double
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)D
+ */
+JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1double
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_int
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1int
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_int64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1int64
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_database_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1database_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_origin_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1origin_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_table_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1table_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_text
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1text
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_text16
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1text16
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_type
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1type
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_value
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Lorg/sqlite/jni/capi/sqlite3_value;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1value
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_collation_needed
+ * Signature: (JLorg/sqlite/jni/capi/CollationNeededCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1collation_1needed
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_commit_hook
+ * Signature: (JLorg/sqlite/jni/capi/CommitHookCallback;)Lorg/sqlite/jni/capi/CommitHookCallback;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1commit_1hook
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_compileoption_get
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1compileoption_1get
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_compileoption_used
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1compileoption_1used
+ (JNIEnv *, jclass, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_complete
+ * Signature: ([B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1complete
+ (JNIEnv *, jclass, jbyteArray);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_config
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__I
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_config
+ * Signature: (Lorg/sqlite/jni/capi/ConfigSqllogCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__Lorg_sqlite_jni_capi_ConfigSqllogCallback_2
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_config
+ * Signature: (Lorg/sqlite/jni/capi/ConfigLogCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__Lorg_sqlite_jni_capi_ConfigLogCallback_2
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_context_db_handle
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)Lorg/sqlite/jni/capi/sqlite3;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1context_1db_1handle
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_create_collation
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;ILorg/sqlite/jni/capi/CollationCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1create_1collation
+ (JNIEnv *, jclass, jobject, jstring, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_create_function
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;IILorg/sqlite/jni/capi/SQLFunction;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1create_1function
+ (JNIEnv *, jclass, jobject, jstring, jint, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_data_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1data_1count
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_config
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;IILorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1config__Lorg_sqlite_jni_capi_sqlite3_2IILorg_sqlite_jni_capi_OutputPointer_Int32_2
+ (JNIEnv *, jclass, jobject, jint, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_config
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1config__Lorg_sqlite_jni_capi_sqlite3_2ILjava_lang_String_2
+ (JNIEnv *, jclass, jobject, jint, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_name
+ * Signature: (JI)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1name
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_filename
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1filename
+ (JNIEnv *, jclass, jobject, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_handle
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Lorg/sqlite/jni/capi/sqlite3;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1handle
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_readonly
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1readonly
+ (JNIEnv *, jclass, jobject, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_release_memory
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1release_1memory
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_db_status
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;Z)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1db_1status
+ (JNIEnv *, jclass, jobject, jint, jobject, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_errcode
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1errcode
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_errmsg
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1errmsg
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_error_offset
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1error_1offset
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_errstr
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1errstr
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_expanded_sql
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1expanded_1sql
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_extended_errcode
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1errcode
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_extended_result_codes
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Z)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1extended_1result_1codes
+ (JNIEnv *, jclass, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_get_autocommit
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1get_1autocommit
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_get_auxdata
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1get_1auxdata
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_finalize
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1finalize
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_initialize
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1initialize
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_interrupt
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1interrupt
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_is_interrupted
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1is_1interrupted
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_keyword_check
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1keyword_1check
+ (JNIEnv *, jclass, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_keyword_count
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1keyword_1count
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_keyword_name
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1keyword_1name
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_last_insert_rowid
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1last_1insert_1rowid
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_libversion
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1libversion
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_libversion_number
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1libversion_1number
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_limit
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;II)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1limit
+ (JNIEnv *, jclass, jobject, jint, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_normalized_sql
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1normalized_1sql
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_open
+ * Signature: (Ljava/lang/String;Lorg/sqlite/jni/capi/OutputPointer/sqlite3;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1open
+ (JNIEnv *, jclass, jstring, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_open_v2
+ * Signature: (Ljava/lang/String;Lorg/sqlite/jni/capi/OutputPointer/sqlite3;ILjava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1open_1v2
+ (JNIEnv *, jclass, jstring, jobject, jint, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_prepare
+ * Signature: (J[BILorg/sqlite/jni/capi/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1prepare
+ (JNIEnv *, jclass, jlong, jbyteArray, jint, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_prepare_v2
+ * Signature: (J[BILorg/sqlite/jni/capi/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1prepare_1v2
+ (JNIEnv *, jclass, jlong, jbyteArray, jint, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_prepare_v3
+ * Signature: (J[BIILorg/sqlite/jni/capi/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1prepare_1v3
+ (JNIEnv *, jclass, jlong, jbyteArray, jint, jint, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_blobwrite
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1blobwrite
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_count
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1count
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_depth
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1depth
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_hook
+ * Signature: (JLorg/sqlite/jni/capi/PreupdateHookCallback;)Lorg/sqlite/jni/capi/PreupdateHookCallback;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1hook
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_new
+ * Signature: (JILorg/sqlite/jni/capi/OutputPointer/sqlite3_value;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1new
+ (JNIEnv *, jclass, jlong, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_preupdate_old
+ * Signature: (JILorg/sqlite/jni/capi/OutputPointer/sqlite3_value;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1preupdate_1old
+ (JNIEnv *, jclass, jlong, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_progress_handler
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILorg/sqlite/jni/capi/ProgressHandlerCallback;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1progress_1handler
+ (JNIEnv *, jclass, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_randomness
+ * Signature: ([B)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1randomness
+ (JNIEnv *, jclass, jbyteArray);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_release_memory
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1release_1memory
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_reset
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1reset
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_reset_auto_extension
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1reset_1auto_1extension
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_double
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;D)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1double
+ (JNIEnv *, jclass, jobject, jdouble);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_error
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BI)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error
+ (JNIEnv *, jclass, jobject, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_error_toobig
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1toobig
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_error_nomem
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1nomem
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_error_code
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1code
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_null
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1null
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_int
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1int
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_int64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;J)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1int64
+ (JNIEnv *, jclass, jobject, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_java_object
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Ljava/lang/Object;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1java_1object
+ (JNIEnv *, jclass, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_value
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Lorg/sqlite/jni/capi/sqlite3_value;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1value
+ (JNIEnv *, jclass, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_zeroblob
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1zeroblob
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_zeroblob64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1zeroblob64
+ (JNIEnv *, jclass, jobject, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_blob
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BI)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1blob
+ (JNIEnv *, jclass, jobject, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_blob64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BJ)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1blob64
+ (JNIEnv *, jclass, jobject, jbyteArray, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_text
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BI)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1text
+ (JNIEnv *, jclass, jobject, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_text64
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;[BJI)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1text64
+ (JNIEnv *, jclass, jobject, jbyteArray, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_rollback_hook
+ * Signature: (JLorg/sqlite/jni/capi/RollbackHookCallback;)Lorg/sqlite/jni/capi/RollbackHookCallback;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1rollback_1hook
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_set_authorizer
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Lorg/sqlite/jni/capi/AuthorizerCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1set_1authorizer
+ (JNIEnv *, jclass, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_set_auxdata
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;ILjava/lang/Object;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1set_1auxdata
+ (JNIEnv *, jclass, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_set_last_insert_rowid
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;J)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1set_1last_1insert_1rowid
+ (JNIEnv *, jclass, jobject, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_shutdown
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1shutdown
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_sleep
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1sleep
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_sourceid
+ * Signature: ()Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1sourceid
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_sql
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1sql
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_status
+ * Signature: (ILorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;Z)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1status
+ (JNIEnv *, jclass, jint, jobject, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_status64
+ * Signature: (ILorg/sqlite/jni/capi/OutputPointer/Int64;Lorg/sqlite/jni/capi/OutputPointer/Int64;Z)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1status64
+ (JNIEnv *, jclass, jint, jobject, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_step
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1step
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_stmt_busy
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1busy
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_stmt_explain
+ * Signature: (JI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1explain
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_stmt_isexplain
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1isexplain
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_stmt_readonly
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1readonly
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_stmt_status
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;IZ)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1stmt_1status
+ (JNIEnv *, jclass, jobject, jint, jboolean);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_strglob
+ * Signature: ([B[B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1strglob
+ (JNIEnv *, jclass, jbyteArray, jbyteArray);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_strlike
+ * Signature: ([B[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1strlike
+ (JNIEnv *, jclass, jbyteArray, jbyteArray, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_system_errno
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1system_1errno
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_table_column_metadata
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/sqlite/jni/capi/OutputPointer/String;Lorg/sqlite/jni/capi/OutputPointer/String;Lorg/sqlite/jni/capi/OutputPointer/Bool;Lorg/sqlite/jni/capi/OutputPointer/Bool;Lorg/sqlite/jni/capi/OutputPointer/Bool;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1table_1column_1metadata
+ (JNIEnv *, jclass, jobject, jstring, jstring, jstring, jobject, jobject, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_threadsafe
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1threadsafe
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_total_changes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1total_1changes
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_total_changes64
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1total_1changes64
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_trace_v2
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;ILorg/sqlite/jni/capi/TraceV2Callback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1trace_1v2
+ (JNIEnv *, jclass, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_txn_state
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1txn_1state
+ (JNIEnv *, jclass, jobject, jstring);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_update_hook
+ * Signature: (JLorg/sqlite/jni/capi/UpdateHookCallback;)Lorg/sqlite/jni/capi/UpdateHookCallback;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1update_1hook
+ (JNIEnv *, jclass, jlong, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_blob
+ * Signature: (J)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1blob
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_bytes
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1bytes
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_bytes16
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1bytes16
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_double
+ * Signature: (J)D
+ */
+JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1double
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_dup
+ * Signature: (J)Lorg/sqlite/jni/capi/sqlite3_value;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1dup
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_encoding
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1encoding
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_free
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1free
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_frombind
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1frombind
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_int
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1int
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_int64
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1int64
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_java_object
+ * Signature: (J)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1java_1object
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_nochange
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1nochange
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_numeric_type
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1numeric_1type
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_subtype
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1subtype
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_text
+ * Signature: (J)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1text
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_text16
+ * Signature: (J)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1text16
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_value_type
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1type
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_jni_internal_details
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1internal_1details
+ (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_sqlite_jni_capi_SQLTester */
+
+#ifndef _Included_org_sqlite_jni_capi_SQLTester
+#define _Included_org_sqlite_jni_capi_SQLTester
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: org_sqlite_jni_capi_SQLTester
+ * Method: strglob
+ * Signature: ([B[B)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_SQLTester_strglob
+ (JNIEnv *, jclass, jbyteArray, jbyteArray);
+
+/*
+ * Class: org_sqlite_jni_capi_SQLTester
+ * Method: installCustomExtensions
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_SQLTester_installCustomExtensions
+ (JNIEnv *, jclass);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_sqlite_jni_fts5_Fts5ExtensionApi */
+
+#ifndef _Included_org_sqlite_jni_fts5_Fts5ExtensionApi
+#define _Included_org_sqlite_jni_fts5_Fts5ExtensionApi
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: getInstance
+ * Signature: ()Lorg/sqlite/jni/fts5/Fts5ExtensionApi;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_getInstance
+ (JNIEnv *, jclass);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xColumnCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnCount
+ (JNIEnv *, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xColumnSize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnSize
+ (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xColumnText
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnText
+ (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xColumnTotalSize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/Int64;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xColumnTotalSize
+ (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xGetAuxdata
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Z)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xGetAuxdata
+ (JNIEnv *, jobject, jobject, jboolean);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xInst
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xInst
+ (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xInstCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xInstCount
+ (JNIEnv *, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseCount
+ (JNIEnv *, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseFirst
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseFirst
+ (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseFirstColumn
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseFirstColumn
+ (JNIEnv *, jobject, jobject, jint, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseNext
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;Lorg/sqlite/jni/capi/OutputPointer/Int32;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseNext
+ (JNIEnv *, jobject, jobject, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseNextColumn
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/fts5/Fts5PhraseIter;Lorg/sqlite/jni/capi/OutputPointer/Int32;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseNextColumn
+ (JNIEnv *, jobject, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xPhraseSize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;I)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xPhraseSize
+ (JNIEnv *, jobject, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xQueryPhrase
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;ILorg/sqlite/jni/fts5/Fts5ExtensionApi/XQueryPhraseCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xQueryPhrase
+ (JNIEnv *, jobject, jobject, jint, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xRowCount
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Lorg/sqlite/jni/capi/OutputPointer/Int64;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xRowCount
+ (JNIEnv *, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xRowid
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xRowid
+ (JNIEnv *, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xSetAuxdata
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;Ljava/lang/Object;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xSetAuxdata
+ (JNIEnv *, jobject, jobject, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xTokenize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;[BLorg/sqlite/jni/fts5/XTokenizeCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xTokenize
+ (JNIEnv *, jobject, jobject, jbyteArray, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_Fts5ExtensionApi
+ * Method: xUserData
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Context;)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_Fts5ExtensionApi_xUserData
+ (JNIEnv *, jobject, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_sqlite_jni_fts5_fts5_api */
+
+#ifndef _Included_org_sqlite_jni_fts5_fts5_api
+#define _Included_org_sqlite_jni_fts5_fts5_api
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef org_sqlite_jni_fts5_fts5_api_iVersion
+#define org_sqlite_jni_fts5_fts5_api_iVersion 2L
+/*
+ * Class: org_sqlite_jni_fts5_fts5_api
+ * Method: getInstanceForDb
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3;)Lorg/sqlite/jni/fts5/fts5_api;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_fts5_1api_getInstanceForDb
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_fts5_fts5_api
+ * Method: xCreateFunction
+ * Signature: (Ljava/lang/String;Ljava/lang/Object;Lorg/sqlite/jni/fts5/fts5_extension_function;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_fts5_1api_xCreateFunction
+ (JNIEnv *, jobject, jstring, jobject, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class org_sqlite_jni_fts5_fts5_tokenizer */
+
+#ifndef _Included_org_sqlite_jni_fts5_fts5_tokenizer
+#define _Included_org_sqlite_jni_fts5_fts5_tokenizer
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: org_sqlite_jni_fts5_fts5_tokenizer
+ * Method: xTokenize
+ * Signature: (Lorg/sqlite/jni/fts5/Fts5Tokenizer;I[BLorg/sqlite/jni/fts5/XTokenizeCallback;)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_fts5_1tokenizer_xTokenize
+ (JNIEnv *, jobject, jobject, jint, jbyteArray, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/annotation/NotNull.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/annotation/NotNull.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/annotation/NotNull.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/annotation/NotNull.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,59 @@
+/*
+** 2023-09-27
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file houses the NotNull annotaion for the sqlite3 C API.
+*/
+package org.sqlite.jni.annotation;
+
+/**
+ This annotation is for flagging parameters which may not legally be
+ null or point to closed/finalized C-side resources.
+
+
In the case of Java types which map directly to C struct types
+ (e.g. {@link org.sqlite.jni.sqlite3}, {@link
+ org.sqlite.jni.sqlite3_stmt}, and {@link
+ org.sqlite.jni.sqlite3_context}), a closed/finalized resource is
+ also considered to be null for purposes this annotation because the
+ C-side effect of passing such a handle is the same as if null is
+ passed.
+
+
When used in the context of Java interfaces which are called
+ from the C APIs, this annotation communicates that the C API will
+ never pass a null value to the callback for that parameter.
+
+
Passing a null, for this annotation's definition of null, for
+ any parameter marked with this annoation specifically invokes
+ undefined behavior.
+
+
Passing 0 (i.e. C NULL) or a negative value for any long-type
+ parameter marked with this annoation specifically invokes undefined
+ behavior. Such values are treated as C pointers in the JNI
+ layer.
+
+
Note that the C-style API does not throw any exceptions on its
+ own because it has a no-throw policy in order to retain its C-style
+ semantics, but it may trigger NullPointerExceptions (or similar) if
+ passed a null for a parameter flagged with this annotation.
+
+
This annotation is informational only. No policy is in place to
+ programmatically ensure that NotNull is conformed to in client
+ code.
+
+
This annotation is solely for the use by the classes in the
+ org.sqlite package and subpackages, but is made public so that
+ javadoc will link to it from the annotated functions. It is not
+ part of the public API and client-level code must not rely on
+ it.
+*/
+@java.lang.annotation.Documented
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER)
+public @interface NotNull{}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/annotation/Nullable.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/annotation/Nullable.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/annotation/Nullable.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/annotation/Nullable.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,32 @@
+/*
+** 2023-09-27
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file houses the Nullable annotaion for the sqlite3 C API.
+*/
+package org.sqlite.jni.annotation;
+
+/**
+ This annotation is for flagging parameters which may legally be
+ null, noting that they may behave differently if passed null but
+ are prepared to expect null as a value. When used in the context of
+ callback methods which are called into from the C APIs, this
+ annotation communicates that the C API may pass a null value to the
+ callback.
+
+
This annotation is solely for the use by the classes in this
+ package but is made public so that javadoc will link to it from the
+ annotated functions. It is not part of the public API and
+ client-level code must not rely on it.
+*/
+@java.lang.annotation.Documented
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER)
+public @interface Nullable{}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/annotation/package-info.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/annotation/package-info.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/annotation/package-info.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/annotation/package-info.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,17 @@
+/*
+** 2023-09-27
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+*/
+/**
+ This package houses annotations specific to the JNI bindings of the
+ SQLite3 C API.
+*/
+package org.sqlite.jni.annotation;
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,34 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.NotNull;
+
+/**
+ An implementation of {@link CollationCallback} which provides a
+ no-op xDestroy() method.
+*/
+public abstract class AbstractCollationCallback
+ implements CollationCallback, XDestroyCallback {
+ /**
+ Must compare the given byte arrays and return the result using
+ {@code memcmp()} semantics.
+ */
+ public abstract int call(@NotNull byte[] lhs, @NotNull byte[] rhs);
+
+ /**
+ Optionally override to be notified when the UDF is finalized by
+ SQLite. This implementation does nothing.
+ */
+ public void xDestroy(){}
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,72 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+
+/**
+ A SQLFunction implementation for aggregate functions. Its T is the
+ data type of its "accumulator" state, an instance of which is
+ intended to be be managed using the getAggregateState() and
+ takeAggregateState() methods.
+*/
+public abstract class AggregateFunction implements SQLFunction {
+
+ /**
+ As for the xStep() argument of the C API's
+ sqlite3_create_function(). If this function throws, the
+ exception is not propagated and a warning might be emitted to a
+ debugging channel.
+ */
+ public abstract void xStep(sqlite3_context cx, sqlite3_value[] args);
+
+ /**
+ As for the xFinal() argument of the C API's sqlite3_create_function().
+ If this function throws, it is translated into an sqlite3_result_error().
+ */
+ public abstract void xFinal(sqlite3_context cx);
+
+ /**
+ Optionally override to be notified when the UDF is finalized by
+ SQLite.
+ */
+ public void xDestroy() {}
+
+ /** Per-invocation state for the UDF. */
+ private final SQLFunction.PerContextState map =
+ new SQLFunction.PerContextState<>();
+
+ /**
+ To be called from the implementation's xStep() method, as well
+ as the xValue() and xInverse() methods of the {@link WindowFunction}
+ subclass, to fetch the current per-call UDF state. On the
+ first call to this method for any given sqlite3_context
+ argument, the context is set to the given initial value. On all other
+ calls, the 2nd argument is ignored.
+
+ @see SQLFunction.PerContextState#getAggregateState
+ */
+ protected final ValueHolder getAggregateState(sqlite3_context cx, T initialValue){
+ return map.getAggregateState(cx, initialValue);
+ }
+
+ /**
+ To be called from the implementation's xFinal() method to fetch
+ the final state of the UDF and remove its mapping.
+
+ see SQLFunction.PerContextState#takeAggregateState
+ */
+ protected final T takeAggregateState(sqlite3_context cx){
+ return map.takeAggregateState(cx);
+ }
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,28 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.*;
+
+/**
+ Callback for use with {@link CApi#sqlite3_set_authorizer}.
+*/
+public interface AuthorizerCallback extends CallbackProxy {
+ /**
+ Must function as described for the C-level
+ sqlite3_set_authorizer() callback.
+ */
+ int call(int opId, @Nullable String s1, @Nullable String s2,
+ @Nullable String s3, @Nullable String s4);
+
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,40 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with the {@link CApi#sqlite3_auto_extension}
+ family of APIs.
+*/
+public interface AutoExtensionCallback extends CallbackProxy {
+ /**
+ Must function as described for a C-level
+ sqlite3_auto_extension() callback.
+
+
This callback may throw and the exception's error message will
+ be set as the db's error string.
+
+
Tips for implementations:
+
+
- Opening a database from an auto-extension handler will lead to
+ an endless recursion of the auto-handler triggering itself
+ indirectly for each newly-opened database.
+
+
- If this routine is stateful, it may be useful to make the
+ overridden method synchronized.
+
+
- Results are undefined if the given db is closed by an auto-extension.
+ */
+ int call(sqlite3 db);
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,26 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_busy_handler}.
+*/
+public interface BusyHandlerCallback extends CallbackProxy {
+ /**
+ Must function as documented for the C-level
+ sqlite3_busy_handler() callback argument, minus the (void*)
+ argument the C-level function requires.
+ */
+ int call(int n);
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,44 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+/**
+ This marker interface exists soley for use as a documentation and
+ class-grouping tool. It should be applied to interfaces or
+ classes which have a call() method implementing some specific
+ callback interface on behalf of the C library.
+
+
Unless very explicitely documented otherwise, callbacks must
+ never throw. Any which do throw but should not might trigger debug
+ output regarding the error, but the exception will not be
+ propagated. For callback interfaces which support returning error
+ info to the core, the JNI binding will convert any exceptions to
+ C-level error information. For callback interfaces which do not
+ support, all exceptions will necessarily be suppressed in order to
+ retain the C-style no-throw semantics.
+
+
Callbacks of this style follow a common naming convention:
+
+
1) They use the UpperCamelCase form of the C function they're
+ proxying for, minus the {@code sqlite3_} prefix, plus a {@code
+ Callback} suffix. e.g. {@code sqlite3_busy_handler()}'s callback is
+ named {@code BusyHandlerCallback}. Exceptions are made where that
+ would potentially be ambiguous, e.g. {@link ConfigSqllogCallback}
+ instead of {@code ConfigCallback} because the {@code
+ sqlite3_config()} interface may need to support more callback types
+ in the future.
+
+
2) They all have a {@code call()} method but its signature is
+ callback-specific.
+*/
+public interface CallbackProxy {}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/CApi.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CApi.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/CApi.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CApi.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,2449 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file declares JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import java.nio.charset.StandardCharsets;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import org.sqlite.jni.annotation.*;
+import java.util.Arrays;
+
+/**
+ This class contains the entire C-style sqlite3 JNI API binding,
+ minus a few bits and pieces declared in other files. For client-side
+ use, a static import is recommended:
+
+
The C-side part can be found in sqlite3-jni.c.
+
+
This class is package-private in order to keep Java clients from
+ having direct access to the low-level C-style APIs, a design
+ decision made by Java developers based on the C-style API being
+ riddled with opportunities for Java developers to proverbially shoot
+ themselves in the foot with. Third-party copies of this code may
+ eliminate that guard by simply changing this class from
+ package-private to public. Its methods which are intended to be
+ exposed that way are all public.
+
+
Only functions which materially differ from their C counterparts
+ are documented here, and only those material differences are
+ documented. The C documentation is otherwise applicable for these
+ APIs:
+
+
A handful of Java-specific APIs have been added which are
+ documented here. A number of convenience overloads are provided
+ which are not documented but whose semantics map 1-to-1 in an
+ intuitive manner. e.g. {@link
+ #sqlite3_result_set(sqlite3_context,int)} is equivalent to {@link
+ #sqlite3_result_int}, and sqlite3_result_set() has many
+ type-specific overloads.
+
+
Notes regarding Java's Modified UTF-8 vs standard UTF-8:
+
+
SQLite internally uses UTF-8 encoding, whereas Java natively uses
+ UTF-16. Java JNI has routines for converting to and from UTF-8,
+ but JNI uses what its docs call modified UTF-8 (see links below)
+ Care must be taken when converting Java strings to or from standard
+ UTF-8 to ensure that the proper conversion is performed. In short,
+ Java's {@code String.getBytes(StandardCharsets.UTF_8)} performs the proper
+ conversion in Java, and there are no JNI C APIs for that conversion
+ (JNI's {@code NewStringUTF()} requires its input to be in MUTF-8).
+
+
The known consequences and limitations this discrepancy places on
+ the SQLite3 JNI binding include:
+
+
+
+
C functions which take C-style strings without a length argument
+ require special care when taking input from Java. In particular,
+ Java strings converted to byte arrays for encoding purposes are not
+ NUL-terminated, and conversion to a Java byte array must sometimes
+ be careful to add one. Functions which take a length do not require
+ this so long as the length is provided. Search the CApi class
+ for "\0" for many examples.
+
+
https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8
+
+*/
+public final class CApi {
+ static {
+ System.loadLibrary("sqlite3-jni");
+ }
+ //! Not used
+ private CApi(){}
+ //! Called from static init code.
+ private static native void init();
+
+ /**
+ Returns a nul-terminated copy of s as a UTF-8-encoded byte array,
+ or null if s is null.
+ */
+ private static byte[] nulTerminateUtf8(String s){
+ return null==s ? null : (s+"\0").getBytes(StandardCharsets.UTF_8);
+ }
+
+ /**
+ Each thread which uses the SQLite3 JNI APIs should call
+ sqlite3_jni_uncache_thread() when it is done with the library -
+ either right before it terminates or when it finishes using the
+ SQLite API. This will clean up any cached per-thread info.
+
+
This process does not close any databases or finalize
+ any prepared statements because their ownership does not depend on
+ a given thread. For proper library behavior, and to
+ avoid C-side leaks, be sure to finalize all statements and close
+ all databases before calling this function.
+
+
Calling this from the main application thread is not strictly
+ required. Additional threads must call this before ending or they
+ will leak cache entries in the C heap, which in turn may keep
+ numerous Java-side global references active.
+
+
This routine returns false without side effects if the current
+ JNIEnv is not cached, else returns true, but this information is
+ primarily for testing of the JNI bindings and is not information
+ which client-level code can use to make any informed decisions.
+ */
+ public static native boolean sqlite3_java_uncache_thread();
+
+ //////////////////////////////////////////////////////////////////////
+ // Maintenance reminder: please keep the sqlite3_.... functions
+ // alphabetized. The SQLITE_... values. on the other hand, are
+ // grouped by category.
+
+ /**
+ Functions exactly like the native form except that (A) the 2nd
+ argument is a boolean instead of an int and (B) the returned
+ value is not a pointer address and is only intended for use as a
+ per-UDF-call lookup key in a higher-level data structure.
+
+
Passing a true second argument is analogous to passing some
+ unspecified small, non-0 positive value to the C API and passing
+ false is equivalent to passing 0 to the C API.
+
+
Like the C API, it returns 0 if allocation fails or if
+ initialize is false and no prior aggregate context was allocated
+ for cx. If initialize is true then it returns 0 only on
+ allocation error. In all casses, 0 is considered the sentinel
+ "not a key" value.
+ */
+ public static native long sqlite3_aggregate_context(sqlite3_context cx, boolean initialize);
+
+ /**
+ Functions almost as documented for the C API, with these
+ exceptions:
+
+
- The callback interface is shorter because of
+ cross-language differences. Specifically, 3rd argument to the C
+ auto-extension callback interface is unnecessary here.
+
+
The C API docs do not specifically say so, but if the list of
+ auto-extensions is manipulated from an auto-extension, it is
+ undefined which, if any, auto-extensions will subsequently
+ execute for the current database. That is, doing so will result
+ in unpredictable, but not undefined, behavior.
+
+
See the AutoExtension class docs for more information.
+ */
+ public static native int sqlite3_auto_extension(@NotNull AutoExtensionCallback callback);
+
+ static native int sqlite3_backup_finish(@NotNull long ptrToBackup);
+
+ public static int sqlite3_backup_finish(@NotNull sqlite3_backup b){
+ return sqlite3_backup_finish(b.clearNativePointer());
+ }
+
+ static native sqlite3_backup sqlite3_backup_init(
+ @NotNull long ptrToDbDest, @NotNull String destTableName,
+ @NotNull long ptrToDbSrc, @NotNull String srcTableName
+ );
+
+ public static sqlite3_backup sqlite3_backup_init(
+ @NotNull sqlite3 dbDest, @NotNull String destTableName,
+ @NotNull sqlite3 dbSrc, @NotNull String srcTableName
+ ){
+ return sqlite3_backup_init( dbDest.getNativePointer(), destTableName,
+ dbSrc.getNativePointer(), srcTableName );
+ }
+
+ static native int sqlite3_backup_pagecount(@NotNull long ptrToBackup);
+
+ public static int sqlite3_backup_pagecount(@NotNull sqlite3_backup b){
+ return sqlite3_backup_pagecount(b.getNativePointer());
+ }
+
+ static native int sqlite3_backup_remaining(@NotNull long ptrToBackup);
+
+ public static int sqlite3_backup_remaining(@NotNull sqlite3_backup b){
+ return sqlite3_backup_remaining(b.getNativePointer());
+ }
+
+ static native int sqlite3_backup_step(@NotNull long ptrToBackup, int nPage);
+
+ public static int sqlite3_backup_step(@NotNull sqlite3_backup b, int nPage){
+ return sqlite3_backup_step(b.getNativePointer(), nPage);
+ }
+
+ static native int sqlite3_bind_blob(
+ @NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int n
+ );
+
+ /**
+ If n is negative, SQLITE_MISUSE is returned. If n>data.length
+ then n is silently truncated to data.length.
+ */
+ static int sqlite3_bind_blob(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n
+ ){
+ return sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, n);
+ }
+
+ public static int sqlite3_bind_blob(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data
+ ){
+ return (null==data)
+ ? sqlite3_bind_null(stmt.getNativePointer(), ndx)
+ : sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, data.length);
+ }
+
+ static native int sqlite3_bind_double(
+ @NotNull long ptrToStmt, int ndx, double v
+ );
+
+ public static int sqlite3_bind_double(
+ @NotNull sqlite3_stmt stmt, int ndx, double v
+ ){
+ return sqlite3_bind_double(stmt.getNativePointer(), ndx, v);
+ }
+
+ static native int sqlite3_bind_int(
+ @NotNull long ptrToStmt, int ndx, int v
+ );
+
+ public static int sqlite3_bind_int(
+ @NotNull sqlite3_stmt stmt, int ndx, int v
+ ){
+ return sqlite3_bind_int(stmt.getNativePointer(), ndx, v);
+ }
+
+ static native int sqlite3_bind_int64(
+ @NotNull long ptrToStmt, int ndx, long v
+ );
+
+ public static int sqlite3_bind_int64(@NotNull sqlite3_stmt stmt, int ndx, long v){
+ return sqlite3_bind_int64( stmt.getNativePointer(), ndx, v );
+ }
+
+ static native int sqlite3_bind_java_object(
+ @NotNull long ptrToStmt, int ndx, @Nullable Object o
+ );
+
+ /**
+ Binds the given object at the given index. If o is null then this behaves like
+ sqlite3_bind_null().
+
+ @see #sqlite3_result_java_object
+ */
+ public static int sqlite3_bind_java_object(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable Object o
+ ){
+ return sqlite3_bind_java_object(stmt.getNativePointer(), ndx, o);
+ }
+
+ static native int sqlite3_bind_null(@NotNull long ptrToStmt, int ndx);
+
+ public static int sqlite3_bind_null(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_bind_null(stmt.getNativePointer(), ndx);
+ }
+
+ static native int sqlite3_bind_parameter_count(@NotNull long ptrToStmt);
+
+ public static int sqlite3_bind_parameter_count(@NotNull sqlite3_stmt stmt){
+ return sqlite3_bind_parameter_count(stmt.getNativePointer());
+ }
+
+ /**
+ Requires that paramName be a NUL-terminated UTF-8 string.
+
+ This overload is private because: (A) to keep users from
+ inadvertently passing non-NUL-terminated byte arrays (an easy
+ thing to do). (B) it is cheaper to NUL-terminate the
+ String-to-byte-array conversion in the public-facing Java-side
+ overload than to do that in C, so that signature is the
+ public-facing one.
+ */
+ private static native int sqlite3_bind_parameter_index(
+ @NotNull long ptrToStmt, @NotNull byte[] paramName
+ );
+
+ public static int sqlite3_bind_parameter_index(
+ @NotNull sqlite3_stmt stmt, @NotNull String paramName
+ ){
+ final byte[] utf8 = nulTerminateUtf8(paramName);
+ return null==utf8 ? 0 : sqlite3_bind_parameter_index(stmt.getNativePointer(), utf8);
+ }
+
+ static native String sqlite3_bind_parameter_name(
+ @NotNull long ptrToStmt, int index
+ );
+
+ public static String sqlite3_bind_parameter_name(@NotNull sqlite3_stmt stmt, int index){
+ return sqlite3_bind_parameter_name(stmt.getNativePointer(), index);
+ }
+
+ static native int sqlite3_bind_text(
+ @NotNull long ptrToStmt, int ndx, @Nullable byte[] utf8, int maxBytes
+ );
+
+ /**
+ Works like the C-level sqlite3_bind_text() but assumes
+ SQLITE_TRANSIENT for the final C API parameter. The byte array is
+ assumed to be in UTF-8 encoding.
+
+
If data is not null and maxBytes>utf8.length then maxBytes is
+ silently truncated to utf8.length. If maxBytes is negative then
+ results are undefined if data is not null and does not contain a
+ NUL byte.
+ */
+ static int sqlite3_bind_text(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] utf8, int maxBytes
+ ){
+ return sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, maxBytes);
+ }
+
+ /**
+ Converts data, if not null, to a UTF-8-encoded byte array and
+ binds it as such, returning the result of the C-level
+ sqlite3_bind_null() or sqlite3_bind_text().
+ */
+ public static int sqlite3_bind_text(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data
+ ){
+ if( null==data ) return sqlite3_bind_null(stmt.getNativePointer(), ndx);
+ final byte[] utf8 = data.getBytes(StandardCharsets.UTF_8);
+ return sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, utf8.length);
+ }
+
+ /**
+ Requires that utf8 be null or in UTF-8 encoding.
+ */
+ public static int sqlite3_bind_text(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] utf8
+ ){
+ return ( null==utf8 )
+ ? sqlite3_bind_null(stmt.getNativePointer(), ndx)
+ : sqlite3_bind_text(stmt.getNativePointer(), ndx, utf8, utf8.length);
+ }
+
+ static native int sqlite3_bind_text16(
+ @NotNull long ptrToStmt, int ndx, @Nullable byte[] data, int maxBytes
+ );
+
+ /**
+ Identical to the sqlite3_bind_text() overload with the same
+ signature but requires that its input be encoded in UTF-16 in
+ platform byte order.
+ */
+ static int sqlite3_bind_text16(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes
+ ){
+ return sqlite3_bind_text16(stmt.getNativePointer(), ndx, data, maxBytes);
+ }
+
+ /**
+ Converts its string argument to UTF-16 and binds it as such, returning
+ the result of the C-side function of the same name. The 3rd argument
+ may be null.
+ */
+ public static int sqlite3_bind_text16(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data
+ ){
+ if(null == data) return sqlite3_bind_null(stmt, ndx);
+ final byte[] bytes = data.getBytes(StandardCharsets.UTF_16);
+ return sqlite3_bind_text16(stmt.getNativePointer(), ndx, bytes, bytes.length);
+ }
+
+ /**
+ Requires that data be null or in UTF-16 encoding in platform byte
+ order. Returns the result of the C-level sqlite3_bind_null() or
+ sqlite3_bind_text16().
+ */
+ public static int sqlite3_bind_text16(
+ @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data
+ ){
+ return (null == data)
+ ? sqlite3_bind_null(stmt.getNativePointer(), ndx)
+ : sqlite3_bind_text16(stmt.getNativePointer(), ndx, data, data.length);
+ }
+
+ static native int sqlite3_bind_value(@NotNull long ptrToStmt, int ndx, long ptrToValue);
+
+ /**
+ Functions like the C-level sqlite3_bind_value(), or
+ sqlite3_bind_null() if val is null.
+ */
+ public static int sqlite3_bind_value(@NotNull sqlite3_stmt stmt, int ndx, sqlite3_value val){
+ return sqlite3_bind_value(stmt.getNativePointer(), ndx,
+ null==val ? 0L : val.getNativePointer());
+ }
+
+ static native int sqlite3_bind_zeroblob(@NotNull long ptrToStmt, int ndx, int n);
+
+ public static int sqlite3_bind_zeroblob(@NotNull sqlite3_stmt stmt, int ndx, int n){
+ return sqlite3_bind_zeroblob(stmt.getNativePointer(), ndx, n);
+ }
+
+ static native int sqlite3_bind_zeroblob64(
+ @NotNull long ptrToStmt, int ndx, long n
+ );
+
+ public static int sqlite3_bind_zeroblob64(@NotNull sqlite3_stmt stmt, int ndx, long n){
+ return sqlite3_bind_zeroblob64(stmt.getNativePointer(), ndx, n);
+ }
+
+ static native int sqlite3_blob_bytes(@NotNull long ptrToBlob);
+
+ public static int sqlite3_blob_bytes(@NotNull sqlite3_blob blob){
+ return sqlite3_blob_bytes(blob.getNativePointer());
+ }
+
+ static native int sqlite3_blob_close(@Nullable long ptrToBlob);
+
+ public static int sqlite3_blob_close(@Nullable sqlite3_blob blob){
+ return sqlite3_blob_close(blob.clearNativePointer());
+ }
+
+ static native int sqlite3_blob_open(
+ @NotNull long ptrToDb, @NotNull String dbName,
+ @NotNull String tableName, @NotNull String columnName,
+ long iRow, int flags, @NotNull OutputPointer.sqlite3_blob out
+ );
+
+ public static int sqlite3_blob_open(
+ @NotNull sqlite3 db, @NotNull String dbName,
+ @NotNull String tableName, @NotNull String columnName,
+ long iRow, int flags, @NotNull OutputPointer.sqlite3_blob out
+ ){
+ return sqlite3_blob_open(db.getNativePointer(), dbName, tableName,
+ columnName, iRow, flags, out);
+ }
+
+ /**
+ Convenience overload.
+ */
+ public static sqlite3_blob sqlite3_blob_open(
+ @NotNull sqlite3 db, @NotNull String dbName,
+ @NotNull String tableName, @NotNull String columnName,
+ long iRow, int flags ){
+ final OutputPointer.sqlite3_blob out = new OutputPointer.sqlite3_blob();
+ sqlite3_blob_open(db.getNativePointer(), dbName, tableName, columnName,
+ iRow, flags, out);
+ return out.take();
+ };
+
+ static native int sqlite3_blob_read(
+ @NotNull long ptrToBlob, @NotNull byte[] target, int iOffset
+ );
+
+ public static int sqlite3_blob_read(
+ @NotNull sqlite3_blob b, @NotNull byte[] target, int iOffset
+ ){
+ return sqlite3_blob_read(b.getNativePointer(), target, iOffset);
+ }
+
+ static native int sqlite3_blob_reopen(
+ @NotNull long ptrToBlob, long newRowId
+ );
+
+ public static int sqlite3_blob_reopen(@NotNull sqlite3_blob b, long newRowId){
+ return sqlite3_blob_reopen(b.getNativePointer(), newRowId);
+ }
+
+ static native int sqlite3_blob_write(
+ @NotNull long ptrToBlob, @NotNull byte[] bytes, int iOffset
+ );
+
+ public static int sqlite3_blob_write(
+ @NotNull sqlite3_blob b, @NotNull byte[] bytes, int iOffset
+ ){
+ return sqlite3_blob_write(b.getNativePointer(), bytes, iOffset);
+ }
+
+ static native int sqlite3_busy_handler(
+ @NotNull long ptrToDb, @Nullable BusyHandlerCallback handler
+ );
+
+ /**
+ As for the C-level function of the same name, with a
+ BusyHandlerCallback instance in place of a callback
+ function. Pass it a null handler to clear the busy handler.
+ */
+ public static int sqlite3_busy_handler(
+ @NotNull sqlite3 db, @Nullable BusyHandlerCallback handler
+ ){
+ return sqlite3_busy_handler(db.getNativePointer(), handler);
+ }
+
+ static native int sqlite3_busy_timeout(@NotNull long ptrToDb, int ms);
+
+ public static int sqlite3_busy_timeout(@NotNull sqlite3 db, int ms){
+ return sqlite3_busy_timeout(db.getNativePointer(), ms);
+ }
+
+ public static native boolean sqlite3_cancel_auto_extension(
+ @NotNull AutoExtensionCallback ax
+ );
+
+ static native int sqlite3_changes(@NotNull long ptrToDb);
+
+ public static int sqlite3_changes(@NotNull sqlite3 db){
+ return sqlite3_changes(db.getNativePointer());
+ }
+
+ static native long sqlite3_changes64(@NotNull long ptrToDb);
+
+ public static long sqlite3_changes64(@NotNull sqlite3 db){
+ return sqlite3_changes64(db.getNativePointer());
+ }
+
+ static native int sqlite3_clear_bindings(@NotNull long ptrToStmt);
+
+ public static int sqlite3_clear_bindings(@NotNull sqlite3_stmt stmt){
+ return sqlite3_clear_bindings(stmt.getNativePointer());
+ }
+
+ static native int sqlite3_close(@Nullable long ptrToDb);
+
+ public static int sqlite3_close(@Nullable sqlite3 db){
+ int rc = 0;
+ if( null!=db ){
+ rc = sqlite3_close(db.getNativePointer());
+ if( 0==rc ) db.clearNativePointer();
+ }
+ return rc;
+ }
+
+ static native int sqlite3_close_v2(@Nullable long ptrToDb);
+
+ public static int sqlite3_close_v2(@Nullable sqlite3 db){
+ return db==null ? 0 : sqlite3_close_v2(db.clearNativePointer());
+ }
+
+ public static native byte[] sqlite3_column_blob(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ static native int sqlite3_column_bytes(@NotNull long ptrToStmt, int ndx);
+
+ public static int sqlite3_column_bytes(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_bytes(stmt.getNativePointer(), ndx);
+ }
+
+ static native int sqlite3_column_bytes16(@NotNull long ptrToStmt, int ndx);
+
+ public static int sqlite3_column_bytes16(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_bytes16(stmt.getNativePointer(), ndx);
+ }
+
+ static native int sqlite3_column_count(@NotNull long ptrToStmt);
+
+ public static int sqlite3_column_count(@NotNull sqlite3_stmt stmt){
+ return sqlite3_column_count(stmt.getNativePointer());
+ }
+
+ static native String sqlite3_column_decltype(@NotNull long ptrToStmt, int ndx);
+
+ public static String sqlite3_column_decltype(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_decltype(stmt.getNativePointer(), ndx);
+ }
+
+ public static native double sqlite3_column_double(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ public static native int sqlite3_column_int(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ public static native long sqlite3_column_int64(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ static native String sqlite3_column_name(@NotNull long ptrToStmt, int ndx);
+
+ public static String sqlite3_column_name(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_name(stmt.getNativePointer(), ndx);
+ }
+
+ static native String sqlite3_column_database_name(@NotNull long ptrToStmt, int ndx);
+
+ public static String sqlite3_column_database_name(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_database_name(stmt.getNativePointer(), ndx);
+ }
+
+ static native String sqlite3_column_origin_name(@NotNull long ptrToStmt, int ndx);
+
+ public static String sqlite3_column_origin_name(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_origin_name(stmt.getNativePointer(), ndx);
+ }
+
+ static native String sqlite3_column_table_name(@NotNull long ptrToStmt, int ndx);
+
+ public static String sqlite3_column_table_name(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_table_name(stmt.getNativePointer(), ndx);
+ }
+
+ /**
+ Functions identially to the C API, and this note is just to
+ stress that the returned bytes are encoded as UTF-8. It returns
+ null if the underlying C-level sqlite3_column_text() returns NULL
+ or on allocation error.
+
+ @see #sqlite3_column_text16(sqlite3_stmt,int)
+ */
+ public static native byte[] sqlite3_column_text(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ public static native String sqlite3_column_text16(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ // The real utility of this function is questionable.
+ // /**
+ // Returns a Java value representation based on the value of
+ // sqlite_value_type(). For integer types it returns either Integer
+ // or Long, depending on whether the value will fit in an
+ // Integer. For floating-point values it always returns type Double.
+
+ // If the column was bound using sqlite3_result_java_object() then
+ // that value, as an Object, is returned.
+ // */
+ // public static Object sqlite3_column_to_java(@NotNull sqlite3_stmt stmt,
+ // int ndx){
+ // sqlite3_value v = sqlite3_column_value(stmt, ndx);
+ // Object rv = null;
+ // if(null == v) return v;
+ // v = sqlite3_value_dup(v)/*need a protected value*/;
+ // if(null == v) return v /* OOM error in C */;
+ // if(112/* 'p' */ == sqlite3_value_subtype(v)){
+ // rv = sqlite3_value_java_object(v);
+ // }else{
+ // switch(sqlite3_value_type(v)){
+ // case SQLITE_INTEGER: {
+ // final long i = sqlite3_value_int64(v);
+ // rv = (i<=0x7fffffff && i>=-0x7fffffff-1)
+ // ? new Integer((int)i) : new Long(i);
+ // break;
+ // }
+ // case SQLITE_FLOAT: rv = new Double(sqlite3_value_double(v)); break;
+ // case SQLITE_BLOB: rv = sqlite3_value_blob(v); break;
+ // case SQLITE_TEXT: rv = sqlite3_value_text16(v); break;
+ // default: break;
+ // }
+ // }
+ // sqlite3_value_free(v);
+ // return rv;
+ // }
+
+ static native int sqlite3_column_type(@NotNull long ptrToStmt, int ndx);
+
+ public static int sqlite3_column_type(@NotNull sqlite3_stmt stmt, int ndx){
+ return sqlite3_column_type(stmt.getNativePointer(), ndx);
+ }
+
+ public static native sqlite3_value sqlite3_column_value(
+ @NotNull sqlite3_stmt stmt, int ndx
+ );
+
+ static native int sqlite3_collation_needed(
+ @NotNull long ptrToDb, @Nullable CollationNeededCallback callback
+ );
+
+ /**
+ This functions like C's sqlite3_collation_needed16() because
+ Java's string type is inherently compatible with that interface.
+ */
+ public static int sqlite3_collation_needed(
+ @NotNull sqlite3 db, @Nullable CollationNeededCallback callback
+ ){
+ return sqlite3_collation_needed(db.getNativePointer(), callback);
+ }
+
+ static native CommitHookCallback sqlite3_commit_hook(
+ @NotNull long ptrToDb, @Nullable CommitHookCallback hook
+ );
+
+ public static CommitHookCallback sqlite3_commit_hook(
+ @NotNull sqlite3 db, @Nullable CommitHookCallback hook
+ ){
+ return sqlite3_commit_hook(db.getNativePointer(), hook);
+ }
+
+ public static native String sqlite3_compileoption_get(int n);
+
+ public static native boolean sqlite3_compileoption_used(String optName);
+
+ /**
+ This implementation is private because it's too easy to pass it
+ non-NUL-terminated byte arrays from client code.
+ */
+ private static native int sqlite3_complete(
+ @NotNull byte[] nulTerminatedUtf8Sql
+ );
+
+ /**
+ Unlike the C API, this returns SQLITE_MISUSE if its argument is
+ null (as opposed to invoking UB).
+ */
+ public static int sqlite3_complete(@NotNull String sql){
+ return sqlite3_complete( nulTerminateUtf8(sql) );
+ }
+
+
+ /**
+
Works like in the C API with the exception that it only supports
+ the following subset of configution flags:
+
+
Others may be added in the future. It returns SQLITE_MISUSE if
+ given an argument it does not handle.
+
+
Note that sqlite3_config() is not threadsafe with regards to
+ the rest of the library. This must not be called when any other
+ library APIs are being called.
+ */
+ public static native int sqlite3_config(int op);
+
+ /**
+ If the native library was built with SQLITE_ENABLE_SQLLOG defined
+ then this acts as a proxy for C's
+ sqlite3_config(SQLITE_ENABLE_SQLLOG,...). This sets or clears the
+ logger. If installation of a logger fails, any previous logger is
+ retained.
+
+
If not built with SQLITE_ENABLE_SQLLOG defined, this returns
+ SQLITE_MISUSE.
+
+
Note that sqlite3_config() is not threadsafe with regards to
+ the rest of the library. This must not be called when any other
+ library APIs are being called.
+ */
+ public static native int sqlite3_config( @Nullable ConfigSqllogCallback logger );
+
+ /**
+ The sqlite3_config() overload for handling the SQLITE_CONFIG_LOG
+ option.
+ */
+ public static native int sqlite3_config( @Nullable ConfigLogCallback logger );
+
+ /**
+ Unlike the C API, this returns null if its argument is
+ null (as opposed to invoking UB).
+ */
+ public static native sqlite3 sqlite3_context_db_handle(
+ @NotNull sqlite3_context cx
+ );
+
+ public static native int sqlite3_create_collation(
+ @NotNull sqlite3 db, @NotNull String name, int eTextRep,
+ @NotNull CollationCallback col
+ );
+
+ /**
+ The Java counterpart to the C-native sqlite3_create_function(),
+ sqlite3_create_function_v2(), and
+ sqlite3_create_window_function(). Which one it behaves like
+ depends on which methods the final argument implements. See
+ SQLFunction's subclasses (ScalarFunction, AggregateFunction,
+ and WindowFunction) for details.
+
+
Unlike the C API, this returns SQLITE_MISUSE null if its db or
+ functionName arguments are null (as opposed to invoking UB).
+ */
+ public static native int sqlite3_create_function(
+ @NotNull sqlite3 db, @NotNull String functionName,
+ int nArg, int eTextRep, @NotNull SQLFunction func
+ );
+
+ static native int sqlite3_data_count(@NotNull long ptrToStmt);
+
+ public static int sqlite3_data_count(@NotNull sqlite3_stmt stmt){
+ return sqlite3_data_count(stmt.getNativePointer());
+ }
+
+ /**
+ Overload for sqlite3_db_config() calls which take (int,int*)
+ variadic arguments. Returns SQLITE_MISUSE if op is not one of the
+ SQLITE_DBCONFIG_... options which uses this call form.
+
+
Unlike the C API, this returns SQLITE_MISUSE if its db argument
+ are null (as opposed to invoking UB).
+ */
+ public static native int sqlite3_db_config(
+ @NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out
+ );
+
+ /**
+ Overload for sqlite3_db_config() calls which take a (const char*)
+ variadic argument. As of SQLite3 v3.43 the only such option is
+ SQLITE_DBCONFIG_MAINDBNAME. Returns SQLITE_MISUSE if op is not
+ SQLITE_DBCONFIG_MAINDBNAME, but that set of options may be
+ extended in future versions.
+ */
+ public static native int sqlite3_db_config(
+ @NotNull sqlite3 db, int op, @NotNull String val
+ );
+
+ private static native String sqlite3_db_name(@NotNull long ptrToDb, int ndx);
+
+ public static String sqlite3_db_name(@NotNull sqlite3 db, int ndx){
+ return null==db ? null : sqlite3_db_name(db.getNativePointer(), ndx);
+ }
+
+
+ public static native String sqlite3_db_filename(
+ @NotNull sqlite3 db, @NotNull String dbName
+ );
+
+ public static native sqlite3 sqlite3_db_handle(@NotNull sqlite3_stmt stmt);
+
+ public static native int sqlite3_db_readonly(@NotNull sqlite3 db, String dbName);
+
+ public static native int sqlite3_db_release_memory(sqlite3 db);
+
+ public static native int sqlite3_db_status(
+ @NotNull sqlite3 db, int op, @NotNull OutputPointer.Int32 pCurrent,
+ @NotNull OutputPointer.Int32 pHighwater, boolean reset
+ );
+
+ public static native int sqlite3_errcode(@NotNull sqlite3 db);
+
+ public static native String sqlite3_errmsg(@NotNull sqlite3 db);
+
+ static native int sqlite3_error_offset(@NotNull long ptrToDb);
+
+ /**
+ Note that the returned byte offset values assume UTF-8-encoded
+ inputs, so won't always match character offsets in Java Strings.
+ */
+ public static int sqlite3_error_offset(@NotNull sqlite3 db){
+ return sqlite3_error_offset(db.getNativePointer());
+ }
+
+ public static native String sqlite3_errstr(int resultCode);
+
+ public static native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt);
+
+ static native int sqlite3_extended_errcode(@NotNull long ptrToDb);
+
+ public static int sqlite3_extended_errcode(@NotNull sqlite3 db){
+ return sqlite3_extended_errcode(db.getNativePointer());
+ }
+
+ public static native boolean sqlite3_extended_result_codes(
+ @NotNull sqlite3 db, boolean onoff
+ );
+
+ static native boolean sqlite3_get_autocommit(@NotNull long ptrToDb);
+
+ public static boolean sqlite3_get_autocommit(@NotNull sqlite3 db){
+ return sqlite3_get_autocommit(db.getNativePointer());
+ }
+
+ public static native Object sqlite3_get_auxdata(
+ @NotNull sqlite3_context cx, int n
+ );
+
+ static native int sqlite3_finalize(long ptrToStmt);
+
+ public static int sqlite3_finalize(@NotNull sqlite3_stmt stmt){
+ return null==stmt ? 0 : sqlite3_finalize(stmt.clearNativePointer());
+ }
+
+ public static native int sqlite3_initialize();
+
+ public static native void sqlite3_interrupt(@NotNull sqlite3 db);
+
+ public static native boolean sqlite3_is_interrupted(@NotNull sqlite3 db);
+
+ public static native boolean sqlite3_keyword_check(@NotNull String word);
+
+ public static native int sqlite3_keyword_count();
+
+ public static native String sqlite3_keyword_name(int index);
+
+
+ public static native long sqlite3_last_insert_rowid(@NotNull sqlite3 db);
+
+ public static native String sqlite3_libversion();
+
+ public static native int sqlite3_libversion_number();
+
+ public static native int sqlite3_limit(@NotNull sqlite3 db, int id, int newVal);
+
+ /**
+ Only available if built with SQLITE_ENABLE_NORMALIZE. If not, it always
+ returns null.
+ */
+ public static native String sqlite3_normalized_sql(@NotNull sqlite3_stmt stmt);
+
+ /**
+ Works like its C counterpart and makes the native pointer of the
+ underling (sqlite3*) object available via
+ ppDb.getNativePointer(). That pointer is necessary for looking up
+ the JNI-side native, but clients need not pay it any
+ heed. Passing the object to sqlite3_close() or sqlite3_close_v2()
+ will clear that pointer mapping.
+
+
Recall that even if opening fails, the output pointer might be
+ non-null. Any error message about the failure will be in that
+ object and it is up to the caller to sqlite3_close() that
+ db handle.
+ */
+ public static native int sqlite3_open(
+ @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb
+ );
+
+ /**
+ Convenience overload which returns its db handle directly. The returned
+ object might not have been successfully opened: use sqlite3_errcode() to
+ check whether it is in an error state.
+
+
Ownership of the returned value is passed to the caller, who must eventually
+ pass it to sqlite3_close() or sqlite3_close_v2().
+ */
+ public static sqlite3 sqlite3_open(@Nullable String filename){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ sqlite3_open(filename, out);
+ return out.take();
+ };
+
+ public static native int sqlite3_open_v2(
+ @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb,
+ int flags, @Nullable String zVfs
+ );
+
+ /**
+ Has the same semantics as the sqlite3-returning sqlite3_open()
+ but uses sqlite3_open_v2() instead of sqlite3_open().
+ */
+ public static sqlite3 sqlite3_open_v2(@Nullable String filename, int flags,
+ @Nullable String zVfs){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ sqlite3_open_v2(filename, out, flags, zVfs);
+ return out.take();
+ };
+
+ /**
+ The sqlite3_prepare() family of functions require slightly
+ different signatures than their native counterparts, but (A) they
+ retain functionally equivalent semantics and (B) overloading
+ allows us to install several convenience forms.
+
+
All of them which take their SQL in the form of a byte[] require
+ that it be in UTF-8 encoding unless explicitly noted otherwise.
+
+
The forms which take a "tail" output pointer return (via that
+ output object) the index into their SQL byte array at which the
+ end of the first SQL statement processed by the call was
+ found. That's fundamentally how the C APIs work but making use of
+ that value requires more copying of the input SQL into
+ consecutively smaller arrays in order to consume all of
+ it. (There is an example of doing that in this project's Tester1
+ class.) For that vast majority of uses, that capability is not
+ necessary, however, and overloads are provided which gloss over
+ that.
+
+
Results are undefined if maxBytes>sqlUtf8.length.
+
+
This routine is private because its maxBytes value is not
+ strictly necessary in the Java interface, as sqlUtf8.length tells
+ us the information we need. Making this public would give clients
+ more ways to shoot themselves in the foot without providing any
+ real utility.
+ */
+ private static native int sqlite3_prepare(
+ @NotNull long ptrToDb, @NotNull byte[] sqlUtf8, int maxBytes,
+ @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ );
+
+ /**
+ Works like the canonical sqlite3_prepare() but its "tail" output
+ argument is returned as the index offset into the given
+ UTF-8-encoded byte array at which SQL parsing stopped. The
+ semantics are otherwise identical to the C API counterpart.
+
+
Several overloads provided simplified call signatures.
+ */
+ public static int sqlite3_prepare(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ ){
+ return sqlite3_prepare(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ outStmt, pTailOffset);
+ }
+
+ public static int sqlite3_prepare(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ return sqlite3_prepare(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ outStmt, null);
+ }
+
+ public static int sqlite3_prepare(
+ @NotNull sqlite3 db, @NotNull String sql,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8);
+ return sqlite3_prepare(db.getNativePointer(), utf8, utf8.length,
+ outStmt, null);
+ }
+
+ /**
+ Convenience overload which returns its statement handle directly,
+ or null on error or when reading only whitespace or
+ comments. sqlite3_errcode() can be used to determine whether
+ there was an error or the input was empty. Ownership of the
+ returned object is passed to the caller, who must eventually pass
+ it to sqlite3_finalize().
+ */
+ public static sqlite3_stmt sqlite3_prepare(
+ @NotNull sqlite3 db, @NotNull String sql
+ ){
+ final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+ sqlite3_prepare(db, sql, out);
+ return out.take();
+ }
+ /**
+ @see #sqlite3_prepare
+ */
+ private static native int sqlite3_prepare_v2(
+ @NotNull long ptrToDb, @NotNull byte[] sqlUtf8, int maxBytes,
+ @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ );
+
+ /**
+ Works like the canonical sqlite3_prepare_v2() but its "tail"
+ output paramter is returned as the index offset into the given
+ byte array at which SQL parsing stopped.
+ */
+ public static int sqlite3_prepare_v2(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ ){
+ return sqlite3_prepare_v2(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ outStmt, pTailOffset);
+ }
+
+ public static int sqlite3_prepare_v2(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ return sqlite3_prepare_v2(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ outStmt, null);
+ }
+
+ public static int sqlite3_prepare_v2(
+ @NotNull sqlite3 db, @NotNull String sql,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8);
+ return sqlite3_prepare_v2(db.getNativePointer(), utf8, utf8.length,
+ outStmt, null);
+ }
+
+ /**
+ Works identically to the sqlite3_stmt-returning sqlite3_prepare()
+ but uses sqlite3_prepare_v2().
+ */
+ public static sqlite3_stmt sqlite3_prepare_v2(
+ @NotNull sqlite3 db, @NotNull String sql
+ ){
+ final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+ sqlite3_prepare_v2(db, sql, out);
+ return out.take();
+ }
+
+ /**
+ @see #sqlite3_prepare
+ */
+ private static native int sqlite3_prepare_v3(
+ @NotNull long ptrToDb, @NotNull byte[] sqlUtf8, int maxBytes,
+ int prepFlags, @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ );
+
+ /**
+ Works like the canonical sqlite3_prepare_v2() but its "tail"
+ output paramter is returned as the index offset into the given
+ byte array at which SQL parsing stopped.
+ */
+ public static int sqlite3_prepare_v3(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags,
+ @NotNull OutputPointer.sqlite3_stmt outStmt,
+ @Nullable OutputPointer.Int32 pTailOffset
+ ){
+ return sqlite3_prepare_v3(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ prepFlags, outStmt, pTailOffset);
+ }
+
+ /**
+ Convenience overload which elides the seldom-used pTailOffset
+ parameter.
+ */
+ public static int sqlite3_prepare_v3(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ return sqlite3_prepare_v3(db.getNativePointer(), sqlUtf8, sqlUtf8.length,
+ prepFlags, outStmt, null);
+ }
+
+ /**
+ Convenience overload which elides the seldom-used pTailOffset
+ parameter and converts the given string to UTF-8 before passing
+ it on.
+ */
+ public static int sqlite3_prepare_v3(
+ @NotNull sqlite3 db, @NotNull String sql, int prepFlags,
+ @NotNull OutputPointer.sqlite3_stmt outStmt
+ ){
+ final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8);
+ return sqlite3_prepare_v3(db.getNativePointer(), utf8, utf8.length,
+ prepFlags, outStmt, null);
+ }
+
+ /**
+ Works identically to the sqlite3_stmt-returning sqlite3_prepare()
+ but uses sqlite3_prepare_v3().
+ */
+ public static sqlite3_stmt sqlite3_prepare_v3(
+ @NotNull sqlite3 db, @NotNull String sql, int prepFlags
+ ){
+ final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+ sqlite3_prepare_v3(db, sql, prepFlags, out);
+ return out.take();
+ }
+
+ /**
+ A convenience wrapper around sqlite3_prepare_v3() which accepts
+ an arbitrary amount of input provided as a UTF-8-encoded byte
+ array. It loops over the input bytes looking for
+ statements. Each one it finds is passed to p.call(), passing
+ ownership of it to that function. If p.call() returns 0, looping
+ continues, else the loop stops.
+
+
If p.call() throws, the exception is propagated.
+
+
How each statement is handled, including whether it is finalized
+ or not, is up to the callback object. e.g. the callback might
+ collect them for later use. If it does not collect them then it
+ must finalize them. See PrepareMultiCallback.Finalize for a
+ simple proxy which does that.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ int preFlags,
+ @NotNull PrepareMultiCallback p){
+ final OutputPointer.Int32 oTail = new OutputPointer.Int32();
+ int pos = 0, n = 1;
+ byte[] sqlChunk = sqlUtf8;
+ int rc = 0;
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ while(0==rc && pos 0){
+ sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
+ sqlChunk.length);
+ }
+ if( 0==sqlChunk.length ) break;
+ rc = sqlite3_prepare_v3(db, sqlChunk, preFlags, outStmt, oTail);
+ if( 0!=rc ) break;
+ pos = oTail.value;
+ stmt = outStmt.take();
+ if( null == stmt ){
+ // empty statement was parsed.
+ continue;
+ }
+ rc = p.call(stmt);
+ }
+ return rc;
+ }
+
+ /**
+ Convenience overload which accepts its SQL as a String and uses
+ no statement-preparation flags.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull byte[] sqlUtf8,
+ @NotNull PrepareMultiCallback p){
+ return sqlite3_prepare_multi(db, sqlUtf8, 0, p);
+ }
+
+ /**
+ Convenience overload which accepts its SQL as a String.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull String sql, int prepFlags,
+ @NotNull PrepareMultiCallback p){
+ return sqlite3_prepare_multi(
+ db, sql.getBytes(StandardCharsets.UTF_8), prepFlags, p
+ );
+ }
+
+ /**
+ Convenience overload which accepts its SQL as a String and uses
+ no statement-preparation flags.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull String sql,
+ @NotNull PrepareMultiCallback p){
+ return sqlite3_prepare_multi(db, sql, 0, p);
+ }
+
+ /**
+ Convenience overload which accepts its SQL as a String
+ array. They will be concatenated together as-is, with no
+ separator, and passed on to one of the other overloads.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull String[] sql, int prepFlags,
+ @NotNull PrepareMultiCallback p){
+ return sqlite3_prepare_multi(db, String.join("",sql), prepFlags, p);
+ }
+
+ /**
+ Convenience overload which uses no statement-preparation flags.
+ */
+ public static int sqlite3_prepare_multi(
+ @NotNull sqlite3 db, @NotNull String[] sql,
+ @NotNull PrepareMultiCallback p){
+ return sqlite3_prepare_multi(db, sql, 0, p);
+ }
+
+ static native int sqlite3_preupdate_blobwrite(@NotNull long ptrToDb);
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+ acts as a proxy for C's sqlite3_preupdate_blobwrite(), else it returns
+ SQLITE_MISUSE with no side effects.
+ */
+ public static int sqlite3_preupdate_blobwrite(@NotNull sqlite3 db){
+ return sqlite3_preupdate_blobwrite(db.getNativePointer());
+ }
+
+ static native int sqlite3_preupdate_count(@NotNull long ptrToDb);
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+ acts as a proxy for C's sqlite3_preupdate_count(), else it returns
+ SQLITE_MISUSE with no side effects.
+ */
+ public static int sqlite3_preupdate_count(@NotNull sqlite3 db){
+ return sqlite3_preupdate_count(db.getNativePointer());
+ }
+
+ static native int sqlite3_preupdate_depth(@NotNull long ptrToDb);
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+ acts as a proxy for C's sqlite3_preupdate_depth(), else it returns
+ SQLITE_MISUSE with no side effects.
+ */
+ public static int sqlite3_preupdate_depth(@NotNull sqlite3 db){
+ return sqlite3_preupdate_depth(db.getNativePointer());
+ }
+
+ static native PreupdateHookCallback sqlite3_preupdate_hook(
+ @NotNull long ptrToDb, @Nullable PreupdateHookCallback hook
+ );
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
+ acts as a proxy for C's sqlite3_preupdate_hook(), else it returns null
+ with no side effects.
+ */
+ public static PreupdateHookCallback sqlite3_preupdate_hook(
+ @NotNull sqlite3 db, @Nullable PreupdateHookCallback hook
+ ){
+ return sqlite3_preupdate_hook(db.getNativePointer(), hook);
+ }
+
+ static native int sqlite3_preupdate_new(@NotNull long ptrToDb, int col,
+ @NotNull OutputPointer.sqlite3_value out);
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined,
+ this acts as a proxy for C's sqlite3_preupdate_new(), else it
+ returns SQLITE_MISUSE with no side effects.
+ */
+ public static int sqlite3_preupdate_new(@NotNull sqlite3 db, int col,
+ @NotNull OutputPointer.sqlite3_value out){
+ return sqlite3_preupdate_new(db.getNativePointer(), col, out);
+ }
+
+ /**
+ Convenience wrapper for the 3-arg sqlite3_preupdate_new() which returns
+ null on error.
+ */
+ public static sqlite3_value sqlite3_preupdate_new(@NotNull sqlite3 db, int col){
+ final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value();
+ sqlite3_preupdate_new(db.getNativePointer(), col, out);
+ return out.take();
+ }
+
+ static native int sqlite3_preupdate_old(@NotNull long ptrToDb, int col,
+ @NotNull OutputPointer.sqlite3_value out);
+
+ /**
+ If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined,
+ this acts as a proxy for C's sqlite3_preupdate_old(), else it
+ returns SQLITE_MISUSE with no side effects.
+ */
+ public static int sqlite3_preupdate_old(@NotNull sqlite3 db, int col,
+ @NotNull OutputPointer.sqlite3_value out){
+ return sqlite3_preupdate_old(db.getNativePointer(), col, out);
+ }
+
+ /**
+ Convenience wrapper for the 3-arg sqlite3_preupdate_old() which returns
+ null on error.
+ */
+ public static sqlite3_value sqlite3_preupdate_old(@NotNull sqlite3 db, int col){
+ final OutputPointer.sqlite3_value out = new OutputPointer.sqlite3_value();
+ sqlite3_preupdate_old(db.getNativePointer(), col, out);
+ return out.take();
+ }
+
+ public static native void sqlite3_progress_handler(
+ @NotNull sqlite3 db, int n, @Nullable ProgressHandlerCallback h
+ );
+
+ public static native void sqlite3_randomness(byte[] target);
+
+ public static native int sqlite3_release_memory(int n);
+
+ public static native int sqlite3_reset(@NotNull sqlite3_stmt stmt);
+
+ /**
+ Works like the C API except that it has no side effects if auto
+ extensions are currently running. (The JNI-level list of
+ extensions cannot be manipulated while it is being traversed.)
+ */
+ public static native void sqlite3_reset_auto_extension();
+
+ public static native void sqlite3_result_double(
+ @NotNull sqlite3_context cx, double v
+ );
+
+ /**
+ The main sqlite3_result_error() impl of which all others are
+ proxies. eTextRep must be one of SQLITE_UTF8 or SQLITE_UTF16 and
+ msg must be encoded correspondingly. Any other eTextRep value
+ results in the C-level sqlite3_result_error() being called with a
+ complaint about the invalid argument.
+ */
+ static native void sqlite3_result_error(
+ @NotNull sqlite3_context cx, @NotNull byte[] msg, int eTextRep
+ );
+
+ public static void sqlite3_result_error(
+ @NotNull sqlite3_context cx, @NotNull byte[] utf8
+ ){
+ sqlite3_result_error(cx, utf8, SQLITE_UTF8);
+ }
+
+ public static void sqlite3_result_error(
+ @NotNull sqlite3_context cx, @NotNull String msg
+ ){
+ final byte[] utf8 = msg.getBytes(StandardCharsets.UTF_8);
+ sqlite3_result_error(cx, utf8, SQLITE_UTF8);
+ }
+
+ public static void sqlite3_result_error16(
+ @NotNull sqlite3_context cx, @NotNull byte[] utf16
+ ){
+ sqlite3_result_error(cx, utf16, SQLITE_UTF16);
+ }
+
+ public static void sqlite3_result_error16(
+ @NotNull sqlite3_context cx, @NotNull String msg
+ ){
+ final byte[] utf16 = msg.getBytes(StandardCharsets.UTF_16);
+ sqlite3_result_error(cx, utf16, SQLITE_UTF16);
+ }
+
+ /**
+ Equivalent to passing e.toString() to {@link
+ #sqlite3_result_error(sqlite3_context,String)}. Note that
+ toString() is used instead of getMessage() because the former
+ prepends the exception type name to the message.
+ */
+ public static void sqlite3_result_error(
+ @NotNull sqlite3_context cx, @NotNull Exception e
+ ){
+ sqlite3_result_error(cx, e.toString());
+ }
+
+ public static native void sqlite3_result_error_toobig(
+ @NotNull sqlite3_context cx
+ );
+
+ public static native void sqlite3_result_error_nomem(
+ @NotNull sqlite3_context cx
+ );
+
+ public static native void sqlite3_result_error_code(
+ @NotNull sqlite3_context cx, int c
+ );
+
+ public static native void sqlite3_result_null(
+ @NotNull sqlite3_context cx
+ );
+
+ public static native void sqlite3_result_int(
+ @NotNull sqlite3_context cx, int v
+ );
+
+ public static native void sqlite3_result_int64(
+ @NotNull sqlite3_context cx, long v
+ );
+
+ /**
+ Binds the SQL result to the given object, or {@link
+ #sqlite3_result_null} if {@code o} is null. Use {@link
+ #sqlite3_value_java_object} to fetch it.
+
+
This is implemented in terms of C's sqlite3_result_pointer(),
+ but that function is not exposed to JNI because (A)
+ cross-language semantic mismatch and (B) Java doesn't need that
+ argument for its intended purpose (type safety).
+
+
Note that there is no sqlite3_column_java_object(), as the
+ C-level API has no sqlite3_column_pointer() to proxy.
+
+ @see #sqlite3_value_java_object
+ @see #sqlite3_bind_java_object
+ */
+ public static native void sqlite3_result_java_object(
+ @NotNull sqlite3_context cx, @NotNull Object o
+ );
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @NotNull Boolean v
+ ){
+ sqlite3_result_int(cx, v ? 1 : 0);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, boolean v
+ ){
+ sqlite3_result_int(cx, v ? 1 : 0);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @NotNull Double v
+ ){
+ sqlite3_result_double(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, double v
+ ){
+ sqlite3_result_double(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @NotNull Integer v
+ ){
+ sqlite3_result_int(cx, v);
+ }
+
+ public static void sqlite3_result_set(@NotNull sqlite3_context cx, int v){
+ sqlite3_result_int(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @NotNull Long v
+ ){
+ sqlite3_result_int64(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, long v
+ ){
+ sqlite3_result_int64(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @Nullable String v
+ ){
+ if( null==v ) sqlite3_result_null(cx);
+ else sqlite3_result_text(cx, v);
+ }
+
+ public static void sqlite3_result_set(
+ @NotNull sqlite3_context cx, @Nullable byte[] blob
+ ){
+ if( null==blob ) sqlite3_result_null(cx);
+ else sqlite3_result_blob(cx, blob, blob.length);
+ }
+
+ public static native void sqlite3_result_value(
+ @NotNull sqlite3_context cx, @NotNull sqlite3_value v
+ );
+
+ public static native void sqlite3_result_zeroblob(
+ @NotNull sqlite3_context cx, int n
+ );
+
+ public static native int sqlite3_result_zeroblob64(
+ @NotNull sqlite3_context cx, long n
+ );
+
+ /**
+ This overload is private because its final parameter is arguably
+ unnecessary in Java.
+ */
+ private static native void sqlite3_result_blob(
+ @NotNull sqlite3_context cx, @Nullable byte[] blob, int maxLen
+ );
+
+ public static void sqlite3_result_blob(
+ @NotNull sqlite3_context cx, @Nullable byte[] blob
+ ){
+ sqlite3_result_blob(cx, blob, (int)(null==blob ? 0 : blob.length));
+ }
+
+ /**
+ Binds the given text using C's sqlite3_result_blob64() unless:
+
+
+
+
@param blob is null: translates to sqlite3_result_null()
+
+
@param blob is too large: translates to
+ sqlite3_result_error_toobig()
+
+
+
+
If @param maxLen is larger than blob.length, it is truncated
+ to that value. If it is negative, results are undefined.
+
+
This overload is private because its final parameter is
+ arguably unnecessary in Java.
+ */
+ private static native void sqlite3_result_blob64(
+ @NotNull sqlite3_context cx, @Nullable byte[] blob, long maxLen
+ );
+
+ public static void sqlite3_result_blob64(
+ @NotNull sqlite3_context cx, @Nullable byte[] blob
+ ){
+ sqlite3_result_blob64(cx, blob, (long)(null==blob ? 0 : blob.length));
+ }
+
+ /**
+ This overload is private because its final parameter is
+ arguably unnecessary in Java.
+ */
+ private static native void sqlite3_result_text(
+ @NotNull sqlite3_context cx, @Nullable byte[] utf8, int maxLen
+ );
+
+ public static void sqlite3_result_text(
+ @NotNull sqlite3_context cx, @Nullable byte[] utf8
+ ){
+ sqlite3_result_text(cx, utf8, null==utf8 ? 0 : utf8.length);
+ }
+
+ public static void sqlite3_result_text(
+ @NotNull sqlite3_context cx, @Nullable String text
+ ){
+ if(null == text) sqlite3_result_null(cx);
+ else{
+ final byte[] utf8 = text.getBytes(StandardCharsets.UTF_8);
+ sqlite3_result_text(cx, utf8, utf8.length);
+ }
+ }
+
+ /**
+ Binds the given text using C's sqlite3_result_text64() unless:
+
+
+
+
text is null: translates to a call to sqlite3_result_null()
+
+
text is too large: translates to a call to
+ {@link #sqlite3_result_error_toobig}
+
+
The @param encoding argument has an invalid value: translates to
+ {@link sqlite3_result_error_code} with code SQLITE_FORMAT.
+
+
+
+ If maxLength (in bytes, not characters) is larger than
+ text.length, it is silently truncated to text.length. If it is
+ negative, results are undefined. If text is null, the subsequent
+ arguments are ignored.
+
+ This overload is private because its maxLength parameter is
+ arguably unnecessary in Java.
+ */
+ private static native void sqlite3_result_text64(
+ @NotNull sqlite3_context cx, @Nullable byte[] text,
+ long maxLength, int encoding
+ );
+
+ /**
+ Sets the current UDF result to the given bytes, which are assumed
+ be encoded in UTF-16 using the platform's byte order.
+ */
+ public static void sqlite3_result_text16(
+ @NotNull sqlite3_context cx, @Nullable byte[] utf16
+ ){
+ if(null == utf16) sqlite3_result_null(cx);
+ else sqlite3_result_text64(cx, utf16, utf16.length, SQLITE_UTF16);
+ }
+
+ public static void sqlite3_result_text16(
+ @NotNull sqlite3_context cx, @Nullable String text
+ ){
+ if(null == text) sqlite3_result_null(cx);
+ else{
+ final byte[] b = text.getBytes(StandardCharsets.UTF_16);
+ sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16);
+ }
+ }
+
+ static native RollbackHookCallback sqlite3_rollback_hook(
+ @NotNull long ptrToDb, @Nullable RollbackHookCallback hook
+ );
+
+ public static RollbackHookCallback sqlite3_rollback_hook(
+ @NotNull sqlite3 db, @Nullable RollbackHookCallback hook
+ ){
+ return sqlite3_rollback_hook(db.getNativePointer(), hook);
+ }
+
+ public static native int sqlite3_set_authorizer(
+ @NotNull sqlite3 db, @Nullable AuthorizerCallback auth
+ );
+
+ public static native void sqlite3_set_auxdata(
+ @NotNull sqlite3_context cx, int n, @Nullable Object data
+ );
+
+ public static native void sqlite3_set_last_insert_rowid(
+ @NotNull sqlite3 db, long rowid
+ );
+
+
+ /**
+ In addition to calling the C-level sqlite3_shutdown(), the JNI
+ binding also cleans up all stale per-thread state managed by the
+ library, as well as any registered auto-extensions, and frees up
+ various bits of memory. Calling this while database handles or
+ prepared statements are still active will leak resources. Trying
+ to use those objects after this routine is called invoked
+ undefined behavior.
+ */
+ public static synchronized native int sqlite3_shutdown();
+
+ public static native int sqlite3_sleep(int ms);
+
+ public static native String sqlite3_sourceid();
+
+ public static native String sqlite3_sql(@NotNull sqlite3_stmt stmt);
+
+ //! Consider removing this. We can use sqlite3_status64() instead,
+ // or use that one's impl with this one's name.
+ public static native int sqlite3_status(
+ int op, @NotNull OutputPointer.Int32 pCurrent,
+ @NotNull OutputPointer.Int32 pHighwater, boolean reset
+ );
+
+ public static native int sqlite3_status64(
+ int op, @NotNull OutputPointer.Int64 pCurrent,
+ @NotNull OutputPointer.Int64 pHighwater, boolean reset
+ );
+
+ public static native int sqlite3_step(@NotNull sqlite3_stmt stmt);
+
+ public static native boolean sqlite3_stmt_busy(@NotNull sqlite3_stmt stmt);
+
+ static native int sqlite3_stmt_explain(@NotNull long ptrToStmt, int op);
+
+ public static int sqlite3_stmt_explain(@NotNull sqlite3_stmt stmt, int op){
+ return sqlite3_stmt_explain(stmt.getNativePointer(), op);
+ }
+
+ static native int sqlite3_stmt_isexplain(@NotNull long ptrToStmt);
+
+ public static int sqlite3_stmt_isexplain(@NotNull sqlite3_stmt stmt){
+ return sqlite3_stmt_isexplain(stmt.getNativePointer());
+ }
+
+ public static native boolean sqlite3_stmt_readonly(@NotNull sqlite3_stmt stmt);
+
+ public static native int sqlite3_stmt_status(
+ @NotNull sqlite3_stmt stmt, int op, boolean reset
+ );
+
+ /**
+ Internal impl of the public sqlite3_strglob() method. Neither
+ argument may be null and both must be NUL-terminated UTF-8.
+
+ This overload is private because: (A) to keep users from
+ inadvertently passing non-NUL-terminated byte arrays (an easy
+ thing to do). (B) it is cheaper to NUL-terminate the
+ String-to-byte-array conversion in the Java implementation
+ (sqlite3_strglob(String,String)) than to do that in C, so that
+ signature is the public-facing one.
+ */
+ private static native int sqlite3_strglob(
+ @NotNull byte[] glob, @NotNull byte[] nullTerminatedUtf8
+ );
+
+ public static int sqlite3_strglob(
+ @NotNull String glob, @NotNull String txt
+ ){
+ return sqlite3_strglob(nulTerminateUtf8(glob),
+ nulTerminateUtf8(txt));
+ }
+
+ /**
+ The LIKE counterpart of the private sqlite3_strglob() method.
+ */
+ private static native int sqlite3_strlike(
+ @NotNull byte[] glob, @NotNull byte[] nullTerminatedUtf8,
+ int escChar
+ );
+
+ public static int sqlite3_strlike(
+ @NotNull String glob, @NotNull String txt, char escChar
+ ){
+ return sqlite3_strlike(nulTerminateUtf8(glob),
+ nulTerminateUtf8(txt),
+ (int)escChar);
+ }
+
+ static native int sqlite3_system_errno(@NotNull long ptrToDb);
+
+ public static int sqlite3_system_errno(@NotNull sqlite3 db){
+ return sqlite3_system_errno(db.getNativePointer());
+ }
+
+ public static native int sqlite3_table_column_metadata(
+ @NotNull sqlite3 db, @NotNull String zDbName,
+ @NotNull String zTableName, @NotNull String zColumnName,
+ @Nullable OutputPointer.String pzDataType,
+ @Nullable OutputPointer.String pzCollSeq,
+ @Nullable OutputPointer.Bool pNotNull,
+ @Nullable OutputPointer.Bool pPrimaryKey,
+ @Nullable OutputPointer.Bool pAutoinc
+ );
+
+ /**
+ Convenience overload which returns its results via a single
+ output object. If this function returns non-0 (error), the the
+ contents of the output object are not modified.
+ */
+ public static int sqlite3_table_column_metadata(
+ @NotNull sqlite3 db, @NotNull String zDbName,
+ @NotNull String zTableName, @NotNull String zColumnName,
+ @NotNull TableColumnMetadata out){
+ return sqlite3_table_column_metadata(
+ db, zDbName, zTableName, zColumnName,
+ out.pzDataType, out.pzCollSeq, out.pNotNull,
+ out.pPrimaryKey, out.pAutoinc);
+ }
+
+ /**
+ Convenience overload which returns the column metadata object on
+ success and null on error.
+ */
+ public static TableColumnMetadata sqlite3_table_column_metadata(
+ @NotNull sqlite3 db, @NotNull String zDbName,
+ @NotNull String zTableName, @NotNull String zColumnName){
+ final TableColumnMetadata out = new TableColumnMetadata();
+ return 0==sqlite3_table_column_metadata(
+ db, zDbName, zTableName, zColumnName, out
+ ) ? out : null;
+ }
+
+ public static native int sqlite3_threadsafe();
+
+ static native int sqlite3_total_changes(@NotNull long ptrToDb);
+
+ public static int sqlite3_total_changes(@NotNull sqlite3 db){
+ return sqlite3_total_changes(db.getNativePointer());
+ }
+
+ static native long sqlite3_total_changes64(@NotNull long ptrToDb);
+
+ public static long sqlite3_total_changes64(@NotNull sqlite3 db){
+ return sqlite3_total_changes64(db.getNativePointer());
+ }
+
+ /**
+ Works like C's sqlite3_trace_v2() except that the 3rd argument to that
+ function is elided here because the roles of that functions' 3rd and 4th
+ arguments are encapsulated in the final argument to this function.
+
+
Unlike the C API, which is documented as always returning 0,
+ this implementation returns non-0 if initialization of the tracer
+ mapping state fails (e.g. on OOM).
+ */
+ public static native int sqlite3_trace_v2(
+ @NotNull sqlite3 db, int traceMask, @Nullable TraceV2Callback tracer
+ );
+
+ public static native int sqlite3_txn_state(
+ @NotNull sqlite3 db, @Nullable String zSchema
+ );
+
+ static native UpdateHookCallback sqlite3_update_hook(
+ @NotNull long ptrToDb, @Nullable UpdateHookCallback hook
+ );
+
+ public static UpdateHookCallback sqlite3_update_hook(
+ @NotNull sqlite3 db, @Nullable UpdateHookCallback hook
+ ){
+ return sqlite3_update_hook(db.getNativePointer(), hook);
+ }
+
+ /*
+ Note that:
+
+ void * sqlite3_user_data(sqlite3_context*)
+
+ Is not relevant in the JNI binding, as its feature is replaced by
+ the ability to pass an object, including any relevant state, to
+ sqlite3_create_function().
+ */
+
+ static native byte[] sqlite3_value_blob(@NotNull long ptrToValue);
+
+ public static byte[] sqlite3_value_blob(@NotNull sqlite3_value v){
+ return sqlite3_value_blob(v.getNativePointer());
+ }
+
+ static native int sqlite3_value_bytes(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_bytes(@NotNull sqlite3_value v){
+ return sqlite3_value_bytes(v.getNativePointer());
+ }
+
+ static native int sqlite3_value_bytes16(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_bytes16(@NotNull sqlite3_value v){
+ return sqlite3_value_bytes16(v.getNativePointer());
+ }
+
+ static native double sqlite3_value_double(@NotNull long ptrToValue);
+
+ public static double sqlite3_value_double(@NotNull sqlite3_value v){
+ return sqlite3_value_double(v.getNativePointer());
+ }
+
+ static native sqlite3_value sqlite3_value_dup(@NotNull long ptrToValue);
+
+ public static sqlite3_value sqlite3_value_dup(@NotNull sqlite3_value v){
+ return sqlite3_value_dup(v.getNativePointer());
+ }
+
+ static native int sqlite3_value_encoding(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_encoding(@NotNull sqlite3_value v){
+ return sqlite3_value_encoding(v.getNativePointer());
+ }
+
+ static native void sqlite3_value_free(@Nullable long ptrToValue);
+
+ public static void sqlite3_value_free(@Nullable sqlite3_value v){
+ sqlite3_value_free(v.getNativePointer());
+ }
+
+ static native boolean sqlite3_value_frombind(@NotNull long ptrToValue);
+
+ public static boolean sqlite3_value_frombind(@NotNull sqlite3_value v){
+ return sqlite3_value_frombind(v.getNativePointer());
+ }
+
+ static native int sqlite3_value_int(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_int(@NotNull sqlite3_value v){
+ return sqlite3_value_int(v.getNativePointer());
+ }
+
+ static native long sqlite3_value_int64(@NotNull long ptrToValue);
+
+ public static long sqlite3_value_int64(@NotNull sqlite3_value v){
+ return sqlite3_value_int64(v.getNativePointer());
+ }
+
+ static native Object sqlite3_value_java_object(@NotNull long ptrToValue);
+
+ /**
+ If the given value was set using {@link
+ #sqlite3_result_java_object} then this function returns that
+ object, else it returns null.
+
+
It is up to the caller to inspect the object to determine its
+ type, and cast it if necessary.
+ */
+ public static Object sqlite3_value_java_object(@NotNull sqlite3_value v){
+ return sqlite3_value_java_object(v.getNativePointer());
+ }
+
+ /**
+ A variant of sqlite3_value_java_object() which returns the
+ fetched object cast to T if the object is an instance of the
+ given Class, else it returns null.
+ */
+ @SuppressWarnings("unchecked")
+ public static T sqlite3_value_java_casted(@NotNull sqlite3_value v,
+ @NotNull Class type){
+ final Object o = sqlite3_value_java_object(v);
+ return type.isInstance(o) ? (T)o : null;
+ }
+
+ static native int sqlite3_value_nochange(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_nochange(@NotNull sqlite3_value v){
+ return sqlite3_value_nochange(v.getNativePointer());
+ }
+
+ static native int sqlite3_value_numeric_type(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_numeric_type(@NotNull sqlite3_value v){
+ return sqlite3_value_numeric_type(v.getNativePointer());
+ }
+
+ static native int sqlite3_value_subtype(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_subtype(@NotNull sqlite3_value v){
+ return sqlite3_value_subtype(v.getNativePointer());
+ }
+
+ static native byte[] sqlite3_value_text(@NotNull long ptrToValue);
+
+ /**
+ Functions identially to the C API, and this note is just to
+ stress that the returned bytes are encoded as UTF-8. It returns
+ null if the underlying C-level sqlite3_value_text() returns NULL
+ or on allocation error.
+ */
+ public static byte[] sqlite3_value_text(@NotNull sqlite3_value v){
+ return sqlite3_value_text(v.getNativePointer());
+ }
+
+ static native String sqlite3_value_text16(@NotNull long ptrToValue);
+
+ public static String sqlite3_value_text16(@NotNull sqlite3_value v){
+ return sqlite3_value_text16(v.getNativePointer());
+ }
+
+ static native int sqlite3_value_type(@NotNull long ptrToValue);
+
+ public static int sqlite3_value_type(@NotNull sqlite3_value v){
+ return sqlite3_value_type(v.getNativePointer());
+ }
+
+ /**
+ This is NOT part of the public API. It exists solely as a place
+ for this code's developers to collect internal metrics and such.
+ It has no stable interface. It may go way or change behavior at
+ any time.
+ */
+ public static native void sqlite3_jni_internal_details();
+
+ //////////////////////////////////////////////////////////////////////
+ // SQLITE_... constants follow...
+
+ // version info
+ public static final int SQLITE_VERSION_NUMBER = sqlite3_libversion_number();
+ public static final String SQLITE_VERSION = sqlite3_libversion();
+ public static final String SQLITE_SOURCE_ID = sqlite3_sourceid();
+
+ // access
+ public static final int SQLITE_ACCESS_EXISTS = 0;
+ public static final int SQLITE_ACCESS_READWRITE = 1;
+ public static final int SQLITE_ACCESS_READ = 2;
+
+ // authorizer
+ public static final int SQLITE_DENY = 1;
+ public static final int SQLITE_IGNORE = 2;
+ public static final int SQLITE_CREATE_INDEX = 1;
+ public static final int SQLITE_CREATE_TABLE = 2;
+ public static final int SQLITE_CREATE_TEMP_INDEX = 3;
+ public static final int SQLITE_CREATE_TEMP_TABLE = 4;
+ public static final int SQLITE_CREATE_TEMP_TRIGGER = 5;
+ public static final int SQLITE_CREATE_TEMP_VIEW = 6;
+ public static final int SQLITE_CREATE_TRIGGER = 7;
+ public static final int SQLITE_CREATE_VIEW = 8;
+ public static final int SQLITE_DELETE = 9;
+ public static final int SQLITE_DROP_INDEX = 10;
+ public static final int SQLITE_DROP_TABLE = 11;
+ public static final int SQLITE_DROP_TEMP_INDEX = 12;
+ public static final int SQLITE_DROP_TEMP_TABLE = 13;
+ public static final int SQLITE_DROP_TEMP_TRIGGER = 14;
+ public static final int SQLITE_DROP_TEMP_VIEW = 15;
+ public static final int SQLITE_DROP_TRIGGER = 16;
+ public static final int SQLITE_DROP_VIEW = 17;
+ public static final int SQLITE_INSERT = 18;
+ public static final int SQLITE_PRAGMA = 19;
+ public static final int SQLITE_READ = 20;
+ public static final int SQLITE_SELECT = 21;
+ public static final int SQLITE_TRANSACTION = 22;
+ public static final int SQLITE_UPDATE = 23;
+ public static final int SQLITE_ATTACH = 24;
+ public static final int SQLITE_DETACH = 25;
+ public static final int SQLITE_ALTER_TABLE = 26;
+ public static final int SQLITE_REINDEX = 27;
+ public static final int SQLITE_ANALYZE = 28;
+ public static final int SQLITE_CREATE_VTABLE = 29;
+ public static final int SQLITE_DROP_VTABLE = 30;
+ public static final int SQLITE_FUNCTION = 31;
+ public static final int SQLITE_SAVEPOINT = 32;
+ public static final int SQLITE_RECURSIVE = 33;
+
+ // blob finalizers: these should, because they are treated as
+ // special pointer values in C, ideally have the same sizeof() as
+ // the platform's (void*), but we can't know that size from here.
+ public static final long SQLITE_STATIC = 0;
+ public static final long SQLITE_TRANSIENT = -1;
+
+ // changeset
+ public static final int SQLITE_CHANGESETSTART_INVERT = 2;
+ public static final int SQLITE_CHANGESETAPPLY_NOSAVEPOINT = 1;
+ public static final int SQLITE_CHANGESETAPPLY_INVERT = 2;
+ public static final int SQLITE_CHANGESETAPPLY_IGNORENOOP = 4;
+ public static final int SQLITE_CHANGESET_DATA = 1;
+ public static final int SQLITE_CHANGESET_NOTFOUND = 2;
+ public static final int SQLITE_CHANGESET_CONFLICT = 3;
+ public static final int SQLITE_CHANGESET_CONSTRAINT = 4;
+ public static final int SQLITE_CHANGESET_FOREIGN_KEY = 5;
+ public static final int SQLITE_CHANGESET_OMIT = 0;
+ public static final int SQLITE_CHANGESET_REPLACE = 1;
+ public static final int SQLITE_CHANGESET_ABORT = 2;
+
+ // config
+ public static final int SQLITE_CONFIG_SINGLETHREAD = 1;
+ public static final int SQLITE_CONFIG_MULTITHREAD = 2;
+ public static final int SQLITE_CONFIG_SERIALIZED = 3;
+ public static final int SQLITE_CONFIG_MALLOC = 4;
+ public static final int SQLITE_CONFIG_GETMALLOC = 5;
+ public static final int SQLITE_CONFIG_SCRATCH = 6;
+ public static final int SQLITE_CONFIG_PAGECACHE = 7;
+ public static final int SQLITE_CONFIG_HEAP = 8;
+ public static final int SQLITE_CONFIG_MEMSTATUS = 9;
+ public static final int SQLITE_CONFIG_MUTEX = 10;
+ public static final int SQLITE_CONFIG_GETMUTEX = 11;
+ public static final int SQLITE_CONFIG_LOOKASIDE = 13;
+ public static final int SQLITE_CONFIG_PCACHE = 14;
+ public static final int SQLITE_CONFIG_GETPCACHE = 15;
+ public static final int SQLITE_CONFIG_LOG = 16;
+ public static final int SQLITE_CONFIG_URI = 17;
+ public static final int SQLITE_CONFIG_PCACHE2 = 18;
+ public static final int SQLITE_CONFIG_GETPCACHE2 = 19;
+ public static final int SQLITE_CONFIG_COVERING_INDEX_SCAN = 20;
+ public static final int SQLITE_CONFIG_SQLLOG = 21;
+ public static final int SQLITE_CONFIG_MMAP_SIZE = 22;
+ public static final int SQLITE_CONFIG_WIN32_HEAPSIZE = 23;
+ public static final int SQLITE_CONFIG_PCACHE_HDRSZ = 24;
+ public static final int SQLITE_CONFIG_PMASZ = 25;
+ public static final int SQLITE_CONFIG_STMTJRNL_SPILL = 26;
+ public static final int SQLITE_CONFIG_SMALL_MALLOC = 27;
+ public static final int SQLITE_CONFIG_SORTERREF_SIZE = 28;
+ public static final int SQLITE_CONFIG_MEMDB_MAXSIZE = 29;
+
+ // data types
+ public static final int SQLITE_INTEGER = 1;
+ public static final int SQLITE_FLOAT = 2;
+ public static final int SQLITE_TEXT = 3;
+ public static final int SQLITE_BLOB = 4;
+ public static final int SQLITE_NULL = 5;
+
+ // db config
+ public static final int SQLITE_DBCONFIG_MAINDBNAME = 1000;
+ public static final int SQLITE_DBCONFIG_LOOKASIDE = 1001;
+ public static final int SQLITE_DBCONFIG_ENABLE_FKEY = 1002;
+ public static final int SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003;
+ public static final int SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004;
+ public static final int SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005;
+ public static final int SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006;
+ public static final int SQLITE_DBCONFIG_ENABLE_QPSG = 1007;
+ public static final int SQLITE_DBCONFIG_TRIGGER_EQP = 1008;
+ public static final int SQLITE_DBCONFIG_RESET_DATABASE = 1009;
+ public static final int SQLITE_DBCONFIG_DEFENSIVE = 1010;
+ public static final int SQLITE_DBCONFIG_WRITABLE_SCHEMA = 1011;
+ public static final int SQLITE_DBCONFIG_LEGACY_ALTER_TABLE = 1012;
+ public static final int SQLITE_DBCONFIG_DQS_DML = 1013;
+ public static final int SQLITE_DBCONFIG_DQS_DDL = 1014;
+ public static final int SQLITE_DBCONFIG_ENABLE_VIEW = 1015;
+ public static final int SQLITE_DBCONFIG_LEGACY_FILE_FORMAT = 1016;
+ public static final int SQLITE_DBCONFIG_TRUSTED_SCHEMA = 1017;
+ public static final int SQLITE_DBCONFIG_STMT_SCANSTATUS = 1018;
+ public static final int SQLITE_DBCONFIG_REVERSE_SCANORDER = 1019;
+ public static final int SQLITE_DBCONFIG_MAX = 1019;
+
+ // db status
+ public static final int SQLITE_DBSTATUS_LOOKASIDE_USED = 0;
+ public static final int SQLITE_DBSTATUS_CACHE_USED = 1;
+ public static final int SQLITE_DBSTATUS_SCHEMA_USED = 2;
+ public static final int SQLITE_DBSTATUS_STMT_USED = 3;
+ public static final int SQLITE_DBSTATUS_LOOKASIDE_HIT = 4;
+ public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5;
+ public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6;
+ public static final int SQLITE_DBSTATUS_CACHE_HIT = 7;
+ public static final int SQLITE_DBSTATUS_CACHE_MISS = 8;
+ public static final int SQLITE_DBSTATUS_CACHE_WRITE = 9;
+ public static final int SQLITE_DBSTATUS_DEFERRED_FKS = 10;
+ public static final int SQLITE_DBSTATUS_CACHE_USED_SHARED = 11;
+ public static final int SQLITE_DBSTATUS_CACHE_SPILL = 12;
+ public static final int SQLITE_DBSTATUS_MAX = 12;
+
+ // encodings
+ public static final int SQLITE_UTF8 = 1;
+ public static final int SQLITE_UTF16LE = 2;
+ public static final int SQLITE_UTF16BE = 3;
+ public static final int SQLITE_UTF16 = 4;
+ public static final int SQLITE_UTF16_ALIGNED = 8;
+
+ // fcntl
+ public static final int SQLITE_FCNTL_LOCKSTATE = 1;
+ public static final int SQLITE_FCNTL_GET_LOCKPROXYFILE = 2;
+ public static final int SQLITE_FCNTL_SET_LOCKPROXYFILE = 3;
+ public static final int SQLITE_FCNTL_LAST_ERRNO = 4;
+ public static final int SQLITE_FCNTL_SIZE_HINT = 5;
+ public static final int SQLITE_FCNTL_CHUNK_SIZE = 6;
+ public static final int SQLITE_FCNTL_FILE_POINTER = 7;
+ public static final int SQLITE_FCNTL_SYNC_OMITTED = 8;
+ public static final int SQLITE_FCNTL_WIN32_AV_RETRY = 9;
+ public static final int SQLITE_FCNTL_PERSIST_WAL = 10;
+ public static final int SQLITE_FCNTL_OVERWRITE = 11;
+ public static final int SQLITE_FCNTL_VFSNAME = 12;
+ public static final int SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13;
+ public static final int SQLITE_FCNTL_PRAGMA = 14;
+ public static final int SQLITE_FCNTL_BUSYHANDLER = 15;
+ public static final int SQLITE_FCNTL_TEMPFILENAME = 16;
+ public static final int SQLITE_FCNTL_MMAP_SIZE = 18;
+ public static final int SQLITE_FCNTL_TRACE = 19;
+ public static final int SQLITE_FCNTL_HAS_MOVED = 20;
+ public static final int SQLITE_FCNTL_SYNC = 21;
+ public static final int SQLITE_FCNTL_COMMIT_PHASETWO = 22;
+ public static final int SQLITE_FCNTL_WIN32_SET_HANDLE = 23;
+ public static final int SQLITE_FCNTL_WAL_BLOCK = 24;
+ public static final int SQLITE_FCNTL_ZIPVFS = 25;
+ public static final int SQLITE_FCNTL_RBU = 26;
+ public static final int SQLITE_FCNTL_VFS_POINTER = 27;
+ public static final int SQLITE_FCNTL_JOURNAL_POINTER = 28;
+ public static final int SQLITE_FCNTL_WIN32_GET_HANDLE = 29;
+ public static final int SQLITE_FCNTL_PDB = 30;
+ public static final int SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = 31;
+ public static final int SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = 32;
+ public static final int SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = 33;
+ public static final int SQLITE_FCNTL_LOCK_TIMEOUT = 34;
+ public static final int SQLITE_FCNTL_DATA_VERSION = 35;
+ public static final int SQLITE_FCNTL_SIZE_LIMIT = 36;
+ public static final int SQLITE_FCNTL_CKPT_DONE = 37;
+ public static final int SQLITE_FCNTL_RESERVE_BYTES = 38;
+ public static final int SQLITE_FCNTL_CKPT_START = 39;
+ public static final int SQLITE_FCNTL_EXTERNAL_READER = 40;
+ public static final int SQLITE_FCNTL_CKSM_FILE = 41;
+ public static final int SQLITE_FCNTL_RESET_CACHE = 42;
+
+ // flock
+ public static final int SQLITE_LOCK_NONE = 0;
+ public static final int SQLITE_LOCK_SHARED = 1;
+ public static final int SQLITE_LOCK_RESERVED = 2;
+ public static final int SQLITE_LOCK_PENDING = 3;
+ public static final int SQLITE_LOCK_EXCLUSIVE = 4;
+
+ // iocap
+ public static final int SQLITE_IOCAP_ATOMIC = 1;
+ public static final int SQLITE_IOCAP_ATOMIC512 = 2;
+ public static final int SQLITE_IOCAP_ATOMIC1K = 4;
+ public static final int SQLITE_IOCAP_ATOMIC2K = 8;
+ public static final int SQLITE_IOCAP_ATOMIC4K = 16;
+ public static final int SQLITE_IOCAP_ATOMIC8K = 32;
+ public static final int SQLITE_IOCAP_ATOMIC16K = 64;
+ public static final int SQLITE_IOCAP_ATOMIC32K = 128;
+ public static final int SQLITE_IOCAP_ATOMIC64K = 256;
+ public static final int SQLITE_IOCAP_SAFE_APPEND = 512;
+ public static final int SQLITE_IOCAP_SEQUENTIAL = 1024;
+ public static final int SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 2048;
+ public static final int SQLITE_IOCAP_POWERSAFE_OVERWRITE = 4096;
+ public static final int SQLITE_IOCAP_IMMUTABLE = 8192;
+ public static final int SQLITE_IOCAP_BATCH_ATOMIC = 16384;
+
+ // limits
+ public static final int SQLITE_LIMIT_LENGTH = 0;
+ public static final int SQLITE_LIMIT_SQL_LENGTH = 1;
+ public static final int SQLITE_LIMIT_COLUMN = 2;
+ public static final int SQLITE_LIMIT_EXPR_DEPTH = 3;
+ public static final int SQLITE_LIMIT_COMPOUND_SELECT = 4;
+ public static final int SQLITE_LIMIT_VDBE_OP = 5;
+ public static final int SQLITE_LIMIT_FUNCTION_ARG = 6;
+ public static final int SQLITE_LIMIT_ATTACHED = 7;
+ public static final int SQLITE_LIMIT_LIKE_PATTERN_LENGTH = 8;
+ public static final int SQLITE_LIMIT_VARIABLE_NUMBER = 9;
+ public static final int SQLITE_LIMIT_TRIGGER_DEPTH = 10;
+ public static final int SQLITE_LIMIT_WORKER_THREADS = 11;
+
+ // open flags
+
+ public static final int SQLITE_OPEN_READONLY = 0x00000001 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_READWRITE = 0x00000002 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_CREATE = 0x00000004 /* Ok for sqlite3_open_v2() */;
+ //public static final int SQLITE_OPEN_DELETEONCLOSE = 0x00000008 /* VFS only */;
+ //public static final int SQLITE_OPEN_EXCLUSIVE = 0x00000010 /* VFS only */;
+ //public static final int SQLITE_OPEN_AUTOPROXY = 0x00000020 /* VFS only */;
+ public static final int SQLITE_OPEN_URI = 0x00000040 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_MEMORY = 0x00000080 /* Ok for sqlite3_open_v2() */;
+ //public static final int SQLITE_OPEN_MAIN_DB = 0x00000100 /* VFS only */;
+ //public static final int SQLITE_OPEN_TEMP_DB = 0x00000200 /* VFS only */;
+ //public static final int SQLITE_OPEN_TRANSIENT_DB = 0x00000400 /* VFS only */;
+ //public static final int SQLITE_OPEN_MAIN_JOURNAL = 0x00000800 /* VFS only */;
+ //public static final int SQLITE_OPEN_TEMP_JOURNAL = 0x00001000 /* VFS only */;
+ //public static final int SQLITE_OPEN_SUBJOURNAL = 0x00002000 /* VFS only */;
+ //public static final int SQLITE_OPEN_SUPER_JOURNAL = 0x00004000 /* VFS only */;
+ public static final int SQLITE_OPEN_NOMUTEX = 0x00008000 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_FULLMUTEX = 0x00010000 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_SHAREDCACHE = 0x00020000 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_PRIVATECACHE = 0x00040000 /* Ok for sqlite3_open_v2() */;
+ //public static final int SQLITE_OPEN_WAL = 0x00080000 /* VFS only */;
+ public static final int SQLITE_OPEN_NOFOLLOW = 0x01000000 /* Ok for sqlite3_open_v2() */;
+ public static final int SQLITE_OPEN_EXRESCODE = 0x02000000 /* Extended result codes */;
+
+ // prepare flags
+ public static final int SQLITE_PREPARE_PERSISTENT = 1;
+ public static final int SQLITE_PREPARE_NORMALIZE = 2;
+ public static final int SQLITE_PREPARE_NO_VTAB = 4;
+
+ // result codes
+ public static final int SQLITE_OK = 0;
+ public static final int SQLITE_ERROR = 1;
+ public static final int SQLITE_INTERNAL = 2;
+ public static final int SQLITE_PERM = 3;
+ public static final int SQLITE_ABORT = 4;
+ public static final int SQLITE_BUSY = 5;
+ public static final int SQLITE_LOCKED = 6;
+ public static final int SQLITE_NOMEM = 7;
+ public static final int SQLITE_READONLY = 8;
+ public static final int SQLITE_INTERRUPT = 9;
+ public static final int SQLITE_IOERR = 10;
+ public static final int SQLITE_CORRUPT = 11;
+ public static final int SQLITE_NOTFOUND = 12;
+ public static final int SQLITE_FULL = 13;
+ public static final int SQLITE_CANTOPEN = 14;
+ public static final int SQLITE_PROTOCOL = 15;
+ public static final int SQLITE_EMPTY = 16;
+ public static final int SQLITE_SCHEMA = 17;
+ public static final int SQLITE_TOOBIG = 18;
+ public static final int SQLITE_CONSTRAINT = 19;
+ public static final int SQLITE_MISMATCH = 20;
+ public static final int SQLITE_MISUSE = 21;
+ public static final int SQLITE_NOLFS = 22;
+ public static final int SQLITE_AUTH = 23;
+ public static final int SQLITE_FORMAT = 24;
+ public static final int SQLITE_RANGE = 25;
+ public static final int SQLITE_NOTADB = 26;
+ public static final int SQLITE_NOTICE = 27;
+ public static final int SQLITE_WARNING = 28;
+ public static final int SQLITE_ROW = 100;
+ public static final int SQLITE_DONE = 101;
+ public static final int SQLITE_ERROR_MISSING_COLLSEQ = 257;
+ public static final int SQLITE_ERROR_RETRY = 513;
+ public static final int SQLITE_ERROR_SNAPSHOT = 769;
+ public static final int SQLITE_IOERR_READ = 266;
+ public static final int SQLITE_IOERR_SHORT_READ = 522;
+ public static final int SQLITE_IOERR_WRITE = 778;
+ public static final int SQLITE_IOERR_FSYNC = 1034;
+ public static final int SQLITE_IOERR_DIR_FSYNC = 1290;
+ public static final int SQLITE_IOERR_TRUNCATE = 1546;
+ public static final int SQLITE_IOERR_FSTAT = 1802;
+ public static final int SQLITE_IOERR_UNLOCK = 2058;
+ public static final int SQLITE_IOERR_RDLOCK = 2314;
+ public static final int SQLITE_IOERR_DELETE = 2570;
+ public static final int SQLITE_IOERR_BLOCKED = 2826;
+ public static final int SQLITE_IOERR_NOMEM = 3082;
+ public static final int SQLITE_IOERR_ACCESS = 3338;
+ public static final int SQLITE_IOERR_CHECKRESERVEDLOCK = 3594;
+ public static final int SQLITE_IOERR_LOCK = 3850;
+ public static final int SQLITE_IOERR_CLOSE = 4106;
+ public static final int SQLITE_IOERR_DIR_CLOSE = 4362;
+ public static final int SQLITE_IOERR_SHMOPEN = 4618;
+ public static final int SQLITE_IOERR_SHMSIZE = 4874;
+ public static final int SQLITE_IOERR_SHMLOCK = 5130;
+ public static final int SQLITE_IOERR_SHMMAP = 5386;
+ public static final int SQLITE_IOERR_SEEK = 5642;
+ public static final int SQLITE_IOERR_DELETE_NOENT = 5898;
+ public static final int SQLITE_IOERR_MMAP = 6154;
+ public static final int SQLITE_IOERR_GETTEMPPATH = 6410;
+ public static final int SQLITE_IOERR_CONVPATH = 6666;
+ public static final int SQLITE_IOERR_VNODE = 6922;
+ public static final int SQLITE_IOERR_AUTH = 7178;
+ public static final int SQLITE_IOERR_BEGIN_ATOMIC = 7434;
+ public static final int SQLITE_IOERR_COMMIT_ATOMIC = 7690;
+ public static final int SQLITE_IOERR_ROLLBACK_ATOMIC = 7946;
+ public static final int SQLITE_IOERR_DATA = 8202;
+ public static final int SQLITE_IOERR_CORRUPTFS = 8458;
+ public static final int SQLITE_LOCKED_SHAREDCACHE = 262;
+ public static final int SQLITE_LOCKED_VTAB = 518;
+ public static final int SQLITE_BUSY_RECOVERY = 261;
+ public static final int SQLITE_BUSY_SNAPSHOT = 517;
+ public static final int SQLITE_BUSY_TIMEOUT = 773;
+ public static final int SQLITE_CANTOPEN_NOTEMPDIR = 270;
+ public static final int SQLITE_CANTOPEN_ISDIR = 526;
+ public static final int SQLITE_CANTOPEN_FULLPATH = 782;
+ public static final int SQLITE_CANTOPEN_CONVPATH = 1038;
+ public static final int SQLITE_CANTOPEN_SYMLINK = 1550;
+ public static final int SQLITE_CORRUPT_VTAB = 267;
+ public static final int SQLITE_CORRUPT_SEQUENCE = 523;
+ public static final int SQLITE_CORRUPT_INDEX = 779;
+ public static final int SQLITE_READONLY_RECOVERY = 264;
+ public static final int SQLITE_READONLY_CANTLOCK = 520;
+ public static final int SQLITE_READONLY_ROLLBACK = 776;
+ public static final int SQLITE_READONLY_DBMOVED = 1032;
+ public static final int SQLITE_READONLY_CANTINIT = 1288;
+ public static final int SQLITE_READONLY_DIRECTORY = 1544;
+ public static final int SQLITE_ABORT_ROLLBACK = 516;
+ public static final int SQLITE_CONSTRAINT_CHECK = 275;
+ public static final int SQLITE_CONSTRAINT_COMMITHOOK = 531;
+ public static final int SQLITE_CONSTRAINT_FOREIGNKEY = 787;
+ public static final int SQLITE_CONSTRAINT_FUNCTION = 1043;
+ public static final int SQLITE_CONSTRAINT_NOTNULL = 1299;
+ public static final int SQLITE_CONSTRAINT_PRIMARYKEY = 1555;
+ public static final int SQLITE_CONSTRAINT_TRIGGER = 1811;
+ public static final int SQLITE_CONSTRAINT_UNIQUE = 2067;
+ public static final int SQLITE_CONSTRAINT_VTAB = 2323;
+ public static final int SQLITE_CONSTRAINT_ROWID = 2579;
+ public static final int SQLITE_CONSTRAINT_PINNED = 2835;
+ public static final int SQLITE_CONSTRAINT_DATATYPE = 3091;
+ public static final int SQLITE_NOTICE_RECOVER_WAL = 283;
+ public static final int SQLITE_NOTICE_RECOVER_ROLLBACK = 539;
+ public static final int SQLITE_WARNING_AUTOINDEX = 284;
+ public static final int SQLITE_AUTH_USER = 279;
+ public static final int SQLITE_OK_LOAD_PERMANENTLY = 256;
+
+ // serialize
+ public static final int SQLITE_SERIALIZE_NOCOPY = 1;
+ public static final int SQLITE_DESERIALIZE_FREEONCLOSE = 1;
+ public static final int SQLITE_DESERIALIZE_READONLY = 4;
+ public static final int SQLITE_DESERIALIZE_RESIZEABLE = 2;
+
+ // session
+ public static final int SQLITE_SESSION_CONFIG_STRMSIZE = 1;
+ public static final int SQLITE_SESSION_OBJCONFIG_SIZE = 1;
+
+ // sqlite3 status
+ public static final int SQLITE_STATUS_MEMORY_USED = 0;
+ public static final int SQLITE_STATUS_PAGECACHE_USED = 1;
+ public static final int SQLITE_STATUS_PAGECACHE_OVERFLOW = 2;
+ public static final int SQLITE_STATUS_MALLOC_SIZE = 5;
+ public static final int SQLITE_STATUS_PARSER_STACK = 6;
+ public static final int SQLITE_STATUS_PAGECACHE_SIZE = 7;
+ public static final int SQLITE_STATUS_MALLOC_COUNT = 9;
+
+ // stmt status
+ public static final int SQLITE_STMTSTATUS_FULLSCAN_STEP = 1;
+ public static final int SQLITE_STMTSTATUS_SORT = 2;
+ public static final int SQLITE_STMTSTATUS_AUTOINDEX = 3;
+ public static final int SQLITE_STMTSTATUS_VM_STEP = 4;
+ public static final int SQLITE_STMTSTATUS_REPREPARE = 5;
+ public static final int SQLITE_STMTSTATUS_RUN = 6;
+ public static final int SQLITE_STMTSTATUS_FILTER_MISS = 7;
+ public static final int SQLITE_STMTSTATUS_FILTER_HIT = 8;
+ public static final int SQLITE_STMTSTATUS_MEMUSED = 99;
+
+ // sync flags
+ public static final int SQLITE_SYNC_NORMAL = 2;
+ public static final int SQLITE_SYNC_FULL = 3;
+ public static final int SQLITE_SYNC_DATAONLY = 16;
+
+ // tracing flags
+ public static final int SQLITE_TRACE_STMT = 1;
+ public static final int SQLITE_TRACE_PROFILE = 2;
+ public static final int SQLITE_TRACE_ROW = 4;
+ public static final int SQLITE_TRACE_CLOSE = 8;
+
+ // transaction state
+ public static final int SQLITE_TXN_NONE = 0;
+ public static final int SQLITE_TXN_READ = 1;
+ public static final int SQLITE_TXN_WRITE = 2;
+
+ // udf flags
+ public static final int SQLITE_DETERMINISTIC = 0x000000800;
+ public static final int SQLITE_DIRECTONLY = 0x000080000;
+ public static final int SQLITE_INNOCUOUS = 0x000200000;
+
+ // virtual tables
+ public static final int SQLITE_INDEX_SCAN_UNIQUE = 1;
+ public static final int SQLITE_INDEX_CONSTRAINT_EQ = 2;
+ public static final int SQLITE_INDEX_CONSTRAINT_GT = 4;
+ public static final int SQLITE_INDEX_CONSTRAINT_LE = 8;
+ public static final int SQLITE_INDEX_CONSTRAINT_LT = 16;
+ public static final int SQLITE_INDEX_CONSTRAINT_GE = 32;
+ public static final int SQLITE_INDEX_CONSTRAINT_MATCH = 64;
+ public static final int SQLITE_INDEX_CONSTRAINT_LIKE = 65;
+ public static final int SQLITE_INDEX_CONSTRAINT_GLOB = 66;
+ public static final int SQLITE_INDEX_CONSTRAINT_REGEXP = 67;
+ public static final int SQLITE_INDEX_CONSTRAINT_NE = 68;
+ public static final int SQLITE_INDEX_CONSTRAINT_ISNOT = 69;
+ public static final int SQLITE_INDEX_CONSTRAINT_ISNOTNULL = 70;
+ public static final int SQLITE_INDEX_CONSTRAINT_ISNULL = 71;
+ public static final int SQLITE_INDEX_CONSTRAINT_IS = 72;
+ public static final int SQLITE_INDEX_CONSTRAINT_LIMIT = 73;
+ public static final int SQLITE_INDEX_CONSTRAINT_OFFSET = 74;
+ public static final int SQLITE_INDEX_CONSTRAINT_FUNCTION = 150;
+ public static final int SQLITE_VTAB_CONSTRAINT_SUPPORT = 1;
+ public static final int SQLITE_VTAB_INNOCUOUS = 2;
+ public static final int SQLITE_VTAB_DIRECTONLY = 3;
+ public static final int SQLITE_VTAB_USES_ALL_SCHEMAS = 4;
+ public static final int SQLITE_ROLLBACK = 1;
+ public static final int SQLITE_FAIL = 3;
+ public static final int SQLITE_REPLACE = 5;
+ static {
+ // This MUST come after the SQLITE_MAX_... values or else
+ // attempting to modify them silently fails.
+ init();
+ }
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CollationCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,35 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.NotNull;
+
+/**
+ Callback for use with {@link CApi#sqlite3_create_collation}.
+
+ @see AbstractCollationCallback
+*/
+public interface CollationCallback
+ extends CallbackProxy, XDestroyCallback {
+ /**
+ Must compare the given byte arrays and return the result using
+ {@code memcmp()} semantics.
+ */
+ int call(@NotNull byte[] lhs, @NotNull byte[] rhs);
+
+ /**
+ Called by SQLite when the collation is destroyed. If a collation
+ requires custom cleanup, override this method.
+ */
+ void xDestroy();
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,28 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_collation_needed}.
+*/
+public interface CollationNeededCallback extends CallbackProxy {
+ /**
+ Has the same semantics as the C-level sqlite3_create_collation()
+ callback.
+
+
If it throws, the exception message is passed on to the db and
+ the exception is suppressed.
+ */
+ int call(sqlite3 db, int eTextRep, String collationName);
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_commit_hook}.
+*/
+public interface CommitHookCallback extends CallbackProxy {
+ /**
+ Works as documented for the C-level sqlite3_commit_hook()
+ callback. Must not throw.
+ */
+ int call();
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 2023-08-23
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A callback for use with sqlite3_config().
+*/
+public interface ConfigLogCallback {
+ /**
+ Must function as described for a C-level callback for
+ {@link CApi#sqlite3_config(ConfigLogCallback)}, with the slight signature change.
+ */
+ void call(int errCode, String msg);
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 2023-08-23
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A callback for use with sqlite3_config().
+*/
+public interface ConfigSqllogCallback {
+ /**
+ Must function as described for a C-level callback for
+ {@link CApi#sqlite3_config(ConfigSqllogCallback)}, with the slight signature change.
+ */
+ void call(sqlite3 db, String msg, int msgType );
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,46 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A helper for passing pointers between JNI C code and Java, in
+ particular for output pointers of high-level object types in the
+ sqlite3 C API, e.g. (sqlite3**) and (sqlite3_stmt**). This is
+ intended to be subclassed and the ContextType is intended to be the
+ class which is doing the subclassing. The intent of the ContextType
+ is strictly to provide some level of type safety by avoiding that
+ NativePointerHolder is not inadvertently passed to an incompatible
+ function signature.
+
+ These objects do not own the pointer they refer to. They are
+ intended simply to communicate that pointer between C and Java.
+*/
+public class NativePointerHolder {
+ //! Only set from JNI, where access permissions don't matter.
+ private volatile long nativePointer = 0;
+ /**
+ For use ONLY by package-level APIs which act as proxies for
+ close/finalize operations. Such ops must call this to zero out
+ the pointer so that this object is not carrying a stale
+ pointer. This function returns the prior value of the pointer and
+ sets it to 0.
+ */
+ final long clearNativePointer() {
+ final long rv = nativePointer;
+ nativePointer= 0;
+ return rv;
+ }
+
+ public final long getNativePointer(){ return nativePointer; }
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/OutputPointer.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,231 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Helper classes for handling JNI output pointers.
+
+
We do not use a generic OutputPointer because working with those
+ from the native JNI code is unduly quirky due to a lack of
+ autoboxing at that level.
+
+
The usage is similar for all of thes types:
+
+
{@code
+ OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ assert( null==out.get() );
+ int rc = sqlite3_open(":memory:", out);
+ if( 0!=rc ) ... error;
+ assert( null!=out.get() );
+ sqlite3 db = out.take();
+ assert( null==out.get() );
+ }
+
+
With the minor exception that the primitive types permit direct
+ access to the object's value via the `value` property, whereas the
+ JNI-level opaque types do not permit client-level code to set that
+ property.
+
+
Warning: do not share instances of these classes across
+ threads. Doing so may lead to corrupting sqlite3-internal state.
+*/
+public final class OutputPointer {
+
+ /**
+ Output pointer for use with routines, such as sqlite3_open(),
+ which return a database handle via an output pointer. These
+ pointers can only be set by the JNI layer, not by client-level
+ code.
+ */
+ public static final class sqlite3 {
+ private org.sqlite.jni.capi.sqlite3 value;
+ /** Initializes with a null value. */
+ public sqlite3(){value = null;}
+ /** Sets the current value to null. */
+ public void clear(){value = null;}
+ /** Returns the current value. */
+ public final org.sqlite.jni.capi.sqlite3 get(){return value;}
+ /** Equivalent to calling get() then clear(). */
+ public final org.sqlite.jni.capi.sqlite3 take(){
+ final org.sqlite.jni.capi.sqlite3 v = value;
+ value = null;
+ return v;
+ }
+ }
+
+ /**
+ Output pointer for sqlite3_blob_open(). These
+ pointers can only be set by the JNI layer, not by client-level
+ code.
+ */
+ public static final class sqlite3_blob {
+ private org.sqlite.jni.capi.sqlite3_blob value;
+ /** Initializes with a null value. */
+ public sqlite3_blob(){value = null;}
+ /** Sets the current value to null. */
+ public void clear(){value = null;}
+ /** Returns the current value. */
+ public final org.sqlite.jni.capi.sqlite3_blob get(){return value;}
+ /** Equivalent to calling get() then clear(). */
+ public final org.sqlite.jni.capi.sqlite3_blob take(){
+ final org.sqlite.jni.capi.sqlite3_blob v = value;
+ value = null;
+ return v;
+ }
+ }
+
+ /**
+ Output pointer for use with routines, such as sqlite3_prepare(),
+ which return a statement handle via an output pointer. These
+ pointers can only be set by the JNI layer, not by client-level
+ code.
+ */
+ public static final class sqlite3_stmt {
+ private org.sqlite.jni.capi.sqlite3_stmt value;
+ /** Initializes with a null value. */
+ public sqlite3_stmt(){value = null;}
+ /** Sets the current value to null. */
+ public void clear(){value = null;}
+ /** Returns the current value. */
+ public final org.sqlite.jni.capi.sqlite3_stmt get(){return value;}
+ /** Equivalent to calling get() then clear(). */
+ public final org.sqlite.jni.capi.sqlite3_stmt take(){
+ final org.sqlite.jni.capi.sqlite3_stmt v = value;
+ value = null;
+ return v;
+ }
+ }
+
+ /**
+ Output pointer for use with routines, such as sqlite3_prepupdate_new(),
+ which return a sqlite3_value handle via an output pointer. These
+ pointers can only be set by the JNI layer, not by client-level
+ code.
+ */
+ public static final class sqlite3_value {
+ private org.sqlite.jni.capi.sqlite3_value value;
+ /** Initializes with a null value. */
+ public sqlite3_value(){value = null;}
+ /** Sets the current value to null. */
+ public void clear(){value = null;}
+ /** Returns the current value. */
+ public final org.sqlite.jni.capi.sqlite3_value get(){return value;}
+ /** Equivalent to calling get() then clear(). */
+ public final org.sqlite.jni.capi.sqlite3_value take(){
+ final org.sqlite.jni.capi.sqlite3_value v = value;
+ value = null;
+ return v;
+ }
+ }
+
+ /**
+ Output pointer for use with native routines which return booleans
+ via integer output pointers.
+ */
+ public static final class Bool {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public boolean value;
+ /** Initializes with the value 0. */
+ public Bool(){this(false);}
+ /** Initializes with the value v. */
+ public Bool(boolean v){value = v;}
+ /** Returns the current value. */
+ public final boolean get(){return value;}
+ /** Sets the current value to v. */
+ public final void set(boolean v){value = v;}
+ }
+
+ /**
+ Output pointer for use with native routines which return integers via
+ output pointers.
+ */
+ public static final class Int32 {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public int value;
+ /** Initializes with the value 0. */
+ public Int32(){this(0);}
+ /** Initializes with the value v. */
+ public Int32(int v){value = v;}
+ /** Returns the current value. */
+ public final int get(){return value;}
+ /** Sets the current value to v. */
+ public final void set(int v){value = v;}
+ }
+
+ /**
+ Output pointer for use with native routines which return 64-bit integers
+ via output pointers.
+ */
+ public static final class Int64 {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public long value;
+ /** Initializes with the value 0. */
+ public Int64(){this(0);}
+ /** Initializes with the value v. */
+ public Int64(long v){value = v;}
+ /** Returns the current value. */
+ public final long get(){return value;}
+ /** Sets the current value. */
+ public final void set(long v){value = v;}
+ }
+
+ /**
+ Output pointer for use with native routines which return strings via
+ output pointers.
+ */
+ public static final class String {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public java.lang.String value;
+ /** Initializes with a null value. */
+ public String(){this(null);}
+ /** Initializes with the value v. */
+ public String(java.lang.String v){value = v;}
+ /** Returns the current value. */
+ public final java.lang.String get(){return value;}
+ /** Sets the current value. */
+ public final void set(java.lang.String v){value = v;}
+ }
+
+ /**
+ Output pointer for use with native routines which return byte
+ arrays via output pointers.
+ */
+ public static final class ByteArray {
+ /**
+ This is public for ease of use. Accessors are provided for
+ consistency with the higher-level types.
+ */
+ public byte[] value;
+ /** Initializes with the value null. */
+ public ByteArray(){this(null);}
+ /** Initializes with the value v. */
+ public ByteArray(byte[] v){value = v;}
+ /** Returns the current value. */
+ public final byte[] get(){return value;}
+ /** Sets the current value. */
+ public final void set(byte[] v){value = v;}
+ }
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/package-info.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/package-info.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/package-info.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/package-info.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,89 @@
+/**
+ This package houses a JNI binding to the SQLite3 C API.
+
+
The primary interfaces are in {@link
+ org.sqlite.jni.capi.CApi}.
+
+
API Goals and Requirements
+
+
+
+
A 1-to-1(-ish) mapping of the C API to Java via JNI, insofar
+ as cross-language semantics allow for. A closely-related goal is
+ that the C
+ documentation should be usable as-is, insofar as possible,
+ for most of the JNI binding. As a rule, undocumented symbols in
+ the Java interface behave as documented for their C API
+ counterpart. Only semantic differences and Java-specific features
+ are documented here.
+
+
Support Java as far back as version 8 (2014).
+
+
Environment-independent. Should work everywhere both Java and
+ SQLite3 do.
+
+
No 3rd-party dependencies beyond the JDK. That includes no
+ build-level dependencies for specific IDEs and toolchains. We
+ welcome the addition of build files for arbitrary environments
+ insofar as they neither interfere with each other nor become a
+ maintenance burden for the sqlite developers.
+
+
+
+
Non-Goals
+
+
+
+
Creation of high-level OO wrapper APIs. Clients are free to
+ create them off of the C-style API.
+
+
Support for mixed-mode operation, where client code accesses
+ SQLite both via the Java-side API and the C API via their own
+ native code. In such cases, proxy functionalities (primarily
+ callback handler wrappers of all sorts) may fail because the
+ C-side use of the SQLite APIs will bypass those proxies.
+
+
+
+
State of this API
+
+
As of version 3.43, this software is in "tech preview" form. We
+ tentatively plan to stamp it as stable with the 3.44 release.
+
+
Threading Considerations
+
+
This API is, if built with SQLITE_THREADSAFE set to 1 or 2,
+ thread-safe, insofar as the C API guarantees, with some addenda:
+
+
+
+
It is not legal to use Java-facing SQLite3 resource handles
+ (sqlite3, sqlite3_stmt, etc) from multiple threads concurrently,
+ nor to use any database-specific resources concurrently in a
+ thread separate from the one the database is currently in use
+ in. i.e. do not use a sqlite3_stmt in thread #2 when thread #1 is
+ using the database which prepared that handle.
+
+ Violating this will eventually corrupt the JNI-level bindings
+ between Java's and C's view of the database. This is a limitation
+ of the JNI bindings, not the lower-level library.
+
+
+
It is legal to use a given handle, and database-specific
+ resources, across threads, so long as no two threads pass
+ resources owned by the same database into the library
+ concurrently.
+
+
+
+
+
Any number of threads may, of course, create and use any number
+ of database handles they wish. Care only needs to be taken when
+ those handles or their associated resources cross threads, or...
+
+
When built with SQLITE_THREADSAFE=0 then no threading guarantees
+ are provided and multi-threaded use of the library will provoke
+ undefined behavior.
+
+*/
+package org.sqlite.jni.capi;
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,78 @@
+/*
+** 2023-09-13
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_prepare_multi}.
+*/
+public interface PrepareMultiCallback extends CallbackProxy {
+
+ /**
+ Gets passed a sqlite3_stmt which it may handle in arbitrary ways,
+ transfering ownership of it to this function.
+
+ sqlite3_prepare_multi() will _not_ finalize st - it is up
+ to the call() implementation how st is handled.
+
+ Must return 0 on success or an SQLITE_... code on error.
+
+ See the {@link Finalize} class for a wrapper which finalizes the
+ statement after calling a proxy PrepareMultiCallback.
+ */
+ int call(sqlite3_stmt st);
+
+ /**
+ A PrepareMultiCallback impl which wraps a separate impl and finalizes
+ any sqlite3_stmt passed to its callback.
+ */
+ public static final class Finalize implements PrepareMultiCallback {
+ private PrepareMultiCallback p;
+ /**
+ p is the proxy to call() when this.call() is called.
+ */
+ public Finalize( PrepareMultiCallback p ){
+ this.p = p;
+ }
+ /**
+ Calls the call() method of the proxied callback and either returns its
+ result or propagates an exception. Either way, it passes its argument to
+ sqlite3_finalize() before returning.
+ */
+ @Override public int call(sqlite3_stmt st){
+ try {
+ return this.p.call(st);
+ }finally{
+ CApi.sqlite3_finalize(st);
+ }
+ }
+ }
+
+ /**
+ A PrepareMultiCallback impl which steps entirely through a result set,
+ ignoring all non-error results.
+ */
+ public static final class StepAll implements PrepareMultiCallback {
+ public StepAll(){}
+ /**
+ Calls sqlite3_step() on st until it returns something other than
+ SQLITE_ROW. If the final result is SQLITE_DONE then 0 is returned,
+ else the result of the final step is returned.
+ */
+ @Override public int call(sqlite3_stmt st){
+ int rc = CApi.SQLITE_DONE;
+ while( CApi.SQLITE_ROW == (rc = CApi.sqlite3_step(st)) ){}
+ return CApi.SQLITE_DONE==rc ? 0 : rc;
+ }
+ }
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,26 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_preupdate_hook}.
+*/
+public interface PreupdateHookCallback extends CallbackProxy {
+ /**
+ Must function as described for the C-level sqlite3_preupdate_hook()
+ callback.
+ */
+ void call(sqlite3 db, int op, String dbName, String dbTable,
+ long iKey1, long iKey2 );
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,27 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_progress_handler}.
+*/
+public interface ProgressHandlerCallback extends CallbackProxy {
+ /**
+ Works as documented for the C-level sqlite3_progress_handler() callback.
+
+
If it throws, the exception message is passed on to the db and
+ the exception is suppressed.
+ */
+ int call();
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/ResultCode.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ResultCode.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/ResultCode.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ResultCode.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,155 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ This enum contains all of the core and "extended" result codes used
+ by the sqlite3 library. It is provided not for use with the C-style
+ API (with which it won't work) but for higher-level code which may
+ find it useful to map SQLite result codes to human-readable names.
+*/
+public enum ResultCode {
+ SQLITE_OK(CApi.SQLITE_OK),
+ SQLITE_ERROR(CApi.SQLITE_ERROR),
+ SQLITE_INTERNAL(CApi.SQLITE_INTERNAL),
+ SQLITE_PERM(CApi.SQLITE_PERM),
+ SQLITE_ABORT(CApi.SQLITE_ABORT),
+ SQLITE_BUSY(CApi.SQLITE_BUSY),
+ SQLITE_LOCKED(CApi.SQLITE_LOCKED),
+ SQLITE_NOMEM(CApi.SQLITE_NOMEM),
+ SQLITE_READONLY(CApi.SQLITE_READONLY),
+ SQLITE_INTERRUPT(CApi.SQLITE_INTERRUPT),
+ SQLITE_IOERR(CApi.SQLITE_IOERR),
+ SQLITE_CORRUPT(CApi.SQLITE_CORRUPT),
+ SQLITE_NOTFOUND(CApi.SQLITE_NOTFOUND),
+ SQLITE_FULL(CApi.SQLITE_FULL),
+ SQLITE_CANTOPEN(CApi.SQLITE_CANTOPEN),
+ SQLITE_PROTOCOL(CApi.SQLITE_PROTOCOL),
+ SQLITE_EMPTY(CApi.SQLITE_EMPTY),
+ SQLITE_SCHEMA(CApi.SQLITE_SCHEMA),
+ SQLITE_TOOBIG(CApi.SQLITE_TOOBIG),
+ SQLITE_CONSTRAINT(CApi.SQLITE_CONSTRAINT),
+ SQLITE_MISMATCH(CApi.SQLITE_MISMATCH),
+ SQLITE_MISUSE(CApi.SQLITE_MISUSE),
+ SQLITE_NOLFS(CApi.SQLITE_NOLFS),
+ SQLITE_AUTH(CApi.SQLITE_AUTH),
+ SQLITE_FORMAT(CApi.SQLITE_FORMAT),
+ SQLITE_RANGE(CApi.SQLITE_RANGE),
+ SQLITE_NOTADB(CApi.SQLITE_NOTADB),
+ SQLITE_NOTICE(CApi.SQLITE_NOTICE),
+ SQLITE_WARNING(CApi.SQLITE_WARNING),
+ SQLITE_ROW(CApi.SQLITE_ROW),
+ SQLITE_DONE(CApi.SQLITE_DONE),
+ SQLITE_ERROR_MISSING_COLLSEQ(CApi.SQLITE_ERROR_MISSING_COLLSEQ),
+ SQLITE_ERROR_RETRY(CApi.SQLITE_ERROR_RETRY),
+ SQLITE_ERROR_SNAPSHOT(CApi.SQLITE_ERROR_SNAPSHOT),
+ SQLITE_IOERR_READ(CApi.SQLITE_IOERR_READ),
+ SQLITE_IOERR_SHORT_READ(CApi.SQLITE_IOERR_SHORT_READ),
+ SQLITE_IOERR_WRITE(CApi.SQLITE_IOERR_WRITE),
+ SQLITE_IOERR_FSYNC(CApi.SQLITE_IOERR_FSYNC),
+ SQLITE_IOERR_DIR_FSYNC(CApi.SQLITE_IOERR_DIR_FSYNC),
+ SQLITE_IOERR_TRUNCATE(CApi.SQLITE_IOERR_TRUNCATE),
+ SQLITE_IOERR_FSTAT(CApi.SQLITE_IOERR_FSTAT),
+ SQLITE_IOERR_UNLOCK(CApi.SQLITE_IOERR_UNLOCK),
+ SQLITE_IOERR_RDLOCK(CApi.SQLITE_IOERR_RDLOCK),
+ SQLITE_IOERR_DELETE(CApi.SQLITE_IOERR_DELETE),
+ SQLITE_IOERR_BLOCKED(CApi.SQLITE_IOERR_BLOCKED),
+ SQLITE_IOERR_NOMEM(CApi.SQLITE_IOERR_NOMEM),
+ SQLITE_IOERR_ACCESS(CApi.SQLITE_IOERR_ACCESS),
+ SQLITE_IOERR_CHECKRESERVEDLOCK(CApi.SQLITE_IOERR_CHECKRESERVEDLOCK),
+ SQLITE_IOERR_LOCK(CApi.SQLITE_IOERR_LOCK),
+ SQLITE_IOERR_CLOSE(CApi.SQLITE_IOERR_CLOSE),
+ SQLITE_IOERR_DIR_CLOSE(CApi.SQLITE_IOERR_DIR_CLOSE),
+ SQLITE_IOERR_SHMOPEN(CApi.SQLITE_IOERR_SHMOPEN),
+ SQLITE_IOERR_SHMSIZE(CApi.SQLITE_IOERR_SHMSIZE),
+ SQLITE_IOERR_SHMLOCK(CApi.SQLITE_IOERR_SHMLOCK),
+ SQLITE_IOERR_SHMMAP(CApi.SQLITE_IOERR_SHMMAP),
+ SQLITE_IOERR_SEEK(CApi.SQLITE_IOERR_SEEK),
+ SQLITE_IOERR_DELETE_NOENT(CApi.SQLITE_IOERR_DELETE_NOENT),
+ SQLITE_IOERR_MMAP(CApi.SQLITE_IOERR_MMAP),
+ SQLITE_IOERR_GETTEMPPATH(CApi.SQLITE_IOERR_GETTEMPPATH),
+ SQLITE_IOERR_CONVPATH(CApi.SQLITE_IOERR_CONVPATH),
+ SQLITE_IOERR_VNODE(CApi.SQLITE_IOERR_VNODE),
+ SQLITE_IOERR_AUTH(CApi.SQLITE_IOERR_AUTH),
+ SQLITE_IOERR_BEGIN_ATOMIC(CApi.SQLITE_IOERR_BEGIN_ATOMIC),
+ SQLITE_IOERR_COMMIT_ATOMIC(CApi.SQLITE_IOERR_COMMIT_ATOMIC),
+ SQLITE_IOERR_ROLLBACK_ATOMIC(CApi.SQLITE_IOERR_ROLLBACK_ATOMIC),
+ SQLITE_IOERR_DATA(CApi.SQLITE_IOERR_DATA),
+ SQLITE_IOERR_CORRUPTFS(CApi.SQLITE_IOERR_CORRUPTFS),
+ SQLITE_LOCKED_SHAREDCACHE(CApi.SQLITE_LOCKED_SHAREDCACHE),
+ SQLITE_LOCKED_VTAB(CApi.SQLITE_LOCKED_VTAB),
+ SQLITE_BUSY_RECOVERY(CApi.SQLITE_BUSY_RECOVERY),
+ SQLITE_BUSY_SNAPSHOT(CApi.SQLITE_BUSY_SNAPSHOT),
+ SQLITE_BUSY_TIMEOUT(CApi.SQLITE_BUSY_TIMEOUT),
+ SQLITE_CANTOPEN_NOTEMPDIR(CApi.SQLITE_CANTOPEN_NOTEMPDIR),
+ SQLITE_CANTOPEN_ISDIR(CApi.SQLITE_CANTOPEN_ISDIR),
+ SQLITE_CANTOPEN_FULLPATH(CApi.SQLITE_CANTOPEN_FULLPATH),
+ SQLITE_CANTOPEN_CONVPATH(CApi.SQLITE_CANTOPEN_CONVPATH),
+ SQLITE_CANTOPEN_SYMLINK(CApi.SQLITE_CANTOPEN_SYMLINK),
+ SQLITE_CORRUPT_VTAB(CApi.SQLITE_CORRUPT_VTAB),
+ SQLITE_CORRUPT_SEQUENCE(CApi.SQLITE_CORRUPT_SEQUENCE),
+ SQLITE_CORRUPT_INDEX(CApi.SQLITE_CORRUPT_INDEX),
+ SQLITE_READONLY_RECOVERY(CApi.SQLITE_READONLY_RECOVERY),
+ SQLITE_READONLY_CANTLOCK(CApi.SQLITE_READONLY_CANTLOCK),
+ SQLITE_READONLY_ROLLBACK(CApi.SQLITE_READONLY_ROLLBACK),
+ SQLITE_READONLY_DBMOVED(CApi.SQLITE_READONLY_DBMOVED),
+ SQLITE_READONLY_CANTINIT(CApi.SQLITE_READONLY_CANTINIT),
+ SQLITE_READONLY_DIRECTORY(CApi.SQLITE_READONLY_DIRECTORY),
+ SQLITE_ABORT_ROLLBACK(CApi.SQLITE_ABORT_ROLLBACK),
+ SQLITE_CONSTRAINT_CHECK(CApi.SQLITE_CONSTRAINT_CHECK),
+ SQLITE_CONSTRAINT_COMMITHOOK(CApi.SQLITE_CONSTRAINT_COMMITHOOK),
+ SQLITE_CONSTRAINT_FOREIGNKEY(CApi.SQLITE_CONSTRAINT_FOREIGNKEY),
+ SQLITE_CONSTRAINT_FUNCTION(CApi.SQLITE_CONSTRAINT_FUNCTION),
+ SQLITE_CONSTRAINT_NOTNULL(CApi.SQLITE_CONSTRAINT_NOTNULL),
+ SQLITE_CONSTRAINT_PRIMARYKEY(CApi.SQLITE_CONSTRAINT_PRIMARYKEY),
+ SQLITE_CONSTRAINT_TRIGGER(CApi.SQLITE_CONSTRAINT_TRIGGER),
+ SQLITE_CONSTRAINT_UNIQUE(CApi.SQLITE_CONSTRAINT_UNIQUE),
+ SQLITE_CONSTRAINT_VTAB(CApi.SQLITE_CONSTRAINT_VTAB),
+ SQLITE_CONSTRAINT_ROWID(CApi.SQLITE_CONSTRAINT_ROWID),
+ SQLITE_CONSTRAINT_PINNED(CApi.SQLITE_CONSTRAINT_PINNED),
+ SQLITE_CONSTRAINT_DATATYPE(CApi.SQLITE_CONSTRAINT_DATATYPE),
+ SQLITE_NOTICE_RECOVER_WAL(CApi.SQLITE_NOTICE_RECOVER_WAL),
+ SQLITE_NOTICE_RECOVER_ROLLBACK(CApi.SQLITE_NOTICE_RECOVER_ROLLBACK),
+ SQLITE_WARNING_AUTOINDEX(CApi.SQLITE_WARNING_AUTOINDEX),
+ SQLITE_AUTH_USER(CApi.SQLITE_AUTH_USER),
+ SQLITE_OK_LOAD_PERMANENTLY(CApi.SQLITE_OK_LOAD_PERMANENTLY);
+
+ public final int value;
+
+ ResultCode(int rc){
+ value = rc;
+ ResultCodeMap.set(rc, this);
+ }
+
+ /**
+ Returns the entry from this enum for the given result code, or
+ null if no match is found.
+ */
+ public static ResultCode getEntryForInt(int rc){
+ return ResultCodeMap.get(rc);
+ }
+
+ /**
+ Internal level of indirection required because we cannot initialize
+ static enum members in an enum before the enum constructor is
+ invoked.
+ */
+ private static final class ResultCodeMap {
+ private static final java.util.Map i2e
+ = new java.util.HashMap<>();
+ private static void set(int rc, ResultCode e){ i2e.put(rc, e); }
+ private static ResultCode get(int rc){ return i2e.get(rc); }
+ }
+
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_rollback_hook}.
+*/
+public interface RollbackHookCallback extends CallbackProxy {
+ /**
+ Works as documented for the C-level sqlite3_rollback_hook()
+ callback.
+ */
+ void call();
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,33 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+
+/**
+ A SQLFunction implementation for scalar functions.
+*/
+public abstract class ScalarFunction implements SQLFunction {
+ /**
+ As for the xFunc() argument of the C API's
+ sqlite3_create_function(). If this function throws, it is
+ translated into an sqlite3_result_error().
+ */
+ public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args);
+
+ /**
+ Optionally override to be notified when the UDF is finalized by
+ SQLite. This default implementation does nothing.
+ */
+ public void xDestroy() {}
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,103 @@
+/*
+** 2023-07-22
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ SQLFunction is used in conjunction with the
+ sqlite3_create_function() JNI-bound API to give that native code
+ access to the callback functions needed in order to implement SQL
+ functions in Java.
+
+
+
+ This class is not used by itself, but is a marker base class. The
+ three UDF types are modelled by the inner classes Scalar,
+ Aggregate, and Window. Most simply, clients may subclass
+ those, or create anonymous classes from them, to implement
+ UDFs. Clients are free to create their own classes for use with
+ UDFs, so long as they conform to the public interfaces defined by
+ those three classes. The JNI layer only actively relies on the
+ SQLFunction base class and the method names and signatures used by
+ the UDF callback interfaces.
+*/
+public interface SQLFunction {
+
+ /**
+ PerContextState assists aggregate and window functions in
+ managing their accumulator state across calls to the UDF's
+ callbacks.
+
+
T must be of a type which can be legally stored as a value in
+ java.util.HashMap.
+
+
If a given aggregate or window function is called multiple times
+ in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)...,
+ then the clients need some way of knowing which call is which so
+ that they can map their state between their various UDF callbacks
+ and reset it via xFinal(). This class takes care of such
+ mappings.
+
+
This class works by mapping
+ sqlite3_context.getAggregateContext() to a single piece of
+ state, of a client-defined type (the T part of this class), which
+ persists across a "matching set" of the UDF's callbacks.
+
+
This class is a helper providing commonly-needed functionality
+ - it is not required for use with aggregate or window functions.
+ Client UDFs are free to perform such mappings using custom
+ approaches. The provided {@link AggregateFunction} and {@link
+ WindowFunction} classes use this.
+ */
+ public static final class PerContextState {
+ private final java.util.Map> map
+ = new java.util.HashMap<>();
+
+ /**
+ Should be called from a UDF's xStep(), xValue(), and xInverse()
+ methods, passing it that method's first argument and an initial
+ value for the persistent state. If there is currently no
+ mapping for the given context within the map, one is created
+ using the given initial value, else the existing one is used
+ and the 2nd argument is ignored. It returns a ValueHolder
+ which can be used to modify that state directly without
+ requiring that the client update the underlying map's entry.
+
+
The caller is obligated to eventually call
+ takeAggregateState() to clear the mapping.
+ */
+ public ValueHolder getAggregateState(sqlite3_context cx, T initialValue){
+ final Long key = cx.getAggregateContext(true);
+ ValueHolder rc = null==key ? null : map.get(key);
+ if( null==rc ){
+ map.put(key, rc = new ValueHolder<>(initialValue));
+ }
+ return rc;
+ }
+
+ /**
+ Should be called from a UDF's xFinal() method and passed that
+ method's first argument. This function removes the value
+ associated with cx.getAggregateContext() from the map and
+ returns it, returning null if no other UDF method has been
+ called to set up such a mapping. The latter condition will be
+ the case if a UDF is used in a statement which has no result
+ rows.
+ */
+ public T takeAggregateState(sqlite3_context cx){
+ final ValueHolder h = map.remove(cx.getAggregateContext(false));
+ return null==h ? null : h.value;
+ }
+ }
+
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_backup.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,31 @@
+/*
+** 2023-09-03
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A wrapper for passing C-level (sqlite3_backup*) instances around in
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class sqlite3_backup extends NativePointerHolder
+ implements AutoCloseable {
+ // Only invoked from JNI.
+ private sqlite3_backup(){}
+
+ @Override public void close(){
+ CApi.sqlite3_backup_finish(this);
+ }
+
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_blob.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,31 @@
+/*
+** 2023-09-03
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A wrapper for passing C-level (sqlite3_blob*) instances around in
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class sqlite3_blob extends NativePointerHolder
+ implements AutoCloseable {
+ // Only invoked from JNI.
+ private sqlite3_blob(){}
+
+ @Override public void close(){
+ CApi.sqlite3_blob_close(this.clearNativePointer());
+ }
+
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_context.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,79 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ sqlite3_context instances are used in conjunction with user-defined
+ SQL functions (a.k.a. UDFs).
+*/
+public final class sqlite3_context extends NativePointerHolder {
+ private Long aggregateContext = null;
+
+ /**
+ getAggregateContext() corresponds to C's
+ sqlite3_aggregate_context(), with a slightly different interface
+ to account for cross-language differences. It serves the same
+ purposes in a slightly different way: it provides a key which is
+ stable across invocations of a UDF's callbacks, such that all
+ calls into those callbacks can determine which "set" of those
+ calls they belong to.
+
+
Note that use of this method is not a requirement for proper use
+ of this class. sqlite3_aggregate_context() can also be used.
+
+
If the argument is true and the aggregate context has not yet
+ been set up, it will be initialized and fetched on demand, else it
+ won't. The intent is that xStep(), xValue(), and xInverse()
+ methods pass true and xFinal() methods pass false.
+
+
This function treats numeric 0 as null, always returning null instead
+ of 0.
+
+
If this object is being used in the context of an aggregate or
+ window UDF, this function returns a non-0 value which is distinct
+ for each set of UDF callbacks from a single invocation of the
+ UDF, otherwise it returns 0. The returned value is only only
+ valid within the context of execution of a single SQL statement,
+ and must not be re-used by future invocations of the UDF in
+ different SQL statements.
+
+
Consider this SQL, where MYFUNC is a user-defined aggregate function:
+
+
{@code
+ SELECT MYFUNC(A), MYFUNC(B) FROM T;
+ }
+
+
The xStep() and xFinal() methods of the callback need to be able
+ to differentiate between those two invocations in order to
+ perform their work properly. The value returned by
+ getAggregateContext() will be distinct for each of those
+ invocations of MYFUNC() and is intended to be used as a lookup
+ key for mapping callback invocations to whatever client-defined
+ state is needed by the UDF.
+
+
There is one case where this will return null in the context
+ of an aggregate or window function: if the result set has no
+ rows, the UDF's xFinal() will be called without any other x...()
+ members having been called. In that one case, no aggregate
+ context key will have been generated. xFinal() implementations
+ need to be prepared to accept that condition as legal.
+ */
+ public synchronized Long getAggregateContext(boolean initIfNeeded){
+ if( aggregateContext==null ){
+ aggregateContext = CApi.sqlite3_aggregate_context(this, initIfNeeded);
+ if( !initIfNeeded && null==aggregateContext ) aggregateContext = 0L;
+ }
+ return (null==aggregateContext || 0!=aggregateContext) ? aggregateContext : null;
+ }
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/sqlite3.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/sqlite3.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,43 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A wrapper for communicating C-level (sqlite3*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java
+ and C via JNI.
+*/
+public final class sqlite3 extends NativePointerHolder
+ implements AutoCloseable {
+
+ // Only invoked from JNI
+ private sqlite3(){}
+
+ public String toString(){
+ final long ptr = getNativePointer();
+ if( 0==ptr ){
+ return sqlite3.class.getSimpleName()+"@null";
+ }
+ final String fn = CApi.sqlite3_db_filename(this, "main");
+ return sqlite3.class.getSimpleName()
+ +"@"+String.format("0x%08x",ptr)
+ +"["+((null == fn) ? "" : fn)+"]"
+ ;
+ }
+
+ @Override public void close(){
+ CApi.sqlite3_close_v2(this.clearNativePointer());
+ }
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_stmt.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,30 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A wrapper for communicating C-level (sqlite3_stmt*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class sqlite3_stmt extends NativePointerHolder
+ implements AutoCloseable {
+ // Only invoked from JNI.
+ private sqlite3_stmt(){}
+
+ @Override public void close(){
+ CApi.sqlite3_finalize(this.clearNativePointer());
+ }
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/sqlite3_value.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,19 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+public final class sqlite3_value extends NativePointerHolder {
+ //! Invoked only from JNI.
+ private sqlite3_value(){}
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/SQLTester.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/SQLTester.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/SQLTester.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/SQLTester.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,1433 @@
+/*
+** 2023-08-08
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains the main application entry pointer for the
+** SQLTester framework.
+*/
+package org.sqlite.jni.capi;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.*;
+import static org.sqlite.jni.capi.CApi.*;
+
+/**
+ Modes for how to escape (or not) column values and names from
+ SQLTester.execSql() to the result buffer output.
+*/
+enum ResultBufferMode {
+ //! Do not append to result buffer
+ NONE,
+ //! Append output escaped.
+ ESCAPED,
+ //! Append output as-is
+ ASIS
+};
+
+/**
+ Modes to specify how to emit multi-row output from
+ SQLTester.execSql() to the result buffer.
+*/
+enum ResultRowMode {
+ //! Keep all result rows on one line, space-separated.
+ ONELINE,
+ //! Add a newline between each result row.
+ NEWLINE
+};
+
+/**
+ Base exception type for test-related failures.
+*/
+class SQLTesterException extends RuntimeException {
+ private boolean bFatal = false;
+
+ SQLTesterException(String msg){
+ super(msg);
+ }
+
+ protected SQLTesterException(String msg, boolean fatal){
+ super(msg);
+ bFatal = fatal;
+ }
+
+ /**
+ Indicates whether the framework should consider this exception
+ type as immediately fatal to the test run or not.
+ */
+ final boolean isFatal(){ return bFatal; }
+}
+
+class DbException extends SQLTesterException {
+ DbException(sqlite3 db, int rc, boolean closeDb){
+ super("DB error #"+rc+": "+sqlite3_errmsg(db),true);
+ if( closeDb ) sqlite3_close_v2(db);
+ }
+ DbException(sqlite3 db, int rc){
+ this(db, rc, false);
+ }
+}
+
+/**
+ Generic test-failed exception.
+ */
+class TestScriptFailed extends SQLTesterException {
+ public TestScriptFailed(TestScript ts, String msg){
+ super(ts.getOutputPrefix()+": "+msg, true);
+ }
+}
+
+/**
+ Thrown when an unknown test command is encountered in a script.
+*/
+class UnknownCommand extends SQLTesterException {
+ public UnknownCommand(TestScript ts, String cmd){
+ super(ts.getOutputPrefix()+": unknown command: "+cmd, false);
+ }
+}
+
+/**
+ Thrown when an "incompatible directive" is found in a script. This
+ can be the presence of a C-preprocessor construct, specific
+ metadata tags within a test script's header, or specific test
+ constructs which are incompatible with this particular
+ implementation.
+*/
+class IncompatibleDirective extends SQLTesterException {
+ public IncompatibleDirective(TestScript ts, String line){
+ super(ts.getOutputPrefix()+": incompatible directive: "+line, false);
+ }
+}
+
+/**
+ Console output utility class.
+*/
+class Outer {
+ private int verbosity = 0;
+
+ static void out(Object val){
+ System.out.print(val);
+ }
+
+ Outer out(Object... vals){
+ for(Object v : vals) out(v);
+ return this;
+ }
+
+ Outer outln(Object... vals){
+ out(vals).out("\n");
+ return this;
+ }
+
+ Outer verbose(Object... vals){
+ if(verbosity>0){
+ out("VERBOSE",(verbosity>1 ? "+: " : ": ")).outln(vals);
+ }
+ return this;
+ }
+
+ void setVerbosity(int level){
+ verbosity = level;
+ }
+
+ int getVerbosity(){
+ return verbosity;
+ }
+
+ public boolean isVerbose(){return verbosity > 0;}
+
+}
+
+/**
+
This class provides an application which aims to implement the
+ rudimentary SQL-driven test tool described in the accompanying
+ {@code test-script-interpreter.md}.
+
+
This class is an internal testing tool, not part of the public
+ interface but is (A) in the same package as the library because
+ access permissions require it to be so and (B) the JDK8 javadoc
+ offers no way to filter individual classes out of the doc
+ generation process (it can only exclude packages, but see (A)).
+
+
An instance of this application provides a core set of services
+ which TestScript instances use for processing testing logic.
+ TestScripts, in turn, delegate the concrete test work to Command
+ objects, which the TestScript parses on their behalf.
+*/
+public class SQLTester {
+ //! List of input script files.
+ private final java.util.List listInFiles = new ArrayList<>();
+ //! Console output utility.
+ private final Outer outer = new Outer();
+ //! Test input buffer.
+ private final StringBuilder inputBuffer = new StringBuilder();
+ //! Test result buffer.
+ private final StringBuilder resultBuffer = new StringBuilder();
+ //! Buffer for REQUIRED_PROPERTIES pragmas.
+ private final StringBuilder dbInitSql = new StringBuilder();
+ //! Output representation of SQL NULL.
+ private String nullView = "nil";
+ //! Total tests run.
+ private int nTotalTest = 0;
+ //! Total test script files run.
+ private int nTestFile = 0;
+ //! Number of scripts which were aborted.
+ private int nAbortedScript = 0;
+ //! Incremented by test case handlers
+ private int nTest = 0;
+ //! True to enable column name output from execSql()
+ private boolean emitColNames;
+ //! True to keep going regardless of how a test fails.
+ private boolean keepGoing = false;
+ //! The list of available db handles.
+ private final sqlite3[] aDb = new sqlite3[7];
+ //! Index into aDb of the current db.
+ private int iCurrentDb = 0;
+ //! Name of the default db, re-created for each script.
+ private final String initialDbName = "test.db";
+
+
+ public SQLTester(){
+ reset();
+ }
+
+ void setVerbosity(int level){
+ this.outer.setVerbosity( level );
+ }
+ int getVerbosity(){
+ return this.outer.getVerbosity();
+ }
+ boolean isVerbose(){
+ return this.outer.isVerbose();
+ }
+
+ void outputColumnNames(boolean b){ emitColNames = b; }
+
+ void verbose(Object... vals){
+ outer.verbose(vals);
+ }
+
+ void outln(Object... vals){
+ outer.outln(vals);
+ }
+
+ void out(Object... vals){
+ outer.out(vals);
+ }
+
+ //! Adds the given test script to the to-test list.
+ public void addTestScript(String filename){
+ listInFiles.add(filename);
+ //verbose("Added file ",filename);
+ }
+
+ private void setupInitialDb() throws DbException {
+ if( null==aDb[0] ){
+ Util.unlink(initialDbName);
+ openDb(0, initialDbName, true);
+ }else{
+ outln("WARNING: setupInitialDb() unexpectedly ",
+ "triggered while it is opened.");
+ }
+ }
+
+ static final String[] startEmoji = {
+ "🚴", "🏄", "🏇", "🤸", "⛹", "🏊", "⛷", "🧗", "🏋"
+ };
+ static final int nStartEmoji = startEmoji.length;
+ static int iStartEmoji = 0;
+
+ private static String nextStartEmoji(){
+ return startEmoji[iStartEmoji++ % nStartEmoji];
+ }
+
+ public void runTests() throws Exception {
+ final long tStart = System.currentTimeMillis();
+ for(String f : listInFiles){
+ reset();
+ ++nTestFile;
+ final TestScript ts = new TestScript(f);
+ outln(nextStartEmoji(), " starting [",f,"]");
+ boolean threw = false;
+ final long timeStart = System.currentTimeMillis();
+ try{
+ ts.run(this);
+ }catch(SQLTesterException e){
+ threw = true;
+ outln("🔥EXCEPTION: ",e.getClass().getSimpleName(),": ",e.getMessage());
+ ++nAbortedScript;
+ if( keepGoing ) outln("Continuing anyway becaure of the keep-going option.");
+ else if( e.isFatal() ) throw e;
+ }finally{
+ final long timeEnd = System.currentTimeMillis();
+ outln("🏁",(threw ? "❌" : "✅")," ",nTest," test(s) in ",
+ (timeEnd-timeStart),"ms.");
+ }
+ }
+ final long tEnd = System.currentTimeMillis();
+ outln("Total run-time: ",(tEnd-tStart),"ms");
+ Util.unlink(initialDbName);
+ }
+
+ private StringBuilder clearBuffer(StringBuilder b){
+ b.setLength(0);;
+ return b;
+ }
+
+ StringBuilder clearInputBuffer(){
+ return clearBuffer(inputBuffer);
+ }
+
+ StringBuilder clearResultBuffer(){
+ return clearBuffer(resultBuffer);
+ }
+
+ StringBuilder getInputBuffer(){ return inputBuffer; }
+
+ void appendInput(String n, boolean addNL){
+ inputBuffer.append(n);
+ if(addNL) inputBuffer.append('\n');
+ }
+
+ void appendResult(String n, boolean addNL){
+ resultBuffer.append(n);
+ if(addNL) resultBuffer.append('\n');
+ }
+
+ void appendDbInitSql(String n) throws DbException {
+ dbInitSql.append(n).append('\n');
+ if( null!=getCurrentDb() ){
+ //outln("RUNNING DB INIT CODE: ",n);
+ execSql(null, true, ResultBufferMode.NONE, null, n);
+ }
+ }
+ String getDbInitSql(){ return dbInitSql.toString(); }
+
+ String getInputText(){ return inputBuffer.toString(); }
+
+ String getResultText(){ return resultBuffer.toString(); }
+
+ private String takeBuffer(StringBuilder b){
+ final String rc = b.toString();
+ clearBuffer(b);
+ return rc;
+ }
+
+ String takeInputBuffer(){ return takeBuffer(inputBuffer); }
+
+ String takeResultBuffer(){ return takeBuffer(resultBuffer); }
+
+ int getCurrentDbId(){ return iCurrentDb; }
+
+ SQLTester affirmDbId(int n) throws IndexOutOfBoundsException {
+ if(n<0 || n>=aDb.length){
+ throw new IndexOutOfBoundsException("illegal db number: "+n);
+ }
+ return this;
+ }
+
+ sqlite3 setCurrentDb(int n) throws Exception{
+ affirmDbId(n);
+ iCurrentDb = n;
+ return this.aDb[n];
+ }
+
+ sqlite3 getCurrentDb(){ return aDb[iCurrentDb]; }
+
+ sqlite3 getDbById(int id) throws Exception{
+ return affirmDbId(id).aDb[id];
+ }
+
+ void closeDb(int id) {
+ final sqlite3 db = affirmDbId(id).aDb[id];
+ if( null != db ){
+ sqlite3_close_v2(db);
+ aDb[id] = null;
+ }
+ }
+
+ void closeDb() { closeDb(iCurrentDb); }
+
+ void closeAllDbs(){
+ for(int i = 0; i 0){
+ //outln("RUNNING DB INIT CODE: ",dbInitSql.toString());
+ rc = execSql(db, false, ResultBufferMode.NONE,
+ null, dbInitSql.toString());
+ }
+ if( 0!=rc ){
+ throw new DbException(db, rc, true);
+ }
+ return aDb[iCurrentDb] = db;
+ }
+
+ sqlite3 openDb(int slot, String name, boolean createIfNeeded) throws DbException {
+ affirmDbId(slot);
+ iCurrentDb = slot;
+ return openDb(name, createIfNeeded);
+ }
+
+ /**
+ Resets all tester context state except for that related to
+ tracking running totals.
+ */
+ void reset(){
+ clearInputBuffer();
+ clearResultBuffer();
+ clearBuffer(dbInitSql);
+ closeAllDbs();
+ nTest = 0;
+ nullView = "nil";
+ emitColNames = false;
+ iCurrentDb = 0;
+ //dbInitSql.append("SELECT 1;");
+ }
+
+ void setNullValue(String v){nullView = v;}
+
+ /**
+ If true, encountering an unknown command in a script causes the
+ remainder of the script to be skipped, rather than aborting the
+ whole script run.
+ */
+ boolean skipUnknownCommands(){
+ // Currently hard-coded. Potentially a flag someday.
+ return true;
+ }
+
+ void incrementTestCounter(){ ++nTest; ++nTotalTest; }
+
+ //! "Special" characters - we have to escape output if it contains any.
+ static final Pattern patternSpecial = Pattern.compile(
+ "[\\x00-\\x20\\x22\\x5c\\x7b\\x7d]"
+ );
+ //! Either of '{' or '}'.
+ static final Pattern patternSquiggly = Pattern.compile("[{}]");
+
+ /**
+ Returns v or some escaped form of v, as defined in the tester's
+ spec doc.
+ */
+ String escapeSqlValue(String v){
+ if( "".equals(v) ) return "{}";
+ Matcher m = patternSpecial.matcher(v);
+ if( !m.find() ){
+ return v /* no escaping needed */;
+ }
+ m = patternSquiggly.matcher(v);
+ if( !m.find() ){
+ return "{"+v+"}";
+ }
+ final StringBuilder sb = new StringBuilder("\"");
+ final int n = v.length();
+ for(int i = 0; i < n; ++i){
+ final char ch = v.charAt(i);
+ switch(ch){
+ case '\\': sb.append("\\\\"); break;
+ case '"': sb.append("\\\""); break;
+ default:
+ //verbose("CHAR ",(int)ch," ",ch," octal=",String.format("\\%03o", (int)ch));
+ if( (int)ch < 32 ) sb.append(String.format("\\%03o", (int)ch));
+ else sb.append(ch);
+ break;
+ }
+ }
+ sb.append("\"");
+ return sb.toString();
+ }
+
+ private void appendDbErr(sqlite3 db, StringBuilder sb, int rc){
+ sb.append(org.sqlite.jni.capi.ResultCode.getEntryForInt(rc)).append(' ');
+ final String msg = escapeSqlValue(sqlite3_errmsg(db));
+ if( '{' == msg.charAt(0) ){
+ sb.append(msg);
+ }else{
+ sb.append('{').append(msg).append('}');
+ }
+ }
+
+ /**
+ Runs SQL on behalf of test commands and outputs the results following
+ the very specific rules of the test framework.
+
+ If db is null, getCurrentDb() is assumed. If throwOnError is true then
+ any db-side error will result in an exception, else they result in
+ the db's result code.
+
+ appendMode specifies how/whether to append results to the result
+ buffer. rowMode specifies whether to output all results in a
+ single line or one line per row. If appendMode is
+ ResultBufferMode.NONE then rowMode is ignored and may be null.
+ */
+ public int execSql(sqlite3 db, boolean throwOnError,
+ ResultBufferMode appendMode, ResultRowMode rowMode,
+ String sql) throws SQLTesterException {
+ if( null==db && null==aDb[0] ){
+ // Delay opening of the initial db to enable tests to change its
+ // name and inject on-connect code via, e.g., the MEMDB
+ // directive. this setup as the potential to misinteract with
+ // auto-extension timing and must be done carefully.
+ setupInitialDb();
+ }
+ final OutputPointer.Int32 oTail = new OutputPointer.Int32();
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8);
+ if( null==db ) db = getCurrentDb();
+ int pos = 0, n = 1;
+ byte[] sqlChunk = sqlUtf8;
+ int rc = 0;
+ sqlite3_stmt stmt = null;
+ int spacing = 0 /* emit a space for --result if>0 */ ;
+ final StringBuilder sb = (ResultBufferMode.NONE==appendMode)
+ ? null : resultBuffer;
+ //outln("sqlChunk len= = ",sqlChunk.length);
+ try{
+ while(pos < sqlChunk.length){
+ if(pos > 0){
+ sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
+ sqlChunk.length);
+ }
+ if( 0==sqlChunk.length ) break;
+ rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
+ /*outln("PREPARE rc ",rc," oTail=",oTail.get(),": ",
+ new String(sqlChunk,StandardCharsets.UTF_8),"\n");*/
+ if( 0!=rc ){
+ if(throwOnError){
+ throw new DbException(db, rc);
+ }else if( null!=sb ){
+ appendDbErr(db, sb, rc);
+ }
+ break;
+ }
+ pos = oTail.value;
+ stmt = outStmt.take();
+ if( null == stmt ){
+ // empty statement was parsed.
+ continue;
+ }
+ if( null!=sb ){
+ // Add the output to the result buffer...
+ final int nCol = sqlite3_column_count(stmt);
+ String colName = null, val = null;
+ while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){
+ for(int i = 0; i < nCol; ++i){
+ if( spacing++ > 0 ) sb.append(' ');
+ if( emitColNames ){
+ colName = sqlite3_column_name(stmt, i);
+ switch(appendMode){
+ case ASIS:
+ sb.append( colName );
+ break;
+ case ESCAPED:
+ sb.append( escapeSqlValue(colName) );
+ break;
+ default:
+ throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode);
+ }
+ sb.append(' ');
+ }
+ val = sqlite3_column_text16(stmt, i);
+ if( null==val ){
+ sb.append( nullView );
+ continue;
+ }
+ switch(appendMode){
+ case ASIS:
+ sb.append( val );
+ break;
+ case ESCAPED:
+ sb.append( escapeSqlValue(val) );
+ break;
+ default:
+ throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode);
+ }
+ }
+ if( ResultRowMode.NEWLINE == rowMode ){
+ spacing = 0;
+ sb.append('\n');
+ }
+ }
+ }else{
+ while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){}
+ }
+ sqlite3_finalize(stmt);
+ stmt = null;
+ if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0;
+ else if( rc!=0 ){
+ if( null!=sb ){
+ appendDbErr(db, sb, rc);
+ }
+ break;
+ }
+ }
+ }finally{
+ sqlite3_reset(stmt
+ /* In order to trigger an exception in the
+ INSERT...RETURNING locking scenario:
+ https://sqlite.org/forum/forumpost/36f7a2e7494897df */);
+ sqlite3_finalize(stmt);
+ }
+ if( 0!=rc && throwOnError ){
+ throw new DbException(db, rc);
+ }
+ return rc;
+ }
+
+ public static void main(String[] argv) throws Exception{
+ installCustomExtensions();
+ boolean dumpInternals = false;
+ final SQLTester t = new SQLTester();
+ for(String a : argv){
+ if(a.startsWith("-")){
+ final String flag = a.replaceFirst("-+","");
+ if( flag.equals("verbose") ){
+ // Use --verbose up to 3 times
+ t.setVerbosity(t.getVerbosity() + 1);
+ }else if( flag.equals("keep-going") ){
+ t.keepGoing = true;
+ }else if( flag.equals("internals") ){
+ dumpInternals = true;
+ }else{
+ throw new IllegalArgumentException("Unhandled flag: "+flag);
+ }
+ continue;
+ }
+ t.addTestScript(a);
+ }
+ final AutoExtensionCallback ax = new AutoExtensionCallback() {
+ private final SQLTester tester = t;
+ @Override public int call(sqlite3 db){
+ final String init = tester.getDbInitSql();
+ if( !init.isEmpty() ){
+ tester.execSql(db, true, ResultBufferMode.NONE, null, init);
+ }
+ return 0;
+ }
+ };
+ sqlite3_auto_extension(ax);
+ try {
+ t.runTests();
+ }finally{
+ sqlite3_cancel_auto_extension(ax);
+ t.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s).");
+ if( t.nAbortedScript > 0 ){
+ t.outln("Aborted ",t.nAbortedScript," script(s).");
+ }
+ if( dumpInternals ){
+ sqlite3_jni_internal_details();
+ }
+ }
+ }
+
+ /**
+ Internal impl of the public strglob() method. Neither argument
+ may be NULL and both _MUST_ be NUL-terminated.
+ */
+ private static native int strglob(byte[] glob, byte[] txt);
+
+ /**
+ Works essentially the same as sqlite3_strglob() except that the
+ glob character '#' matches a sequence of one or more digits. It
+ does not match when it appears at the start or middle of a series
+ of digits, e.g. "#23" or "1#3", but will match at the end,
+ e.g. "12#".
+ */
+ static int strglob(String glob, String txt){
+ return strglob(
+ (glob+"\0").getBytes(StandardCharsets.UTF_8),
+ (txt+"\0").getBytes(StandardCharsets.UTF_8)
+ );
+ }
+
+ /**
+ Sets up C-side components needed by the test framework. This must
+ not be called until main() is triggered so that it does not
+ interfere with library clients who don't use this class.
+ */
+ static native void installCustomExtensions();
+ static {
+ System.loadLibrary("sqlite3-jni")
+ /* Interestingly, when SQLTester is the main app, we have to
+ load that lib from here. The same load from CApi does
+ not happen early enough. Without this,
+ installCustomExtensions() is an unresolved symbol. */;
+ }
+
+}
+
+/**
+ General utilities for the SQLTester bits.
+*/
+final class Util {
+
+ //! Throws a new T, appending all msg args into a string for the message.
+ static void toss(Class extends Exception> errorType, Object... msg) throws Exception {
+ StringBuilder sb = new StringBuilder();
+ for(Object s : msg) sb.append(s);
+ final java.lang.reflect.Constructor extends Exception> ctor =
+ errorType.getConstructor(String.class);
+ throw ctor.newInstance(sb.toString());
+ }
+
+ static void toss(Object... msg) throws Exception{
+ toss(RuntimeException.class, msg);
+ }
+
+ //! Tries to delete the given file, silently ignoring failure.
+ static void unlink(String filename){
+ try{
+ final java.io.File f = new java.io.File(filename);
+ f.delete();
+ }catch(Exception e){
+ /* ignore */
+ }
+ }
+
+ /**
+ Appends all entries in argv[1..end] into a space-separated
+ string, argv[0] is not included because it's expected to be a
+ command name.
+ */
+ static String argvToString(String[] argv){
+ StringBuilder sb = new StringBuilder();
+ for(int i = 1; i < argv.length; ++i ){
+ if( i>1 ) sb.append(" ");
+ sb.append( argv[i] );
+ }
+ return sb.toString();
+ }
+
+}
+
+/**
+ Base class for test script commands. It provides a set of utility
+ APIs for concrete command implementations.
+
+ Each subclass must have a public no-arg ctor and must implement
+ the process() method which is abstract in this class.
+
+ Commands are intended to be stateless, except perhaps for counters
+ and similar internals. Specifically, no state which changes the
+ behavior between any two invocations of process() should be
+ retained.
+*/
+abstract class Command {
+ protected Command(){}
+
+ /**
+ Must process one command-unit of work and either return
+ (on success) or throw (on error).
+
+ The first two arguments specify the context of the test. The TestScript
+ provides the content of the test and the SQLTester providers the sandbox
+ in which that script is being evaluated.
+
+ argv is a list with the command name followed by any arguments to
+ that command. The argcCheck() method from this class provides
+ very basic argc validation.
+ */
+ public abstract void process(
+ SQLTester st, TestScript ts, String[] argv
+ ) throws Exception;
+
+ /**
+ If argv.length-1 (-1 because the command's name is in argv[0]) does not
+ fall in the inclusive range (min,max) then this function throws. Use
+ a max value of -1 to mean unlimited.
+ */
+ protected final void argcCheck(TestScript ts, String[] argv, int min, int max) throws Exception{
+ int argc = argv.length-1;
+ if(argc=0 && argc>max)){
+ if( min==max ){
+ ts.toss(argv[0]," requires exactly ",min," argument(s)");
+ }else if(max>0){
+ ts.toss(argv[0]," requires ",min,"-",max," arguments.");
+ }else{
+ ts.toss(argv[0]," requires at least ",min," arguments.");
+ }
+ }
+ }
+
+ /**
+ Equivalent to argcCheck(argv,argc,argc).
+ */
+ protected final void argcCheck(TestScript ts, String[] argv, int argc) throws Exception{
+ argcCheck(ts, argv, argc, argc);
+ }
+}
+
+//! --close command
+class CloseDbCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,0,1);
+ Integer id;
+ if(argv.length>1){
+ String arg = argv[1];
+ if("all".equals(arg)){
+ t.closeAllDbs();
+ return;
+ }
+ else{
+ id = Integer.parseInt(arg);
+ }
+ }else{
+ id = t.getCurrentDbId();
+ }
+ t.closeDb(id);
+ }
+}
+
+//! --column-names command
+class ColumnNamesCommand extends Command {
+ public void process(
+ SQLTester st, TestScript ts, String[] argv
+ ) throws Exception{
+ argcCheck(ts,argv,1);
+ st.outputColumnNames( Integer.parseInt(argv[1])!=0 );
+ }
+}
+
+//! --db command
+class DbCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,1);
+ t.setCurrentDb( Integer.parseInt(argv[1]) );
+ }
+}
+
+//! --glob command
+class GlobCommand extends Command {
+ private boolean negate = false;
+ public GlobCommand(){}
+ protected GlobCommand(boolean negate){ this.negate = negate; }
+
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,1,-1);
+ t.incrementTestCounter();
+ final String sql = t.takeInputBuffer();
+ int rc = t.execSql(null, true, ResultBufferMode.ESCAPED,
+ ResultRowMode.ONELINE, sql);
+ final String result = t.getResultText();
+ final String sArgs = Util.argvToString(argv);
+ //t2.verbose2(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs);
+ final String glob = Util.argvToString(argv);
+ rc = SQLTester.strglob(glob, result);
+ if( (negate && 0==rc) || (!negate && 0!=rc) ){
+ ts.toss(argv[0], " mismatch: ", glob," vs input: ",result);
+ }
+ }
+}
+
+//! --json command
+class JsonCommand extends ResultCommand {
+ public JsonCommand(){ super(ResultBufferMode.ASIS); }
+}
+
+//! --json-block command
+class JsonBlockCommand extends TableResultCommand {
+ public JsonBlockCommand(){ super(true); }
+}
+
+//! --new command
+class NewDbCommand extends OpenDbCommand {
+ public NewDbCommand(){ super(true); }
+}
+
+//! Placeholder dummy/no-op commands
+class NoopCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ }
+}
+
+//! --notglob command
+class NotGlobCommand extends GlobCommand {
+ public NotGlobCommand(){
+ super(true);
+ }
+}
+
+//! --null command
+class NullCommand extends Command {
+ public void process(
+ SQLTester st, TestScript ts, String[] argv
+ ) throws Exception{
+ argcCheck(ts,argv,1);
+ st.setNullValue( argv[1] );
+ }
+}
+
+//! --open command
+class OpenDbCommand extends Command {
+ private boolean createIfNeeded = false;
+ public OpenDbCommand(){}
+ protected OpenDbCommand(boolean c){createIfNeeded = c;}
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,1);
+ t.openDb(argv[1], createIfNeeded);
+ }
+}
+
+//! --print command
+class PrintCommand extends Command {
+ public void process(
+ SQLTester st, TestScript ts, String[] argv
+ ) throws Exception{
+ st.out(ts.getOutputPrefix(),": ");
+ if( 1==argv.length ){
+ st.out( st.getInputText() );
+ }else{
+ st.outln( Util.argvToString(argv) );
+ }
+ }
+}
+
+//! --result command
+class ResultCommand extends Command {
+ private final ResultBufferMode bufferMode;
+ protected ResultCommand(ResultBufferMode bm){ bufferMode = bm; }
+ public ResultCommand(){ this(ResultBufferMode.ESCAPED); }
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,0,-1);
+ t.incrementTestCounter();
+ final String sql = t.takeInputBuffer();
+ //ts.verbose2(argv[0]," SQL =\n",sql);
+ int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql);
+ final String result = t.getResultText().trim();
+ final String sArgs = argv.length>1 ? Util.argvToString(argv) : "";
+ if( !result.equals(sArgs) ){
+ t.outln(argv[0]," FAILED comparison. Result buffer:\n",
+ result,"\nExpected result:\n",sArgs);
+ ts.toss(argv[0]+" comparison failed.");
+ }
+ }
+}
+
+//! --run command
+class RunCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,0,1);
+ final sqlite3 db = (1==argv.length)
+ ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) );
+ final String sql = t.takeInputBuffer();
+ final int rc = t.execSql(db, false, ResultBufferMode.NONE,
+ ResultRowMode.ONELINE, sql);
+ if( 0!=rc && t.isVerbose() ){
+ String msg = sqlite3_errmsg(db);
+ ts.verbose1(argv[0]," non-fatal command error #",rc,": ",
+ msg,"\nfor SQL:\n",sql);
+ }
+ }
+}
+
+//! --tableresult command
+class TableResultCommand extends Command {
+ private final boolean jsonMode;
+ protected TableResultCommand(boolean jsonMode){ this.jsonMode = jsonMode; }
+ public TableResultCommand(){ this(false); }
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,0);
+ t.incrementTestCounter();
+ String body = ts.fetchCommandBody(t);
+ if( null==body ) ts.toss("Missing ",argv[0]," body.");
+ body = body.trim();
+ if( !body.endsWith("\n--end") ){
+ ts.toss(argv[0], " must be terminated with --end.");
+ }else{
+ body = body.substring(0, body.length()-6);
+ }
+ final String[] globs = body.split("\\s*\\n\\s*");
+ if( globs.length < 1 ){
+ ts.toss(argv[0], " requires 1 or more ",
+ (jsonMode ? "json snippets" : "globs"),".");
+ }
+ final String sql = t.takeInputBuffer();
+ t.execSql(null, true,
+ jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED,
+ ResultRowMode.NEWLINE, sql);
+ final String rbuf = t.getResultText();
+ final String[] res = rbuf.split("\n");
+ if( res.length != globs.length ){
+ ts.toss(argv[0], " failure: input has ", res.length,
+ " row(s) but expecting ",globs.length);
+ }
+ for(int i = 0; i < res.length; ++i){
+ final String glob = globs[i].replaceAll("\\s+"," ").trim();
+ //ts.verbose2(argv[0]," <<",glob,">> vs <<",res[i],">>");
+ if( jsonMode ){
+ if( !glob.equals(res[i]) ){
+ ts.toss(argv[0], " json <<",glob, ">> does not match: <<",
+ res[i],">>");
+ }
+ }else if( 0 != SQLTester.strglob(glob, res[i]) ){
+ ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>");
+ }
+ }
+ }
+}
+
+//! --testcase command
+class TestCaseCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,1);
+ ts.setTestCaseName(argv[1]);
+ t.clearResultBuffer();
+ t.clearInputBuffer();
+ }
+}
+
+//! --verbosity command
+class VerbosityCommand extends Command {
+ public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{
+ argcCheck(ts,argv,1);
+ ts.setVerbosity( Integer.parseInt(argv[1]) );
+ }
+}
+
+class CommandDispatcher {
+
+ private static java.util.Map commandMap =
+ new java.util.HashMap<>();
+
+ /**
+ Returns a (cached) instance mapped to name, or null if no match
+ is found.
+ */
+ static Command getCommandByName(String name){
+ Command rv = commandMap.get(name);
+ if( null!=rv ) return rv;
+ switch(name){
+ case "close": rv = new CloseDbCommand(); break;
+ case "column-names": rv = new ColumnNamesCommand(); break;
+ case "db": rv = new DbCommand(); break;
+ case "glob": rv = new GlobCommand(); break;
+ case "json": rv = new JsonCommand(); break;
+ case "json-block": rv = new JsonBlockCommand(); break;
+ case "new": rv = new NewDbCommand(); break;
+ case "notglob": rv = new NotGlobCommand(); break;
+ case "null": rv = new NullCommand(); break;
+ case "oom": rv = new NoopCommand(); break;
+ case "open": rv = new OpenDbCommand(); break;
+ case "print": rv = new PrintCommand(); break;
+ case "result": rv = new ResultCommand(); break;
+ case "run": rv = new RunCommand(); break;
+ case "tableresult": rv = new TableResultCommand(); break;
+ case "testcase": rv = new TestCaseCommand(); break;
+ case "verbosity": rv = new VerbosityCommand(); break;
+ default: rv = null; break;
+ }
+ if( null!=rv ) commandMap.put(name, rv);
+ return rv;
+ }
+
+ /**
+ Treats argv[0] as a command name, looks it up with
+ getCommandByName(), and calls process() on that instance, passing
+ it arguments given to this function.
+ */
+ static void dispatch(SQLTester tester, TestScript ts, String[] argv) throws Exception{
+ final Command cmd = getCommandByName(argv[0]);
+ if(null == cmd){
+ throw new UnknownCommand(ts, argv[0]);
+ }
+ cmd.process(tester, ts, argv);
+ }
+}
+
+
+/**
+ This class represents a single test script. It handles (or
+ delegates) its the reading-in and parsing, but the details of
+ evaluation are delegated elsewhere.
+*/
+class TestScript {
+ //! input file
+ private String filename = null;
+ //! Name pulled from the SCRIPT_MODULE_NAME directive of the file
+ private String moduleName = null;
+ //! Current test case name.
+ private String testCaseName = null;
+ //! Content buffer state.
+ private final Cursor cur = new Cursor();
+ //! Utility for console output.
+ private final Outer outer = new Outer();
+
+ //! File content and parse state.
+ private static final class Cursor {
+ private final StringBuilder sb = new StringBuilder();
+ byte[] src = null;
+ //! Current position in this.src.
+ int pos = 0;
+ //! Current line number. Starts at 0 for internal reasons and will
+ // line up with 1-based reality once parsing starts.
+ int lineNo = 0 /* yes, zero */;
+ //! Putback value for this.pos.
+ int putbackPos = 0;
+ //! Putback line number
+ int putbackLineNo = 0;
+ //! Peeked-to pos, used by peekLine() and consumePeeked().
+ int peekedPos = 0;
+ //! Peeked-to line number.
+ int peekedLineNo = 0;
+
+ //! Restore parsing state to the start of the stream.
+ void rewind(){
+ sb.setLength(0);
+ pos = lineNo = putbackPos = putbackLineNo = peekedPos = peekedLineNo = 0
+ /* kinda missing memset() about now. */;
+ }
+ }
+
+ private byte[] readFile(String filename) throws Exception {
+ return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename));
+ }
+
+ /**
+ Initializes the script with the content of the given file.
+ Throws if it cannot read the file.
+ */
+ public TestScript(String filename) throws Exception{
+ this.filename = filename;
+ setVerbosity(2);
+ cur.src = readFile(filename);
+ }
+
+ public String getFilename(){
+ return filename;
+ }
+
+ public String getModuleName(){
+ return moduleName;
+ }
+
+ /**
+ Verbosity level 0 produces no debug/verbose output. Level 1 produces
+ some and level 2 produces more.
+ */
+ public void setVerbosity(int level){
+ outer.setVerbosity(level);
+ }
+
+ public String getOutputPrefix(){
+ String rc = "["+(moduleName==null ? "" : moduleName)+"]";
+ if( null!=testCaseName ) rc += "["+testCaseName+"]";
+ if( null!=filename ) rc += "["+filename+"]";
+ return rc + " line "+ cur.lineNo;
+ }
+
+ static final String[] verboseLabel = {"🔈",/*"🔉",*/"🔊","📢"};
+ //! Output vals only if level<=current verbosity level.
+ private TestScript verboseN(int level, Object... vals){
+ final int verbosity = outer.getVerbosity();
+ if(verbosity>=level){
+ outer.out( verboseLabel[level-1], getOutputPrefix(), " ",level,": "
+ ).outln(vals);
+ }
+ return this;
+ }
+
+ TestScript verbose1(Object... vals){return verboseN(1,vals);}
+ TestScript verbose2(Object... vals){return verboseN(2,vals);}
+ TestScript verbose3(Object... vals){return verboseN(3,vals);}
+
+ private void reset(){
+ testCaseName = null;
+ cur.rewind();
+ }
+
+ void setTestCaseName(String n){ testCaseName = n; }
+
+ /**
+ Returns the next line from the buffer, minus the trailing EOL.
+
+ Returns null when all input is consumed. Throws if it reads
+ illegally-encoded input, e.g. (non-)characters in the range
+ 128-256.
+ */
+ String getLine(){
+ if( cur.pos==cur.src.length ){
+ return null /* EOF */;
+ }
+ cur.putbackPos = cur.pos;
+ cur.putbackLineNo = cur.lineNo;
+ cur.sb.setLength(0);
+ final boolean skipLeadingWs = false;
+ byte b = 0, prevB = 0;
+ int i = cur.pos;
+ if(skipLeadingWs) {
+ /* Skip any leading spaces, including newlines. This will eliminate
+ blank lines. */
+ for(; i < cur.src.length; ++i, prevB=b){
+ b = cur.src[i];
+ switch((int)b){
+ case 32/*space*/: case 9/*tab*/: case 13/*CR*/: continue;
+ case 10/*NL*/: ++cur.lineNo; continue;
+ default: break;
+ }
+ break;
+ }
+ if( i==cur.src.length ){
+ return null /* EOF */;
+ }
+ }
+ boolean doBreak = false;
+ final byte[] aChar = {0,0,0,0} /* multi-byte char buffer */;
+ int nChar = 0 /* number of bytes in the char */;
+ for(; i < cur.src.length && !doBreak; ++i){
+ b = cur.src[i];
+ switch( (int)b ){
+ case 13/*CR*/: continue;
+ case 10/*NL*/:
+ ++cur.lineNo;
+ if(cur.sb.length()>0) doBreak = true;
+ // Else it's an empty string
+ break;
+ default:
+ /* Multi-byte chars need to be gathered up and appended at
+ one time. Appending individual bytes to the StringBuffer
+ appends their integer value. */
+ nChar = 1;
+ switch( b & 0xF0 ){
+ case 0xC0: nChar = 2; break;
+ case 0xE0: nChar = 3; break;
+ case 0xF0: nChar = 4; break;
+ default:
+ if( b > 127 ) this.toss("Invalid character (#"+(int)b+").");
+ break;
+ }
+ if( 1==nChar ){
+ cur.sb.append((char)b);
+ }else{
+ for(int x = 0; x < nChar; ++x) aChar[x] = cur.src[i+x];
+ cur.sb.append(new String(Arrays.copyOf(aChar, nChar),
+ StandardCharsets.UTF_8));
+ i += nChar-1;
+ }
+ break;
+ }
+ }
+ cur.pos = i;
+ final String rv = cur.sb.toString();
+ if( i==cur.src.length && 0==rv.length() ){
+ return null /* EOF */;
+ }
+ return rv;
+ }/*getLine()*/
+
+ /**
+ Fetches the next line then resets the cursor to its pre-call
+ state. consumePeeked() can be used to consume this peeked line
+ without having to re-parse it.
+ */
+ String peekLine(){
+ final int oldPos = cur.pos;
+ final int oldPB = cur.putbackPos;
+ final int oldPBL = cur.putbackLineNo;
+ final int oldLine = cur.lineNo;
+ try{ return getLine(); }
+ finally{
+ cur.peekedPos = cur.pos;
+ cur.peekedLineNo = cur.lineNo;
+ cur.pos = oldPos;
+ cur.lineNo = oldLine;
+ cur.putbackPos = oldPB;
+ cur.putbackLineNo = oldPBL;
+ }
+ }
+
+ /**
+ Only valid after calling peekLine() and before calling getLine().
+ This places the cursor to the position it would have been at had
+ the peekLine() had been fetched with getLine().
+ */
+ void consumePeeked(){
+ cur.pos = cur.peekedPos;
+ cur.lineNo = cur.peekedLineNo;
+ }
+
+ /**
+ Restores the cursor to the position it had before the previous
+ call to getLine().
+ */
+ void putbackLine(){
+ cur.pos = cur.putbackPos;
+ cur.lineNo = cur.putbackLineNo;
+ }
+
+ private boolean checkRequiredProperties(SQLTester t, String[] props) throws SQLTesterException{
+ if( true ) return false;
+ int nOk = 0;
+ for(String rp : props){
+ verbose1("REQUIRED_PROPERTIES: ",rp);
+ switch(rp){
+ case "RECURSIVE_TRIGGERS":
+ t.appendDbInitSql("pragma recursive_triggers=on;");
+ ++nOk;
+ break;
+ case "TEMPSTORE_FILE":
+ /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2,
+ which we just happen to know is the case */
+ t.appendDbInitSql("pragma temp_store=1;");
+ ++nOk;
+ break;
+ case "TEMPSTORE_MEM":
+ /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2,
+ which we just happen to know is the case */
+ t.appendDbInitSql("pragma temp_store=0;");
+ ++nOk;
+ break;
+ case "AUTOVACUUM":
+ t.appendDbInitSql("pragma auto_vacuum=full;");
+ ++nOk;
+ case "INCRVACUUM":
+ t.appendDbInitSql("pragma auto_vacuum=incremental;");
+ ++nOk;
+ default:
+ break;
+ }
+ }
+ return props.length == nOk;
+ }
+
+ private static final Pattern patternRequiredProperties =
+ Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(\\S.*)\\s*$");
+ private static final Pattern patternScriptModuleName =
+ Pattern.compile(" SCRIPT_MODULE_NAME:[ \\t]*(\\S+)\\s*$");
+ private static final Pattern patternMixedModuleName =
+ Pattern.compile(" ((MIXED_)?MODULE_NAME):[ \\t]*(\\S+)\\s*$");
+ private static final Pattern patternCommand =
+ Pattern.compile("^--(([a-z-]+)( .*)?)$");
+
+ /**
+ Looks for "directives." If a compatible one is found, it is
+ processed and this function returns. If an incompatible one is found,
+ a description of it is returned and processing of the test must
+ end immediately.
+ */
+ private void checkForDirective(
+ SQLTester tester, String line
+ ) throws IncompatibleDirective {
+ if(line.startsWith("#")){
+ throw new IncompatibleDirective(this, "C-preprocessor input: "+line);
+ }else if(line.startsWith("---")){
+ new IncompatibleDirective(this, "triple-dash: "+line);
+ }
+ Matcher m = patternScriptModuleName.matcher(line);
+ if( m.find() ){
+ moduleName = m.group(1);
+ return;
+ }
+ m = patternRequiredProperties.matcher(line);
+ if( m.find() ){
+ final String rp = m.group(1);
+ if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){
+ throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp);
+ }
+ }
+ m = patternMixedModuleName.matcher(line);
+ if( m.find() ){
+ throw new IncompatibleDirective(this, m.group(1)+": "+m.group(3));
+ }
+ if( line.indexOf("\n|")>=0 ){
+ throw new IncompatibleDirective(this, "newline-pipe combination.");
+ }
+ return;
+ }
+
+ boolean isCommandLine(String line, boolean checkForImpl){
+ final Matcher m = patternCommand.matcher(line);
+ boolean rc = m.find();
+ if( rc && checkForImpl ){
+ rc = null!=CommandDispatcher.getCommandByName(m.group(2));
+ }
+ return rc;
+ }
+
+ /**
+ If line looks like a command, returns an argv for that command
+ invocation, else returns null.
+ */
+ String[] getCommandArgv(String line){
+ final Matcher m = patternCommand.matcher(line);
+ return m.find() ? m.group(1).trim().split("\\s+") : null;
+ }
+
+ /**
+ Fetches lines until the next recognized command. Throws if
+ checkForDirective() does. Returns null if there is no input or
+ it's only whitespace. The returned string retains all whitespace.
+
+ Note that "subcommands", --command-like constructs in the body
+ which do not match a known command name are considered to be
+ content, not commands.
+ */
+ String fetchCommandBody(SQLTester tester){
+ final StringBuilder sb = new StringBuilder();
+ String line;
+ while( (null != (line = peekLine())) ){
+ checkForDirective(tester, line);
+ if( isCommandLine(line, true) ) break;
+ else {
+ sb.append(line).append("\n");
+ consumePeeked();
+ }
+ }
+ line = sb.toString();
+ return line.trim().isEmpty() ? null : line;
+ }
+
+ private void processCommand(SQLTester t, String[] argv) throws Exception{
+ verbose1("running command: ",argv[0], " ", Util.argvToString(argv));
+ if(outer.getVerbosity()>1){
+ final String input = t.getInputText();
+ if( !input.isEmpty() ) verbose3("Input buffer = ",input);
+ }
+ CommandDispatcher.dispatch(t, this, argv);
+ }
+
+ void toss(Object... msg) throws TestScriptFailed {
+ StringBuilder sb = new StringBuilder();
+ for(Object s : msg) sb.append(s);
+ throw new TestScriptFailed(this, sb.toString());
+ }
+
+ /**
+ Runs this test script in the context of the given tester object.
+ */
+ public boolean run(SQLTester tester) throws Exception {
+ reset();
+ setVerbosity(tester.getVerbosity());
+ String line, directive;
+ String[] argv;
+ while( null != (line = getLine()) ){
+ verbose3("input line: ",line);
+ checkForDirective(tester, line);
+ argv = getCommandArgv(line);
+ if( null!=argv ){
+ processCommand(tester, argv);
+ continue;
+ }
+ tester.appendInput(line,true);
+ }
+ return true;
+ }
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,35 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A wrapper object for use with sqlite3_table_column_metadata().
+ They are populated only via that interface.
+*/
+public final class TableColumnMetadata {
+ OutputPointer.Bool pNotNull = new OutputPointer.Bool();
+ OutputPointer.Bool pPrimaryKey = new OutputPointer.Bool();
+ OutputPointer.Bool pAutoinc = new OutputPointer.Bool();
+ OutputPointer.String pzCollSeq = new OutputPointer.String();
+ OutputPointer.String pzDataType = new OutputPointer.String();
+
+ public TableColumnMetadata(){
+ }
+
+ public String getDataType(){ return pzDataType.value; }
+ public String getCollation(){ return pzCollSeq.value; }
+ public boolean isNotNull(){ return pNotNull.value; }
+ public boolean isPrimaryKey(){ return pPrimaryKey.value; }
+ public boolean isAutoincrement(){ return pAutoinc.value; }
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/Tester1.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/Tester1.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/Tester1.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/Tester1.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,1976 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.capi;
+import static org.sqlite.jni.capi.CApi.*;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ An annotation for Tester1 tests which we do not want to run in
+ reflection-driven test mode because either they are not suitable
+ for multi-threaded threaded mode or we have to control their execution
+ order.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface ManualTest{}
+/**
+ Annotation for Tester1 tests which mark those which must be skipped
+ in multi-threaded mode.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface SingleThreadOnly{}
+
+public class Tester1 implements Runnable {
+ //! True when running in multi-threaded mode.
+ private static boolean mtMode = false;
+ //! True to sleep briefly between tests.
+ private static boolean takeNaps = false;
+ //! True to shuffle the order of the tests.
+ private static boolean shuffle = false;
+ //! True to dump the list of to-run tests to stdout.
+ private static boolean listRunTests = false;
+ //! True to squelch all out() and outln() output.
+ private static boolean quietMode = false;
+ //! Total number of runTests() calls.
+ private static int nTestRuns = 0;
+ //! List of test*() methods to run.
+ private static List testMethods = null;
+ //! List of exceptions collected by run()
+ private static List listErrors = new ArrayList<>();
+ private static final class Metrics {
+ //! Number of times createNewDb() (or equivalent) is invoked.
+ volatile int dbOpen = 0;
+ }
+
+ private Integer tId;
+
+ Tester1(Integer id){
+ tId = id;
+ }
+
+ static final Metrics metrics = new Metrics();
+
+ public static synchronized void outln(){
+ if( !quietMode ){
+ System.out.println("");
+ }
+ }
+
+ public static synchronized void outPrefix(){
+ if( !quietMode ){
+ System.out.print(Thread.currentThread().getName()+": ");
+ }
+ }
+
+ public static synchronized void outln(Object val){
+ if( !quietMode ){
+ outPrefix();
+ System.out.println(val);
+ }
+ }
+
+ public static synchronized void out(Object val){
+ if( !quietMode ){
+ System.out.print(val);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static synchronized void out(Object... vals){
+ if( !quietMode ){
+ outPrefix();
+ for(Object v : vals) out(v);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static synchronized void outln(Object... vals){
+ if( !quietMode ){
+ out(vals); out("\n");
+ }
+ }
+
+ static volatile int affirmCount = 0;
+ public static synchronized int affirm(Boolean v, String comment){
+ ++affirmCount;
+ if( false ) assert( v /* prefer assert over exception if it's enabled because
+ the JNI layer sometimes has to suppress exceptions,
+ so they might be squelched on their way back to the
+ top. */);
+ if( !v ) throw new RuntimeException(comment);
+ return affirmCount;
+ }
+
+ public static void affirm(Boolean v){
+ affirm(v, "Affirmation failed.");
+ }
+
+ @SingleThreadOnly /* because it's thread-agnostic */
+ private void test1(){
+ affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
+ }
+
+ public static sqlite3 createNewDb(){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ int rc = sqlite3_open(":memory:", out);
+ ++metrics.dbOpen;
+ sqlite3 db = out.take();
+ if( 0!=rc ){
+ final String msg =
+ null==db ? sqlite3_errstr(rc) : sqlite3_errmsg(db);
+ sqlite3_close(db);
+ throw new RuntimeException("Opening db failed: "+msg);
+ }
+ affirm( null == out.get() );
+ affirm( 0 != db.getNativePointer() );
+ rc = sqlite3_busy_timeout(db, 2000);
+ affirm( 0 == rc );
+ return db;
+ }
+
+ public static void execSql(sqlite3 db, String[] sql){
+ execSql(db, String.join("", sql));
+ }
+
+ public static int execSql(sqlite3 db, boolean throwOnError, String sql){
+ OutputPointer.Int32 oTail = new OutputPointer.Int32();
+ final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8);
+ int pos = 0, n = 1;
+ byte[] sqlChunk = sqlUtf8;
+ int rc = 0;
+ sqlite3_stmt stmt = null;
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ while(pos < sqlChunk.length){
+ if(pos > 0){
+ sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
+ sqlChunk.length);
+ }
+ if( 0==sqlChunk.length ) break;
+ rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
+ if(throwOnError) affirm(0 == rc);
+ else if( 0!=rc ) break;
+ pos = oTail.value;
+ stmt = outStmt.take();
+ if( null == stmt ){
+ // empty statement was parsed.
+ continue;
+ }
+ affirm(0 != stmt.getNativePointer());
+ while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){
+ }
+ sqlite3_finalize(stmt);
+ affirm(0 == stmt.getNativePointer());
+ if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){
+ break;
+ }
+ }
+ sqlite3_finalize(stmt);
+ if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0;
+ if( 0!=rc && throwOnError){
+ throw new RuntimeException("db op failed with rc="
+ +rc+": "+sqlite3_errmsg(db));
+ }
+ return rc;
+ }
+
+ public static void execSql(sqlite3 db, String sql){
+ execSql(db, true, sql);
+ }
+
+ public static sqlite3_stmt prepare(sqlite3 db, boolean throwOnError, String sql){
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ int rc = sqlite3_prepare_v2(db, sql, outStmt);
+ if( throwOnError ){
+ affirm( 0 == rc );
+ }
+ final sqlite3_stmt rv = outStmt.take();
+ affirm( null == outStmt.get() );
+ if( throwOnError ){
+ affirm( 0 != rv.getNativePointer() );
+ }
+ return rv;
+ }
+
+ public static sqlite3_stmt prepare(sqlite3 db, String sql){
+ return prepare(db, true, sql);
+ }
+
+ private void showCompileOption(){
+ int i = 0;
+ String optName;
+ outln("compile options:");
+ for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){
+ outln("\t"+optName+"\t (used="+
+ sqlite3_compileoption_used(optName)+")");
+ }
+ }
+
+ private void testCompileOption(){
+ int i = 0;
+ String optName;
+ for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){
+ }
+ affirm( i > 10 );
+ affirm( null==sqlite3_compileoption_get(-1) );
+ }
+
+ private void testOpenDb1(){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ int rc = sqlite3_open(":memory:", out);
+ ++metrics.dbOpen;
+ sqlite3 db = out.get();
+ affirm(0 == rc);
+ affirm(db.getNativePointer()!=0);
+ sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, null)
+ /* This function has different mangled names in jdk8 vs jdk19,
+ and this call is here to ensure that the build fails
+ if it cannot find both names. */;
+
+ affirm( 0==sqlite3_db_readonly(db,"main") );
+ affirm( 0==sqlite3_db_readonly(db,null) );
+ affirm( 0>sqlite3_db_readonly(db,"nope") );
+ affirm( 0>sqlite3_db_readonly(null,null) );
+ affirm( 0==sqlite3_last_insert_rowid(null) );
+
+ // These interrupt checks are only to make sure that the JNI binding
+ // has the proper exported symbol names. They don't actually test
+ // anything useful.
+ affirm( !sqlite3_is_interrupted(db) );
+ sqlite3_interrupt(db);
+ affirm( sqlite3_is_interrupted(db) );
+ sqlite3_close_v2(db);
+ affirm(0 == db.getNativePointer());
+ }
+
+ private void testOpenDb2(){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ int rc = sqlite3_open_v2(":memory:", out,
+ SQLITE_OPEN_READWRITE
+ | SQLITE_OPEN_CREATE, null);
+ ++metrics.dbOpen;
+ affirm(0 == rc);
+ sqlite3 db = out.get();
+ affirm(0 != db.getNativePointer());
+ sqlite3_close_v2(db);
+ affirm(0 == db.getNativePointer());
+ }
+
+ private void testPrepare123(){
+ sqlite3 db = createNewDb();
+ int rc;
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ rc = sqlite3_prepare(db, "CREATE TABLE t1(a);", outStmt);
+ affirm(0 == rc);
+ sqlite3_stmt stmt = outStmt.take();
+ affirm(0 != stmt.getNativePointer());
+ affirm( !sqlite3_stmt_readonly(stmt) );
+ affirm( db == sqlite3_db_handle(stmt) );
+ rc = sqlite3_step(stmt);
+ affirm(SQLITE_DONE == rc);
+ sqlite3_finalize(stmt);
+ affirm( null == sqlite3_db_handle(stmt) );
+ affirm(0 == stmt.getNativePointer());
+
+ { /* Demonstrate how to use the "zTail" option of
+ sqlite3_prepare() family of functions. */
+ OutputPointer.Int32 oTail = new OutputPointer.Int32();
+ final byte[] sqlUtf8 =
+ "CREATE TABLE t2(a); INSERT INTO t2(a) VALUES(1),(2),(3)"
+ .getBytes(StandardCharsets.UTF_8);
+ int pos = 0, n = 1;
+ byte[] sqlChunk = sqlUtf8;
+ while(pos < sqlChunk.length){
+ if(pos > 0){
+ sqlChunk = Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length);
+ }
+ //outln("SQL chunk #"+n+" length = "+sqlChunk.length+", pos = "+pos);
+ if( 0==sqlChunk.length ) break;
+ rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
+ affirm(0 == rc);
+ stmt = outStmt.get();
+ pos = oTail.value;
+ /*outln("SQL tail pos = "+pos+". Chunk = "+
+ (new String(Arrays.copyOfRange(sqlChunk,0,pos),
+ StandardCharsets.UTF_8)));*/
+ switch(n){
+ case 1: affirm(19 == pos); break;
+ case 2: affirm(36 == pos); break;
+ default: affirm( false /* can't happen */ );
+
+ }
+ ++n;
+ affirm(0 != stmt.getNativePointer());
+ rc = sqlite3_step(stmt);
+ affirm(SQLITE_DONE == rc);
+ sqlite3_finalize(stmt);
+ affirm(0 == stmt.getNativePointer());
+ }
+ }
+
+
+ rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)",
+ SQLITE_PREPARE_NORMALIZE, outStmt);
+ affirm(0 == rc);
+ stmt = outStmt.get();
+ affirm(0 != stmt.getNativePointer());
+ sqlite3_finalize(stmt);
+ affirm(0 == stmt.getNativePointer() );
+
+ affirm( 0==sqlite3_errcode(db) );
+ stmt = sqlite3_prepare(db, "intentional error");
+ affirm( null==stmt );
+ affirm( 0!=sqlite3_errcode(db) );
+ affirm( 0==sqlite3_errmsg(db).indexOf("near \"intentional\"") );
+ sqlite3_finalize(stmt);
+ stmt = sqlite3_prepare(db, "/* empty input*/\n-- comments only");
+ affirm( null==stmt );
+ affirm( 0==sqlite3_errcode(db) );
+ sqlite3_close_v2(db);
+ }
+
+ private void testBindFetchInt(){
+ sqlite3 db = createNewDb();
+ execSql(db, "CREATE TABLE t(a)");
+
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(:a);");
+ affirm(1 == sqlite3_bind_parameter_count(stmt));
+ final int paramNdx = sqlite3_bind_parameter_index(stmt, ":a");
+ affirm(1 == paramNdx);
+ affirm( ":a".equals(sqlite3_bind_parameter_name(stmt, paramNdx)));
+ int total1 = 0;
+ long rowid = -1;
+ int changes = sqlite3_changes(db);
+ int changesT = sqlite3_total_changes(db);
+ long changes64 = sqlite3_changes64(db);
+ long changesT64 = sqlite3_total_changes64(db);
+ int rc;
+ for(int i = 99; i < 102; ++i ){
+ total1 += i;
+ rc = sqlite3_bind_int(stmt, paramNdx, i);
+ affirm(0 == rc);
+ rc = sqlite3_step(stmt);
+ sqlite3_reset(stmt);
+ affirm(SQLITE_DONE == rc);
+ long x = sqlite3_last_insert_rowid(db);
+ affirm(x > rowid);
+ rowid = x;
+ }
+ sqlite3_finalize(stmt);
+ affirm(300 == total1);
+ affirm(sqlite3_changes(db) > changes);
+ affirm(sqlite3_total_changes(db) > changesT);
+ affirm(sqlite3_changes64(db) > changes64);
+ affirm(sqlite3_total_changes64(db) > changesT64);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+ affirm( sqlite3_stmt_readonly(stmt) );
+ affirm( !sqlite3_stmt_busy(stmt) );
+ int total2 = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ affirm( sqlite3_stmt_busy(stmt) );
+ total2 += sqlite3_column_int(stmt, 0);
+ sqlite3_value sv = sqlite3_column_value(stmt, 0);
+ affirm( null != sv );
+ affirm( 0 != sv.getNativePointer() );
+ affirm( SQLITE_INTEGER == sqlite3_value_type(sv) );
+ }
+ affirm( !sqlite3_stmt_busy(stmt) );
+ sqlite3_finalize(stmt);
+ affirm(total1 == total2);
+
+ // sqlite3_value_frombind() checks...
+ stmt = prepare(db, "SELECT 1, ?");
+ sqlite3_bind_int(stmt, 1, 2);
+ rc = sqlite3_step(stmt);
+ affirm( SQLITE_ROW==rc );
+ affirm( !sqlite3_value_frombind(sqlite3_column_value(stmt, 0)) );
+ affirm( sqlite3_value_frombind(sqlite3_column_value(stmt, 1)) );
+ sqlite3_finalize(stmt);
+
+ sqlite3_close_v2(db);
+ affirm(0 == db.getNativePointer());
+ }
+
+ private void testBindFetchInt64(){
+ try (sqlite3 db = createNewDb()){
+ execSql(db, "CREATE TABLE t(a)");
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+ long total1 = 0;
+ for(long i = 0xffffffff; i < 0xffffffff + 3; ++i ){
+ total1 += i;
+ sqlite3_bind_int64(stmt, 1, i);
+ sqlite3_step(stmt);
+ sqlite3_reset(stmt);
+ }
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+ long total2 = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ total2 += sqlite3_column_int64(stmt, 0);
+ }
+ sqlite3_finalize(stmt);
+ affirm(total1 == total2);
+ //sqlite3_close_v2(db);
+ }
+ }
+
+ private void testBindFetchDouble(){
+ try (sqlite3 db = createNewDb()){
+ execSql(db, "CREATE TABLE t(a)");
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+ double total1 = 0;
+ for(double i = 1.5; i < 5.0; i = i + 1.0 ){
+ total1 += i;
+ sqlite3_bind_double(stmt, 1, i);
+ sqlite3_step(stmt);
+ sqlite3_reset(stmt);
+ }
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+ double total2 = 0;
+ int counter = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ ++counter;
+ total2 += sqlite3_column_double(stmt, 0);
+ }
+ affirm(4 == counter);
+ sqlite3_finalize(stmt);
+ affirm(total2<=total1+0.01 && total2>=total1-0.01);
+ //sqlite3_close_v2(db);
+ }
+ }
+
+ private void testBindFetchText(){
+ sqlite3 db = createNewDb();
+ execSql(db, "CREATE TABLE t(a)");
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+ String[] list1 = { "hell🤩", "w😃rld", "!🤩" };
+ int rc;
+ int n = 0;
+ for( String e : list1 ){
+ rc = (0==n)
+ ? sqlite3_bind_text(stmt, 1, e)
+ : sqlite3_bind_text16(stmt, 1, e);
+ affirm(0 == rc);
+ rc = sqlite3_step(stmt);
+ affirm(SQLITE_DONE==rc);
+ sqlite3_reset(stmt);
+ }
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+ StringBuilder sbuf = new StringBuilder();
+ n = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ final sqlite3_value sv = sqlite3_value_dup(sqlite3_column_value(stmt,0));
+ final String txt = sqlite3_column_text16(stmt, 0);
+ sbuf.append( txt );
+ affirm( txt.equals(new String(
+ sqlite3_column_text(stmt, 0),
+ StandardCharsets.UTF_8
+ )) );
+ affirm( txt.length() < sqlite3_value_bytes(sv) );
+ affirm( txt.equals(new String(
+ sqlite3_value_text(sv),
+ StandardCharsets.UTF_8)) );
+ affirm( txt.length() == sqlite3_value_bytes16(sv)/2 );
+ affirm( txt.equals(sqlite3_value_text16(sv)) );
+ sqlite3_value_free(sv);
+ ++n;
+ }
+ sqlite3_finalize(stmt);
+ affirm(3 == n);
+ affirm("w😃rldhell🤩!🤩".equals(sbuf.toString()));
+
+ try( sqlite3_stmt stmt2 = prepare(db, "SELECT ?, ?") ){
+ rc = sqlite3_bind_text(stmt2, 1, "");
+ affirm( 0==rc );
+ rc = sqlite3_bind_text(stmt2, 2, (String)null);
+ affirm( 0==rc );
+ rc = sqlite3_step(stmt2);
+ affirm( SQLITE_ROW==rc );
+ byte[] colBa = sqlite3_column_text(stmt2, 0);
+ affirm( 0==colBa.length );
+ colBa = sqlite3_column_text(stmt2, 1);
+ affirm( null==colBa );
+ //sqlite3_finalize(stmt);
+ }
+
+ if(true){
+ sqlite3_close_v2(db);
+ }else{
+ // Let the Object.finalize() override deal with it.
+ }
+ }
+
+ private void testBindFetchBlob(){
+ sqlite3 db = createNewDb();
+ execSql(db, "CREATE TABLE t(a)");
+ sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
+ byte[] list1 = { 0x32, 0x33, 0x34 };
+ int rc = sqlite3_bind_blob(stmt, 1, list1);
+ affirm( 0==rc );
+ rc = sqlite3_step(stmt);
+ affirm(SQLITE_DONE == rc);
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
+ int n = 0;
+ int total = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ byte[] blob = sqlite3_column_blob(stmt, 0);
+ affirm(3 == blob.length);
+ int i = 0;
+ for(byte b : blob){
+ affirm(b == list1[i++]);
+ total += b;
+ }
+ ++n;
+ }
+ sqlite3_finalize(stmt);
+ affirm(1 == n);
+ affirm(total == 0x32 + 0x33 + 0x34);
+ sqlite3_close_v2(db);
+ }
+
+ private void testSql(){
+ sqlite3 db = createNewDb();
+ sqlite3_stmt stmt = prepare(db, "SELECT 1");
+ affirm( "SELECT 1".equals(sqlite3_sql(stmt)) );
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT ?");
+ sqlite3_bind_text(stmt, 1, "hell😃");
+ final String expect = "SELECT 'hell😃'";
+ affirm( expect.equals(sqlite3_expanded_sql(stmt)) );
+ String n = sqlite3_normalized_sql(stmt);
+ affirm( null==n || "SELECT?;".equals(n) );
+ sqlite3_finalize(stmt);
+ sqlite3_close(db);
+ }
+
+ private void testCollation(){
+ final sqlite3 db = createNewDb();
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ final ValueHolder xDestroyCalled = new ValueHolder<>(0);
+ final CollationCallback myCollation = new CollationCallback() {
+ private String myState =
+ "this is local state. There is much like it, but this is mine.";
+ @Override
+ // Reverse-sorts its inputs...
+ public int call(byte[] lhs, byte[] rhs){
+ int len = lhs.length > rhs.length ? rhs.length : lhs.length;
+ int c = 0, i = 0;
+ for(i = 0; i < len; ++i){
+ c = lhs[i] - rhs[i];
+ if(0 != c) break;
+ }
+ if(0==c){
+ if(i < lhs.length) c = 1;
+ else if(i < rhs.length) c = -1;
+ }
+ return -c;
+ }
+ @Override
+ public void xDestroy() {
+ // Just demonstrates that xDestroy is called.
+ ++xDestroyCalled.value;
+ }
+ };
+ final CollationNeededCallback collLoader = new CollationNeededCallback(){
+ @Override
+ public int call(sqlite3 dbArg, int eTextRep, String collationName){
+ affirm(dbArg == db/* as opposed to a temporary object*/);
+ return sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation);
+ }
+ };
+ int rc = sqlite3_collation_needed(db, collLoader);
+ affirm( 0 == rc );
+ rc = sqlite3_collation_needed(db, collLoader);
+ affirm( 0 == rc /* Installing the same object again is a no-op */);
+ sqlite3_stmt stmt = prepare(db, "SELECT a FROM t ORDER BY a COLLATE reversi");
+ int counter = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ final String val = sqlite3_column_text16(stmt, 0);
+ ++counter;
+ //outln("REVERSI'd row#"+counter+": "+val);
+ switch(counter){
+ case 1: affirm("c".equals(val)); break;
+ case 2: affirm("b".equals(val)); break;
+ case 3: affirm("a".equals(val)); break;
+ }
+ }
+ affirm(3 == counter);
+ sqlite3_finalize(stmt);
+ stmt = prepare(db, "SELECT a FROM t ORDER BY a");
+ counter = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ final String val = sqlite3_column_text16(stmt, 0);
+ ++counter;
+ //outln("Non-REVERSI'd row#"+counter+": "+val);
+ switch(counter){
+ case 3: affirm("c".equals(val)); break;
+ case 2: affirm("b".equals(val)); break;
+ case 1: affirm("a".equals(val)); break;
+ }
+ }
+ affirm(3 == counter);
+ sqlite3_finalize(stmt);
+ affirm( 0 == xDestroyCalled.value );
+ rc = sqlite3_collation_needed(db, null);
+ affirm( 0 == rc );
+ sqlite3_close_v2(db);
+ affirm( 0 == db.getNativePointer() );
+ affirm( 1 == xDestroyCalled.value );
+ }
+
+ @SingleThreadOnly /* because it's thread-agnostic */
+ private void testToUtf8(){
+ /**
+ https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html
+
+ Let's ensure that we can convert to standard UTF-8 in Java code
+ (noting that the JNI native API has no way to do this).
+ */
+ final byte[] ba = "a \0 b".getBytes(StandardCharsets.UTF_8);
+ affirm( 5 == ba.length /* as opposed to 6 in modified utf-8 */);
+ }
+
+ private void testStatus(){
+ final OutputPointer.Int64 cur64 = new OutputPointer.Int64();
+ final OutputPointer.Int64 high64 = new OutputPointer.Int64();
+ final OutputPointer.Int32 cur32 = new OutputPointer.Int32();
+ final OutputPointer.Int32 high32 = new OutputPointer.Int32();
+ final sqlite3 db = createNewDb();
+ execSql(db, "create table t(a); insert into t values(1),(2),(3)");
+
+ int rc = sqlite3_status(SQLITE_STATUS_MEMORY_USED, cur32, high32, false);
+ affirm( 0 == rc );
+ affirm( cur32.value > 0 );
+ affirm( high32.value >= cur32.value );
+
+ rc = sqlite3_status64(SQLITE_STATUS_MEMORY_USED, cur64, high64, false);
+ affirm( 0 == rc );
+ affirm( cur64.value > 0 );
+ affirm( high64.value >= cur64.value );
+
+ cur32.value = 0;
+ high32.value = 1;
+ rc = sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, cur32, high32, false);
+ affirm( 0 == rc );
+ affirm( cur32.value > 0 );
+ affirm( high32.value == 0 /* always 0 for SCHEMA_USED */ );
+
+ sqlite3_close_v2(db);
+ }
+
+ private void testUdf1(){
+ final sqlite3 db = createNewDb();
+ // These ValueHolders are just to confirm that the func did what we want...
+ final ValueHolder xDestroyCalled = new ValueHolder<>(false);
+ final ValueHolder xFuncAccum = new ValueHolder<>(0);
+ final ValueHolder neverEverDoThisInClientCode = new ValueHolder<>(null);
+ final ValueHolder neverEverDoThisInClientCode2 = new ValueHolder<>(null);
+
+ // Create an SQLFunction instance using one of its 3 subclasses:
+ // Scalar, Aggregate, or Window:
+ SQLFunction func =
+ // Each of the 3 subclasses requires a different set of
+ // functions, all of which must be implemented. Anonymous
+ // classes are a convenient way to implement these.
+ new ScalarFunction(){
+ public void xFunc(sqlite3_context cx, sqlite3_value[] args){
+ affirm(db == sqlite3_context_db_handle(cx));
+ if( null==neverEverDoThisInClientCode.value ){
+ /* !!!NEVER!!! hold a reference to an sqlite3_value or
+ sqlite3_context object like this in client code! They
+ are ONLY legal for the duration of their single
+ call. We do it here ONLY to test that the defenses
+ against clients doing this are working. */
+ neverEverDoThisInClientCode2.value = cx;
+ neverEverDoThisInClientCode.value = args;
+ }
+ int result = 0;
+ for( sqlite3_value v : args ) result += sqlite3_value_int(v);
+ xFuncAccum.value += result;// just for post-run testing
+ sqlite3_result_int(cx, result);
+ }
+ /* OPTIONALLY override xDestroy... */
+ public void xDestroy(){
+ xDestroyCalled.value = true;
+ }
+ };
+
+ // Register and use the function...
+ int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func);
+ affirm(0 == rc);
+ affirm(0 == xFuncAccum.value);
+ final sqlite3_stmt stmt = prepare(db, "SELECT myfunc(1,2,3)");
+ int n = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ affirm( 6 == sqlite3_column_int(stmt, 0) );
+ ++n;
+ }
+ sqlite3_finalize(stmt);
+ affirm(1 == n);
+ affirm(6 == xFuncAccum.value);
+ affirm( !xDestroyCalled.value );
+ affirm( null!=neverEverDoThisInClientCode.value );
+ affirm( null!=neverEverDoThisInClientCode2.value );
+ affirm( 0 xFuncAccum = new ValueHolder<>(0);
+
+ SQLFunction funcAgg = new AggregateFunction(){
+ @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){
+ /** Throwing from here should emit loud noise on stdout or stderr
+ but the exception is supressed because we have no way to inform
+ sqlite about it from these callbacks. */
+ //throw new RuntimeException("Throwing from an xStep");
+ }
+ @Override public void xFinal(sqlite3_context cx){
+ throw new RuntimeException("Throwing from an xFinal");
+ }
+ };
+ int rc = sqlite3_create_function(db, "myagg", 1, SQLITE_UTF8, funcAgg);
+ affirm(0 == rc);
+ affirm(0 == xFuncAccum.value);
+ sqlite3_stmt stmt = prepare(db, "SELECT myagg(1)");
+ rc = sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ affirm( 0 != rc );
+ affirm( sqlite3_errmsg(db).indexOf("an xFinal") > 0 );
+
+ SQLFunction funcSc = new ScalarFunction(){
+ @Override public void xFunc(sqlite3_context cx, sqlite3_value[] args){
+ throw new RuntimeException("Throwing from an xFunc");
+ }
+ };
+ rc = sqlite3_create_function(db, "mysca", 0, SQLITE_UTF8, funcSc);
+ affirm(0 == rc);
+ affirm(0 == xFuncAccum.value);
+ stmt = prepare(db, "SELECT mysca()");
+ rc = sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ affirm( 0 != rc );
+ affirm( sqlite3_errmsg(db).indexOf("an xFunc") > 0 );
+ rc = sqlite3_create_function(db, "mysca", 1, -1, funcSc);
+ affirm( SQLITE_FORMAT==rc, "invalid encoding value." );
+ sqlite3_close_v2(db);
+ }
+
+ @SingleThreadOnly
+ private void testUdfJavaObject(){
+ affirm( !mtMode );
+ final sqlite3 db = createNewDb();
+ final ValueHolder testResult = new ValueHolder<>(db);
+ final ValueHolder boundObj = new ValueHolder<>(42);
+ final SQLFunction func = new ScalarFunction(){
+ public void xFunc(sqlite3_context cx, sqlite3_value args[]){
+ sqlite3_result_java_object(cx, testResult.value);
+ affirm( sqlite3_value_java_object(args[0]) == boundObj );
+ }
+ };
+ int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func);
+ affirm(0 == rc);
+ sqlite3_stmt stmt = prepare(db, "select myfunc(?)");
+ affirm( 0 != stmt.getNativePointer() );
+ affirm( testResult.value == db );
+ rc = sqlite3_bind_java_object(stmt, 1, boundObj);
+ affirm( 0==rc );
+ int n = 0;
+ if( SQLITE_ROW == sqlite3_step(stmt) ){
+ final sqlite3_value v = sqlite3_column_value(stmt, 0);
+ affirm( testResult.value == sqlite3_value_java_object(v) );
+ affirm( testResult.value == sqlite3_value_java_casted(v, sqlite3.class) );
+ affirm( testResult.value ==
+ sqlite3_value_java_casted(v, testResult.value.getClass()) );
+ affirm( testResult.value == sqlite3_value_java_casted(v, Object.class) );
+ affirm( null == sqlite3_value_java_casted(v, String.class) );
+ ++n;
+ }
+ sqlite3_finalize(stmt);
+ affirm( 1 == n );
+ affirm( 0==sqlite3_db_release_memory(db) );
+ sqlite3_close_v2(db);
+ }
+
+ private void testUdfAggregate(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder xFinalNull =
+ // To confirm that xFinal() is called with no aggregate state
+ // when the corresponding result set is empty.
+ new ValueHolder<>(false);
+ SQLFunction func = new AggregateFunction(){
+ @Override
+ public void xStep(sqlite3_context cx, sqlite3_value[] args){
+ final ValueHolder agg = this.getAggregateState(cx, 0);
+ agg.value += sqlite3_value_int(args[0]);
+ affirm( agg == this.getAggregateState(cx, 0) );
+ }
+ @Override
+ public void xFinal(sqlite3_context cx){
+ final Integer v = this.takeAggregateState(cx);
+ if(null == v){
+ xFinalNull.value = true;
+ sqlite3_result_null(cx);
+ }else{
+ sqlite3_result_int(cx, v);
+ }
+ }
+ };
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES(1),(2),(3)");
+ int rc = sqlite3_create_function(db, "myfunc", 1, SQLITE_UTF8, func);
+ affirm(0 == rc);
+ sqlite3_stmt stmt = prepare(db, "select myfunc(a), myfunc(a+10) from t");
+ affirm( 0==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) );
+ int n = 0;
+ if( SQLITE_ROW == sqlite3_step(stmt) ){
+ int v = sqlite3_column_int(stmt, 0);
+ affirm( 6 == v );
+ int v2 = sqlite3_column_int(stmt, 1);
+ affirm( 30+v == v2 );
+ ++n;
+ }
+ affirm( 1==n );
+ affirm(!xFinalNull.value);
+ sqlite3_reset(stmt);
+ affirm( 1==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) );
+ // Ensure that the accumulator is reset on subsequent calls...
+ n = 0;
+ if( SQLITE_ROW == sqlite3_step(stmt) ){
+ final int v = sqlite3_column_int(stmt, 0);
+ affirm( 6 == v );
+ ++n;
+ }
+ sqlite3_finalize(stmt);
+ affirm( 1==n );
+
+ stmt = prepare(db, "select myfunc(a), myfunc(a+a) from t order by a");
+ n = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ final int c0 = sqlite3_column_int(stmt, 0);
+ final int c1 = sqlite3_column_int(stmt, 1);
+ ++n;
+ affirm( 6 == c0 );
+ affirm( 12 == c1 );
+ }
+ sqlite3_finalize(stmt);
+ affirm( 1 == n );
+ affirm(!xFinalNull.value);
+
+ execSql(db, "SELECT myfunc(1) WHERE 0");
+ affirm(xFinalNull.value);
+ sqlite3_close_v2(db);
+ }
+
+ private void testUdfWindow(){
+ final sqlite3 db = createNewDb();
+ /* Example window function, table, and results taken from:
+ https://sqlite.org/windowfunctions.html#udfwinfunc */
+ final SQLFunction func = new WindowFunction(){
+
+ private void xStepInverse(sqlite3_context cx, int v){
+ this.getAggregateState(cx,0).value += v;
+ }
+ @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){
+ this.xStepInverse(cx, sqlite3_value_int(args[0]));
+ }
+ @Override public void xInverse(sqlite3_context cx, sqlite3_value[] args){
+ this.xStepInverse(cx, -sqlite3_value_int(args[0]));
+ }
+
+ private void xFinalValue(sqlite3_context cx, Integer v){
+ if(null == v) sqlite3_result_null(cx);
+ else sqlite3_result_int(cx, v);
+ }
+ @Override public void xFinal(sqlite3_context cx){
+ xFinalValue(cx, this.takeAggregateState(cx));
+ }
+ @Override public void xValue(sqlite3_context cx){
+ xFinalValue(cx, this.getAggregateState(cx,null).value);
+ }
+ };
+ int rc = sqlite3_create_function(db, "winsumint", 1, SQLITE_UTF8, func);
+ affirm( 0 == rc );
+ execSql(db, new String[] {
+ "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES",
+ "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)"
+ });
+ final sqlite3_stmt stmt = prepare(db,
+ "SELECT x, winsumint(y) OVER ("+
+ "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"+
+ ") AS sum_y "+
+ "FROM twin ORDER BY x;");
+ affirm( 0 == rc );
+ int n = 0;
+ while( SQLITE_ROW == sqlite3_step(stmt) ){
+ final String s = sqlite3_column_text16(stmt, 0);
+ final int i = sqlite3_column_int(stmt, 1);
+ switch(++n){
+ case 1: affirm( "a".equals(s) && 9==i ); break;
+ case 2: affirm( "b".equals(s) && 12==i ); break;
+ case 3: affirm( "c".equals(s) && 16==i ); break;
+ case 4: affirm( "d".equals(s) && 12==i ); break;
+ case 5: affirm( "e".equals(s) && 9==i ); break;
+ default: affirm( false /* cannot happen */ );
+ }
+ }
+ sqlite3_finalize(stmt);
+ affirm( 5 == n );
+ sqlite3_close_v2(db);
+ }
+
+ private void listBoundMethods(){
+ if(false){
+ final java.lang.reflect.Field[] declaredFields =
+ CApi.class.getDeclaredFields();
+ outln("Bound constants:\n");
+ for(java.lang.reflect.Field field : declaredFields) {
+ if(java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
+ outln("\t",field.getName());
+ }
+ }
+ }
+ final java.lang.reflect.Method[] declaredMethods =
+ CApi.class.getDeclaredMethods();
+ final java.util.List funcList = new java.util.ArrayList<>();
+ for(java.lang.reflect.Method m : declaredMethods){
+ if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0){
+ final String name = m.getName();
+ if(name.startsWith("sqlite3_")){
+ funcList.add(name);
+ }
+ }
+ }
+ int count = 0;
+ java.util.Collections.sort(funcList);
+ for(String n : funcList){
+ ++count;
+ outln("\t",n,"()");
+ }
+ outln(count," functions named sqlite3_*.");
+ }
+
+ private void testTrace(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder counter = new ValueHolder<>(0);
+ /* Ensure that characters outside of the UTF BMP survive the trip
+ from Java to sqlite3 and back to Java. (At no small efficiency
+ penalty.) */
+ final String nonBmpChar = "😃";
+ int rc = sqlite3_trace_v2(
+ db, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE
+ | SQLITE_TRACE_ROW | SQLITE_TRACE_CLOSE,
+ new TraceV2Callback(){
+ @Override public int call(int traceFlag, Object pNative, Object x){
+ ++counter.value;
+ //outln("TRACE "+traceFlag+" pNative = "+pNative.getClass().getName());
+ switch(traceFlag){
+ case SQLITE_TRACE_STMT:
+ affirm(pNative instanceof sqlite3_stmt);
+ //outln("TRACE_STMT sql = "+x);
+ affirm(x instanceof String);
+ affirm( ((String)x).indexOf(nonBmpChar) > 0 );
+ break;
+ case SQLITE_TRACE_PROFILE:
+ affirm(pNative instanceof sqlite3_stmt);
+ affirm(x instanceof Long);
+ //outln("TRACE_PROFILE time = "+x);
+ break;
+ case SQLITE_TRACE_ROW:
+ affirm(pNative instanceof sqlite3_stmt);
+ affirm(null == x);
+ //outln("TRACE_ROW = "+sqlite3_column_text16((sqlite3_stmt)pNative, 0));
+ break;
+ case SQLITE_TRACE_CLOSE:
+ affirm(pNative instanceof sqlite3);
+ affirm(null == x);
+ break;
+ default:
+ affirm(false /*cannot happen*/);
+ break;
+ }
+ return 0;
+ }
+ });
+ affirm( 0==rc );
+ execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+
+ "SELECT 'w"+nonBmpChar+"orld'");
+ affirm( 6 == counter.value );
+ sqlite3_close_v2(db);
+ affirm( 7 == counter.value );
+ }
+
+ @SingleThreadOnly /* because threads inherently break this test */
+ private static void testBusy(){
+ final String dbName = "_busy-handler.db";
+ final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3();
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+
+ int rc = sqlite3_open(dbName, outDb);
+ ++metrics.dbOpen;
+ affirm( 0 == rc );
+ final sqlite3 db1 = outDb.get();
+ execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)");
+ rc = sqlite3_open(dbName, outDb);
+ ++metrics.dbOpen;
+ affirm( 0 == rc );
+ affirm( outDb.get() != db1 );
+ final sqlite3 db2 = outDb.get();
+
+ affirm( "main".equals( sqlite3_db_name(db1, 0) ) );
+ rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo");
+ affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) );
+ affirm( "foo".equals( sqlite3_db_name(db1, 0) ) );
+
+ final ValueHolder xBusyCalled = new ValueHolder<>(0);
+ BusyHandlerCallback handler = new BusyHandlerCallback(){
+ @Override public int call(int n){
+ //outln("busy handler #"+n);
+ return n > 2 ? 0 : ++xBusyCalled.value;
+ }
+ };
+ rc = sqlite3_busy_handler(db2, handler);
+ affirm(0 == rc);
+
+ // Force a locked condition...
+ execSql(db1, "BEGIN EXCLUSIVE");
+ rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt);
+ affirm( SQLITE_BUSY == rc);
+ affirm( null == outStmt.get() );
+ affirm( 3 == xBusyCalled.value );
+ sqlite3_close_v2(db1);
+ sqlite3_close_v2(db2);
+ try{
+ final java.io.File f = new java.io.File(dbName);
+ f.delete();
+ }catch(Exception e){
+ /* ignore */
+ }
+ }
+
+ private void testProgress(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder counter = new ValueHolder<>(0);
+ sqlite3_progress_handler(db, 1, new ProgressHandlerCallback(){
+ @Override public int call(){
+ ++counter.value;
+ return 0;
+ }
+ });
+ execSql(db, "SELECT 1; SELECT 2;");
+ affirm( counter.value > 0 );
+ int nOld = counter.value;
+ sqlite3_progress_handler(db, 0, null);
+ execSql(db, "SELECT 1; SELECT 2;");
+ affirm( nOld == counter.value );
+ sqlite3_close_v2(db);
+ }
+
+ private void testCommitHook(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder counter = new ValueHolder<>(0);
+ final ValueHolder hookResult = new ValueHolder<>(0);
+ final CommitHookCallback theHook = new CommitHookCallback(){
+ @Override public int call(){
+ ++counter.value;
+ return hookResult.value;
+ }
+ };
+ CommitHookCallback oldHook = sqlite3_commit_hook(db, theHook);
+ affirm( null == oldHook );
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 2 == counter.value );
+ execSql(db, "BEGIN; SELECT 1; SELECT 2; COMMIT;");
+ affirm( 2 == counter.value /* NOT invoked if no changes are made */ );
+ execSql(db, "BEGIN; update t set a='d' where a='c'; COMMIT;");
+ affirm( 3 == counter.value );
+ oldHook = sqlite3_commit_hook(db, theHook);
+ affirm( theHook == oldHook );
+ execSql(db, "BEGIN; update t set a='e' where a='d'; COMMIT;");
+ affirm( 4 == counter.value );
+ oldHook = sqlite3_commit_hook(db, null);
+ affirm( theHook == oldHook );
+ execSql(db, "BEGIN; update t set a='f' where a='e'; COMMIT;");
+ affirm( 4 == counter.value );
+ oldHook = sqlite3_commit_hook(db, null);
+ affirm( null == oldHook );
+ execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;");
+ affirm( 4 == counter.value );
+
+ final CommitHookCallback newHook = new CommitHookCallback(){
+ @Override public int call(){return 0;}
+ };
+ oldHook = sqlite3_commit_hook(db, newHook);
+ affirm( null == oldHook );
+ execSql(db, "BEGIN; update t set a='h' where a='g'; COMMIT;");
+ affirm( 4 == counter.value );
+ oldHook = sqlite3_commit_hook(db, theHook);
+ affirm( newHook == oldHook );
+ execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;");
+ affirm( 5 == counter.value );
+ hookResult.value = SQLITE_ERROR;
+ int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;");
+ affirm( SQLITE_CONSTRAINT == rc );
+ affirm( 6 == counter.value );
+ sqlite3_close_v2(db);
+ }
+
+ private void testUpdateHook(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder counter = new ValueHolder<>(0);
+ final ValueHolder expectedOp = new ValueHolder<>(0);
+ final UpdateHookCallback theHook = new UpdateHookCallback(){
+ @Override
+ public void call(int opId, String dbName, String tableName, long rowId){
+ ++counter.value;
+ if( 0!=expectedOp.value ){
+ affirm( expectedOp.value == opId );
+ }
+ }
+ };
+ UpdateHookCallback oldHook = sqlite3_update_hook(db, theHook);
+ affirm( null == oldHook );
+ expectedOp.value = SQLITE_INSERT;
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 3 == counter.value );
+ expectedOp.value = SQLITE_UPDATE;
+ execSql(db, "update t set a='d' where a='c';");
+ affirm( 4 == counter.value );
+ oldHook = sqlite3_update_hook(db, theHook);
+ affirm( theHook == oldHook );
+ expectedOp.value = SQLITE_DELETE;
+ execSql(db, "DELETE FROM t where a='d'");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_update_hook(db, null);
+ affirm( theHook == oldHook );
+ execSql(db, "update t set a='e' where a='b';");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_update_hook(db, null);
+ affirm( null == oldHook );
+
+ final UpdateHookCallback newHook = new UpdateHookCallback(){
+ @Override public void call(int opId, String dbName, String tableName, long rowId){
+ }
+ };
+ oldHook = sqlite3_update_hook(db, newHook);
+ affirm( null == oldHook );
+ execSql(db, "update t set a='h' where a='a'");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_update_hook(db, theHook);
+ affirm( newHook == oldHook );
+ expectedOp.value = SQLITE_UPDATE;
+ execSql(db, "update t set a='i' where a='h'");
+ affirm( 6 == counter.value );
+ sqlite3_close_v2(db);
+ }
+
+ /**
+ This test is functionally identical to testUpdateHook(), only with a
+ different callback type.
+ */
+ private void testPreUpdateHook(){
+ if( !sqlite3_compileoption_used("ENABLE_PREUPDATE_HOOK") ){
+ //outln("Skipping testPreUpdateHook(): no pre-update hook support.");
+ return;
+ }
+ final sqlite3 db = createNewDb();
+ final ValueHolder counter = new ValueHolder<>(0);
+ final ValueHolder expectedOp = new ValueHolder<>(0);
+ final PreupdateHookCallback theHook = new PreupdateHookCallback(){
+ @Override
+ public void call(sqlite3 db, int opId, String dbName, String dbTable,
+ long iKey1, long iKey2 ){
+ ++counter.value;
+ switch( opId ){
+ case SQLITE_UPDATE:
+ affirm( 0 < sqlite3_preupdate_count(db) );
+ affirm( null != sqlite3_preupdate_new(db, 0) );
+ affirm( null != sqlite3_preupdate_old(db, 0) );
+ break;
+ case SQLITE_INSERT:
+ affirm( null != sqlite3_preupdate_new(db, 0) );
+ break;
+ case SQLITE_DELETE:
+ affirm( null != sqlite3_preupdate_old(db, 0) );
+ break;
+ default:
+ break;
+ }
+ if( 0!=expectedOp.value ){
+ affirm( expectedOp.value == opId );
+ }
+ }
+ };
+ PreupdateHookCallback oldHook = sqlite3_preupdate_hook(db, theHook);
+ affirm( null == oldHook );
+ expectedOp.value = SQLITE_INSERT;
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 3 == counter.value );
+ expectedOp.value = SQLITE_UPDATE;
+ execSql(db, "update t set a='d' where a='c';");
+ affirm( 4 == counter.value );
+ oldHook = sqlite3_preupdate_hook(db, theHook);
+ affirm( theHook == oldHook );
+ expectedOp.value = SQLITE_DELETE;
+ execSql(db, "DELETE FROM t where a='d'");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_preupdate_hook(db, null);
+ affirm( theHook == oldHook );
+ execSql(db, "update t set a='e' where a='b';");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_preupdate_hook(db, null);
+ affirm( null == oldHook );
+
+ final PreupdateHookCallback newHook = new PreupdateHookCallback(){
+ @Override
+ public void call(sqlite3 db, int opId, String dbName,
+ String tableName, long iKey1, long iKey2){
+ }
+ };
+ oldHook = sqlite3_preupdate_hook(db, newHook);
+ affirm( null == oldHook );
+ execSql(db, "update t set a='h' where a='a'");
+ affirm( 5 == counter.value );
+ oldHook = sqlite3_preupdate_hook(db, theHook);
+ affirm( newHook == oldHook );
+ expectedOp.value = SQLITE_UPDATE;
+ execSql(db, "update t set a='i' where a='h'");
+ affirm( 6 == counter.value );
+
+ sqlite3_close_v2(db);
+ }
+
+ private void testRollbackHook(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder counter = new ValueHolder<>(0);
+ final RollbackHookCallback theHook = new RollbackHookCallback(){
+ @Override public void call(){
+ ++counter.value;
+ }
+ };
+ RollbackHookCallback oldHook = sqlite3_rollback_hook(db, theHook);
+ affirm( null == oldHook );
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ affirm( 0 == counter.value );
+ execSql(db, false, "BEGIN; SELECT 1; SELECT 2; ROLLBACK;");
+ affirm( 1 == counter.value /* contra to commit hook, is invoked if no changes are made */ );
+
+ final RollbackHookCallback newHook = new RollbackHookCallback(){
+ @Override public void call(){return;}
+ };
+ oldHook = sqlite3_rollback_hook(db, newHook);
+ affirm( theHook == oldHook );
+ execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+ affirm( 1 == counter.value );
+ oldHook = sqlite3_rollback_hook(db, theHook);
+ affirm( newHook == oldHook );
+ execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+ affirm( 2 == counter.value );
+ int rc = execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;");
+ affirm( 0 == rc );
+ affirm( 3 == counter.value );
+ sqlite3_close_v2(db);
+ }
+
+ /**
+ If FTS5 is available, runs FTS5 tests, else returns with no side
+ effects. If it is available but loading of the FTS5 bits fails,
+ it throws.
+ */
+ @SuppressWarnings("unchecked")
+ @SingleThreadOnly /* because the Fts5 parts are not yet known to be
+ thread-safe */
+ private void testFts5() throws Exception {
+ if( !sqlite3_compileoption_used("ENABLE_FTS5") ){
+ //outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests.");
+ return;
+ }
+ Exception err = null;
+ try {
+ Class t = Class.forName("org.sqlite.jni.fts5.TesterFts5");
+ java.lang.reflect.Constructor ctor = t.getConstructor();
+ ctor.setAccessible(true);
+ final long timeStart = System.currentTimeMillis();
+ ctor.newInstance() /* will run all tests */;
+ final long timeEnd = System.currentTimeMillis();
+ outln("FTS5 Tests done in ",(timeEnd - timeStart),"ms");
+ }catch(ClassNotFoundException e){
+ outln("FTS5 classes not loaded.");
+ err = e;
+ }catch(NoSuchMethodException e){
+ outln("FTS5 tester ctor not found.");
+ err = e;
+ }catch(Exception e){
+ outln("Instantiation of FTS5 tester threw.");
+ err = e;
+ }
+ if( null != err ){
+ outln("Exception: "+err);
+ err.printStackTrace();
+ throw err;
+ }
+ }
+
+ private void testAuthorizer(){
+ final sqlite3 db = createNewDb();
+ final ValueHolder counter = new ValueHolder<>(0);
+ final ValueHolder authRc = new ValueHolder<>(0);
+ final AuthorizerCallback auth = new AuthorizerCallback(){
+ public int call(int op, String s0, String s1, String s2, String s3){
+ ++counter.value;
+ //outln("xAuth(): "+s0+" "+s1+" "+s2+" "+s3);
+ return authRc.value;
+ }
+ };
+ execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
+ sqlite3_set_authorizer(db, auth);
+ execSql(db, "UPDATE t SET a=1");
+ affirm( 1 == counter.value );
+ authRc.value = SQLITE_DENY;
+ int rc = execSql(db, false, "UPDATE t SET a=2");
+ affirm( SQLITE_AUTH==rc );
+ // TODO: expand these tests considerably
+ sqlite3_close(db);
+ }
+
+ @SingleThreadOnly /* because multiple threads legitimately make these
+ results unpredictable */
+ private synchronized void testAutoExtension(){
+ final ValueHolder val = new ValueHolder<>(0);
+ final ValueHolder toss = new ValueHolder<>(null);
+ final AutoExtensionCallback ax = new AutoExtensionCallback(){
+ @Override public int call(sqlite3 db){
+ ++val.value;
+ if( null!=toss.value ){
+ throw new RuntimeException(toss.value);
+ }
+ return 0;
+ }
+ };
+ int rc = sqlite3_auto_extension( ax );
+ affirm( 0==rc );
+ sqlite3_close(createNewDb());
+ affirm( 1==val.value );
+ sqlite3_close(createNewDb());
+ affirm( 2==val.value );
+ sqlite3_reset_auto_extension();
+ sqlite3_close(createNewDb());
+ affirm( 2==val.value );
+ rc = sqlite3_auto_extension( ax );
+ affirm( 0==rc );
+ // Must not add a new entry
+ rc = sqlite3_auto_extension( ax );
+ affirm( 0==rc );
+ sqlite3_close( createNewDb() );
+ affirm( 3==val.value );
+
+ sqlite3 db = createNewDb();
+ affirm( 4==val.value );
+ execSql(db, "ATTACH ':memory:' as foo");
+ affirm( 4==val.value, "ATTACH uses the same connection, not sub-connections." );
+ sqlite3_close(db);
+ db = null;
+
+ affirm( sqlite3_cancel_auto_extension(ax) );
+ affirm( !sqlite3_cancel_auto_extension(ax) );
+ sqlite3_close(createNewDb());
+ affirm( 4==val.value );
+ rc = sqlite3_auto_extension( ax );
+ affirm( 0==rc );
+ Exception err = null;
+ toss.value = "Throwing from auto_extension.";
+ try{
+ sqlite3_close(createNewDb());
+ }catch(Exception e){
+ err = e;
+ }
+ affirm( err!=null );
+ affirm( err.getMessage().indexOf(toss.value)>0 );
+ toss.value = null;
+
+ val.value = 0;
+ final AutoExtensionCallback ax2 = new AutoExtensionCallback(){
+ @Override public synchronized int call(sqlite3 db){
+ ++val.value;
+ return 0;
+ }
+ };
+ rc = sqlite3_auto_extension( ax2 );
+ affirm( 0 == rc );
+ sqlite3_close(createNewDb());
+ affirm( 2 == val.value );
+ affirm( sqlite3_cancel_auto_extension(ax) );
+ affirm( !sqlite3_cancel_auto_extension(ax) );
+ sqlite3_close(createNewDb());
+ affirm( 3 == val.value );
+ rc = sqlite3_auto_extension( ax );
+ affirm( 0 == rc );
+ sqlite3_close(createNewDb());
+ affirm( 5 == val.value );
+ affirm( sqlite3_cancel_auto_extension(ax2) );
+ affirm( !sqlite3_cancel_auto_extension(ax2) );
+ sqlite3_close(createNewDb());
+ affirm( 6 == val.value );
+ rc = sqlite3_auto_extension( ax2 );
+ affirm( 0 == rc );
+ sqlite3_close(createNewDb());
+ affirm( 8 == val.value );
+
+ sqlite3_reset_auto_extension();
+ sqlite3_close(createNewDb());
+ affirm( 8 == val.value );
+ affirm( !sqlite3_cancel_auto_extension(ax) );
+ affirm( !sqlite3_cancel_auto_extension(ax2) );
+ sqlite3_close(createNewDb());
+ affirm( 8 == val.value );
+ }
+
+
+ private void testColumnMetadata(){
+ final sqlite3 db = createNewDb();
+ execSql(db, new String[] {
+ "CREATE TABLE t(a duck primary key not null collate noCase); ",
+ "INSERT INTO t(a) VALUES(1),(2),(3);"
+ });
+ OutputPointer.Bool bNotNull = new OutputPointer.Bool();
+ OutputPointer.Bool bPrimaryKey = new OutputPointer.Bool();
+ OutputPointer.Bool bAutoinc = new OutputPointer.Bool();
+ OutputPointer.String zCollSeq = new OutputPointer.String();
+ OutputPointer.String zDataType = new OutputPointer.String();
+ int rc = sqlite3_table_column_metadata(
+ db, "main", "t", "a", zDataType, zCollSeq,
+ bNotNull, bPrimaryKey, bAutoinc);
+ affirm( 0==rc );
+ affirm( bPrimaryKey.value );
+ affirm( !bAutoinc.value );
+ affirm( bNotNull.value );
+ affirm( "noCase".equals(zCollSeq.value) );
+ affirm( "duck".equals(zDataType.value) );
+
+ TableColumnMetadata m =
+ sqlite3_table_column_metadata(db, "main", "t", "a");
+ affirm( null != m );
+ affirm( bPrimaryKey.value == m.isPrimaryKey() );
+ affirm( bAutoinc.value == m.isAutoincrement() );
+ affirm( bNotNull.value == m.isNotNull() );
+ affirm( zCollSeq.value.equals(m.getCollation()) );
+ affirm( zDataType.value.equals(m.getDataType()) );
+
+ affirm( null == sqlite3_table_column_metadata(db, "nope", "t", "a") );
+ affirm( null == sqlite3_table_column_metadata(db, "main", "nope", "a") );
+
+ m = sqlite3_table_column_metadata(db, "main", "t", null)
+ /* Check only for existence of table */;
+ affirm( null != m );
+ affirm( m.isPrimaryKey() );
+ affirm( !m.isAutoincrement() );
+ affirm( !m.isNotNull() );
+ affirm( "BINARY".equalsIgnoreCase(m.getCollation()) );
+ affirm( "INTEGER".equalsIgnoreCase(m.getDataType()) );
+
+ sqlite3_close_v2(db);
+ }
+
+ private void testTxnState(){
+ final sqlite3 db = createNewDb();
+ affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
+ affirm( sqlite3_get_autocommit(db) );
+ execSql(db, "BEGIN;");
+ affirm( !sqlite3_get_autocommit(db) );
+ affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
+ execSql(db, "SELECT * FROM sqlite_schema;");
+ affirm( SQLITE_TXN_READ == sqlite3_txn_state(db, "main") );
+ execSql(db, "CREATE TABLE t(a);");
+ affirm( SQLITE_TXN_WRITE == sqlite3_txn_state(db, null) );
+ execSql(db, "ROLLBACK;");
+ affirm( SQLITE_TXN_NONE == sqlite3_txn_state(db, null) );
+ sqlite3_close_v2(db);
+ }
+
+
+ private void testExplain(){
+ final sqlite3 db = createNewDb();
+ sqlite3_stmt stmt = prepare(db,"SELECT 1");
+
+ affirm( 0 == sqlite3_stmt_isexplain(stmt) );
+ int rc = sqlite3_stmt_explain(stmt, 1);
+ affirm( 1 == sqlite3_stmt_isexplain(stmt) );
+ rc = sqlite3_stmt_explain(stmt, 2);
+ affirm( 2 == sqlite3_stmt_isexplain(stmt) );
+ sqlite3_finalize(stmt);
+ sqlite3_close_v2(db);
+ }
+
+ private void testLimit(){
+ final sqlite3 db = createNewDb();
+ int v;
+
+ v = sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1);
+ affirm( v > 0 );
+ affirm( v == sqlite3_limit(db, SQLITE_LIMIT_LENGTH, v-1) );
+ affirm( v-1 == sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1) );
+ sqlite3_close_v2(db);
+ }
+
+ private void testComplete(){
+ affirm( 0==sqlite3_complete("select 1") );
+ affirm( 0!=sqlite3_complete("select 1;") );
+ affirm( 0!=sqlite3_complete("nope 'nope' 'nope' 1;"), "Yup" );
+ }
+
+ private void testKeyword(){
+ final int n = sqlite3_keyword_count();
+ affirm( n>0 );
+ affirm( !sqlite3_keyword_check("_nope_") );
+ affirm( sqlite3_keyword_check("seLect") );
+ affirm( null!=sqlite3_keyword_name(0) );
+ affirm( null!=sqlite3_keyword_name(n-1) );
+ affirm( null==sqlite3_keyword_name(n) );
+ }
+
+ private void testBackup(){
+ final sqlite3 dbDest = createNewDb();
+
+ try (sqlite3 dbSrc = createNewDb()) {
+ execSql(dbSrc, new String[]{
+ "pragma page_size=512; VACUUM;",
+ "create table t(a);",
+ "insert into t(a) values(1),(2),(3);"
+ });
+ affirm( null==sqlite3_backup_init(dbSrc,"main",dbSrc,"main") );
+ try (sqlite3_backup b = sqlite3_backup_init(dbDest,"main",dbSrc,"main")) {
+ affirm( null!=b );
+ affirm( b.getNativePointer()!=0 );
+ int rc;
+ while( SQLITE_DONE!=(rc = sqlite3_backup_step(b, 1)) ){
+ affirm( 0==rc );
+ }
+ affirm( sqlite3_backup_pagecount(b) > 0 );
+ rc = sqlite3_backup_finish(b);
+ affirm( 0==rc );
+ affirm( b.getNativePointer()==0 );
+ }
+ }
+
+ try (sqlite3_stmt stmt = prepare(dbDest,"SELECT sum(a) from t")) {
+ sqlite3_step(stmt);
+ affirm( sqlite3_column_int(stmt,0) == 6 );
+ }
+ sqlite3_close_v2(dbDest);
+ }
+
+ private void testRandomness(){
+ byte[] foo = new byte[20];
+ int i = 0;
+ for( byte b : foo ){
+ i += b;
+ }
+ affirm( i==0 );
+ sqlite3_randomness(foo);
+ for( byte b : foo ){
+ if(b!=0) ++i;
+ }
+ affirm( i!=0, "There's a very slight chance that 0 is actually correct." );
+ }
+
+ private void testBlobOpen(){
+ final sqlite3 db = createNewDb();
+
+ execSql(db, "CREATE TABLE T(a BLOB);"
+ +"INSERT INTO t(rowid,a) VALUES(1, 'def'),(2, 'XYZ');"
+ );
+ final OutputPointer.sqlite3_blob pOut = new OutputPointer.sqlite3_blob();
+ int rc = sqlite3_blob_open(db, "main", "t", "a",
+ sqlite3_last_insert_rowid(db), 1, pOut);
+ affirm( 0==rc );
+ sqlite3_blob b = pOut.take();
+ affirm( null!=b );
+ affirm( 0!=b.getNativePointer() );
+ affirm( 3==sqlite3_blob_bytes(b) );
+ rc = sqlite3_blob_write( b, new byte[] {100, 101, 102 /*"DEF"*/}, 0);
+ affirm( 0==rc );
+ rc = sqlite3_blob_close(b);
+ affirm( 0==rc );
+ rc = sqlite3_blob_close(b);
+ affirm( 0!=rc );
+ affirm( 0==b.getNativePointer() );
+ sqlite3_stmt stmt = prepare(db,"SELECT length(a), a FROM t ORDER BY a");
+ affirm( SQLITE_ROW == sqlite3_step(stmt) );
+ affirm( 3 == sqlite3_column_int(stmt,0) );
+ affirm( "def".equals(sqlite3_column_text16(stmt,1)) );
+ sqlite3_finalize(stmt);
+
+ b = sqlite3_blob_open(db, "main", "t", "a",
+ sqlite3_last_insert_rowid(db), 1);
+ affirm( null!=b );
+ rc = sqlite3_blob_reopen(b, 2);
+ affirm( 0==rc );
+ final byte[] tgt = new byte[3];
+ rc = sqlite3_blob_read(b, tgt, 0);
+ affirm( 0==rc );
+ affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" );
+ rc = sqlite3_blob_close(b);
+ affirm( 0==rc );
+ sqlite3_close_v2(db);
+ }
+
+ private void testPrepareMulti(){
+ final sqlite3 db = createNewDb();
+ final String[] sql = {
+ "create table t(","a)",
+ "; insert into t(a) values(1),(2),(3);",
+ "select a from t;"
+ };
+ final List liStmt = new ArrayList();
+ final PrepareMultiCallback proxy = new PrepareMultiCallback.StepAll();
+ PrepareMultiCallback m = new PrepareMultiCallback() {
+ @Override public int call(sqlite3_stmt st){
+ liStmt.add(st);
+ return proxy.call(st);
+ }
+ };
+ int rc = sqlite3_prepare_multi(db, sql, m);
+ affirm( 0==rc );
+ affirm( liStmt.size() == 3 );
+ for( sqlite3_stmt st : liStmt ){
+ sqlite3_finalize(st);
+ }
+ sqlite3_close_v2(db);
+ }
+
+ /* Copy/paste/rename this to add new tests. */
+ private void _testTemplate(){
+ final sqlite3 db = createNewDb();
+ sqlite3_stmt stmt = prepare(db,"SELECT 1");
+ sqlite3_finalize(stmt);
+ sqlite3_close_v2(db);
+ }
+
+
+ @ManualTest /* we really only want to run this test manually */
+ private void testSleep(){
+ out("Sleeping briefly... ");
+ sqlite3_sleep(600);
+ outln("Woke up.");
+ }
+
+ private void nap() throws InterruptedException {
+ if( takeNaps ){
+ Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 17), 0);
+ }
+ }
+
+ @ManualTest /* because we only want to run this test on demand */
+ private void testFail(){
+ affirm( false, "Intentional failure." );
+ }
+
+ private void runTests(boolean fromThread) throws Exception {
+ if(false) showCompileOption();
+ List mlist = testMethods;
+ affirm( null!=mlist );
+ if( shuffle ){
+ mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) );
+ java.util.Collections.shuffle(mlist);
+ }
+ if( listRunTests ){
+ synchronized(this.getClass()){
+ if( !fromThread ){
+ out("Initial test"," list: ");
+ for(java.lang.reflect.Method m : testMethods){
+ out(m.getName()+" ");
+ }
+ outln();
+ outln("(That list excludes some which are hard-coded to run.)");
+ }
+ out("Running"," tests: ");
+ for(java.lang.reflect.Method m : mlist){
+ out(m.getName()+" ");
+ }
+ outln();
+ }
+ }
+ for(java.lang.reflect.Method m : mlist){
+ nap();
+ try{
+ m.invoke(this);
+ }catch(java.lang.reflect.InvocationTargetException e){
+ outln("FAILURE: ",m.getName(),"(): ", e.getCause());
+ throw e;
+ }
+ }
+ synchronized( this.getClass() ){
+ ++nTestRuns;
+ }
+ }
+
+ public void run() {
+ try {
+ runTests(0!=this.tId);
+ }catch(Exception e){
+ synchronized( listErrors ){
+ listErrors.add(e);
+ }
+ }finally{
+ affirm( sqlite3_java_uncache_thread() );
+ affirm( !sqlite3_java_uncache_thread() );
+ }
+ }
+
+ /**
+ Runs the basic sqlite3 JNI binding sanity-check suite.
+
+ CLI flags:
+
+ -q|-quiet: disables most test output.
+
+ -t|-thread N: runs the tests in N threads
+ concurrently. Default=1.
+
+ -r|-repeat N: repeats the tests in a loop N times, each one
+ consisting of the -thread value's threads.
+
+ -shuffle: randomizes the order of most of the test functions.
+
+ -naps: sleep small random intervals between tests in order to add
+ some chaos for cross-thread contention.
+
+ -list-tests: outputs the list of tests being run, minus some
+ which are hard-coded. This is noisy in multi-threaded mode.
+
+ -fail: forces an exception to be thrown during the test run. Use
+ with -shuffle to make its appearance unpredictable.
+
+ -v: emit some developer-mode info at the end.
+ */
+ public static void main(String[] args) throws Exception {
+ Integer nThread = 1;
+ boolean doSomethingForDev = false;
+ Integer nRepeat = 1;
+ boolean forceFail = false;
+ boolean sqlLog = false;
+ boolean configLog = false;
+ boolean squelchTestOutput = false;
+ for( int i = 0; i < args.length; ){
+ String arg = args[i++];
+ if(arg.startsWith("-")){
+ arg = arg.replaceFirst("-+","");
+ if(arg.equals("v")){
+ doSomethingForDev = true;
+ //listBoundMethods();
+ }else if(arg.equals("t") || arg.equals("thread")){
+ nThread = Integer.parseInt(args[i++]);
+ }else if(arg.equals("r") || arg.equals("repeat")){
+ nRepeat = Integer.parseInt(args[i++]);
+ }else if(arg.equals("shuffle")){
+ shuffle = true;
+ }else if(arg.equals("list-tests")){
+ listRunTests = true;
+ }else if(arg.equals("fail")){
+ forceFail = true;
+ }else if(arg.equals("sqllog")){
+ sqlLog = true;
+ }else if(arg.equals("configlog")){
+ configLog = true;
+ }else if(arg.equals("naps")){
+ takeNaps = true;
+ }else if(arg.equals("q") || arg.equals("quiet")){
+ squelchTestOutput = true;
+ }else{
+ throw new IllegalArgumentException("Unhandled flag:"+arg);
+ }
+ }
+ }
+
+ if( sqlLog ){
+ if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){
+ final ConfigSqllogCallback log = new ConfigSqllogCallback() {
+ @Override public void call(sqlite3 db, String msg, int op){
+ switch(op){
+ case 0: outln("Opening db: ",db); break;
+ case 1: outln("SQL ",db,": ",msg); break;
+ case 2: outln("Closing db: ",db); break;
+ }
+ }
+ };
+ int rc = sqlite3_config( log );
+ affirm( 0==rc );
+ rc = sqlite3_config( (ConfigSqllogCallback)null );
+ affirm( 0==rc );
+ rc = sqlite3_config( log );
+ affirm( 0==rc );
+ }else{
+ outln("WARNING: -sqllog is not active because library was built ",
+ "without SQLITE_ENABLE_SQLLOG.");
+ }
+ }
+ if( configLog ){
+ final ConfigLogCallback log = new ConfigLogCallback() {
+ @Override public void call(int code, String msg){
+ outln("ConfigLogCallback: ",ResultCode.getEntryForInt(code),": ", msg);
+ };
+ };
+ int rc = sqlite3_config( log );
+ affirm( 0==rc );
+ rc = sqlite3_config( (ConfigLogCallback)null );
+ affirm( 0==rc );
+ rc = sqlite3_config( log );
+ affirm( 0==rc );
+ }
+
+ quietMode = squelchTestOutput;
+ outln("If you just saw warning messages regarding CallStaticObjectMethod, ",
+ "you are very likely seeing the side effects of a known openjdk8 ",
+ "bug. It is unsightly but does not affect the library.");
+
+ {
+ // Build list of tests to run from the methods named test*().
+ testMethods = new ArrayList<>();
+ int nSkipped = 0;
+ for(final java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){
+ final String name = m.getName();
+ if( name.equals("testFail") ){
+ if( forceFail ){
+ testMethods.add(m);
+ }
+ }else if( !m.isAnnotationPresent( ManualTest.class ) ){
+ if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){
+ if( 0==nSkipped++ ){
+ out("Skipping tests in multi-thread mode:");
+ }
+ out(" "+name+"()");
+ }else if( name.startsWith("test") ){
+ testMethods.add(m);
+ }
+ }
+ }
+ if( nSkipped>0 ) out("\n");
+ }
+
+ final long timeStart = System.currentTimeMillis();
+ int nLoop = 0;
+ switch( sqlite3_threadsafe() ){ /* Sanity checking */
+ case 0:
+ affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ),
+ "Could not switch to single-thread mode." );
+ affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_MULTITHREAD ),
+ "Could switch to multithread mode." );
+ affirm( SQLITE_ERROR==sqlite3_config( SQLITE_CONFIG_SERIALIZED ),
+ "Could not switch to serialized threading mode." );
+ outln("This is a single-threaded build. Not using threads.");
+ nThread = 1;
+ break;
+ case 1:
+ case 2:
+ affirm( 0==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ),
+ "Could not switch to single-thread mode." );
+ affirm( 0==sqlite3_config( SQLITE_CONFIG_MULTITHREAD ),
+ "Could not switch to multithread mode." );
+ affirm( 0==sqlite3_config( SQLITE_CONFIG_SERIALIZED ),
+ "Could not switch to serialized threading mode." );
+ break;
+ default:
+ affirm( false, "Unhandled SQLITE_THREADSAFE value." );
+ }
+ outln("libversion_number: ",
+ sqlite3_libversion_number(),"\n",
+ sqlite3_libversion(),"\n",SQLITE_SOURCE_ID,"\n",
+ "SQLITE_THREADSAFE=",sqlite3_threadsafe());
+ final boolean showLoopCount = (nRepeat>1 && nThread>1);
+ if( showLoopCount ){
+ outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
+ }
+ if( takeNaps ) outln("Napping between tests is enabled.");
+ for( int n = 0; n < nRepeat; ++n ){
+ ++nLoop;
+ if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop);
+ if( nThread<=1 ){
+ new Tester1(0).runTests(false);
+ continue;
+ }
+ Tester1.mtMode = true;
+ final ExecutorService ex = Executors.newFixedThreadPool( nThread );
+ for( int i = 0; i < nThread; ++i ){
+ ex.submit( new Tester1(i), i );
+ }
+ ex.shutdown();
+ try{
+ ex.awaitTermination(nThread*200, java.util.concurrent.TimeUnit.MILLISECONDS);
+ ex.shutdownNow();
+ }catch (InterruptedException ie){
+ ex.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+ if( !listErrors.isEmpty() ){
+ quietMode = false;
+ outln("TEST ERRORS:");
+ Exception err = null;
+ for( Exception e : listErrors ){
+ e.printStackTrace();
+ if( null==err ) err = e;
+ }
+ if( null!=err ) throw err;
+ }
+ }
+ if( showLoopCount ) outln();
+ quietMode = false;
+
+ final long timeEnd = System.currentTimeMillis();
+ outln("Tests done. Metrics across ",nTestRuns," total iteration(s):");
+ outln("\tAssertions checked: ",affirmCount);
+ outln("\tDatabases opened: ",metrics.dbOpen);
+ if( doSomethingForDev ){
+ sqlite3_jni_internal_details();
+ }
+ affirm( 0==sqlite3_release_memory(1) );
+ sqlite3_shutdown();
+ int nMethods = 0;
+ int nNatives = 0;
+ final java.lang.reflect.Method[] declaredMethods =
+ CApi.class.getDeclaredMethods();
+ for(java.lang.reflect.Method m : declaredMethods){
+ final int mod = m.getModifiers();
+ if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){
+ final String name = m.getName();
+ if(name.startsWith("sqlite3_")){
+ ++nMethods;
+ if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){
+ ++nNatives;
+ }
+ }
+ }
+ }
+ outln("\tCApi.sqlite3_*() methods: "+
+ nMethods+" total, with "+
+ nNatives+" native, "+
+ (nMethods - nNatives)+" Java"
+ );
+ outln("\tTotal test time = "
+ +(timeEnd - timeStart)+"ms");
+ }
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,50 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+import org.sqlite.jni.annotation.Nullable;
+
+/**
+ Callback for use with {@link CApi#sqlite3_trace_v2}.
+*/
+public interface TraceV2Callback extends CallbackProxy {
+ /**
+ Called by sqlite3 for various tracing operations, as per
+ sqlite3_trace_v2(). Note that this interface elides the 2nd
+ argument to the native trace callback, as that role is better
+ filled by instance-local state.
+
+
These callbacks may throw, in which case their exceptions are
+ converted to C-level error information.
+
+
The 2nd argument to this function, if non-null, will be a an
+ sqlite3 or sqlite3_stmt object, depending on the first argument
+ (see below).
+
+
The final argument to this function is the "X" argument
+ documented for sqlite3_trace() and sqlite3_trace_v2(). Its type
+ depends on value of the first argument:
+
+
- SQLITE_TRACE_STMT: pNative is a sqlite3_stmt. pX is a String
+ containing the prepared SQL.
+
+
- SQLITE_TRACE_PROFILE: pNative is a sqlite3_stmt. pX is a Long
+ holding an approximate number of nanoseconds the statement took
+ to run.
+
+
- SQLITE_TRACE_ROW: pNative is a sqlite3_stmt. pX is null.
+
+
- SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null.
+ */
+ int call(int traceFlag, Object pNative, @Nullable Object pX);
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for use with {@link CApi#sqlite3_update_hook}.
+*/
+public interface UpdateHookCallback extends CallbackProxy {
+ /**
+ Must function as described for the C-level sqlite3_update_hook()
+ callback.
+ */
+ void call(int opId, String dbName, String tableName, long rowId);
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 2023-10-16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ A helper class which simply holds a single value. Its primary use
+ is for communicating values out of anonymous classes, as doing so
+ requires a "final" reference.
+*/
+public class ValueHolder {
+ public T value;
+ public ValueHolder(){}
+ public ValueHolder(T v){value = v;}
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/WindowFunction.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,39 @@
+/*
+** 2023-08-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+
+/**
+ A SQLFunction implementation for window functions. Note that
+ WindowFunction inherits from {@link AggregateFunction} and each
+ instance is required to implement the inherited abstract methods
+ from that class. See {@link AggregateFunction} for information on
+ managing the UDF's invocation-specific state.
+*/
+public abstract class WindowFunction extends AggregateFunction {
+
+ /**
+ As for the xInverse() argument of the C API's
+ sqlite3_create_window_function(). If this function throws, the
+ exception is not propagated and a warning might be emitted
+ to a debugging channel.
+ */
+ public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args);
+
+ /**
+ As for the xValue() argument of the C API's sqlite3_create_window_function().
+ See xInverse() for the fate of any exceptions this throws.
+ */
+ public abstract void xValue(sqlite3_context cx);
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,37 @@
+/*
+** 2023-07-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file declares JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.capi;
+
+/**
+ Callback for a hook called by SQLite when certain client-provided
+ state are destroyed. It gets its name from the pervasive use of
+ the symbol name xDestroy() for this purpose in the C API
+ documentation.
+*/
+public interface XDestroyCallback {
+ /**
+ Must perform any cleanup required by this object. Must not
+ throw. Must not call back into the sqlite3 API, else it might
+ invoke a deadlock.
+
+ WARNING: as a rule, it is never safe to register individual
+ instances with this interface multiple times in the
+ library. e.g., do not register the same CollationCallback with
+ multiple arities or names using sqlite3_create_collation(). If
+ this rule is violated, the library will eventually try to free
+ each individual reference, leading to memory corruption or a
+ crash via duplicate free().
+ */
+ public void xDestroy();
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_api.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,76 @@
+/*
+** 2023-08-05
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.annotation.*;
+import org.sqlite.jni.capi.*;
+
+/**
+ A wrapper for communicating C-level (fts5_api*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class fts5_api extends NativePointerHolder {
+ /* Only invoked from JNI */
+ private fts5_api(){}
+
+ public static final int iVersion = 2;
+
+ /**
+ Returns the fts5_api instance associated with the given db, or
+ null if something goes horribly wrong.
+ */
+ public static synchronized native fts5_api getInstanceForDb(@NotNull sqlite3 db);
+
+ public synchronized native int xCreateFunction(@NotNull String name,
+ @Nullable Object userData,
+ @NotNull fts5_extension_function xFunction);
+
+ /**
+ Convenience overload which passes null as the 2nd argument to the
+ 3-parameter form.
+ */
+ public int xCreateFunction(@NotNull String name,
+ @NotNull fts5_extension_function xFunction){
+ return xCreateFunction(name, null, xFunction);
+ }
+
+ // /* Create a new auxiliary function */
+ // int (*xCreateFunction)(
+ // fts5_api *pApi,
+ // const char *zName,
+ // void *pContext,
+ // fts5_extension_function xFunction,
+ // void (*xDestroy)(void*)
+ // );
+
+ // Still potentially todo:
+
+ // int (*xCreateTokenizer)(
+ // fts5_api *pApi,
+ // const char *zName,
+ // void *pContext,
+ // fts5_tokenizer *pTokenizer,
+ // void (*xDestroy)(void*)
+ // );
+
+ // /* Find an existing tokenizer */
+ // int (*xFindTokenizer)(
+ // fts5_api *pApi,
+ // const char *zName,
+ // void **ppContext,
+ // fts5_tokenizer *pTokenizer
+ // );
+
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5Context.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,24 @@
+/*
+** 2023-08-04
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.*;
+
+/**
+ A wrapper for communicating C-level (Fts5Context*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class Fts5Context extends NativePointerHolder {
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5ExtensionApi.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,97 @@
+/*
+** 2023-08-04
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import java.nio.charset.StandardCharsets;
+import org.sqlite.jni.capi.*;
+import org.sqlite.jni.annotation.*;
+
+/**
+*/
+public final class Fts5ExtensionApi extends NativePointerHolder {
+ //! Only called from JNI
+ private Fts5ExtensionApi(){}
+ private final int iVersion = 2;
+
+ /* Callback type for used by xQueryPhrase(). */
+ public static interface XQueryPhraseCallback {
+ int call(Fts5ExtensionApi fapi, Fts5Context cx);
+ }
+
+ /**
+ Returns the singleton instance of this class.
+ */
+ public static native Fts5ExtensionApi getInstance();
+
+ public native int xColumnCount(@NotNull Fts5Context fcx);
+
+ public native int xColumnSize(@NotNull Fts5Context cx, int iCol,
+ @NotNull OutputPointer.Int32 pnToken);
+
+ public native int xColumnText(@NotNull Fts5Context cx, int iCol,
+ @NotNull OutputPointer.String txt);
+
+ public native int xColumnTotalSize(@NotNull Fts5Context fcx, int iCol,
+ @NotNull OutputPointer.Int64 pnToken);
+
+ public native Object xGetAuxdata(@NotNull Fts5Context cx, boolean clearIt);
+
+ public native int xInst(@NotNull Fts5Context cx, int iIdx,
+ @NotNull OutputPointer.Int32 piPhrase,
+ @NotNull OutputPointer.Int32 piCol,
+ @NotNull OutputPointer.Int32 piOff);
+
+ public native int xInstCount(@NotNull Fts5Context fcx,
+ @NotNull OutputPointer.Int32 pnInst);
+
+ public native int xPhraseCount(@NotNull Fts5Context fcx);
+
+ public native int xPhraseFirst(@NotNull Fts5Context cx, int iPhrase,
+ @NotNull Fts5PhraseIter iter,
+ @NotNull OutputPointer.Int32 iCol,
+ @NotNull OutputPointer.Int32 iOff);
+
+ public native int xPhraseFirstColumn(@NotNull Fts5Context cx, int iPhrase,
+ @NotNull Fts5PhraseIter iter,
+ @NotNull OutputPointer.Int32 iCol);
+ public native void xPhraseNext(@NotNull Fts5Context cx,
+ @NotNull Fts5PhraseIter iter,
+ @NotNull OutputPointer.Int32 iCol,
+ @NotNull OutputPointer.Int32 iOff);
+ public native void xPhraseNextColumn(@NotNull Fts5Context cx,
+ @NotNull Fts5PhraseIter iter,
+ @NotNull OutputPointer.Int32 iCol);
+ public native int xPhraseSize(@NotNull Fts5Context fcx, int iPhrase);
+
+ public native int xQueryPhrase(@NotNull Fts5Context cx, int iPhrase,
+ @NotNull XQueryPhraseCallback callback);
+ public native int xRowCount(@NotNull Fts5Context fcx,
+ @NotNull OutputPointer.Int64 nRow);
+
+ public native long xRowid(@NotNull Fts5Context cx);
+ /* Note that the JNI binding lacks the C version's xDelete()
+ callback argument. Instead, if pAux has an xDestroy() method, it
+ is called if the FTS5 API finalizes the aux state (including if
+ allocation of storage for the auxdata fails). Any reference to
+ pAux held by the JNI layer will be relinquished regardless of
+ whether pAux has an xDestroy() method. */
+
+ public native int xSetAuxdata(@NotNull Fts5Context cx, @Nullable Object pAux);
+
+ public native int xTokenize(@NotNull Fts5Context cx, @NotNull byte[] pText,
+ @NotNull XTokenizeCallback callback);
+
+ public native Object xUserData(Fts5Context cx);
+ //^^^ returns the pointer passed as the 3rd arg to the C-level
+ // fts5_api::xCreateFunction().
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,47 @@
+/*
+** 2023-08-05
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.sqlite3_context;
+import org.sqlite.jni.capi.sqlite3_value;
+
+/**
+ JNI-level wrapper for C's fts5_extension_function type.
+*/
+public interface fts5_extension_function {
+ // typedef void (*fts5_extension_function)(
+ // const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
+ // Fts5Context *pFts, /* First arg to pass to pApi functions */
+ // sqlite3_context *pCtx, /* Context for returning result/error */
+ // int nVal, /* Number of values in apVal[] array */
+ // sqlite3_value **apVal /* Array of trailing arguments */
+ // );
+
+ /**
+ The callback implementation, corresponding to the xFunction
+ argument of C's fts5_api::xCreateFunction().
+ */
+ void call(Fts5ExtensionApi ext, Fts5Context fCx,
+ sqlite3_context pCx, sqlite3_value argv[]);
+ /**
+ Is called when this function is destroyed by sqlite3. Typically
+ this function will be empty.
+ */
+ void xDestroy();
+
+ public static abstract class Abstract implements fts5_extension_function {
+ @Override public abstract void call(Fts5ExtensionApi ext, Fts5Context fCx,
+ sqlite3_context pCx, sqlite3_value argv[]);
+ @Override public void xDestroy(){}
+ }
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/Fts5.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/Fts5.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,32 @@
+/*
+** 2023-08-05
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+
+/**
+ INCOMPLETE AND COMPLETELY UNTESTED.
+
+ A utility object for holding FTS5-specific types and constants
+ which are used by multiple FTS5 classes.
+*/
+public final class Fts5 {
+ /* Not used */
+ private Fts5(){}
+
+
+ public static final int FTS5_TOKENIZE_QUERY = 0x0001;
+ public static final int FTS5_TOKENIZE_PREFIX = 0x0002;
+ public static final int FTS5_TOKENIZE_DOCUMENT = 0x0004;
+ public static final int FTS5_TOKENIZE_AUX = 0x0008;
+ public static final int FTS5_TOKEN_COLOCATED = 0x0001;
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5PhraseIter.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 2023-08-04
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.NativePointerHolder;
+
+/**
+ A wrapper for C-level Fts5PhraseIter. They are only modified and
+ inspected by native-level code.
+*/
+public final class Fts5PhraseIter extends NativePointerHolder {
+ //! Updated and used only by native code.
+ private long a;
+ private long b;
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,49 @@
+/*
+** 2023-08-05
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.NativePointerHolder;
+import org.sqlite.jni.annotation.NotNull;
+
+/**
+ A wrapper for communicating C-level (fts5_tokenizer*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+*/
+public final class fts5_tokenizer extends NativePointerHolder {
+ /* Only invoked by JNI */
+ private fts5_tokenizer(){}
+
+ // int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut);
+ // void (*xDelete)(Fts5Tokenizer*);
+
+ public native int xTokenize(@NotNull Fts5Tokenizer t, int tokFlags,
+ @NotNull byte pText[],
+ @NotNull XTokenizeCallback callback);
+
+
+ // int (*xTokenize)(Fts5Tokenizer*,
+ // void *pCtx,
+ // int flags, /* Mask of FTS5_TOKENIZE_* flags */
+ // const char *pText, int nText,
+ // int (*xToken)(
+ // void *pCtx, /* Copy of 2nd argument to xTokenize() */
+ // int tflags, /* Mask of FTS5_TOKEN_* flags */
+ // const char *pToken, /* Pointer to buffer containing token */
+ // int nToken, /* Size of token in bytes */
+ // int iStart, /* Byte offset of token within input text */
+ // int iEnd /* Byte offset of end of token within input text */
+ // )
+ // );
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/Fts5Tokenizer.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,31 @@
+/*
+** 2023-08-05x
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+import org.sqlite.jni.capi.NativePointerHolder;
+
+/**
+ INCOMPLETE AND COMPLETELY UNTESTED.
+
+ A wrapper for communicating C-level (Fts5Tokenizer*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java and C
+ via JNI.
+
+ At the C level, the Fts5Tokenizer type is essentially a void
+ pointer used specifically for tokenizers.
+*/
+public final class Fts5Tokenizer extends NativePointerHolder {
+ //! Only called from JNI.
+ private Fts5Tokenizer(){}
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/TesterFts5.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,832 @@
+/*
+** 2023-08-04
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.fts5;
+import static org.sqlite.jni.capi.CApi.*;
+import static org.sqlite.jni.capi.Tester1.*;
+import org.sqlite.jni.capi.*;
+import org.sqlite.jni.fts5.*;
+
+import java.util.*;
+
+public class TesterFts5 {
+
+ private static void test1(){
+ final Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance();
+ affirm( null != fea );
+ affirm( fea.getNativePointer() != 0 );
+ affirm( fea == Fts5ExtensionApi.getInstance() )/*singleton*/;
+
+ sqlite3 db = createNewDb();
+ fts5_api fApi = fts5_api.getInstanceForDb(db);
+ affirm( fApi != null );
+ affirm( fApi == fts5_api.getInstanceForDb(db) /* singleton per db */ );
+
+ execSql(db, new String[] {
+ "CREATE VIRTUAL TABLE ft USING fts5(a, b);",
+ "INSERT INTO ft(rowid, a, b) VALUES(1, 'X Y', 'Y Z');",
+ "INSERT INTO ft(rowid, a, b) VALUES(2, 'A Z', 'Y Y');"
+ });
+
+ final String pUserData = "This is pUserData";
+ final int outputs[] = {0, 0};
+ final fts5_extension_function func = new fts5_extension_function(){
+ @Override public void call(Fts5ExtensionApi ext, Fts5Context fCx,
+ sqlite3_context pCx, sqlite3_value argv[]){
+ final int nCols = ext.xColumnCount(fCx);
+ affirm( 2 == nCols );
+ affirm( nCols == argv.length );
+ affirm( ext.xUserData(fCx) == pUserData );
+ final OutputPointer.String op = new OutputPointer.String();
+ final OutputPointer.Int32 colsz = new OutputPointer.Int32();
+ final OutputPointer.Int64 colTotalSz = new OutputPointer.Int64();
+ for(int i = 0; i < nCols; ++i ){
+ int rc = ext.xColumnText(fCx, i, op);
+ affirm( 0 == rc );
+ final String val = op.value;
+ affirm( val.equals(sqlite3_value_text16(argv[i])) );
+ rc = ext.xColumnSize(fCx, i, colsz);
+ affirm( 0==rc );
+ affirm( 3==sqlite3_value_bytes(argv[i]) );
+ rc = ext.xColumnTotalSize(fCx, i, colTotalSz);
+ affirm( 0==rc );
+ }
+ ++outputs[0];
+ }
+ public void xDestroy(){
+ outputs[1] = 1;
+ }
+ };
+
+ int rc = fApi.xCreateFunction("myaux", pUserData, func);
+ affirm( 0==rc );
+
+ affirm( 0==outputs[0] );
+ execSql(db, "select myaux(ft,a,b) from ft;");
+ affirm( 2==outputs[0] );
+ affirm( 0==outputs[1] );
+ sqlite3_close_v2(db);
+ affirm( 1==outputs[1] );
+ }
+
+ /*
+ ** Argument sql is a string containing one or more SQL statements
+ ** separated by ";" characters. This function executes each of these
+ ** statements against the database passed as the first argument. If
+ ** no error occurs, the results of the SQL script are returned as
+ ** an array of strings. If an error does occur, a RuntimeException is
+ ** thrown.
+ */
+ private static String[] sqlite3_exec(sqlite3 db, String sql) {
+ List aOut = new ArrayList();
+
+ /* Iterate through the list of SQL statements. For each, step through
+ ** it and add any results to the aOut[] array. */
+ int rc = sqlite3_prepare_multi(db, sql, new PrepareMultiCallback() {
+ @Override public int call(sqlite3_stmt pStmt){
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ int ii;
+ for(ii=0; ii, );
+ */
+ class fts5_aux implements fts5_extension_function {
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length>1 ){
+ throw new RuntimeException("fts5_aux: wrong number of args");
+ }
+
+ boolean bClear = (argv.length==1);
+ Object obj = ext.xGetAuxdata(fCx, bClear);
+ if( obj instanceof String ){
+ sqlite3_result_text16(pCx, (String)obj);
+ }
+
+ if( argv.length==1 ){
+ String val = sqlite3_value_text16(argv[0]);
+ if( !val.equals("") ){
+ ext.xSetAuxdata(fCx, val);
+ }
+ }
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_inst();
+ **
+ ** This is used to test the xInstCount() and xInst() APIs. It returns a
+ ** text value containing a Tcl list with xInstCount() elements. Each
+ ** element is itself a list of 3 integers - the phrase number, column
+ ** number and token offset returned by each call to xInst().
+ */
+ fts5_extension_function fts5_inst = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=0 ){
+ throw new RuntimeException("fts5_inst: wrong number of args");
+ }
+
+ OutputPointer.Int32 pnInst = new OutputPointer.Int32();
+ OutputPointer.Int32 piPhrase = new OutputPointer.Int32();
+ OutputPointer.Int32 piCol = new OutputPointer.Int32();
+ OutputPointer.Int32 piOff = new OutputPointer.Int32();
+ String ret = new String();
+
+ int rc = ext.xInstCount(fCx, pnInst);
+ int nInst = pnInst.get();
+ int ii;
+
+ for(ii=0; rc==SQLITE_OK && ii0 ) ret += " ";
+ ret += "{"+piPhrase.get()+" "+piCol.get()+" "+piOff.get()+"}";
+ }
+
+ sqlite3_result_text(pCx, ret);
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_pinst();
+ **
+ ** Like SQL function fts5_inst(), except using the following
+ **
+ ** xPhraseCount
+ ** xPhraseFirst
+ ** xPhraseNext
+ */
+ fts5_extension_function fts5_pinst = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=0 ){
+ throw new RuntimeException("fts5_pinst: wrong number of args");
+ }
+
+ OutputPointer.Int32 piCol = new OutputPointer.Int32();
+ OutputPointer.Int32 piOff = new OutputPointer.Int32();
+ String ret = new String();
+ int rc = SQLITE_OK;
+
+ int nPhrase = ext.xPhraseCount(fCx);
+ int ii;
+
+ for(ii=0; rc==SQLITE_OK && ii=0;
+ ext.xPhraseNext(fCx, pIter, piCol, piOff)
+ ){
+ if( !ret.equals("") ) ret += " ";
+ ret += "{"+ii+" "+piCol.get()+" "+piOff.get()+"}";
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException("fts5_pinst: rc=" + rc);
+ }else{
+ sqlite3_result_text(pCx, ret);
+ }
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_pcolinst();
+ **
+ ** Like SQL function fts5_pinst(), except using the following
+ **
+ ** xPhraseFirstColumn
+ ** xPhraseNextColumn
+ */
+ fts5_extension_function fts5_pcolinst = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=0 ){
+ throw new RuntimeException("fts5_pcolinst: wrong number of args");
+ }
+
+ OutputPointer.Int32 piCol = new OutputPointer.Int32();
+ String ret = new String();
+ int rc = SQLITE_OK;
+
+ int nPhrase = ext.xPhraseCount(fCx);
+ int ii;
+
+ for(ii=0; rc==SQLITE_OK && ii=0;
+ ext.xPhraseNextColumn(fCx, pIter, piCol)
+ ){
+ if( !ret.equals("") ) ret += " ";
+ ret += "{"+ii+" "+piCol.get()+"}";
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException("fts5_pcolinst: rc=" + rc);
+ }else{
+ sqlite3_result_text(pCx, ret);
+ }
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_rowcount();
+ */
+ fts5_extension_function fts5_rowcount = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=0 ){
+ throw new RuntimeException("fts5_rowcount: wrong number of args");
+ }
+ OutputPointer.Int64 pnRow = new OutputPointer.Int64();
+
+ int rc = ext.xRowCount(fCx, pnRow);
+ if( rc==SQLITE_OK ){
+ sqlite3_result_int64(pCx, pnRow.get());
+ }else{
+ throw new RuntimeException("fts5_rowcount: rc=" + rc);
+ }
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_phrasesize();
+ */
+ fts5_extension_function fts5_phrasesize = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=1 ){
+ throw new RuntimeException("fts5_phrasesize: wrong number of args");
+ }
+ int iPhrase = sqlite3_value_int(argv[0]);
+
+ int sz = ext.xPhraseSize(fCx, iPhrase);
+ sqlite3_result_int(pCx, sz);
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_phrasehits(, );
+ **
+ ** Use the xQueryPhrase() API to determine how many hits, in total,
+ ** there are for phrase in the database.
+ */
+ fts5_extension_function fts5_phrasehits = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=1 ){
+ throw new RuntimeException("fts5_phrasesize: wrong number of args");
+ }
+ int iPhrase = sqlite3_value_int(argv[0]);
+ int rc = SQLITE_OK;
+
+ class MyCallback implements Fts5ExtensionApi.XQueryPhraseCallback {
+ public int nRet = 0;
+ public int getRet() { return nRet; }
+
+ @Override
+ public int call(Fts5ExtensionApi fapi, Fts5Context cx){
+ OutputPointer.Int32 pnInst = new OutputPointer.Int32();
+ int rc = fapi.xInstCount(cx, pnInst);
+ nRet += pnInst.get();
+ return rc;
+ }
+ };
+
+ MyCallback xCall = new MyCallback();
+ rc = ext.xQueryPhrase(fCx, iPhrase, xCall);
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException("fts5_phrasehits: rc=" + rc);
+ }
+ sqlite3_result_int(pCx, xCall.getRet());
+ }
+ public void xDestroy(){ }
+ };
+
+ /*
+ ** fts5_tokenize(, )
+ */
+ fts5_extension_function fts5_tokenize = new fts5_extension_function(){
+ @Override public void call(
+ Fts5ExtensionApi ext,
+ Fts5Context fCx,
+ sqlite3_context pCx,
+ sqlite3_value argv[]
+ ){
+ if( argv.length!=1 ){
+ throw new RuntimeException("fts5_tokenize: wrong number of args");
+ }
+ byte[] utf8 = sqlite3_value_text(argv[0]);
+ int rc = SQLITE_OK;
+
+ class MyCallback implements XTokenizeCallback {
+ private List myList = new ArrayList();
+
+ public String getval() {
+ return String.join("+", myList);
+ }
+
+ @Override
+ public int call(int tFlags, byte[] txt, int iStart, int iEnd){
+ try {
+ String str = new String(txt, "UTF-8");
+ myList.add(str);
+ } catch (Exception e) {
+ }
+ return SQLITE_OK;
+ }
+ };
+
+ MyCallback xCall = new MyCallback();
+ ext.xTokenize(fCx, utf8, xCall);
+ sqlite3_result_text16(pCx, xCall.getval());
+
+ if( rc!=SQLITE_OK ){
+ throw new RuntimeException("fts5_tokenize: rc=" + rc);
+ }
+ }
+ public void xDestroy(){ }
+ };
+
+ fts5_api api = fts5_api.getInstanceForDb(db);
+ api.xCreateFunction("fts5_rowid", fts5_rowid);
+ api.xCreateFunction("fts5_columncount", fts5_columncount);
+ api.xCreateFunction("fts5_columnsize", fts5_columnsize);
+ api.xCreateFunction("fts5_columntext", fts5_columntext);
+ api.xCreateFunction("fts5_columntotalsize", fts5_columntsize);
+
+ api.xCreateFunction("fts5_aux1", new fts5_aux());
+ api.xCreateFunction("fts5_aux2", new fts5_aux());
+
+ api.xCreateFunction("fts5_inst", fts5_inst);
+ api.xCreateFunction("fts5_pinst", fts5_pinst);
+ api.xCreateFunction("fts5_pcolinst", fts5_pcolinst);
+ api.xCreateFunction("fts5_rowcount", fts5_rowcount);
+ api.xCreateFunction("fts5_phrasesize", fts5_phrasesize);
+ api.xCreateFunction("fts5_phrasehits", fts5_phrasehits);
+ api.xCreateFunction("fts5_tokenize", fts5_tokenize);
+ }
+ /*
+ ** Test of various Fts5ExtensionApi methods
+ */
+ private static void test2(){
+
+ /* Open db and populate an fts5 table */
+ sqlite3 db = createNewDb();
+ do_execsql_test(db,
+ "CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
+ "INSERT INTO ft(rowid, a, b) VALUES(-9223372036854775808, 'x', 'x');" +
+ "INSERT INTO ft(rowid, a, b) VALUES(0, 'x', 'x');" +
+ "INSERT INTO ft(rowid, a, b) VALUES(1, 'x y z', 'x y z');" +
+ "INSERT INTO ft(rowid, a, b) VALUES(2, 'x y z', 'x z');" +
+ "INSERT INTO ft(rowid, a, b) VALUES(3, 'x y z', 'x y z');" +
+ "INSERT INTO ft(rowid, a, b) VALUES(9223372036854775807, 'x', 'x');"
+ );
+
+ create_test_functions(db);
+
+ /* Test that fts5_rowid() seems to work */
+ do_execsql_test(db,
+ "SELECT rowid==fts5_rowid(ft) FROM ft('x')",
+ "[1, 1, 1, 1, 1, 1]"
+ );
+
+ /* Test fts5_columncount() */
+ do_execsql_test(db,
+ "SELECT fts5_columncount(ft) FROM ft('x')",
+ "[2, 2, 2, 2, 2, 2]"
+ );
+
+ /* Test fts5_columnsize() */
+ do_execsql_test(db,
+ "SELECT fts5_columnsize(ft, 0) FROM ft('x') ORDER BY rowid",
+ "[1, 1, 3, 3, 3, 1]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columnsize(ft, 1) FROM ft('x') ORDER BY rowid",
+ "[1, 1, 3, 2, 3, 1]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columnsize(ft, -1) FROM ft('x') ORDER BY rowid",
+ "[2, 2, 6, 5, 6, 2]"
+ );
+
+ /* Test that xColumnSize() returns SQLITE_RANGE if the column number
+ ** is out-of range */
+ try {
+ do_execsql_test(db,
+ "SELECT fts5_columnsize(ft, 2) FROM ft('x') ORDER BY rowid"
+ );
+ } catch( RuntimeException e ){
+ affirm( e.getMessage().matches(".*column index out of range") );
+ }
+
+ /* Test fts5_columntext() */
+ do_execsql_test(db,
+ "SELECT fts5_columntext(ft, 0) FROM ft('x') ORDER BY rowid",
+ "[x, x, x y z, x y z, x y z, x]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columntext(ft, 1) FROM ft('x') ORDER BY rowid",
+ "[x, x, x y z, x z, x y z, x]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columntext(ft, 2) FROM ft('x') ORDER BY rowid",
+ "[null, null, null, null, null, null]"
+ );
+
+ /* Test fts5_columntotalsize() */
+ do_execsql_test(db,
+ "SELECT fts5_columntotalsize(ft, 0) FROM ft('x') ORDER BY rowid",
+ "[12, 12, 12, 12, 12, 12]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columntotalsize(ft, 1) FROM ft('x') ORDER BY rowid",
+ "[11, 11, 11, 11, 11, 11]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_columntotalsize(ft, -1) FROM ft('x') ORDER BY rowid",
+ "[23, 23, 23, 23, 23, 23]"
+ );
+
+ /* Test that xColumnTotalSize() returns SQLITE_RANGE if the column
+ ** number is out-of range */
+ try {
+ do_execsql_test(db,
+ "SELECT fts5_columntotalsize(ft, 2) FROM ft('x') ORDER BY rowid"
+ );
+ } catch( RuntimeException e ){
+ affirm( e.getMessage().matches(".*column index out of range") );
+ }
+
+ do_execsql_test(db,
+ "SELECT rowid, fts5_rowcount(ft) FROM ft('z')",
+ "[1, 6, 2, 6, 3, 6]"
+ );
+
+ sqlite3_close_v2(db);
+ }
+
+ /*
+ ** Test of various Fts5ExtensionApi methods
+ */
+ private static void test3(){
+
+ /* Open db and populate an fts5 table */
+ sqlite3 db = createNewDb();
+ do_execsql_test(db,
+ "CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
+ "INSERT INTO ft(a, b) VALUES('the one', 1);" +
+ "INSERT INTO ft(a, b) VALUES('the two', 2);" +
+ "INSERT INTO ft(a, b) VALUES('the three', 3);" +
+ "INSERT INTO ft(a, b) VALUES('the four', '');"
+ );
+ create_test_functions(db);
+
+ /* Test fts5_aux1() + fts5_aux2() - users of xGetAuxdata and xSetAuxdata */
+ do_execsql_test(db,
+ "SELECT fts5_aux1(ft, a) FROM ft('the')",
+ "[null, the one, the two, the three]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_aux2(ft, b) FROM ft('the')",
+ "[null, 1, 2, 3]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_aux1(ft, a), fts5_aux2(ft, b) FROM ft('the')",
+ "[null, null, the one, 1, the two, 2, the three, 3]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_aux1(ft, b), fts5_aux1(ft) FROM ft('the')",
+ "[null, 1, 1, 2, 2, 3, 3, null]"
+ );
+ }
+
+ /*
+ ** Test of various Fts5ExtensionApi methods
+ */
+ private static void test4(){
+
+ /* Open db and populate an fts5 table */
+ sqlite3 db = createNewDb();
+ create_test_functions(db);
+ do_execsql_test(db,
+ "CREATE VIRTUAL TABLE ft USING fts5(a, b);" +
+ "INSERT INTO ft(a, b) VALUES('one two three', 'two three four');" +
+ "INSERT INTO ft(a, b) VALUES('two three four', 'three four five');" +
+ "INSERT INTO ft(a, b) VALUES('three four five', 'four five six');"
+ );
+
+
+ do_execsql_test(db,
+ "SELECT fts5_inst(ft) FROM ft('two')",
+ "[{0 0 1} {0 1 0}, {0 0 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_inst(ft) FROM ft('four')",
+ "[{0 1 2}, {0 0 2} {0 1 1}, {0 0 1} {0 1 0}]"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_inst(ft) FROM ft('a OR b OR four')",
+ "[{2 1 2}, {2 0 2} {2 1 1}, {2 0 1} {2 1 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_inst(ft) FROM ft('two four')",
+ "[{0 0 1} {0 1 0} {1 1 2}, {0 0 0} {1 0 2} {1 1 1}]"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_pinst(ft) FROM ft('two')",
+ "[{0 0 1} {0 1 0}, {0 0 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pinst(ft) FROM ft('four')",
+ "[{0 1 2}, {0 0 2} {0 1 1}, {0 0 1} {0 1 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pinst(ft) FROM ft('a OR b OR four')",
+ "[{2 1 2}, {2 0 2} {2 1 1}, {2 0 1} {2 1 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pinst(ft) FROM ft('two four')",
+ "[{0 0 1} {0 1 0} {1 1 2}, {0 0 0} {1 0 2} {1 1 1}]"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_pcolinst(ft) FROM ft('two')",
+ "[{0 0} {0 1}, {0 0}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pcolinst(ft) FROM ft('four')",
+ "[{0 1}, {0 0} {0 1}, {0 0} {0 1}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pcolinst(ft) FROM ft('a OR b OR four')",
+ "[{2 1}, {2 0} {2 1}, {2 0} {2 1}]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_pcolinst(ft) FROM ft('two four')",
+ "[{0 0} {0 1} {1 1}, {0 0} {1 0} {1 1}]"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_phrasesize(ft, 0) FROM ft('four five six') LIMIT 1;",
+ "[1]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_phrasesize(ft, 0) FROM ft('four + five + six') LIMIT 1;",
+ "[3]"
+ );
+
+
+ sqlite3_close_v2(db);
+ }
+
+ private static void test5(){
+ /* Open db and populate an fts5 table */
+ sqlite3 db = createNewDb();
+ create_test_functions(db);
+ do_execsql_test(db,
+ "CREATE VIRTUAL TABLE ft USING fts5(x, b);" +
+ "INSERT INTO ft(x) VALUES('one two three four five six seven eight');" +
+ "INSERT INTO ft(x) VALUES('one two one four one six one eight');" +
+ "INSERT INTO ft(x) VALUES('one two three four five six seven eight');"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_phrasehits(ft, 0) FROM ft('one') LIMIT 1",
+ "[6]"
+ );
+
+ sqlite3_close_v2(db);
+ }
+
+ private static void test6(){
+ sqlite3 db = createNewDb();
+ create_test_functions(db);
+ do_execsql_test(db,
+ "CREATE VIRTUAL TABLE ft USING fts5(x, b);" +
+ "INSERT INTO ft(x) VALUES('one two three four five six seven eight');"
+ );
+
+ do_execsql_test(db,
+ "SELECT fts5_tokenize(ft, 'abc def ghi') FROM ft('one')",
+ "[abc+def+ghi]"
+ );
+ do_execsql_test(db,
+ "SELECT fts5_tokenize(ft, 'it''s BEEN a...') FROM ft('one')",
+ "[it+s+been+a]"
+ );
+
+ sqlite3_close_v2(db);
+ }
+
+ private static synchronized void runTests(){
+ test1();
+ test2();
+ test3();
+ test4();
+ test5();
+ test6();
+ }
+
+ public TesterFts5(){
+ runTests();
+ }
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/fts5/XTokenizeCallback.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,22 @@
+/*
+** 2023-08-04
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the JNI bindings for the sqlite3 C API.
+*/
+package org.sqlite.jni.fts5;
+
+
+/**
+ Callback type for use with xTokenize() variants.
+*/
+public interface XTokenizeCallback {
+ int call(int tFlags, byte[] txt, int iStart, int iEnd);
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/test-script-interpreter.md sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/test-script-interpreter.md
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/test-script-interpreter.md 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/test-script-interpreter.md 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,269 @@
+# Specifications For A Rudimentary SQLite Test Script Interpreter
+
+## Overview
+
+The purpose of the Test Script Interpreter is to read and interpret
+script files that contain SQL commands and desired results. The
+interpreter will check results and report an discrepencies found.
+
+The test script files are ASCII text files. The filename always ends with
+".test". Each script is evaluated independently; context does not carry
+forward from one script to the next. So, for example, the --null command
+run in one test script does not cause any changes in the behavior of
+subsequent test scripts. All open database connections are closed
+at the end of each test script. All database files created by a test
+script are deleted when the script finishes.
+
+## Parsing Rules:
+
+ 1. The test script is read line by line, where a line is a sequence of
+ characters that runs up to the next '\\n' (0x0a) character or until
+ the end of the file. There is never a need to read ahead past the
+ end of the current line.
+
+ 2. If any line contains the string " MODULE_NAME:" (with a space before
+ the initial "M") or "MIXED_MODULE_NAME:" then that test script is
+ incompatible with this spec. Processing of the test script should
+ end immediately. There is no need to read any more of the file.
+ In verbose mode, the interpreter might choose to emit an informational
+ messages saying that the test script was abandoned due to an
+ incompatible module type.
+
+ 3. If any line contains the string "SCRIPT_MODULE_NAME:" then the input
+ script is known to be of the correct type for this specification and
+ processing may continue. The "MODULE_NAME" checking in steps 2 and 3
+ may optionally be discontinued after sighting a "SCRIPT_MODULE_NAME".
+
+ 4. If any line contains "REQUIRED_PROPERTIES:" and that substring is followed
+ by any non-whitespace text, then the script is not compatible with this
+ spec. Processing should stop immediately. In verbose mode, the
+ interpreter might choose to emit an information message saying that the
+ test script was abandoned due to unsupported requirement properties.
+
+ 5. If any line begins with the "\|" (0x7c) character, that indicates that
+ the input script is not compatible with this specification. Processing
+ of the script should stop immediately. In verbose mode, the interpreter
+ might choose to emit an informational message indicating that the
+ test script was abandoned because it contained "a dbtotxt format database
+ specification".
+
+ 6. Any line that begins with "#" is a C-preprocessor line. The interpreter
+ described by this spec does not know how to deal with C-preprocessor lines.
+ Hence, processing should be abandoned. In verbose mode, the interpreter
+ might emit an informational message similar to
+ "script NAME abandoned due to C-preprocessor line: ..."
+
+ 7. If a line begins with exactly two minus signs followed by a
+ lowercase letter, that is a command. Process commands as described
+ below.
+
+ 8. All other lines should be accumulated into the "input buffer".
+ The various commands will have access to this input buffer.
+ Some commands will reset the buffer.
+
+## Initialization
+
+The initial state of the interpreter at the start of processing each script
+is as if the following command sequence had been run:
+
+> ~~~
+--close all
+--db 0
+--new test.db
+--null nil
+~~~
+
+In words, all database connections are closed except for connection 0 (the
+default) which is open on an empty database named "test.db". The string
+"nil" is displayed for NULL column values.
+
+The only context carried forward after the evaluation of one test script
+into the evaluation of the next test script is the count of the number of
+tests run and the number of failures seen.
+
+## Commands:
+
+Each command looks like an SQL comment. The command begins at the left
+margin (no leading space) and starts with exactly 2 minus signs ("-").
+The command name consists of lowercase letters and maybe a "-" or two.
+Some commands have arguments.
+The arguments are separated from the command name by one or more spaces.
+
+Commands have access to the input buffer and might reset the input buffer.
+The command can also optionally read (and consume) additional text from
+script that comes after the command.
+
+Unknown or unrecognized commands indicate that the script contains features
+that are not (yet) supported by this specification. Processing of the
+script should terminate immediately. When this happens and when the
+interpreter is in a "verbose" mode, the interpreter might choose to emit
+an informational message along the lines of "test script NAME abandoned
+due to unsupported command: --whatever".
+
+The initial implemention will only recognize a few commands. Other
+commands may be added later. The following is the initial set of
+commands:
+
+### The --testcase command
+
+Every test case starts with a --testcase command. The --testcase
+command resets both the "input buffer" and the "result buffer". The
+argument to the --testcase command is the name of the test case. That
+test case name is used for logging and debugging and when printing
+errors. The input buffer is set to the body of the test case.
+
+### The --result command
+
+The --result command tries to execute the text in the input buffer as SQL.
+For each row of result coming out of this SQL, the text of that result is
+appended to the "result buffer". If a result row contains multiple columns,
+the columns are processed from left to right. For each column, text is
+appended to the result buffer according to the following rules:
+
+ * If the result buffer already contains some text, append a space.
+ (In this way, all column values and all row values are separated from
+ each other by a single space.)
+
+ * If sqlite3_column_text() returns NULL, then append "nil" - or
+ some other text that is specified by the --null command - and skip
+ all subsequent rules.
+
+ * If sqlite3_column_text() is an empty string, append `{}` to the
+ result buffer and skip all subsequent rules.
+
+ * If sqlite3_column_text() does not contain any special
+ characters, append it to the result buffer without any
+ formatting and skip all subsequent rules. Special characters are:
+ 0x00 to 0x20 (inclusive), double-quote (0x22), backslash (0x5c),
+ curly braces (0x7b and 0x7d).
+
+ * If sqlite3_column_text() does not contains curly braces, then put
+ the text inside of `{...}` and append it and skip all subsequent rules.
+
+ * Append the text within double-quotes (`"..."`) and within the text
+ escape '"' and '\\' by prepending a single '\\' and escape any
+ control characters (characters less than 0x20) using octal notation:
+ '\\NNN'.
+
+If an error is encountered while running the SQL, then append the
+symbolic C-preprocessor name for the error
+code (ex: "SQLITE_CONSTRAINT") as if it were a column value. Then append
+the error message text as if it where a column value. Then stop processing.
+
+After the SQL text has been run, compare the content of the result buffer
+against the argument to the --result command and report a testing error if
+there are any differences.
+
+The --result command resets the input buffer, but it does not reset
+the result buffer. This distinction does not matter for the --result
+command itself, but it is important for related commands like --glob
+and --notglob. Sometimes test cases will contains a bunch of SQL
+followed by multiple --glob and/or --notglob statements. All of the
+globs should be evaluted agains the result buffer correct, but the SQL
+should only be run once. This is accomplished by resetting the input
+buffer but not the result buffer.
+
+### The --glob command
+
+The --glob command works just like --result except that the argument to
+--glob is interpreted as a TEST-GLOB pattern and the results are compared
+using that glob pattern rather than using strcmp(). Other than that,
+the two operate the same.
+
+The TEST-GLOB pattern is slightly different for a standard GLOB:
+
+ * The '*' character matches zero or more characters.
+
+ * The '?' character matches any single character
+
+ * The '[...]' character sequence machines a single character
+ in between the brackets.
+
+ * The '#' character matches one or more digits (This is the main
+ difference between standard unix-glob and TEST-GLOB. unix-glob
+ does not have this feature. It was added to because it comes
+ up a lot during SQLite testing.)
+
+### The --notglob command
+
+The --notglob command works just like --glob except that it reports an
+error if the GLOB does match, rather than if the GLOB does not matches.
+
+### The --oom command
+
+This command is to be used for out-of-memory testing. It means that
+OOM errors should be simulated to ensure that SQLite is able to deal with
+them. This command can be silently ignored for now. We might add support
+for this later.
+
+### The --tableresult command
+
+The --tableresult command works like --glob except that the GLOB pattern
+to be matched is taken from subsequent lines of the input script up to
+the next --end. Every span of one or more whitespace characters in this
+pattern text is collapsed into a single space (0x20).
+Leading and trailing whitespace are removed from the pattern.
+The --end that ends the GLOB pattern is not part of the GLOB pattern, but
+the --end is consumed from the script input.
+
+### The --new and --open commands
+
+The --new and --open commands cause a database file to be opened.
+The name of the file is the argument to the command. The --new command
+opens an initially empty database (it deletes the file before opening it)
+whereas the --open command opens an existing database if it already
+exists.
+
+### The --db command
+
+The script interpreter can have up to 7 different SQLite database
+connections open at a time. The --db command is used to switch between
+them. The argument to --db is an integer between 0 and 6 that selects
+which database connection to use moving forward.
+
+### The --close command
+
+The --close command causes an existing database connection to close.
+This command is a no-op if the database connection is not currently
+open. There can be up to 7 different database connections, numbered 0
+through 6. The number of the database connection to close is an
+argument to the --close command, which will fail if an out-of-range
+value is provided. Or if the argument to --close is "all" then all
+open database connections are closed. If passed no argument, the
+currently-active database is assumed.
+
+### The --null command
+
+The NULL command changes the text that is used to represent SQL NULL
+values in the result buffer.
+
+### The --run command
+
+The --run command executes text in the input buffer as if it where SQL.
+However, nothing is added to the result buffer. Any output from the SQL
+is silently ignored. Errors in the SQL are silently ignored.
+
+The --run command normally executes the SQL in the current database
+connection. However, if --run has an argument that is an integer between
+0 and 6 then the SQL is run in the alternative database connection specified
+by that argument.
+
+### The --json and --json-block commands
+
+The --json and --json-block commands work like --result and --tableresult,
+respectively. The difference is that column values are appended to the
+result buffer literally, without ever enclosing the values in `{...}` or
+`"..."` and without escaping any characters in the column value and comparison
+is always an exact strcmp() not a GLOB.
+
+### The --print command
+
+The --print command emits both its arguments and its body (if any) to
+stdout, indenting each line of output.
+
+### The --column-names command
+
+The --column-names command requires 0 or 1 as an argument, to disable
+resp. enable it, and modifies SQL execution to include column names
+in output. When this option is on, each column value emitted gets
+prefixed by its column name, with a single space between them.
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,82 @@
+/*
+** 2023-10-16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import org.sqlite.jni.capi.CApi;
+import org.sqlite.jni.annotation.*;
+import org.sqlite.jni.capi.sqlite3_context;
+import org.sqlite.jni.capi.sqlite3_value;
+
+/**
+ EXPERIMENTAL/INCOMPLETE/UNTESTED
+
+ A SqlFunction implementation for aggregate functions. The T type
+ represents the type of data accumulated by this aggregate while it
+ works. e.g. a SUM()-like UDF might use Integer or Long and a
+ CONCAT()-like UDF might use a StringBuilder or a List.
+*/
+public abstract class AggregateFunction implements SqlFunction {
+
+ /**
+ As for the xStep() argument of the C API's
+ sqlite3_create_function(). If this function throws, the
+ exception is reported via sqlite3_result_error().
+ */
+ public abstract void xStep(SqlFunction.Arguments args);
+
+ /**
+ As for the xFinal() argument of the C API's
+ sqlite3_create_function(). If this function throws, it is
+ translated into sqlite3_result_error().
+
+ Note that the passed-in object will not actually contain any
+ arguments for xFinal() but will contain the context object needed
+ for setting the call's result or error state.
+ */
+ public abstract void xFinal(SqlFunction.Arguments args);
+
+ /**
+ Optionally override to be notified when the UDF is finalized by
+ SQLite.
+ */
+ public void xDestroy() {}
+
+ /** Per-invocation state for the UDF. */
+ private final SqlFunction.PerContextState map =
+ new SqlFunction.PerContextState<>();
+
+ /**
+ To be called from the implementation's xStep() method, as well
+ as the xValue() and xInverse() methods of the {@link WindowFunction}
+ subclass, to fetch the current per-call UDF state. On the
+ first call to this method for any given sqlite3_context
+ argument, the context is set to the given initial value. On all other
+ calls, the 2nd argument is ignored.
+
+ @see SQLFunction.PerContextState#getAggregateState
+ */
+ protected final ValueHolder getAggregateState(SqlFunction.Arguments args, T initialValue){
+ return map.getAggregateState(args, initialValue);
+ }
+
+ /**
+ To be called from the implementation's xFinal() method to fetch
+ the final state of the UDF and remove its mapping.
+
+ see SQLFunction.PerContextState#takeAggregateState
+ */
+ protected final T takeAggregateState(SqlFunction.Arguments args){
+ return map.takeAggregateState(args);
+ }
+
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,37 @@
+/*
+** 2023-10-16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import org.sqlite.jni.capi.CApi;
+import org.sqlite.jni.annotation.*;
+import org.sqlite.jni.capi.sqlite3_context;
+import org.sqlite.jni.capi.sqlite3_value;
+
+/**
+ The SqlFunction type for scalar SQL functions.
+*/
+public abstract class ScalarFunction implements SqlFunction {
+ /**
+ As for the xFunc() argument of the C API's
+ sqlite3_create_function(). If this function throws, it is
+ translated into an sqlite3_result_error().
+ */
+ public abstract void xFunc(SqlFunction.Arguments args);
+
+ /**
+ Optionally override to be notified when the UDF is finalized by
+ SQLite. This default implementation does nothing.
+ */
+ public void xDestroy() {}
+
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,301 @@
+/*
+** 2023-10-16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import org.sqlite.jni.capi.CApi;
+import org.sqlite.jni.capi.sqlite3_context;
+import org.sqlite.jni.capi.sqlite3_value;
+
+/**
+ Base marker interface for SQLite's three types of User-Defined SQL
+ Functions (UDFs): Scalar, Aggregate, and Window functions.
+*/
+public interface SqlFunction {
+
+ /**
+ The Arguments type is an abstraction on top of the lower-level
+ UDF function argument types. It provides _most_ of the functionality
+ of the lower-level interface, insofar as possible without "leaking"
+ those types into this API.
+ */
+ public final static class Arguments implements Iterable{
+ private final sqlite3_context cx;
+ private final sqlite3_value args[];
+ public final int length;
+
+ /**
+ Must be passed the context and arguments for the UDF call this
+ object is wrapping. Intended to be used by internal proxy
+ classes which "convert" the lower-level interface into this
+ package's higher-level interface, e.g. ScalarAdapter and
+ AggregateAdapter.
+
+ Passing null for the args is equivalent to passing a length-0
+ array.
+ */
+ Arguments(sqlite3_context cx, sqlite3_value args[]){
+ this.cx = cx;
+ this.args = args==null ? new sqlite3_value[0] : args;;
+ this.length = this.args.length;
+ }
+
+ /**
+ Wrapper for a single SqlFunction argument. Primarily intended
+ for use with the Arguments class's Iterable interface.
+ */
+ public final static class Arg {
+ private final Arguments a;
+ private final int ndx;
+ /* Only for use by the Arguments class. */
+ private Arg(Arguments a, int ndx){
+ this.a = a;
+ this.ndx = ndx;
+ }
+ /** Returns this argument's index in its parent argument list. */
+ public int getIndex(){return ndx;}
+ public int getInt(){return a.getInt(ndx);}
+ public long getInt64(){return a.getInt64(ndx);}
+ public double getDouble(){return a.getDouble(ndx);}
+ public byte[] getBlob(){return a.getBlob(ndx);}
+ public byte[] getText(){return a.getText(ndx);}
+ public String getText16(){return a.getText16(ndx);}
+ public int getBytes(){return a.getBytes(ndx);}
+ public int getBytes16(){return a.getBytes16(ndx);}
+ public Object getObject(){return a.getObject(ndx);}
+ public T getObjectCasted(Class type){ return a.getObjectCasted(ndx, type); }
+ public int getType(){return a.getType(ndx);}
+ public Object getAuxData(){return a.getAuxData(ndx);}
+ public void setAuxData(Object o){a.setAuxData(ndx, o);}
+ }
+
+ @Override
+ public java.util.Iterator iterator(){
+ final Arg[] proxies = new Arg[args.length];
+ for( int i = 0; i < args.length; ++i ){
+ proxies[i] = new Arg(this, i);
+ }
+ return java.util.Arrays.stream(proxies).iterator();
+ }
+
+ /**
+ Returns the sqlite3_value at the given argument index or throws
+ an IllegalArgumentException exception if ndx is out of range.
+ */
+ private sqlite3_value valueAt(int ndx){
+ if(ndx<0 || ndx>=args.length){
+ throw new IllegalArgumentException(
+ "SQL function argument index "+ndx+" is out of range."
+ );
+ }
+ return args[ndx];
+ }
+
+ sqlite3_context getContext(){return cx;}
+
+ public int getArgCount(){ return args.length; }
+
+ public int getInt(int arg){return CApi.sqlite3_value_int(valueAt(arg));}
+ public long getInt64(int arg){return CApi.sqlite3_value_int64(valueAt(arg));}
+ public double getDouble(int arg){return CApi.sqlite3_value_double(valueAt(arg));}
+ public byte[] getBlob(int arg){return CApi.sqlite3_value_blob(valueAt(arg));}
+ public byte[] getText(int arg){return CApi.sqlite3_value_text(valueAt(arg));}
+ public String getText16(int arg){return CApi.sqlite3_value_text16(valueAt(arg));}
+ public int getBytes(int arg){return CApi.sqlite3_value_bytes(valueAt(arg));}
+ public int getBytes16(int arg){return CApi.sqlite3_value_bytes16(valueAt(arg));}
+ public Object getObject(int arg){return CApi.sqlite3_value_java_object(valueAt(arg));}
+ public T getObjectCasted(int arg, Class type){
+ return CApi.sqlite3_value_java_casted(valueAt(arg), type);
+ }
+
+ public int getType(int arg){return CApi.sqlite3_value_type(valueAt(arg));}
+ public int getSubtype(int arg){return CApi.sqlite3_value_subtype(valueAt(arg));}
+ public int getNumericType(int arg){return CApi.sqlite3_value_numeric_type(valueAt(arg));}
+ public int getNoChange(int arg){return CApi.sqlite3_value_nochange(valueAt(arg));}
+ public boolean getFromBind(int arg){return CApi.sqlite3_value_frombind(valueAt(arg));}
+ public int getEncoding(int arg){return CApi.sqlite3_value_encoding(valueAt(arg));}
+
+ public void resultInt(int v){ CApi.sqlite3_result_int(cx, v); }
+ public void resultInt64(long v){ CApi.sqlite3_result_int64(cx, v); }
+ public void resultDouble(double v){ CApi.sqlite3_result_double(cx, v); }
+ public void resultError(String msg){CApi.sqlite3_result_error(cx, msg);}
+ public void resultError(Exception e){CApi.sqlite3_result_error(cx, e);}
+ public void resultErrorTooBig(){CApi.sqlite3_result_error_toobig(cx);}
+ public void resultErrorCode(int rc){CApi.sqlite3_result_error_code(cx, rc);}
+ public void resultObject(Object o){CApi.sqlite3_result_java_object(cx, o);}
+ public void resultNull(){CApi.sqlite3_result_null(cx);}
+ public void resultArg(int argNdx){CApi.sqlite3_result_value(cx, valueAt(argNdx));}
+ public void resultZeroBlob(long n){
+ // Throw on error? If n is too big,
+ // sqlite3_result_error_toobig() is automatically called.
+ CApi.sqlite3_result_zeroblob64(cx, n);
+ }
+
+ public void resultBlob(byte[] blob){CApi.sqlite3_result_blob(cx, blob);}
+ public void resultText(byte[] utf8){CApi.sqlite3_result_text(cx, utf8);}
+ public void resultText(String txt){CApi.sqlite3_result_text(cx, txt);}
+ public void resultText16(byte[] utf16){CApi.sqlite3_result_text16(cx, utf16);}
+ public void resultText16(String txt){CApi.sqlite3_result_text16(cx, txt);}
+
+ public void setAuxData(int arg, Object o){
+ /* From the API docs: https://www.sqlite.org/c3ref/get_auxdata.html
+
+ The value of the N parameter to these interfaces should be
+ non-negative. Future enhancements may make use of negative N
+ values to define new kinds of function caching behavior.
+ */
+ valueAt(arg);
+ CApi.sqlite3_set_auxdata(cx, arg, o);
+ }
+
+ public Object getAuxData(int arg){
+ valueAt(arg);
+ return CApi.sqlite3_get_auxdata(cx, arg);
+ }
+ }
+
+ /**
+ PerContextState assists aggregate and window functions in
+ managing their accumulator state across calls to the UDF's
+ callbacks.
+
+
T must be of a type which can be legally stored as a value in
+ java.util.HashMap.
+
+
If a given aggregate or window function is called multiple times
+ in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)...,
+ then the clients need some way of knowing which call is which so
+ that they can map their state between their various UDF callbacks
+ and reset it via xFinal(). This class takes care of such
+ mappings.
+
+
This class works by mapping
+ sqlite3_context.getAggregateContext() to a single piece of
+ state, of a client-defined type (the T part of this class), which
+ persists across a "matching set" of the UDF's callbacks.
+
+
This class is a helper providing commonly-needed functionality
+ - it is not required for use with aggregate or window functions.
+ Client UDFs are free to perform such mappings using custom
+ approaches. The provided {@link AggregateFunction} and {@link
+ WindowFunction} classes use this.
+ */
+ public static final class PerContextState {
+ private final java.util.Map> map
+ = new java.util.HashMap<>();
+
+ /**
+ Should be called from a UDF's xStep(), xValue(), and xInverse()
+ methods, passing it that method's first argument and an initial
+ value for the persistent state. If there is currently no
+ mapping for the given context within the map, one is created
+ using the given initial value, else the existing one is used
+ and the 2nd argument is ignored. It returns a ValueHolder
+ which can be used to modify that state directly without
+ requiring that the client update the underlying map's entry.
+
+
The caller is obligated to eventually call
+ takeAggregateState() to clear the mapping.
+ */
+ public ValueHolder getAggregateState(SqlFunction.Arguments args, T initialValue){
+ final Long key = args.getContext().getAggregateContext(true);
+ ValueHolder rc = null==key ? null : map.get(key);
+ if( null==rc ){
+ map.put(key, rc = new ValueHolder<>(initialValue));
+ }
+ return rc;
+ }
+
+ /**
+ Should be called from a UDF's xFinal() method and passed that
+ method's first argument. This function removes the value
+ associated with with the arguments' aggregate context from the
+ map and returns it, returning null if no other UDF method has
+ been called to set up such a mapping. The latter condition will
+ be the case if a UDF is used in a statement which has no result
+ rows.
+ */
+ public T takeAggregateState(SqlFunction.Arguments args){
+ final ValueHolder h = map.remove(args.getContext().getAggregateContext(false));
+ return null==h ? null : h.value;
+ }
+ }
+
+ /**
+ Internal-use adapter for wrapping this package's ScalarFunction
+ for use with the org.sqlite.jni.capi.ScalarFunction interface.
+ */
+ static final class ScalarAdapter extends org.sqlite.jni.capi.ScalarFunction {
+ final ScalarFunction impl;
+ ScalarAdapter(ScalarFunction impl){
+ this.impl = impl;
+ }
+ /**
+ Proxies this.impl.xFunc(), adapting the call arguments to that
+ function's signature. If the proxy throws, it's translated to
+ sqlite_result_error() with the exception's message.
+ */
+ public void xFunc(sqlite3_context cx, sqlite3_value[] args){
+ try{
+ impl.xFunc( new SqlFunction.Arguments(cx, args) );
+ }catch(Exception e){
+ CApi.sqlite3_result_error(cx, e);
+ }
+ }
+
+ public void xDestroy(){
+ impl.xDestroy();
+ }
+ }
+
+ /**
+ Internal-use adapter for wrapping this package's AggregateFunction
+ for use with the org.sqlite.jni.capi.AggregateFunction interface.
+ */
+ static final class AggregateAdapter extends org.sqlite.jni.capi.AggregateFunction {
+ final AggregateFunction impl;
+ AggregateAdapter(AggregateFunction impl){
+ this.impl = impl;
+ }
+
+ /**
+ Proxies this.impl.xStep(), adapting the call arguments to that
+ function's signature. If the proxied function throws, it is
+ translated to sqlite_result_error() with the exception's
+ message.
+ */
+ public void xStep(sqlite3_context cx, sqlite3_value[] args){
+ try{
+ impl.xStep( new SqlFunction.Arguments(cx, args) );
+ }catch(Exception e){
+ CApi.sqlite3_result_error(cx, e);
+ }
+ }
+
+ /**
+ As for the xFinal() argument of the C API's sqlite3_create_function().
+ If the proxied function throws, it is translated into a sqlite3_result_error().
+ */
+ public void xFinal(sqlite3_context cx){
+ try{
+ impl.xFinal( new SqlFunction.Arguments(cx, null) );
+ }catch(Exception e){
+ CApi.sqlite3_result_error(cx, e);
+ }
+ }
+
+ public void xDestroy(){
+ impl.xDestroy();
+ }
+ }
+
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,82 @@
+/*
+** 2023-10-09
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import static org.sqlite.jni.capi.CApi.*;
+import org.sqlite.jni.capi.sqlite3;
+
+/**
+ A wrapper for communicating C-level (sqlite3*) instances with
+ Java. These wrappers do not own their associated pointer, they
+ simply provide a type-safe way to communicate it between Java
+ and C via JNI.
+*/
+public final class SqliteException extends java.lang.RuntimeException {
+ int errCode = SQLITE_ERROR;
+ int xerrCode = SQLITE_ERROR;
+ int errOffset = -1;
+ int sysErrno = 0;
+
+ /**
+ Records the given error string and uses SQLITE_ERROR for both the
+ error code and extended error code.
+ */
+ public SqliteException(String msg){
+ super(msg);
+ }
+
+ /**
+ Uses sqlite3_errstr(sqlite3ResultCode) for the error string and
+ sets both the error code and extended error code to the given
+ value.
+ */
+ public SqliteException(int sqlite3ResultCode){
+ super(sqlite3_errstr(sqlite3ResultCode));
+ errCode = xerrCode = sqlite3ResultCode;
+ }
+
+ /**
+ Records the current error state of db (which must not be null and
+ must refer to an opened db object). Note that this does NOT close
+ the db.
+
+ Design note: closing the db on error is likely only useful during
+ a failed db-open operation, and the place(s) where that can
+ happen are inside this library, not client-level code.
+ */
+ SqliteException(sqlite3 db){
+ super(sqlite3_errmsg(db));
+ errCode = sqlite3_errcode(db);
+ xerrCode = sqlite3_extended_errcode(db);
+ errOffset = sqlite3_error_offset(db);
+ sysErrno = sqlite3_system_errno(db);
+ }
+
+ /**
+ Records the current error state of db (which must not be null and
+ must refer to an open database).
+ */
+ public SqliteException(Sqlite db){
+ this(db.nativeHandle());
+ }
+
+ public SqliteException(Sqlite.Stmt stmt){
+ this( stmt.db() );
+ }
+
+ public int errcode(){ return errCode; }
+ public int extendedErrcode(){ return xerrCode; }
+ public int errorOffset(){ return errOffset; }
+ public int systemErrno(){ return sysErrno; }
+
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,218 @@
+/*
+** 2023-10-09
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import java.nio.charset.StandardCharsets;
+import static org.sqlite.jni.capi.CApi.*;
+import org.sqlite.jni.capi.CApi;
+import org.sqlite.jni.capi.sqlite3;
+import org.sqlite.jni.capi.sqlite3_stmt;
+import org.sqlite.jni.capi.OutputPointer;
+
+/**
+ This class represents a database connection, analog to the C-side
+ sqlite3 class but with added argument validation, exceptions, and
+ similar "smoothing of sharp edges" to make the API safe to use from
+ Java. It also acts as a namespace for other types for which
+ individual instances are tied to a specific database connection.
+*/
+public final class Sqlite implements AutoCloseable {
+ private sqlite3 db;
+
+ //! Used only by the open() factory functions.
+ private Sqlite(sqlite3 db){
+ this.db = db;
+ }
+
+ /**
+ Returns a newly-opened db connection or throws SqliteException if
+ opening fails. All arguments are as documented for
+ sqlite3_open_v2().
+
+ Design question: do we want static factory functions or should
+ this be reformulated as a constructor?
+ */
+ public static Sqlite open(String filename, int flags, String vfsName){
+ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+ final int rc = sqlite3_open_v2(filename, out, flags, vfsName);
+ final sqlite3 n = out.take();
+ if( 0!=rc ){
+ if( null==n ) throw new SqliteException(rc);
+ final SqliteException ex = new SqliteException(n);
+ n.close();
+ throw ex;
+ }
+ return new Sqlite(n);
+ }
+
+ public static Sqlite open(String filename, int flags){
+ return open(filename, flags, null);
+ }
+
+ public static Sqlite open(String filename){
+ return open(filename, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, null);
+ }
+
+ @Override public void close(){
+ if(null!=this.db){
+ this.db.close();
+ this.db = null;
+ }
+ }
+
+ /**
+ Returns this object's underlying native db handle, or null if
+ this instance has been closed. This is very specifically not
+ public.
+ */
+ sqlite3 nativeHandle(){ return this.db; }
+
+ private sqlite3 affirmOpen(){
+ if( null==db || 0==db.getNativePointer() ){
+ throw new IllegalArgumentException("This database instance is closed.");
+ }
+ return this.db;
+ }
+
+ // private byte[] stringToUtf8(String s){
+ // return s==null ? null : s.getBytes(StandardCharsets.UTF_8);
+ // }
+
+ private void affirmRcOk(int rc){
+ if( 0!=rc ){
+ throw new SqliteException(db);
+ }
+ }
+
+ /**
+ Corresponds to the sqlite3_stmt class. Use Sqlite.prepare() to
+ create new instances.
+ */
+ public final class Stmt implements AutoCloseable {
+ private Sqlite _db = null;
+ private sqlite3_stmt stmt = null;
+ /** Only called by the prepare() factory functions. */
+ Stmt(Sqlite db, sqlite3_stmt stmt){
+ this._db = db;
+ this.stmt = stmt;
+ }
+
+ sqlite3_stmt nativeHandle(){
+ return stmt;
+ }
+
+ private sqlite3_stmt affirmOpen(){
+ if( null==stmt || 0==stmt.getNativePointer() ){
+ throw new IllegalArgumentException("This Stmt has been finalized.");
+ }
+ return stmt;
+ }
+
+ /**
+ Corresponds to sqlite3_finalize(), but we cannot override the
+ name finalize() here because this one requires a different
+ signature. It does not throw on error here because "destructors
+ do not throw." If it returns non-0, the object is still
+ finalized.
+ */
+ public int finalizeStmt(){
+ int rc = 0;
+ if( null!=stmt ){
+ sqlite3_finalize(stmt);
+ stmt = null;
+ }
+ return rc;
+ }
+
+ @Override public void close(){
+ finalizeStmt();
+ }
+
+ /**
+ Throws if rc is any value other than 0, SQLITE_ROW, or
+ SQLITE_DONE, else returns rc.
+ */
+ private int checkRc(int rc){
+ switch(rc){
+ case 0:
+ case SQLITE_ROW:
+ case SQLITE_DONE: return rc;
+ default:
+ throw new SqliteException(this);
+ }
+ }
+
+ /**
+ Works like sqlite3_step() but throws SqliteException for any
+ result other than 0, SQLITE_ROW, or SQLITE_DONE.
+ */
+ public int step(){
+ return checkRc(sqlite3_step(affirmOpen()));
+ }
+
+ public Sqlite db(){ return this._db; }
+
+ /**
+ Works like sqlite3_reset() but throws on error.
+ */
+ public void reset(){
+ checkRc(sqlite3_reset(affirmOpen()));
+ }
+
+ public void clearBindings(){
+ sqlite3_clear_bindings( affirmOpen() );
+ }
+ }
+
+
+ /**
+ prepare() TODOs include:
+
+ - overloads taking byte[] and ByteBuffer.
+
+ - multi-statement processing, like CApi.sqlite3_prepare_multi()
+ but using a callback specific to the higher-level Stmt class
+ rather than the sqlite3_stmt class.
+ */
+ public Stmt prepare(String sql, int prepFlags){
+ final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+ final int rc = sqlite3_prepare_v3(affirmOpen(), sql, prepFlags, out);
+ affirmRcOk(rc);
+ return new Stmt(this, out.take());
+ }
+
+ public Stmt prepare(String sql){
+ return prepare(sql, 0);
+ }
+
+ public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f ){
+ int rc = CApi.sqlite3_create_function(affirmOpen(), name, nArg, eTextRep,
+ new SqlFunction.ScalarAdapter(f));
+ if( 0!=rc ) throw new SqliteException(db);
+ }
+
+ public void createFunction(String name, int nArg, ScalarFunction f){
+ this.createFunction(name, nArg, CApi.SQLITE_UTF8, f);
+ }
+
+ public void createFunction(String name, int nArg, int eTextRep, AggregateFunction f ){
+ int rc = CApi.sqlite3_create_function(affirmOpen(), name, nArg, eTextRep,
+ new SqlFunction.AggregateAdapter(f));
+ if( 0!=rc ) throw new SqliteException(db);
+ }
+
+ public void createFunction(String name, int nArg, AggregateFunction f){
+ this.createFunction(name, nArg, CApi.SQLITE_UTF8, f);
+ }
+
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,584 @@
+/*
+** 2023-10-09
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.wrapper1;
+//import static org.sqlite.jni.capi.CApi.*;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import org.sqlite.jni.capi.*;
+
+/**
+ An annotation for Tester2 tests which we do not want to run in
+ reflection-driven test mode because either they are not suitable
+ for multi-threaded threaded mode or we have to control their execution
+ order.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface ManualTest{}
+/**
+ Annotation for Tester2 tests which mark those which must be skipped
+ in multi-threaded mode.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface SingleThreadOnly{}
+
+public class Tester2 implements Runnable {
+ //! True when running in multi-threaded mode.
+ private static boolean mtMode = false;
+ //! True to sleep briefly between tests.
+ private static boolean takeNaps = false;
+ //! True to shuffle the order of the tests.
+ private static boolean shuffle = false;
+ //! True to dump the list of to-run tests to stdout.
+ private static boolean listRunTests = false;
+ //! True to squelch all out() and outln() output.
+ private static boolean quietMode = false;
+ //! Total number of runTests() calls.
+ private static int nTestRuns = 0;
+ //! List of test*() methods to run.
+ private static List testMethods = null;
+ //! List of exceptions collected by run()
+ private static List listErrors = new ArrayList<>();
+ private static final class Metrics {
+ //! Number of times createNewDb() (or equivalent) is invoked.
+ volatile int dbOpen = 0;
+ }
+
+ //! Instance ID.
+ private Integer tId;
+
+ Tester2(Integer id){
+ tId = id;
+ }
+
+ static final Metrics metrics = new Metrics();
+
+ public static synchronized void outln(){
+ if( !quietMode ){
+ System.out.println("");
+ }
+ }
+
+ public static synchronized void outPrefix(){
+ if( !quietMode ){
+ System.out.print(Thread.currentThread().getName()+": ");
+ }
+ }
+
+ public static synchronized void outln(Object val){
+ if( !quietMode ){
+ outPrefix();
+ System.out.println(val);
+ }
+ }
+
+ public static synchronized void out(Object val){
+ if( !quietMode ){
+ System.out.print(val);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static synchronized void out(Object... vals){
+ if( !quietMode ){
+ outPrefix();
+ for(Object v : vals) out(v);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static synchronized void outln(Object... vals){
+ if( !quietMode ){
+ out(vals); out("\n");
+ }
+ }
+
+ static volatile int affirmCount = 0;
+ public static synchronized int affirm(Boolean v, String comment){
+ ++affirmCount;
+ if( false ) assert( v /* prefer assert over exception if it's enabled because
+ the JNI layer sometimes has to suppress exceptions,
+ so they might be squelched on their way back to the
+ top. */);
+ if( !v ) throw new RuntimeException(comment);
+ return affirmCount;
+ }
+
+ public static void affirm(Boolean v){
+ affirm(v, "Affirmation failed.");
+ }
+
+
+ public static void execSql(Sqlite db, String[] sql){
+ execSql(db, String.join("", sql));
+ }
+
+ public static int execSql(Sqlite dbw, boolean throwOnError, String sql){
+ final sqlite3 db = dbw.nativeHandle();
+ OutputPointer.Int32 oTail = new OutputPointer.Int32();
+ final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8);
+ int pos = 0, n = 1;
+ byte[] sqlChunk = sqlUtf8;
+ int rc = 0;
+ sqlite3_stmt stmt = null;
+ final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
+ while(pos < sqlChunk.length){
+ if(pos > 0){
+ sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
+ sqlChunk.length);
+ }
+ if( 0==sqlChunk.length ) break;
+ rc = CApi.sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
+ if(throwOnError) affirm(0 == rc);
+ else if( 0!=rc ) break;
+ pos = oTail.value;
+ stmt = outStmt.take();
+ if( null == stmt ){
+ // empty statement was parsed.
+ continue;
+ }
+ affirm(0 != stmt.getNativePointer());
+ while( CApi.SQLITE_ROW == (rc = CApi.sqlite3_step(stmt)) ){
+ }
+ CApi.sqlite3_finalize(stmt);
+ affirm(0 == stmt.getNativePointer());
+ if(0!=rc && CApi.SQLITE_ROW!=rc && CApi.SQLITE_DONE!=rc){
+ break;
+ }
+ }
+ CApi.sqlite3_finalize(stmt);
+ if(CApi.SQLITE_ROW==rc || CApi.SQLITE_DONE==rc) rc = 0;
+ if( 0!=rc && throwOnError){
+ throw new SqliteException(db);
+ }
+ return rc;
+ }
+
+ static void execSql(Sqlite db, String sql){
+ execSql(db, true, sql);
+ }
+
+ @SingleThreadOnly /* because it's thread-agnostic */
+ private void test1(){
+ affirm(CApi.sqlite3_libversion_number() == CApi.SQLITE_VERSION_NUMBER);
+ }
+
+ /* Copy/paste/rename this to add new tests. */
+ private void _testTemplate(){
+ //final sqlite3 db = createNewDb();
+ //sqlite3_stmt stmt = prepare(db,"SELECT 1");
+ //sqlite3_finalize(stmt);
+ //sqlite3_close_v2(db);
+ }
+
+ private void nap() throws InterruptedException {
+ if( takeNaps ){
+ Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 17), 0);
+ }
+ }
+
+ Sqlite openDb(String name){
+ final Sqlite db = Sqlite.open(name, CApi.SQLITE_OPEN_READWRITE|
+ CApi.SQLITE_OPEN_CREATE|
+ CApi.SQLITE_OPEN_EXRESCODE);
+ ++metrics.dbOpen;
+ return db;
+ }
+
+ Sqlite openDb(){ return openDb(":memory:"); }
+
+ void testOpenDb1(){
+ Sqlite db = openDb();
+ affirm( 0!=db.nativeHandle().getNativePointer() );
+ db.close();
+ affirm( null==db.nativeHandle() );
+
+ SqliteException ex = null;
+ try {
+ db = openDb("/no/such/dir/.../probably");
+ }catch(SqliteException e){
+ ex = e;
+ }
+ affirm( ex!=null );
+ affirm( ex.errcode() != 0 );
+ affirm( ex.extendedErrcode() != 0 );
+ affirm( ex.errorOffset() < 0 );
+ // there's no reliable way to predict what ex.systemErrno() might be
+ }
+
+ void testPrepare1(){
+ try (Sqlite db = openDb()) {
+ Sqlite.Stmt stmt = db.prepare("SELECT 1");
+ affirm( null!=stmt.nativeHandle() );
+ affirm( CApi.SQLITE_ROW == stmt.step() );
+ affirm( CApi.SQLITE_DONE == stmt.step() );
+ stmt.reset();
+ affirm( CApi.SQLITE_ROW == stmt.step() );
+ affirm( CApi.SQLITE_DONE == stmt.step() );
+ affirm( 0 == stmt.finalizeStmt() );
+ affirm( null==stmt.nativeHandle() );
+
+ stmt = db.prepare("SELECT 1");
+ affirm( CApi.SQLITE_ROW == stmt.step() );
+ affirm( 0 == stmt.finalizeStmt() )
+ /* getting a non-0 out of sqlite3_finalize() is tricky */;
+ affirm( null==stmt.nativeHandle() );
+ }
+ }
+
+ void testUdfScalar(){
+ final ValueHolder xDestroyCalled = new ValueHolder<>(0);
+ try (Sqlite db = openDb()) {
+ execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)");
+ final ValueHolder vh = new ValueHolder<>(0);
+ final ScalarFunction f = new ScalarFunction(){
+ public void xFunc(SqlFunction.Arguments args){
+ for( SqlFunction.Arguments.Arg arg : args ){
+ vh.value += arg.getInt();
+ }
+ }
+ public void xDestroy(){
+ ++xDestroyCalled.value;
+ }
+ };
+ db.createFunction("myfunc", -1, f);
+ execSql(db, "select myfunc(1,2,3)");
+ affirm( 6 == vh.value );
+ vh.value = 0;
+ execSql(db, "select myfunc(-1,-2,-3)");
+ affirm( -6 == vh.value );
+ affirm( 0 == xDestroyCalled.value );
+ }
+ affirm( 1 == xDestroyCalled.value );
+ }
+
+ void testUdfAggregate(){
+ final ValueHolder xDestroyCalled = new ValueHolder<>(0);
+ final ValueHolder vh = new ValueHolder<>(0);
+ try (Sqlite db = openDb()) {
+ execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)");
+ final AggregateFunction f = new AggregateFunction(){
+ public void xStep(SqlFunction.Arguments args){
+ final ValueHolder agg = this.getAggregateState(args, 0);
+ for( SqlFunction.Arguments.Arg arg : args ){
+ agg.value += arg.getInt();
+ }
+ }
+ public void xFinal(SqlFunction.Arguments args){
+ final Integer v = this.takeAggregateState(args);
+ if( null==v ) args.resultNull();
+ else args.resultInt(v);
+ vh.value = v;
+ }
+ public void xDestroy(){
+ ++xDestroyCalled.value;
+ }
+ };
+ db.createFunction("myagg", -1, f);
+ execSql(db, "select myagg(a) from t");
+ affirm( 6 == vh.value );
+ affirm( 0 == xDestroyCalled.value );
+ }
+ affirm( 1 == xDestroyCalled.value );
+ }
+
+ private void runTests(boolean fromThread) throws Exception {
+ List mlist = testMethods;
+ affirm( null!=mlist );
+ if( shuffle ){
+ mlist = new ArrayList<>( testMethods.subList(0, testMethods.size()) );
+ java.util.Collections.shuffle(mlist);
+ }
+ if( listRunTests ){
+ synchronized(this.getClass()){
+ if( !fromThread ){
+ out("Initial test"," list: ");
+ for(java.lang.reflect.Method m : testMethods){
+ out(m.getName()+" ");
+ }
+ outln();
+ outln("(That list excludes some which are hard-coded to run.)");
+ }
+ out("Running"," tests: ");
+ for(java.lang.reflect.Method m : mlist){
+ out(m.getName()+" ");
+ }
+ outln();
+ }
+ }
+ for(java.lang.reflect.Method m : mlist){
+ nap();
+ try{
+ m.invoke(this);
+ }catch(java.lang.reflect.InvocationTargetException e){
+ outln("FAILURE: ",m.getName(),"(): ", e.getCause());
+ throw e;
+ }
+ }
+ synchronized( this.getClass() ){
+ ++nTestRuns;
+ }
+ }
+
+ public void run() {
+ try {
+ runTests(0!=this.tId);
+ }catch(Exception e){
+ synchronized( listErrors ){
+ listErrors.add(e);
+ }
+ }finally{
+ affirm( CApi.sqlite3_java_uncache_thread() );
+ affirm( !CApi.sqlite3_java_uncache_thread() );
+ }
+ }
+
+ /**
+ Runs the basic sqlite3 JNI binding sanity-check suite.
+
+ CLI flags:
+
+ -q|-quiet: disables most test output.
+
+ -t|-thread N: runs the tests in N threads
+ concurrently. Default=1.
+
+ -r|-repeat N: repeats the tests in a loop N times, each one
+ consisting of the -thread value's threads.
+
+ -shuffle: randomizes the order of most of the test functions.
+
+ -naps: sleep small random intervals between tests in order to add
+ some chaos for cross-thread contention.
+
+ -list-tests: outputs the list of tests being run, minus some
+ which are hard-coded. This is noisy in multi-threaded mode.
+
+ -fail: forces an exception to be thrown during the test run. Use
+ with -shuffle to make its appearance unpredictable.
+
+ -v: emit some developer-mode info at the end.
+ */
+ public static void main(String[] args) throws Exception {
+ Integer nThread = 1;
+ boolean doSomethingForDev = false;
+ Integer nRepeat = 1;
+ boolean forceFail = false;
+ boolean sqlLog = false;
+ boolean configLog = false;
+ boolean squelchTestOutput = false;
+ for( int i = 0; i < args.length; ){
+ String arg = args[i++];
+ if(arg.startsWith("-")){
+ arg = arg.replaceFirst("-+","");
+ if(arg.equals("v")){
+ doSomethingForDev = true;
+ //listBoundMethods();
+ }else if(arg.equals("t") || arg.equals("thread")){
+ nThread = Integer.parseInt(args[i++]);
+ }else if(arg.equals("r") || arg.equals("repeat")){
+ nRepeat = Integer.parseInt(args[i++]);
+ }else if(arg.equals("shuffle")){
+ shuffle = true;
+ }else if(arg.equals("list-tests")){
+ listRunTests = true;
+ }else if(arg.equals("fail")){
+ forceFail = true;
+ }else if(arg.equals("sqllog")){
+ sqlLog = true;
+ }else if(arg.equals("configlog")){
+ configLog = true;
+ }else if(arg.equals("naps")){
+ takeNaps = true;
+ }else if(arg.equals("q") || arg.equals("quiet")){
+ squelchTestOutput = true;
+ }else{
+ throw new IllegalArgumentException("Unhandled flag:"+arg);
+ }
+ }
+ }
+
+ if( sqlLog ){
+ if( CApi.sqlite3_compileoption_used("ENABLE_SQLLOG") ){
+ final ConfigSqllogCallback log = new ConfigSqllogCallback() {
+ @Override public void call(sqlite3 db, String msg, int op){
+ switch(op){
+ case 0: outln("Opening db: ",db); break;
+ case 1: outln("SQL ",db,": ",msg); break;
+ case 2: outln("Closing db: ",db); break;
+ }
+ }
+ };
+ int rc = CApi.sqlite3_config( log );
+ affirm( 0==rc );
+ rc = CApi.sqlite3_config( (ConfigSqllogCallback)null );
+ affirm( 0==rc );
+ rc = CApi.sqlite3_config( log );
+ affirm( 0==rc );
+ }else{
+ outln("WARNING: -sqllog is not active because library was built ",
+ "without SQLITE_ENABLE_SQLLOG.");
+ }
+ }
+ if( configLog ){
+ final ConfigLogCallback log = new ConfigLogCallback() {
+ @Override public void call(int code, String msg){
+ outln("ConfigLogCallback: ",ResultCode.getEntryForInt(code),": ", msg);
+ };
+ };
+ int rc = CApi.sqlite3_config( log );
+ affirm( 0==rc );
+ rc = CApi.sqlite3_config( (ConfigLogCallback)null );
+ affirm( 0==rc );
+ rc = CApi.sqlite3_config( log );
+ affirm( 0==rc );
+ }
+
+ quietMode = squelchTestOutput;
+ outln("If you just saw warning messages regarding CallStaticObjectMethod, ",
+ "you are very likely seeing the side effects of a known openjdk8 ",
+ "bug. It is unsightly but does not affect the library.");
+
+ {
+ // Build list of tests to run from the methods named test*().
+ testMethods = new ArrayList<>();
+ int nSkipped = 0;
+ for(final java.lang.reflect.Method m : Tester2.class.getDeclaredMethods()){
+ final String name = m.getName();
+ if( name.equals("testFail") ){
+ if( forceFail ){
+ testMethods.add(m);
+ }
+ }else if( !m.isAnnotationPresent( ManualTest.class ) ){
+ if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){
+ if( 0==nSkipped++ ){
+ out("Skipping tests in multi-thread mode:");
+ }
+ out(" "+name+"()");
+ }else if( name.startsWith("test") ){
+ testMethods.add(m);
+ }
+ }
+ }
+ if( nSkipped>0 ) out("\n");
+ }
+
+ final long timeStart = System.currentTimeMillis();
+ int nLoop = 0;
+ switch( CApi.sqlite3_threadsafe() ){ /* Sanity checking */
+ case 0:
+ affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SINGLETHREAD ),
+ "Could not switch to single-thread mode." );
+ affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_MULTITHREAD ),
+ "Could switch to multithread mode." );
+ affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SERIALIZED ),
+ "Could not switch to serialized threading mode." );
+ outln("This is a single-threaded build. Not using threads.");
+ nThread = 1;
+ break;
+ case 1:
+ case 2:
+ affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SINGLETHREAD ),
+ "Could not switch to single-thread mode." );
+ affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_MULTITHREAD ),
+ "Could not switch to multithread mode." );
+ affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SERIALIZED ),
+ "Could not switch to serialized threading mode." );
+ break;
+ default:
+ affirm( false, "Unhandled SQLITE_THREADSAFE value." );
+ }
+ outln("libversion_number: ",
+ CApi.sqlite3_libversion_number(),"\n",
+ CApi.sqlite3_libversion(),"\n",CApi.SQLITE_SOURCE_ID,"\n",
+ "SQLITE_THREADSAFE=",CApi.sqlite3_threadsafe());
+ final boolean showLoopCount = (nRepeat>1 && nThread>1);
+ if( showLoopCount ){
+ outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
+ }
+ if( takeNaps ) outln("Napping between tests is enabled.");
+ for( int n = 0; n < nRepeat; ++n ){
+ ++nLoop;
+ if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop);
+ if( nThread<=1 ){
+ new Tester2(0).runTests(false);
+ continue;
+ }
+ Tester2.mtMode = true;
+ final ExecutorService ex = Executors.newFixedThreadPool( nThread );
+ for( int i = 0; i < nThread; ++i ){
+ ex.submit( new Tester2(i), i );
+ }
+ ex.shutdown();
+ try{
+ ex.awaitTermination(nThread*200, java.util.concurrent.TimeUnit.MILLISECONDS);
+ ex.shutdownNow();
+ }catch (InterruptedException ie){
+ ex.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+ if( !listErrors.isEmpty() ){
+ quietMode = false;
+ outln("TEST ERRORS:");
+ Exception err = null;
+ for( Exception e : listErrors ){
+ e.printStackTrace();
+ if( null==err ) err = e;
+ }
+ if( null!=err ) throw err;
+ }
+ }
+ if( showLoopCount ) outln();
+ quietMode = false;
+
+ final long timeEnd = System.currentTimeMillis();
+ outln("Tests done. Metrics across ",nTestRuns," total iteration(s):");
+ outln("\tAssertions checked: ",affirmCount);
+ outln("\tDatabases opened: ",metrics.dbOpen);
+ if( doSomethingForDev ){
+ CApi.sqlite3_jni_internal_details();
+ }
+ affirm( 0==CApi.sqlite3_release_memory(1) );
+ CApi.sqlite3_shutdown();
+ int nMethods = 0;
+ int nNatives = 0;
+ int nCanonical = 0;
+ final java.lang.reflect.Method[] declaredMethods =
+ CApi.class.getDeclaredMethods();
+ for(java.lang.reflect.Method m : declaredMethods){
+ final int mod = m.getModifiers();
+ if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){
+ final String name = m.getName();
+ if(name.startsWith("sqlite3_")){
+ ++nMethods;
+ if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){
+ ++nNatives;
+ }
+ }
+ }
+ }
+ outln("\tCApi.sqlite3_*() methods: "+
+ nMethods+" total, with "+
+ nNatives+" native, "+
+ (nMethods - nNatives)+" Java"
+ );
+ outln("\tTotal test time = "
+ +(timeEnd - timeStart)+"ms");
+ }
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java
--- sqlite3-3.42.0/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+** 2023-10-16
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains a set of tests for the sqlite3 JNI bindings.
+*/
+package org.sqlite.jni.wrapper1;
+
+/**
+ A helper class which simply holds a single value. Its primary use
+ is for communicating values out of anonymous classes, as doing so
+ requires a "final" reference.
+*/
+public class ValueHolder {
+ public T value;
+ public ValueHolder(){}
+ public ValueHolder(T v){value = v;}
+}
diff -Nru sqlite3-3.42.0/ext/jni/src/tests/000-000-sanity.test sqlite3-3.44.0-0/ext/jni/src/tests/000-000-sanity.test
--- sqlite3-3.42.0/ext/jni/src/tests/000-000-sanity.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/tests/000-000-sanity.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,53 @@
+/*
+** This is a comment. There are many like it but this one is mine.
+**
+** SCRIPT_MODULE_NAME: sanity-check
+** xMIXED_MODULE_NAME: mixed-module
+** xMODULE_NAME: module-name
+** xREQUIRED_PROPERTIES: small fast reliable
+** xREQUIRED_PROPERTIES: RECURSIVE_TRIGGERS
+** xREQUIRED_PROPERTIES: TEMPSTORE_FILE TEMPSTORE_MEM
+** xREQUIRED_PROPERTIES: AUTOVACUUM INCRVACUUM
+**
+*/
+--print starting up 😃
+--close all
+--oom
+--db 0
+--new my.db
+--null zilch
+--testcase 1.0
+SELECT 1, null;
+--result 1 zilch
+--glob *zil*
+--notglob *ZIL*
+SELECT 1, 2;
+intentional error;
+--run
+--testcase json-1
+SELECT json_array(1,2,3)
+--json [1,2,3]
+--testcase tableresult-1
+ select 1, 'a';
+ select 2, 'b';
+--tableresult
+ # [a-z]
+ 2 b
+--end
+--testcase json-block-1
+ select json_array(1,2,3);
+ select json_object('a',1,'b',2);
+--json-block
+ [1,2,3]
+ {"a":1,"b":2}
+--end
+--testcase col-names-on
+--column-names 1
+ select 1 as 'a', 2 as 'b';
+--result a 1 b 2
+--testcase col-names-off
+--column-names 0
+ select 1 as 'a', 2 as 'b';
+--result 1 2
+--close
+--print reached the end 😃
diff -Nru sqlite3-3.42.0/ext/jni/src/tests/000-001-ignored.test sqlite3-3.44.0-0/ext/jni/src/tests/000-001-ignored.test
--- sqlite3-3.42.0/ext/jni/src/tests/000-001-ignored.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/tests/000-001-ignored.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,9 @@
+/*
+** This script must be marked as ignored because it contains
+** content which triggers that condition.
+**
+** SCRIPT_MODULE_NAME: ignored
+**
+*/
+
+|
diff -Nru sqlite3-3.42.0/ext/jni/src/tests/900-001-fts.test sqlite3-3.44.0-0/ext/jni/src/tests/900-001-fts.test
--- sqlite3-3.42.0/ext/jni/src/tests/900-001-fts.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/jni/src/tests/900-001-fts.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,12 @@
+/*
+** SCRIPT_MODULE_NAME: fts5-sanity-checks
+** xREQUIRED_PROPERTIES: FTS5
+**
+*/
+
+--testcase 1.0
+CREATE VIRTUAL TABLE email USING fts5(sender, title, body);
+insert into email values('fred','Help!','Dear Sir...');
+insert into email values('barney','Assistance','Dear Madam...');
+select * from email where email match 'assistance';
+--result barney Assistance {Dear Madam...}
diff -Nru sqlite3-3.42.0/ext/lsm1/lsm_vtab.c sqlite3-3.44.0-0/ext/lsm1/lsm_vtab.c
--- sqlite3-3.42.0/ext/lsm1/lsm_vtab.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/lsm1/lsm_vtab.c 2023-11-04 14:23:58.000000000 +0000
@@ -1061,6 +1061,11 @@
lsm1Rollback, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
diff -Nru sqlite3-3.42.0/ext/misc/amatch.c sqlite3-3.44.0-0/ext/misc/amatch.c
--- sqlite3-3.42.0/ext/misc/amatch.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/amatch.c 2023-11-04 14:23:58.000000000 +0000
@@ -1475,7 +1475,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.42.0/ext/misc/btreeinfo.c sqlite3-3.44.0-0/ext/misc/btreeinfo.c
--- sqlite3-3.42.0/ext/misc/btreeinfo.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/btreeinfo.c 2023-11-04 14:23:58.000000000 +0000
@@ -411,7 +411,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
return sqlite3_create_module(db, "sqlite_btreeinfo", &binfo_module, 0);
}
diff -Nru sqlite3-3.42.0/ext/misc/carray.c sqlite3-3.44.0-0/ext/misc/carray.c
--- sqlite3-3.42.0/ext/misc/carray.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/carray.c 2023-11-04 14:23:58.000000000 +0000
@@ -409,6 +409,11 @@
0, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadow */
+ 0 /* xIntegrity */
};
/*
diff -Nru sqlite3-3.42.0/ext/misc/closure.c sqlite3-3.44.0-0/ext/misc/closure.c
--- sqlite3-3.42.0/ext/misc/closure.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/closure.c 2023-11-04 14:23:58.000000000 +0000
@@ -939,7 +939,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.42.0/ext/misc/completion.c sqlite3-3.44.0-0/ext/misc/completion.c
--- sqlite3-3.42.0/ext/misc/completion.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/completion.c 2023-11-04 14:23:58.000000000 +0000
@@ -470,7 +470,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.42.0/ext/misc/csv.c sqlite3-3.44.0-0/ext/misc/csv.c
--- sqlite3-3.42.0/ext/misc/csv.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/csv.c 2023-11-04 14:23:58.000000000 +0000
@@ -897,6 +897,11 @@
0, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
#ifdef SQLITE_TEST
@@ -929,6 +934,11 @@
0, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_TEST */
diff -Nru sqlite3-3.42.0/ext/misc/decimal.c sqlite3-3.44.0-0/ext/misc/decimal.c
--- sqlite3-3.42.0/ext/misc/decimal.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/decimal.c 2023-11-04 14:23:58.000000000 +0000
@@ -58,41 +58,24 @@
}
/*
-** Allocate a new Decimal object. Initialize it to the number given
-** by the input string.
+** Allocate a new Decimal object initialized to the text in zIn[].
+** Return NULL if any kind of error occurs.
*/
-static Decimal *decimal_new(
- sqlite3_context *pCtx,
- sqlite3_value *pIn,
- int nAlt,
- const unsigned char *zAlt
-){
- Decimal *p;
- int n, i;
- const unsigned char *zIn;
+static Decimal *decimalNewFromText(const char *zIn, int n){
+ Decimal *p = 0;
+ int i;
int iExp = 0;
+
p = sqlite3_malloc( sizeof(*p) );
- if( p==0 ) goto new_no_mem;
+ if( p==0 ) goto new_from_text_failed;
p->sign = 0;
p->oom = 0;
p->isInit = 1;
p->isNull = 0;
p->nDigit = 0;
p->nFrac = 0;
- if( zAlt ){
- n = nAlt,
- zIn = zAlt;
- }else{
- if( sqlite3_value_type(pIn)==SQLITE_NULL ){
- p->a = 0;
- p->isNull = 1;
- return p;
- }
- n = sqlite3_value_bytes(pIn);
- zIn = sqlite3_value_text(pIn);
- }
p->a = sqlite3_malloc64( n+1 );
- if( p->a==0 ) goto new_no_mem;
+ if( p->a==0 ) goto new_from_text_failed;
for(i=0; isspace(zIn[i]); i++){}
if( zIn[i]=='-' ){
p->sign = 1;
@@ -143,7 +126,7 @@
}
if( iExp>0 ){
p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 );
- if( p->a==0 ) goto new_no_mem;
+ if( p->a==0 ) goto new_from_text_failed;
memset(p->a+p->nDigit, 0, iExp);
p->nDigit += iExp;
}
@@ -162,7 +145,7 @@
}
if( iExp>0 ){
p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 );
- if( p->a==0 ) goto new_no_mem;
+ if( p->a==0 ) goto new_from_text_failed;
memmove(p->a+iExp, p->a, p->nDigit);
memset(p->a, 0, iExp);
p->nDigit += iExp;
@@ -171,7 +154,76 @@
}
return p;
-new_no_mem:
+new_from_text_failed:
+ if( p ){
+ if( p->a ) sqlite3_free(p->a);
+ sqlite3_free(p);
+ }
+ return 0;
+}
+
+/* Forward reference */
+static Decimal *decimalFromDouble(double);
+
+/*
+** Allocate a new Decimal object from an sqlite3_value. Return a pointer
+** to the new object, or NULL if there is an error. If the pCtx argument
+** is not NULL, then errors are reported on it as well.
+**
+** If the pIn argument is SQLITE_TEXT or SQLITE_INTEGER, it is converted
+** directly into a Decimal. For SQLITE_FLOAT or for SQLITE_BLOB of length
+** 8 bytes, the resulting double value is expanded into its decimal equivalent.
+** If pIn is NULL or if it is a BLOB that is not exactly 8 bytes in length,
+** then NULL is returned.
+*/
+static Decimal *decimal_new(
+ sqlite3_context *pCtx, /* Report error here, if not null */
+ sqlite3_value *pIn, /* Construct the decimal object from this */
+ int bTextOnly /* Always interpret pIn as text if true */
+){
+ Decimal *p = 0;
+ int eType = sqlite3_value_type(pIn);
+ if( bTextOnly && (eType==SQLITE_FLOAT || eType==SQLITE_BLOB) ){
+ eType = SQLITE_TEXT;
+ }
+ switch( eType ){
+ case SQLITE_TEXT:
+ case SQLITE_INTEGER: {
+ const char *zIn = (const char*)sqlite3_value_text(pIn);
+ int n = sqlite3_value_bytes(pIn);
+ p = decimalNewFromText(zIn, n);
+ if( p==0 ) goto new_failed;
+ break;
+ }
+
+ case SQLITE_FLOAT: {
+ p = decimalFromDouble(sqlite3_value_double(pIn));
+ break;
+ }
+
+ case SQLITE_BLOB: {
+ const unsigned char *x;
+ unsigned int i;
+ sqlite3_uint64 v = 0;
+ double r;
+
+ if( sqlite3_value_bytes(pIn)!=sizeof(r) ) break;
+ x = sqlite3_value_blob(pIn);
+ for(i=0; ioom ){
+ sqlite3_result_error_nomem(pCtx);
+ return;
+ }
+ if( p->isNull ){
+ sqlite3_result_null(pCtx);
+ return;
+ }
+ for(nDigit=p->nDigit; nDigit>0 && p->a[nDigit-1]==0; nDigit--){}
+ for(nZero=0; nZeroa[nZero]==0; nZero++){}
+ nFrac = p->nFrac + (nDigit - p->nDigit);
+ nDigit -= nZero;
+ z = sqlite3_malloc( nDigit+20 );
+ if( z==0 ){
+ sqlite3_result_error_nomem(pCtx);
+ return;
+ }
+ if( nDigit==0 ){
+ zero = 0;
+ a = &zero;
+ nDigit = 1;
+ nFrac = 0;
+ }else{
+ a = &p->a[nZero];
+ }
+ if( p->sign && nDigit>0 ){
+ z[0] = '-';
+ }else{
+ z[0] = '+';
+ }
+ z[1] = a[0]+'0';
+ z[2] = '.';
+ if( nDigit==1 ){
+ z[3] = '0';
+ i = 4;
+ }else{
+ for(i=1; iisNull ) goto cmp_done;
- pB = decimal_new(context, argv[1], 0, 0);
+ pB = decimal_new(context, argv[1], 1);
if( pB==0 || pB->isNull ) goto cmp_done;
rc = decimal_cmp(pA, pB);
if( rc<0 ) rc = -1;
@@ -338,7 +435,7 @@
}
/*
-** Add the value pB into pA.
+** Add the value pB into pA. A := A + B.
**
** Both pA and pB might become denormalized by this routine.
*/
@@ -408,6 +505,172 @@
}
/*
+** Multiply A by B. A := A * B
+**
+** All significant digits after the decimal point are retained.
+** Trailing zeros after the decimal point are omitted as long as
+** the number of digits after the decimal point is no less than
+** either the number of digits in either input.
+*/
+static void decimalMul(Decimal *pA, Decimal *pB){
+ signed char *acc = 0;
+ int i, j, k;
+ int minFrac;
+
+ if( pA==0 || pA->oom || pA->isNull
+ || pB==0 || pB->oom || pB->isNull
+ ){
+ goto mul_end;
+ }
+ acc = sqlite3_malloc64( pA->nDigit + pB->nDigit + 2 );
+ if( acc==0 ){
+ pA->oom = 1;
+ goto mul_end;
+ }
+ memset(acc, 0, pA->nDigit + pB->nDigit + 2);
+ minFrac = pA->nFrac;
+ if( pB->nFracnFrac;
+ for(i=pA->nDigit-1; i>=0; i--){
+ signed char f = pA->a[i];
+ int carry = 0, x;
+ for(j=pB->nDigit-1, k=i+j+3; j>=0; j--, k--){
+ x = acc[k] + f*pB->a[j] + carry;
+ acc[k] = x%10;
+ carry = x/10;
+ }
+ x = acc[k] + carry;
+ acc[k] = x%10;
+ acc[k-1] += x/10;
+ }
+ sqlite3_free(pA->a);
+ pA->a = acc;
+ acc = 0;
+ pA->nDigit += pB->nDigit + 2;
+ pA->nFrac += pB->nFrac;
+ pA->sign ^= pB->sign;
+ while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){
+ pA->nFrac--;
+ pA->nDigit--;
+ }
+
+mul_end:
+ sqlite3_free(acc);
+}
+
+/*
+** Create a new Decimal object that contains an integer power of 2.
+*/
+static Decimal *decimalPow2(int N){
+ Decimal *pA = 0; /* The result to be returned */
+ Decimal *pX = 0; /* Multiplier */
+ if( N<-20000 || N>20000 ) goto pow2_fault;
+ pA = decimalNewFromText("1.0", 3);
+ if( pA==0 || pA->oom ) goto pow2_fault;
+ if( N==0 ) return pA;
+ if( N>0 ){
+ pX = decimalNewFromText("2.0", 3);
+ }else{
+ N = -N;
+ pX = decimalNewFromText("0.5", 3);
+ }
+ if( pX==0 || pX->oom ) goto pow2_fault;
+ while( 1 /* Exit by break */ ){
+ if( N & 1 ){
+ decimalMul(pA, pX);
+ if( pA->oom ) goto pow2_fault;
+ }
+ N >>= 1;
+ if( N==0 ) break;
+ decimalMul(pX, pX);
+ }
+ decimal_free(pX);
+ return pA;
+
+pow2_fault:
+ decimal_free(pA);
+ decimal_free(pX);
+ return 0;
+}
+
+/*
+** Use an IEEE754 binary64 ("double") to generate a new Decimal object.
+*/
+static Decimal *decimalFromDouble(double r){
+ sqlite3_int64 m, a;
+ int e;
+ int isNeg;
+ Decimal *pA;
+ Decimal *pX;
+ char zNum[100];
+ if( r<0.0 ){
+ isNeg = 1;
+ r = -r;
+ }else{
+ isNeg = 0;
+ }
+ memcpy(&a,&r,sizeof(a));
+ if( a==0 ){
+ e = 0;
+ m = 0;
+ }else{
+ e = a>>52;
+ m = a & ((((sqlite3_int64)1)<<52)-1);
+ if( e==0 ){
+ m <<= 1;
+ }else{
+ m |= ((sqlite3_int64)1)<<52;
+ }
+ while( e<1075 && m>0 && (m&1)==0 ){
+ m >>= 1;
+ e++;
+ }
+ if( isNeg ) m = -m;
+ e = e - 1075;
+ if( e>971 ){
+ return 0; /* A NaN or an Infinity */
+ }
+ }
+
+ /* At this point m is the integer significand and e is the exponent */
+ sqlite3_snprintf(sizeof(zNum), zNum, "%lld", m);
+ pA = decimalNewFromText(zNum, (int)strlen(zNum));
+ pX = decimalPow2(e);
+ decimalMul(pA, pX);
+ decimal_free(pX);
+ return pA;
+}
+
+/*
+** SQL Function: decimal(X)
+** OR: decimal_exp(X)
+**
+** Convert input X into decimal and then back into text.
+**
+** If X is originally a float, then a full decimal expansion of that floating
+** point value is done. Or if X is an 8-byte blob, it is interpreted
+** as a float and similarly expanded.
+**
+** The decimal_exp(X) function returns the result in exponential notation.
+** decimal(X) returns a complete decimal, without the e+NNN at the end.
+*/
+static void decimalFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ Decimal *p = decimal_new(context, argv[0], 0);
+ UNUSED_PARAMETER(argc);
+ if( p ){
+ if( sqlite3_user_data(context)!=0 ){
+ decimal_result_sci(context, p);
+ }else{
+ decimal_result(context, p);
+ }
+ decimal_free(p);
+ }
+}
+
+/*
** Compare text in decimal order.
*/
static int decimalCollFunc(
@@ -417,8 +680,8 @@
){
const unsigned char *zA = (const unsigned char*)pKey1;
const unsigned char *zB = (const unsigned char*)pKey2;
- Decimal *pA = decimal_new(0, 0, nKey1, zA);
- Decimal *pB = decimal_new(0, 0, nKey2, zB);
+ Decimal *pA = decimalNewFromText((const char*)zA, nKey1);
+ Decimal *pB = decimalNewFromText((const char*)zB, nKey2);
int rc;
UNUSED_PARAMETER(notUsed);
if( pA==0 || pB==0 ){
@@ -443,8 +706,8 @@
int argc,
sqlite3_value **argv
){
- Decimal *pA = decimal_new(context, argv[0], 0, 0);
- Decimal *pB = decimal_new(context, argv[1], 0, 0);
+ Decimal *pA = decimal_new(context, argv[0], 1);
+ Decimal *pB = decimal_new(context, argv[1], 1);
UNUSED_PARAMETER(argc);
decimal_add(pA, pB);
decimal_result(context, pA);
@@ -456,8 +719,8 @@
int argc,
sqlite3_value **argv
){
- Decimal *pA = decimal_new(context, argv[0], 0, 0);
- Decimal *pB = decimal_new(context, argv[1], 0, 0);
+ Decimal *pA = decimal_new(context, argv[0], 1);
+ Decimal *pB = decimal_new(context, argv[1], 1);
UNUSED_PARAMETER(argc);
if( pB ){
pB->sign = !pB->sign;
@@ -495,7 +758,7 @@
p->nFrac = 0;
}
if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
- pArg = decimal_new(context, argv[0], 0, 0);
+ pArg = decimal_new(context, argv[0], 1);
decimal_add(p, pArg);
decimal_free(pArg);
}
@@ -510,7 +773,7 @@
p = sqlite3_aggregate_context(context, sizeof(*p));
if( p==0 ) return;
if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
- pArg = decimal_new(context, argv[0], 0, 0);
+ pArg = decimal_new(context, argv[0], 1);
if( pArg ) pArg->sign = !pArg->sign;
decimal_add(p, pArg);
decimal_free(pArg);
@@ -531,66 +794,49 @@
** SQL Function: decimal_mul(X, Y)
**
** Return the product of X and Y.
-**
-** All significant digits after the decimal point are retained.
-** Trailing zeros after the decimal point are omitted as long as
-** the number of digits after the decimal point is no less than
-** either the number of digits in either input.
*/
static void decimalMulFunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
- Decimal *pA = decimal_new(context, argv[0], 0, 0);
- Decimal *pB = decimal_new(context, argv[1], 0, 0);
- signed char *acc = 0;
- int i, j, k;
- int minFrac;
+ Decimal *pA = decimal_new(context, argv[0], 1);
+ Decimal *pB = decimal_new(context, argv[1], 1);
UNUSED_PARAMETER(argc);
if( pA==0 || pA->oom || pA->isNull
|| pB==0 || pB->oom || pB->isNull
){
goto mul_end;
}
- acc = sqlite3_malloc64( pA->nDigit + pB->nDigit + 2 );
- if( acc==0 ){
- sqlite3_result_error_nomem(context);
+ decimalMul(pA, pB);
+ if( pA->oom ){
goto mul_end;
}
- memset(acc, 0, pA->nDigit + pB->nDigit + 2);
- minFrac = pA->nFrac;
- if( pB->nFracnFrac;
- for(i=pA->nDigit-1; i>=0; i--){
- signed char f = pA->a[i];
- int carry = 0, x;
- for(j=pB->nDigit-1, k=i+j+3; j>=0; j--, k--){
- x = acc[k] + f*pB->a[j] + carry;
- acc[k] = x%10;
- carry = x/10;
- }
- x = acc[k] + carry;
- acc[k] = x%10;
- acc[k-1] += x/10;
- }
- sqlite3_free(pA->a);
- pA->a = acc;
- acc = 0;
- pA->nDigit += pB->nDigit + 2;
- pA->nFrac += pB->nFrac;
- pA->sign ^= pB->sign;
- while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){
- pA->nFrac--;
- pA->nDigit--;
- }
decimal_result(context, pA);
mul_end:
- sqlite3_free(acc);
decimal_free(pA);
decimal_free(pB);
}
+/*
+** SQL Function: decimal_pow2(N)
+**
+** Return the N-th power of 2. N must be an integer.
+*/
+static void decimalPow2Func(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ UNUSED_PARAMETER(argc);
+ if( sqlite3_value_type(argv[0])==SQLITE_INTEGER ){
+ Decimal *pA = decimalPow2(sqlite3_value_int(argv[0]));
+ decimal_result_sci(context, pA);
+ decimal_free(pA);
+ }
+}
+
#ifdef _WIN32
__declspec(dllexport)
#endif
@@ -603,13 +849,16 @@
static const struct {
const char *zFuncName;
int nArg;
+ int iArg;
void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
} aFunc[] = {
- { "decimal", 1, decimalFunc },
- { "decimal_cmp", 2, decimalCmpFunc },
- { "decimal_add", 2, decimalAddFunc },
- { "decimal_sub", 2, decimalSubFunc },
- { "decimal_mul", 2, decimalMulFunc },
+ { "decimal", 1, 0, decimalFunc },
+ { "decimal_exp", 1, 1, decimalFunc },
+ { "decimal_cmp", 2, 0, decimalCmpFunc },
+ { "decimal_add", 2, 0, decimalAddFunc },
+ { "decimal_sub", 2, 0, decimalSubFunc },
+ { "decimal_mul", 2, 0, decimalMulFunc },
+ { "decimal_pow2", 1, 0, decimalPow2Func },
};
unsigned int i;
(void)pzErrMsg; /* Unused parameter */
@@ -619,7 +868,7 @@
for(i=0; i<(int)(sizeof(aFunc)/sizeof(aFunc[0])) && rc==SQLITE_OK; i++){
rc = sqlite3_create_function(db, aFunc[i].zFuncName, aFunc[i].nArg,
SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC,
- 0, aFunc[i].xFunc, 0, 0);
+ aFunc[i].iArg ? db : 0, aFunc[i].xFunc, 0, 0);
}
if( rc==SQLITE_OK ){
rc = sqlite3_create_window_function(db, "decimal_sum", 1,
diff -Nru sqlite3-3.42.0/ext/misc/explain.c sqlite3-3.44.0-0/ext/misc/explain.c
--- sqlite3-3.42.0/ext/misc/explain.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/explain.c 2023-11-04 14:23:58.000000000 +0000
@@ -293,6 +293,7 @@
0, /* xRelease */
0, /* xRollbackTo */
0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.42.0/ext/misc/fileio.c sqlite3-3.44.0-0/ext/misc/fileio.c
--- sqlite3-3.42.0/ext/misc/fileio.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/fileio.c 2023-11-04 14:23:58.000000000 +0000
@@ -983,6 +983,7 @@
0, /* xRelease */
0, /* xRollbackTo */
0, /* xShadowName */
+ 0 /* xIntegrity */
};
int rc = sqlite3_create_module(db, "fsdir", &fsdirModule, 0);
diff -Nru sqlite3-3.42.0/ext/misc/fossildelta.c sqlite3-3.44.0-0/ext/misc/fossildelta.c
--- sqlite3-3.42.0/ext/misc/fossildelta.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/fossildelta.c 2023-11-04 14:23:58.000000000 +0000
@@ -1058,7 +1058,8 @@
/* xSavepoint */ 0,
/* xRelease */ 0,
/* xRollbackTo */ 0,
- /* xShadowName */ 0
+ /* xShadowName */ 0,
+ /* xIntegrity */ 0
};
diff -Nru sqlite3-3.42.0/ext/misc/fuzzer.c sqlite3-3.44.0-0/ext/misc/fuzzer.c
--- sqlite3-3.42.0/ext/misc/fuzzer.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/fuzzer.c 2023-11-04 14:23:58.000000000 +0000
@@ -1165,6 +1165,11 @@
0, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.42.0/ext/misc/ieee754.c sqlite3-3.44.0-0/ext/misc/ieee754.c
--- sqlite3-3.42.0/ext/misc/ieee754.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/ieee754.c 2023-11-04 14:23:58.000000000 +0000
@@ -256,6 +256,37 @@
}
}
+/*
+** SQL Function: ieee754_inc(r,N)
+**
+** Move the floating point value r by N quantums and return the new
+** values.
+**
+** Behind the scenes: this routine merely casts r into a 64-bit unsigned
+** integer, adds N, then casts the value back into float.
+**
+** Example: To find the smallest positive number:
+**
+** SELECT ieee754_inc(0.0,+1);
+*/
+static void ieee754inc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ double r;
+ sqlite3_int64 N;
+ sqlite3_uint64 m1, m2;
+ double r2;
+ UNUSED_PARAMETER(argc);
+ r = sqlite3_value_double(argv[0]);
+ N = sqlite3_value_int64(argv[1]);
+ memcpy(&m1, &r, 8);
+ m2 = m1 + N;
+ memcpy(&r2, &m2, 8);
+ sqlite3_result_double(context, r2);
+}
+
#ifdef _WIN32
__declspec(dllexport)
@@ -277,7 +308,7 @@
{ "ieee754_exponent", 1, 2, ieee754func },
{ "ieee754_to_blob", 1, 0, ieee754func_to_blob },
{ "ieee754_from_blob", 1, 0, ieee754func_from_blob },
-
+ { "ieee754_inc", 2, 0, ieee754inc },
};
unsigned int i;
int rc = SQLITE_OK;
diff -Nru sqlite3-3.42.0/ext/misc/memstat.c sqlite3-3.44.0-0/ext/misc/memstat.c
--- sqlite3-3.42.0/ext/misc/memstat.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/memstat.c 2023-11-04 14:23:58.000000000 +0000
@@ -396,6 +396,7 @@
0, /* xRelease */
0, /* xRollbackTo */
0, /* xShadowName */
+ 0 /* xIntegrity */
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.42.0/ext/misc/mmapwarm.c sqlite3-3.44.0-0/ext/misc/mmapwarm.c
--- sqlite3-3.42.0/ext/misc/mmapwarm.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/mmapwarm.c 2023-11-04 14:23:58.000000000 +0000
@@ -38,7 +38,7 @@
int rc = SQLITE_OK;
char *zSql = 0;
int pgsz = 0;
- int nTotal = 0;
+ unsigned int nTotal = 0;
if( 0==sqlite3_get_autocommit(db) ) return SQLITE_MISUSE;
@@ -86,8 +86,8 @@
rc = p->xFetch(pFd, pgsz*iPg, pgsz, (void**)&pMap);
if( rc!=SQLITE_OK || pMap==0 ) break;
- nTotal += pMap[0];
- nTotal += pMap[pgsz-1];
+ nTotal += (unsigned int)pMap[0];
+ nTotal += (unsigned int)pMap[pgsz-1];
rc = p->xUnfetch(pFd, pgsz*iPg, (void*)pMap);
if( rc!=SQLITE_OK ) break;
@@ -103,5 +103,6 @@
if( rc==SQLITE_OK ) rc = rc2;
}
+ (void)nTotal;
return rc;
}
diff -Nru sqlite3-3.42.0/ext/misc/pcachetrace.c sqlite3-3.44.0-0/ext/misc/pcachetrace.c
--- sqlite3-3.42.0/ext/misc/pcachetrace.c 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/pcachetrace.c 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,179 @@
+/*
+** 2023-06-21
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file implements an extension that uses the SQLITE_CONFIG_PCACHE2
+** mechanism to add a tracing layer on top of pluggable page cache of
+** SQLite. If this extension is registered prior to sqlite3_initialize(),
+** it will cause all page cache activities to be logged on standard output,
+** or to some other FILE specified by the initializer.
+**
+** This file needs to be compiled into the application that uses it.
+**
+** This extension is used to implement the --pcachetrace option of the
+** command-line shell.
+*/
+#include
+#include
+#include
+
+/* The original page cache routines */
+static sqlite3_pcache_methods2 pcacheBase;
+static FILE *pcachetraceOut;
+
+/* Methods that trace pcache activity */
+static int pcachetraceInit(void *pArg){
+ int nRes;
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xInit(%p)\n", pArg);
+ }
+ nRes = pcacheBase.xInit(pArg);
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xInit(%p) -> %d\n", pArg, nRes);
+ }
+ return nRes;
+}
+static void pcachetraceShutdown(void *pArg){
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xShutdown(%p)\n", pArg);
+ }
+ pcacheBase.xShutdown(pArg);
+}
+static sqlite3_pcache *pcachetraceCreate(int szPage, int szExtra, int bPurge){
+ sqlite3_pcache *pRes;
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xCreate(%d,%d,%d)\n",
+ szPage, szExtra, bPurge);
+ }
+ pRes = pcacheBase.xCreate(szPage, szExtra, bPurge);
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xCreate(%d,%d,%d) -> %p\n",
+ szPage, szExtra, bPurge, pRes);
+ }
+ return pRes;
+}
+static void pcachetraceCachesize(sqlite3_pcache *p, int nCachesize){
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xCachesize(%p, %d)\n", p, nCachesize);
+ }
+ pcacheBase.xCachesize(p, nCachesize);
+}
+static int pcachetracePagecount(sqlite3_pcache *p){
+ int nRes;
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xPagecount(%p)\n", p);
+ }
+ nRes = pcacheBase.xPagecount(p);
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xPagecount(%p) -> %d\n", p, nRes);
+ }
+ return nRes;
+}
+static sqlite3_pcache_page *pcachetraceFetch(
+ sqlite3_pcache *p,
+ unsigned key,
+ int crFg
+){
+ sqlite3_pcache_page *pRes;
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xFetch(%p,%u,%d)\n", p, key, crFg);
+ }
+ pRes = pcacheBase.xFetch(p, key, crFg);
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xFetch(%p,%u,%d) -> %p\n",
+ p, key, crFg, pRes);
+ }
+ return pRes;
+}
+static void pcachetraceUnpin(
+ sqlite3_pcache *p,
+ sqlite3_pcache_page *pPg,
+ int bDiscard
+){
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xUnpin(%p, %p, %d)\n",
+ p, pPg, bDiscard);
+ }
+ pcacheBase.xUnpin(p, pPg, bDiscard);
+}
+static void pcachetraceRekey(
+ sqlite3_pcache *p,
+ sqlite3_pcache_page *pPg,
+ unsigned oldKey,
+ unsigned newKey
+){
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xRekey(%p, %p, %u, %u)\n",
+ p, pPg, oldKey, newKey);
+ }
+ pcacheBase.xRekey(p, pPg, oldKey, newKey);
+}
+static void pcachetraceTruncate(sqlite3_pcache *p, unsigned n){
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xTruncate(%p, %u)\n", p, n);
+ }
+ pcacheBase.xTruncate(p, n);
+}
+static void pcachetraceDestroy(sqlite3_pcache *p){
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xDestroy(%p)\n", p);
+ }
+ pcacheBase.xDestroy(p);
+}
+static void pcachetraceShrink(sqlite3_pcache *p){
+ if( pcachetraceOut ){
+ fprintf(pcachetraceOut, "PCACHETRACE: xShrink(%p)\n", p);
+ }
+ pcacheBase.xShrink(p);
+}
+
+/* The substitute pcache methods */
+static sqlite3_pcache_methods2 ersaztPcacheMethods = {
+ 0,
+ 0,
+ pcachetraceInit,
+ pcachetraceShutdown,
+ pcachetraceCreate,
+ pcachetraceCachesize,
+ pcachetracePagecount,
+ pcachetraceFetch,
+ pcachetraceUnpin,
+ pcachetraceRekey,
+ pcachetraceTruncate,
+ pcachetraceDestroy,
+ pcachetraceShrink
+};
+
+/* Begin tracing memory allocations to out. */
+int sqlite3PcacheTraceActivate(FILE *out){
+ int rc = SQLITE_OK;
+ if( pcacheBase.xFetch==0 ){
+ rc = sqlite3_config(SQLITE_CONFIG_GETPCACHE2, &pcacheBase);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_config(SQLITE_CONFIG_PCACHE2, &ersaztPcacheMethods);
+ }
+ }
+ pcachetraceOut = out;
+ return rc;
+}
+
+/* Deactivate memory tracing */
+int sqlite3PcacheTraceDeactivate(void){
+ int rc = SQLITE_OK;
+ if( pcacheBase.xFetch!=0 ){
+ rc = sqlite3_config(SQLITE_CONFIG_PCACHE2, &pcacheBase);
+ if( rc==SQLITE_OK ){
+ memset(&pcacheBase, 0, sizeof(pcacheBase));
+ }
+ }
+ pcachetraceOut = 0;
+ return rc;
+}
diff -Nru sqlite3-3.42.0/ext/misc/prefixes.c sqlite3-3.44.0-0/ext/misc/prefixes.c
--- sqlite3-3.42.0/ext/misc/prefixes.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/prefixes.c 2023-11-04 14:23:58.000000000 +0000
@@ -248,7 +248,8 @@
/* xSavepoint */ 0,
/* xRelease */ 0,
/* xRollbackTo */ 0,
- /* xShadowName */ 0
+ /* xShadowName */ 0,
+ /* xIntegrity */ 0
};
/*
diff -Nru sqlite3-3.42.0/ext/misc/qpvtab.c sqlite3-3.44.0-0/ext/misc/qpvtab.c
--- sqlite3-3.42.0/ext/misc/qpvtab.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/qpvtab.c 2023-11-04 14:23:58.000000000 +0000
@@ -439,7 +439,8 @@
/* xSavepoint */ 0,
/* xRelease */ 0,
/* xRollbackTo */ 0,
- /* xShadowName */ 0
+ /* xShadowName */ 0,
+ /* xIntegrity */ 0
};
#endif /* SQLITE_OMIT_VIRTUALTABLE */
diff -Nru sqlite3-3.42.0/ext/misc/series.c sqlite3-3.44.0-0/ext/misc/series.c
--- sqlite3-3.42.0/ext/misc/series.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/misc/series.c 2023-11-04 14:23:58.000000000 +0000
@@ -330,6 +330,10 @@
return SQLITE_OK;
}
+#ifndef LARGEST_UINT64
+#define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32))
+#endif
+
/*
** Return the rowid for the current row, logically equivalent to n+1 where
** "n" is the ascending integer in the aforesaid production definition.
@@ -337,7 +341,7 @@
static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
series_cursor *pCur = (series_cursor*)cur;
sqlite3_uint64 n = pCur->ss.uSeqIndexNow;
- *pRowid = (sqlite3_int64)((n<0xffffffffffffffff)? n+1 : 0);
+ *pRowid = (sqlite3_int64)((n
#include
#include
+#include
#include
@@ -2200,7 +2201,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollback */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
int rc = sqlite3_create_module(db, "zipfile" , &zipfileModule, 0);
diff -Nru sqlite3-3.42.0/ext/recover/dbdata.c sqlite3-3.44.0-0/ext/recover/dbdata.c
--- sqlite3-3.42.0/ext/recover/dbdata.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/recover/dbdata.c 2023-11-04 14:23:58.000000000 +0000
@@ -73,13 +73,12 @@
*/
#if !defined(SQLITEINT_H)
-#include "sqlite3ext.h"
+#include "sqlite3.h"
typedef unsigned char u8;
typedef unsigned int u32;
#endif
-SQLITE_EXTENSION_INIT1
#include
#include
@@ -664,8 +663,14 @@
if( pCsr->pHdrPtr>&pCsr->pRec[pCsr->nRec] ){
bNextPage = 1;
}else{
+ int szField = 0;
pCsr->pHdrPtr += dbdataGetVarintU32(pCsr->pHdrPtr, &iType);
- pCsr->pPtr += dbdataValueBytes(iType);
+ szField = dbdataValueBytes(iType);
+ if( (pCsr->nRec - (pCsr->pPtr - pCsr->pRec))pPtr = &pCsr->pRec[pCsr->nRec];
+ }else{
+ pCsr->pPtr += szField;
+ }
}
}
}
@@ -928,7 +933,8 @@
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- 0 /* xShadowName */
+ 0, /* xShadowName */
+ 0 /* xIntegrity */
};
int rc = sqlite3_create_module(db, "sqlite_dbdata", &dbdata_module, 0);
@@ -938,15 +944,11 @@
return rc;
}
-#ifdef _WIN32
-__declspec(dllexport)
-#endif
int sqlite3_dbdata_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
- SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg;
return sqlite3DbdataRegister(db);
}
diff -Nru sqlite3-3.42.0/ext/recover/recovercorrupt2.test sqlite3-3.44.0-0/ext/recover/recovercorrupt2.test
--- sqlite3-3.42.0/ext/recover/recovercorrupt2.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/recover/recovercorrupt2.test 2023-11-04 14:23:58.000000000 +0000
@@ -285,5 +285,244 @@
list [catch { $R finish } msg] $msg
} {0 {}}
+#-------------------------------------------------------------------------
+#
+reset_db
+do_test 6.0 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+| size 8192 pagesize 4096 filename abc.db
+| page 1 offset 0
+| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
+| 16: 10 00 01 01 00 40 20 20 00 00 00 02 00 00 00 02 .....@ ........
+| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................
+| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
+| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 ................
+| 96: 00 2e 6e b8 0d 00 00 00 01 0f dc 00 0f dc 00 00 ..n.............
+| 4048: 00 00 00 00 00 00 00 00 00 00 00 00 22 01 06 17 ................
+| 4064: 11 11 01 31 74 61 62 6c 65 74 31 74 31 02 43 52 ...1tablet1t1.CR
+| 4080: 45 41 54 45 20 54 41 42 4c 45 20 74 31 28 78 29 EATE TABLE t1(x)
+| page 2 offset 4096
+| 0: 0d 00 00 00 01 0f e2 00 0f e2 00 00 00 00 00 00 ................
+| 4064: 00 00 1c 01 02 41 61 62 63 64 65 66 67 68 69 6a .....Aabcdefghij
+| 4080: 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a klmnopqrstuvwxyz
+| end abc.db
+}]} {}
+do_test 6.1 {
+ set R [sqlite3_recover_init db main test.db2]
+ catch { $R run }
+ list [catch { $R finish } msg] $msg
+} {0 {}}
+
+reset_db
+breakpoint
+do_test 6.2 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+| size 8192 pagesize 4096 filename abc.db
+| page 1 offset 0
+| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
+| 16: 10 00 01 01 00 40 20 20 00 00 00 02 00 00 00 02 .....@ ........
+| 32: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 04 ................
+| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
+| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 ................
+| 96: 00 2e 6e b8 0d 00 00 00 01 0f dc 00 0f dc 00 00 ..n.............
+| 4048: 00 00 00 00 00 00 00 00 00 00 00 00 22 01 06 17 ................
+| 4064: 11 11 01 31 74 61 62 6c 65 74 31 74 31 02 43 52 ...1tablet1t1.CR
+| 4080: 45 41 54 45 20 54 41 42 4c 45 20 74 31 28 78 29 EATE TABLE t1(x)
+| page 2 offset 4096
+| 0: 0d 00 00 00 01 0f e2 00 0f e2 00 00 00 00 00 00 ................
+| 4064: 00 00 1c 01 02 8F FF FF FF 7E 65 66 67 68 69 6a .....Aabcdefghij
+| 4080: 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a klmnopqrstuvwxyz
+| end abc.db
+}]} {}
+do_test 6.3 {
+ set R [sqlite3_recover_init db main test.db2]
+ catch { $R run }
+ list [catch { $R finish } msg] $msg
+} {0 {}}
+
+reset_db
+breakpoint
+do_test 7.0 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+| size 4108 pagesize 4096 filename x1.db
+| page 1 offset 0
+| 0: 02 01 00 00 00 00 14 15 40 00 00 00 00 00 00 00 ........@.......
+| 16: 33 3a 6d 65 6d 6f 72 79 3a 02 02 02 02 02 02 02 3:memory:.......
+| 32: 02 02 02 02 02 02 12 02 02 02 63 6f 6c 6f 72 20 ..........color
+| 48: 73 70 61 63 00 f3 a0 81 a1 00 00 a0 02 02 02 02 spac............
+| 64: 69 95 73 6f 36 00 ff 0d 00 97 8c 90 3f 0a 70 02 i.so6.......?.p.
+| 80: 02 02 02 02 02 02 02 02 02 02 02 02 02 01 00 00 ................
+| 96: 06 02 02 02 02 5f 02 02 02 2c 02 02 02 02 02 02 ....._...,......
+| 112: 02 02 02 02 02 02 02 02 02 12 02 02 02 63 6f 6c .............col
+| 128: 6f 72 20 73 70 61 63 00 f3 a0 81 a1 00 00 a0 02 or spac.........
+| 144: 02 02 02 69 95 73 6f 36 00 ff 0d 00 97 8c 90 3f ...i.so6.......?
+| 160: 0a 70 02 02 02 02 02 02 02 02 02 02 02 02 02 02 .p..............
+| 176: 01 00 00 06 02 02 02 02 5f 02 02 02 2c 02 02 00 ........_...,...
+| 192: 00 01 00 01 00 00 00 01 00 02 fe 00 00 03 00 01 ................
+| 208: 00 00 00 01 c5 04 00 00 00 01 00 01 00 00 00 01 ................
+| 224: 00 fa 02 00 00 00 03 00 01 00 00 00 81 00 04 00 ................
+| 240: 00 00 01 00 01 00 00 00 01 00 02 00 fe 00 03 00 ................
+| 256: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
+| 272: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................
+| 288: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
+| 304: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
+| 320: 01 00 02 00 00 00 03 00 01 00 00 00 40 00 84 00 ............@...
+| 336: 84 00 84 00 01 00 00 00 09 00 06 00 f5 00 01 00 ................
+| 352: 08 01 03 00 03 00 62 00 62 00 23 00 01 00 62 00 ......b.b.#...b.
+| 368: 04 00 1e 00 62 00 62 00 62 00 01 00 00 00 0a 00 ....b.b.b.......
+| 384: 01 00 03 00 01 00 03 00 04 00 02 00 01 00 01 00 ................
+| 400: 08 00 01 00 31 c6 00 03 00 0c 00 12 00 18 00 02 ....1...........
+| 416: 00 05 00 08 00 02 00 06 00 08 00 02 00 07 00 08 ................
+| 432: 00 02 00 01 00 01 00 08 00 01 00 0c 00 03 00 16 ................
+| 448: 00 1c 00 22 00 01 00 03 00 05 00 06 00 07 00 02 ................
+| 464: 00 05 00 09 00 02 00 06 00 09 00 02 00 07 00 09 ................
+| 480: 00 00 00 00 01 00 05 00 00 00 01 00 01 00 00 00 ................
+| 496: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................
+| 512: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
+| 528: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
+| 544: 01 00 02 00 00 f6 03 00 00 02 00 00 01 00 04 00 ................
+| 560: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
+| 576: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
+| 592: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................
+| 608: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
+| 624: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
+| 640: 01 3d 02 00 00 00 03 00 06 00 00 00 01 00 01 00 .=..............
+| 656: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
+| 672: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
+| 688: 01 00 02 00 00 00 55 52 4c 52 65 71 75 65 73 74 ......URLRequest
+| 704: 43 6f 6e 00 00 00 01 01 0e d4 00 04 00 00 00 01 Con.............
+| 720: 0e f8 00 04 00 00 00 01 0f 1c 00 04 00 00 00 01 ................
+| 736: 0f 00 00 01 00 00 00 01 0f 86 00 01 00 00 00 01 ................
+| 752: 0f 84 00 01 00 00 00 01 00 00 01 0f c0 00 01 00 ................
+| 768: 00 00 01 0f e8 00 d6 0f 00 01 6f 00 02 0f d6 00 ..........o.....
+| 784: 02 34 03 03 03 00 01 00 00 00 01 00 05 00 00 00 .4..............
+| 800: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................
+| 816: 00 00 01 00 04 00 00 00 01 00 01 00 00 00 01 00 ................
+| 832: 02 00 00 00 03 00 01 00 10 00 01 00 04 00 00 00 ................
+| 848: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 00 02 ................
+| 864: 00 00 01 40 04 00 00 03 01 00 01 00 00 00 01 00 ...@............
+| 880: 02 00 00 00 03 00 01 00 00 00 00 00 01 0e f8 00 ................
+| 896: 04 77 4f 46 32 73 40 23 70 00 00 00 70 00 1f 00 .wOF2s@#p...p...
+| 912: 00 00 d8 00 00 00 ff ff 00 00 00 00 43 00 00 00 ............C...
+| 928: 00 00 ff ff ff ff ff 00 00 a8 00 00 0c 00 00 00 ................
+| 1024: 00 00 00 00 00 00 00 00 00 00 10 22 00 22 0f 00 ................
+| 1040: 00 00 00 00 00 00 10 22 00 00 70 00 1f 00 00 0f ..........p.....
+| 1056: d8 00 00 00 00 00 00 00 00 00 03 00 00 00 00 00 ................
+| 1072: 00 01 00 00 00 3f 23 70 00 00 00 01 0f 1c 00 04 .....?#p........
+| 1088: 00 00 00 01 0f 40 00 01 00 00 00 01 0f 86 00 01 .....@..........
+| 1104: 00 00 00 01 0f 84 00 01 00 00 00 01 00 00 01 0f ................
+| 1120: c0 00 01 00 00 00 01 0f e8 00 01 0f d6 00 6f 00 ..............o.
+| 1136: 02 0f d6 00 03 02 31 03 2b 03 2a f2 00 0f d4 00 ......1.+.*.....
+| 1152: 01 00 08 00 01 00 04 03 2b 00 02 02 32 00 01 0f ........+...2...
+| 1168: c8 01 15 00 02 20 c8 00 02 12 ad 02 00 24 06 c0 ..... .......$..
+| 1184: 00 00 00 03 00 00 01 24 00 2a 06 e4 00 00 00 03 .......$.*......
+| 1200: 00 00 01 25 00 38 07 0e 00 00 00 03 00 00 01 26 ...%.8.........&
+| 1216: 00 34 07 46 00 00 00 03 00 00 01 27 00 1c 07 7a .4.F.......'...z
+| 1232: 00 00 00 03 00 00 01 28 00 2a 07 96 00 00 00 03 .......(.*......
+| 1248: 00 e5 01 29 00 34 07 c0 00 00 00 03 00 00 01 2a ...).4.........*
+| 1264: 67 34 07 f4 00 00 00 03 00 00 01 2b 00 22 08 28 g4.........+...(
+| 1280: 00 00 00 00 01 00 01 00 00 00 01 00 02 00 00 00 ................
+| 1296: 03 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 ................
+| 1312: 01 00 00 00 01 00 02 00 00 00 03 00 00 02 00 00 ................
+| 1328: 01 00 04 00 00 00 01 00 01 00 00 00 01 00 02 00 ................
+| 1344: 00 00 03 00 01 00 00 00 01 00 04 00 00 00 01 00 ................
+| 1360: 01 00 00 00 01 00 02 00 00 00 03 00 01 00 00 21 ...............!
+| 1376: 04 00 01 00 00 00 00 00 01 00 00 00 01 00 02 00 ................
+| 1392: 00 00 03 00 01 00 00 00 01 00 04 00 00 00 01 00 ................
+| 1408: 01 00 00 00 01 00 02 00 00 00 03 00 01 00 00 00 ................
+| 1424: 01 00 05 00 00 00 01 00 01 00 00 01 00 02 02 02 ................
+| 1440: 12 02 02 02 63 6f 6c 6f 72 20 73 70 61 63 00 f3 ....color spac..
+| 1456: a0 81 a1 00 00 a0 02 02 02 02 69 95 73 6f 36 00 ..........i.so6.
+| 1472: ff 0d 00 97 8c 90 3f 0a 70 02 02 02 02 02 02 02 ......?.p.......
+| 1488: 02 02 02 02 02 02 02 01 00 00 06 02 02 02 02 5f ..............._
+| 1504: 02 02 02 2c 02 02 00 00 01 00 01 00 00 00 01 00 ...,............
+| 1520: 02 fe 00 00 03 00 01 00 00 00 01 c5 04 00 00 00 ................
+| 1536: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................
+| 1552: 00 00 81 00 04 00 00 00 01 00 01 00 00 00 01 00 ................
+| 1568: 02 00 fe 00 03 00 01 00 00 00 01 00 04 00 00 00 ................
+| 1584: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................
+| 1600: 00 00 01 00 04 00 00 00 01 00 01 00 00 00 01 00 ................
+| 1616: 02 00 00 00 03 00 01 00 00 00 01 00 04 00 00 00 ................
+| 1632: 01 00 01 00 00 00 01 00 02 00 00 00 03 00 01 00 ................
+| 1648: 00 00 40 00 84 00 84 00 84 00 01 00 00 00 09 00 ..@.............
+| 1664: 06 00 f5 00 01 00 08 01 03 15 15 15 15 15 15 15 ................
+| 1680: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................
+| 1696: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................
+| 1712: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................
+| 1728: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................
+| 1744: 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 ................
+| 1760: 15 15 15 15 15 15 15 15 15 15 15 00 03 00 62 00 ..............b.
+| 1776: 62 00 23 00 01 00 62 00 04 00 1e 00 62 00 62 00 b.#...b.....b.b.
+| 1792: 62 00 01 00 00 00 0a 00 01 00 03 00 01 00 03 00 b...............
+| 1808: 04 00 02 00 01 00 01 00 08 00 01 00 31 c6 00 03 ............1...
+| 1824: 00 0c 00 12 00 18 00 02 00 05 00 08 00 02 00 06 ................
+| 1840: 00 08 00 02 00 07 00 08 00 02 00 01 00 01 00 08 ................
+| 1856: 00 01 00 0c 00 03 00 16 00 1c 00 22 00 01 00 03 ................
+| 1872: 00 05 00 06 00 07 00 02 00 05 00 09 00 02 00 06 ................
+| 1888: 00 09 00 02 00 07 00 09 00 00 00 00 01 00 05 00 ................
+| 1904: 00 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 ................
+| 1920: 01 00 00 00 01 00 04 00 00 00 01 00 01 00 00 00 ................
+| 1936: 01 00 02 00 00 00 03 00 01 00 00 00 01 00 04 00 ................
+| 1952: 00 00 01 0f d6 00 02 34 03 03 03 00 01 00 00 00 .......4........
+| 1968: 01 00 05 00 00 00 01 00 01 00 00 00 01 00 02 00 ................
+| 1984: 00 00 03 00 01 00 00 00 01 00 04 00 00 00 01 00 ................
+| 2000: 01 00 00 00 01 00 02 fc 42 dc 19 5c 74 23 18 cd ........B...t#..
+| 2016: b3 a5 a8 7a 90 40 1d 66 12 5d e5 4f 85 00 68 f4 ...z.@.f.].O..h.
+| 2032: 05 98 86 25 24 dd bc c2 f6 f6 4e a3 e2 61 d2 c6 ...%$.....N..a..
+| 2048: aa c1 56 50 d4 80 82 35 f1 e2 59 41 50 a6 da 51 ..VP...5..YAP..Q
+| 2064: d4 62 9c 19 94 58 aa 31 30 8a 22 c2 5f 33 2b c9 .b...X.10..._3+.
+| 2080: b6 e6 b4 11 4e 51 82 c4 d8 b6 d8 b4 06 04 fb 68 ....NQ.........h
+| 2096: f4 d2 6f e7 cb 8a a8 82 d5 74 00 00 00 00 00 00 ..o......t......
+| 2368: 00 00 00 00 00 00 00 00 00 08 00 00 00 00 00 00 ................
+| 2432: 00 00 00 00 00 03 00 01 00 10 00 01 00 04 00 00 ................
+| 2448: 00 01 00 01 00 00 00 01 00 02 00 00 00 03 00 00 ................
+| 2464: 02 00 00 01 40 04 00 00 03 01 00 01 00 00 00 01 ....@...........
+| 2480: 00 02 00 00 00 03 00 01 00 00 00 00 00 01 0e f8 ................
+| 2496: 00 04 77 4f 46 32 73 40 23 70 00 00 00 70 00 1f ..wOF2s@#p...p..
+| 2512: 00 00 00 d8 00 00 00 ff ff 00 00 00 00 43 00 00 .............C..
+| 2528: 00 00 00 ff ff ff ff ff 00 00 a8 00 00 0c 00 00 ................
+| 2624: 00 00 00 00 00 00 00 00 00 00 00 10 22 00 22 0f ................
+| 2640: 00 00 00 00 00 00 00 10 22 00 00 70 00 1f 00 00 ...........p....
+| 2656: 0f d8 00 00 00 00 00 00 00 00 00 03 00 00 00 00 ................
+| 2672: 00 00 01 00 00 00 3f 23 70 00 00 00 01 0f 1c 00 ......?#p.......
+| 2688: 04 00 00 00 01 0f 40 00 01 00 00 00 01 0f 86 00 ......@.........
+| 2704: 01 00 00 00 01 0f 84 00 01 00 00 00 01 00 00 01 ................
+| 2720: 0f c0 00 01 00 00 00 01 0f e8 00 01 0f d6 00 6f ...............o
+| 2736: 00 02 0f d6 00 03 02 31 03 2b 03 2a f2 00 0f d4 .......1.+.*....
+| 2752: 00 01 00 08 00 01 00 04 03 2b 00 02 02 32 00 01 .........+...2..
+| 2768: 0f c8 01 15 00 02 20 c8 00 02 12 ad 02 00 24 06 ...... .......$.
+| 2784: c0 00 00 5a 03 00 00 01 24 00 2a 06 e4 00 00 00 ...Z....$.*.....
+| 2800: 03 00 00 01 25 00 38 07 0e 00 00 00 03 00 00 01 ....%.8.........
+| 2816: 26 00 34 07 46 00 00 00 03 00 00 01 27 00 1c 07 &.4.F.......'...
+| 2832: 7a 00 00 00 03 00 00 01 28 00 2a 07 96 00 00 00 z.......(.*.....
+| 2848: 03 00 e5 01 29 00 34 07 c0 00 00 00 03 00 00 01 ....).4.........
+| 2864: 2a 67 34 07 f4 00 00 00 03 00 00 01 2b 00 22 08 *g4.........+...
+| 2880: 28 00 00 00 00 01 00 01 00 00 00 01 00 02 00 00 (...............
+| 2896: 00 03 00 01 00 00 00 01 00 00 00 01 00 00 00 01 ................
+| 2912: 00 01 00 00 00 01 00 02 00 00 00 03 00 00 02 00 ................
+| 2928: 00 01 00 04 00 00 00 01 00 01 00 00 00 00 00 00 ................
+| 2992: 00 48 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .H..............
+| 3504: 00 00 00 00 00 00 00 00 00 00 00 97 00 00 00 00 ................
+| 3904: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 09 ................
+| 3920: 03 fe 00 00 01 36 00 3c 0a 38 00 00 00 03 00 00 .....6.<.8......
+| 3936: 01 37 00 20 0a 74 00 00 00 fb ff ff 00 38 00 2a .7. .t.......8.*
+| 3952: 0a 94 00 00 00 03 00 00 01 39 4f 54 54 4f 00 0e .........9OTTO..
+| 3968: 00 80 00 03 00 60 43 46 46 20 e3 ae 89 2a 00 00 .....`CFF ...*..
+| 3984: 02 b0 00 00 02 76 42 50 4f 53 00 15 00 0a 00 00 .....vBPOS......
+| 4000: 05 28 00 00 00 0c 54 53 55 42 c9 70 c3 06 00 00 .(....TSUB.p....
+| 4016: 05 34 1f 00 40 00 48 00 00 00 00 00 00 00 00 00 .4..@.H.........
+| 4064: 00 00 00 00 00 08 00 01 00 01 00 01 00 01 00 06 ................
+| 4080: 00 02 00 08 00 01 00 01 00 01 00 01 00 00 00 00 ................
+| end x1.db
+}]} {}
+do_test 7.1 {
+ set R [sqlite3_recover_init db main test.db2]
+ catch { $R run }
+ list [catch { $R finish } msg] $msg
+} {1 {file is not a database}}
+
finish_test
diff -Nru sqlite3-3.42.0/ext/recover/sqlite3recover.c sqlite3-3.44.0-0/ext/recover/sqlite3recover.c
--- sqlite3-3.42.0/ext/recover/sqlite3recover.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/recover/sqlite3recover.c 2023-11-04 14:23:58.000000000 +0000
@@ -2103,7 +2103,7 @@
if( iFree>(n-4) ) return 0;
iNext = recoverGetU16(&a[iFree]);
nByte = recoverGetU16(&a[iFree+2]);
- if( iFree+nByte>n ) return 0;
+ if( iFree+nByte>n || nByte<4 ) return 0;
if( iNext && iNextnBusy = 1;
pRtree->base.pModule = &rtreeModule;
pRtree->zDb = (char *)&pRtree[1];
pRtree->zName = &pRtree->zDb[nDb+1];
+ pRtree->zNodeName = &pRtree->zName[nName+1];
pRtree->eCoordType = RTREE_COORD_REAL32;
pRtree->nDim = 2;
pRtree->nDim2 = 4;
memcpy(pRtree->zDb, argv[1], nDb);
memcpy(pRtree->zName, argv[2], nName);
+ memcpy(pRtree->zNodeName, argv[2], nName);
+ memcpy(&pRtree->zNodeName[nName], "_node", 6);
/* Create/Connect to the underlying relational database schema. If
@@ -1683,7 +1687,6 @@
}
if( rc==SQLITE_OK ){
int rc2;
- pRtree->iReinsertHeight = -1;
rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0);
rc2 = nodeRelease(pRtree, pLeaf);
if( rc==SQLITE_OK ){
@@ -1780,7 +1783,8 @@
rtreeSavepoint, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- rtreeShadowName /* xShadowName */
+ rtreeShadowName, /* xShadowName */
+ rtreeIntegrity /* xIntegrity */
};
static int sqlite3_geopoly_init(sqlite3 *db){
diff -Nru sqlite3-3.42.0/ext/rtree/rtree1.test sqlite3-3.44.0-0/ext/rtree/rtree1.test
--- sqlite3-3.42.0/ext/rtree/rtree1.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtree1.test 2023-11-04 14:23:58.000000000 +0000
@@ -756,4 +756,45 @@
SELECT * FROM t1 JOIN t0 ON true RIGHT JOIN rt0 ON x0>a WHERE x0 = 0;
} {- - 0 0.0 0.0}
+# 2023-05-19 https://sqlite.org/forum/forumpost/da61c4a1b5b4af19
+# Do not omit constraints that involve equality comparisons of
+# floating-point values.
+#
+reset_db
+do_execsql_test 21.0 {
+ CREATE VIRTUAL TABLE t1 USING rtree(id, x0, x1);
+ INSERT INTO t1 VALUES(0, 1, 9223372036854775807);
+ SELECT count(*) FROM t1 WHERE x1=9223372036854775807;
+} {0}
+do_execsql_test 21.1 {
+ SELECT x1=9223372036854775807 FROM t1;
+} {0}
+
+# 2023-05-22 https://sqlite.org/forum/forumpost/da70ee0d0d
+# Round-off error associated with using large integer constraints on
+# a rtree search.
+#
+if {$tcl_platform(machine)!="i686" || $tcl_platform(os)!="Linux"} {
+ reset_db
+ do_execsql_test 22.0 {
+ CREATE VIRTUAL TABLE t1 USING rtree ( id, x0, x1 );
+ INSERT INTO t1 VALUES (123, 9223372036854775799, 9223372036854775800);
+ SELECT id FROM t1 WHERE x0 > 9223372036854775807;
+ } {123}
+ do_execsql_test 22.1 {
+ SELECT id, x0 > 9223372036854775807 AS 'a0' FROM t1;
+ } {123 1}
+}
+
+# 2023-10-14 dbsqlfuzz --sql-fuzz find. rtreecheck() should not call
+# BEGIN/COMMIT because that causes problems with statement transactions,
+# and it is unnecessary.
+#
+reset_db
+do_test 23.0 {
+ db eval {CREATE TABLE t1(a,b,c);}
+ catch {db eval {CREATE TABLE t2 AS SELECT rtreecheck('t1') AS y;}}
+ db eval {PRAGMA integrity_check;}
+} {ok}
+
finish_test
diff -Nru sqlite3-3.42.0/ext/rtree/rtree8.test sqlite3-3.44.0-0/ext/rtree/rtree8.test
--- sqlite3-3.42.0/ext/rtree/rtree8.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtree8.test 2023-11-04 14:23:58.000000000 +0000
@@ -75,7 +75,7 @@
#
populate_t1 1500
do_rtree_integrity_test rtree8-1.3.0 t1
-do_execsql_test rtree8-1.3.1 { SELECT max(nodeno) FROM t1_node } {164}
+do_execsql_test rtree8-1.3.1 { SELECT max(nodeno) FROM t1_node } {183}
do_test rtree8-1.3.2 {
set rowids [execsql {SELECT min(rowid) FROM t1_rowid GROUP BY nodeno}]
set stmt_list [list]
diff -Nru sqlite3-3.42.0/ext/rtree/rtreeA.test sqlite3-3.44.0-0/ext/rtree/rtreeA.test
--- sqlite3-3.42.0/ext/rtree/rtreeA.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtreeA.test 2023-11-04 14:23:58.000000000 +0000
@@ -113,7 +113,7 @@
SELECT rtreecheck('main', 't1')
} {{Node 1 missing from database
Wrong number of entries in %_rowid table - expected 0, actual 500
-Wrong number of entries in %_parent table - expected 0, actual 23}}
+Wrong number of entries in %_parent table - expected 0, actual 25}}
do_execsql_test rtreeA-1.2.0 { DROP TABLE t1_node } {}
do_corruption_tests rtreeA-1.2 -error "database disk image is malformed" {
@@ -191,7 +191,7 @@
SELECT rtreecheck('main', 't1')
} {{Rtree depth out of range (65535)
Wrong number of entries in %_rowid table - expected 0, actual 499
-Wrong number of entries in %_parent table - expected 0, actual 23}}
+Wrong number of entries in %_parent table - expected 0, actual 25}}
#-------------------------------------------------------------------------
# Set the "number of entries" field on some nodes incorrectly.
diff -Nru sqlite3-3.42.0/ext/rtree/rtree.c sqlite3-3.44.0-0/ext/rtree/rtree.c
--- sqlite3-3.42.0/ext/rtree/rtree.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtree.c 2023-11-04 14:23:58.000000000 +0000
@@ -96,6 +96,11 @@
#endif
#endif /* !defined(SQLITE_AMALGAMATION) */
+/* Macro to check for 4-byte alignment. Only used inside of assert() */
+#ifdef SQLITE_DEBUG
+# define FOUR_BYTE_ALIGNED(X) ((((char*)(X) - (char*)0) & 3)==0)
+#endif
+
#include
#include
#include
@@ -161,6 +166,7 @@
int iDepth; /* Current depth of the r-tree structure */
char *zDb; /* Name of database containing r-tree table */
char *zName; /* Name of r-tree table */
+ char *zNodeName; /* Name of the %_node table */
u32 nBusy; /* Current number of users of this structure */
i64 nRowEst; /* Estimated number of rows in this table */
u32 nCursor; /* Number of open cursors */
@@ -173,7 +179,6 @@
** headed by the node (leaf nodes have RtreeNode.iNode==0).
*/
RtreeNode *pDeleted;
- int iReinsertHeight; /* Height of sub-trees Reinsert() has run on */
/* Blob I/O on xxx_node */
sqlite3_blob *pNodeBlob;
@@ -195,7 +200,7 @@
/* Statement for writing to the "aux:" fields, if there are any */
sqlite3_stmt *pWriteAux;
- RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */
+ RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */
};
/* Possible values for Rtree.eCoordType: */
@@ -470,15 +475,20 @@
** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined
** at run-time.
*/
-#ifndef SQLITE_BYTEORDER
-# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \
+#ifndef SQLITE_BYTEORDER /* Replicate changes at tag-20230904a */
+# if defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_BIG_ENDIAN__
+# define SQLITE_BYTEORDER 4321
+# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__
+# define SQLITE_BYTEORDER 1234
+# elif defined(__BIG_ENDIAN__) && __BIG_ENDIAN__==1
+# define SQLITE_BYTEORDER 4321
+# elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \
defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \
defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \
defined(__ARMEL__) || defined(__AARCH64EL__) || defined(_M_ARM64)
-# define SQLITE_BYTEORDER 1234
-# elif defined(sparc) || defined(__ppc__) || \
- defined(__ARMEB__) || defined(__AARCH64EB__)
-# define SQLITE_BYTEORDER 4321
+# define SQLITE_BYTEORDER 1234
+# elif defined(sparc) || defined(__ARMEB__) || defined(__AARCH64EB__)
+# define SQLITE_BYTEORDER 4321
# else
# define SQLITE_BYTEORDER 0
# endif
@@ -502,7 +512,7 @@
return (p[0]<<8) + p[1];
}
static void readCoord(u8 *p, RtreeCoord *pCoord){
- assert( (((sqlite3_uint64)p)&3)==0 ); /* p is always 4-byte aligned */
+ assert( FOUR_BYTE_ALIGNED(p) );
#if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
pCoord->u = _byteswap_ulong(*(u32*)p);
#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
@@ -556,7 +566,7 @@
}
static int writeCoord(u8 *p, RtreeCoord *pCoord){
u32 i;
- assert( (((sqlite3_uint64)p)&3)==0 ); /* p is always 4-byte aligned */
+ assert( FOUR_BYTE_ALIGNED(p) );
assert( sizeof(RtreeCoord)==4 );
assert( sizeof(u32)==4 );
#if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
@@ -727,11 +737,9 @@
}
}
if( pRtree->pNodeBlob==0 ){
- char *zTab = sqlite3_mprintf("%s_node", pRtree->zName);
- if( zTab==0 ) return SQLITE_NOMEM;
- rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, zTab, "data", iNode, 0,
+ rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, pRtree->zNodeName,
+ "data", iNode, 0,
&pRtree->pNodeBlob);
- sqlite3_free(zTab);
}
if( rc ){
nodeBlobReset(pRtree);
@@ -1284,7 +1292,7 @@
assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE
|| p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE
|| p->op==RTREE_FALSE );
- assert( (((sqlite3_uint64)pCellData)&3)==0 ); /* 4-byte aligned */
+ assert( FOUR_BYTE_ALIGNED(pCellData) );
switch( p->op ){
case RTREE_TRUE: return; /* Always satisfied */
case RTREE_FALSE: break; /* Never satisfied */
@@ -1337,7 +1345,7 @@
|| p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE
|| p->op==RTREE_FALSE );
pCellData += 8 + p->iCoord*4;
- assert( (((sqlite3_uint64)pCellData)&3)==0 ); /* 4-byte aligned */
+ assert( FOUR_BYTE_ALIGNED(pCellData) );
RTREE_DECODE_COORD(eInt, pCellData, xN);
switch( p->op ){
case RTREE_TRUE: return; /* Always satisfied */
@@ -1907,7 +1915,20 @@
p->pInfo->nCoord = pRtree->nDim2;
p->pInfo->anQueue = pCsr->anQueue;
p->pInfo->mxLevel = pRtree->iDepth + 1;
- }else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
+ }else if( eType==SQLITE_INTEGER ){
+ sqlite3_int64 iVal = sqlite3_value_int64(argv[ii]);
+#ifdef SQLITE_RTREE_INT_ONLY
+ p->u.rValue = iVal;
+#else
+ p->u.rValue = (double)iVal;
+ if( iVal>=((sqlite3_int64)1)<<48
+ || iVal<=-(((sqlite3_int64)1)<<48)
+ ){
+ if( p->op==RTREE_LT ) p->op = RTREE_LE;
+ if( p->op==RTREE_GT ) p->op = RTREE_GE;
+ }
+#endif
+ }else if( eType==SQLITE_FLOAT ){
#ifdef SQLITE_RTREE_INT_ONLY
p->u.rValue = sqlite3_value_int64(argv[ii]);
#else
@@ -2038,11 +2059,12 @@
|| p->op==SQLITE_INDEX_CONSTRAINT_MATCH)
){
u8 op;
+ u8 doOmit = 1;
switch( p->op ){
- case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; break;
- case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; break;
+ case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; doOmit = 0; break;
+ case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; doOmit = 0; break;
case SQLITE_INDEX_CONSTRAINT_LE: op = RTREE_LE; break;
- case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; break;
+ case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; doOmit = 0; break;
case SQLITE_INDEX_CONSTRAINT_GE: op = RTREE_GE; break;
case SQLITE_INDEX_CONSTRAINT_MATCH: op = RTREE_MATCH; break;
default: op = 0; break;
@@ -2051,15 +2073,19 @@
zIdxStr[iIdx++] = op;
zIdxStr[iIdx++] = (char)(p->iColumn - 1 + '0');
pIdxInfo->aConstraintUsage[ii].argvIndex = (iIdx/2);
- pIdxInfo->aConstraintUsage[ii].omit = 1;
+ pIdxInfo->aConstraintUsage[ii].omit = doOmit;
}
}
}
pIdxInfo->idxNum = 2;
pIdxInfo->needToFreeIdxStr = 1;
- if( iIdx>0 && 0==(pIdxInfo->idxStr = sqlite3_mprintf("%s", zIdxStr)) ){
- return SQLITE_NOMEM;
+ if( iIdx>0 ){
+ pIdxInfo->idxStr = sqlite3_malloc( iIdx+1 );
+ if( pIdxInfo->idxStr==0 ){
+ return SQLITE_NOMEM;
+ }
+ memcpy(pIdxInfo->idxStr, zIdxStr, iIdx+1);
}
nRow = pRtree->nRowEst >> (iIdx/2);
@@ -2138,31 +2164,22 @@
*/
static int cellContains(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){
int ii;
- int isInt = (pRtree->eCoordType==RTREE_COORD_INT32);
- for(ii=0; iinDim2; ii+=2){
- RtreeCoord *a1 = &p1->aCoord[ii];
- RtreeCoord *a2 = &p2->aCoord[ii];
- if( (!isInt && (a2[0].fa1[1].f))
- || ( isInt && (a2[0].ia1[1].i))
- ){
- return 0;
+ if( pRtree->eCoordType==RTREE_COORD_INT32 ){
+ for(ii=0; iinDim2; ii+=2){
+ RtreeCoord *a1 = &p1->aCoord[ii];
+ RtreeCoord *a2 = &p2->aCoord[ii];
+ if( a2[0].ia1[1].i ) return 0;
+ }
+ }else{
+ for(ii=0; iinDim2; ii+=2){
+ RtreeCoord *a1 = &p1->aCoord[ii];
+ RtreeCoord *a2 = &p2->aCoord[ii];
+ if( a2[0].fa1[1].f ) return 0;
}
}
return 1;
}
-/*
-** Return the amount cell p would grow by if it were unioned with pCell.
-*/
-static RtreeDValue cellGrowth(Rtree *pRtree, RtreeCell *p, RtreeCell *pCell){
- RtreeDValue area;
- RtreeCell cell;
- memcpy(&cell, p, sizeof(RtreeCell));
- area = cellArea(pRtree, &cell);
- cellUnion(pRtree, &cell, pCell);
- return (cellArea(pRtree, &cell)-area);
-}
-
static RtreeDValue cellOverlap(
Rtree *pRtree,
RtreeCell *p,
@@ -2209,38 +2226,52 @@
for(ii=0; rc==SQLITE_OK && ii<(pRtree->iDepth-iHeight); ii++){
int iCell;
sqlite3_int64 iBest = 0;
-
+ int bFound = 0;
RtreeDValue fMinGrowth = RTREE_ZERO;
RtreeDValue fMinArea = RTREE_ZERO;
-
int nCell = NCELL(pNode);
- RtreeCell cell;
RtreeNode *pChild = 0;
- RtreeCell *aCell = 0;
-
- /* Select the child node which will be enlarged the least if pCell
- ** is inserted into it. Resolve ties by choosing the entry with
- ** the smallest area.
+ /* First check to see if there is are any cells in pNode that completely
+ ** contains pCell. If two or more cells in pNode completely contain pCell
+ ** then pick the smallest.
*/
for(iCell=0; iCell1 ){
- int iLeft = 0;
- int iRight = 0;
-
- int nLeft = nIdx/2;
- int nRight = nIdx-nLeft;
- int *aLeft = aIdx;
- int *aRight = &aIdx[nLeft];
-
- SortByDistance(aLeft, nLeft, aDistance, aSpare);
- SortByDistance(aRight, nRight, aDistance, aSpare);
-
- memcpy(aSpare, aLeft, sizeof(int)*nLeft);
- aLeft = aSpare;
-
- while( iLeftnDim; iDim++){
- aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2]);
- aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2+1]);
- }
- }
- for(iDim=0; iDimnDim; iDim++){
- aCenterCoord[iDim] = (aCenterCoord[iDim]/(nCell*(RtreeDValue)2));
- }
-
- for(ii=0; iinDim; iDim++){
- RtreeDValue coord = (DCOORD(aCell[ii].aCoord[iDim*2+1]) -
- DCOORD(aCell[ii].aCoord[iDim*2]));
- aDistance[ii] += (coord-aCenterCoord[iDim])*(coord-aCenterCoord[iDim]);
- }
- }
-
- SortByDistance(aOrder, nCell, aDistance, aSpare);
- nodeZero(pRtree, pNode);
-
- for(ii=0; rc==SQLITE_OK && ii<(nCell-(RTREE_MINCELLS(pRtree)+1)); ii++){
- RtreeCell *p = &aCell[aOrder[ii]];
- nodeInsertCell(pRtree, pNode, p);
- if( p->iRowid==pCell->iRowid ){
- if( iHeight==0 ){
- rc = rowidWrite(pRtree, p->iRowid, pNode->iNode);
- }else{
- rc = parentWrite(pRtree, p->iRowid, pNode->iNode);
- }
- }
- }
- if( rc==SQLITE_OK ){
- rc = fixBoundingBox(pRtree, pNode);
- }
- for(; rc==SQLITE_OK && iiiNode currently contains
- ** the height of the sub-tree headed by the cell.
- */
- RtreeNode *pInsert;
- RtreeCell *p = &aCell[aOrder[ii]];
- rc = ChooseLeaf(pRtree, p, iHeight, &pInsert);
- if( rc==SQLITE_OK ){
- int rc2;
- rc = rtreeInsertCell(pRtree, pInsert, p, iHeight);
- rc2 = nodeRelease(pRtree, pInsert);
- if( rc==SQLITE_OK ){
- rc = rc2;
- }
- }
- }
-
- sqlite3_free(aCell);
- return rc;
-}
-
+
/*
** Insert cell pCell into node pNode. Node pNode is the head of a
** subtree iHeight high (leaf nodes have iHeight==0).
@@ -2989,12 +2848,7 @@
}
}
if( nodeInsertCell(pRtree, pNode, pCell) ){
- if( iHeight<=pRtree->iReinsertHeight || pNode->iNode==1){
- rc = SplitNode(pRtree, pNode, pCell, iHeight);
- }else{
- pRtree->iReinsertHeight = iHeight;
- rc = Reinsert(pRtree, pNode, pCell, iHeight);
- }
+ rc = SplitNode(pRtree, pNode, pCell, iHeight);
}else{
rc = AdjustTree(pRtree, pNode, pCell);
if( ALWAYS(rc==SQLITE_OK) ){
@@ -3337,7 +3191,6 @@
}
if( rc==SQLITE_OK ){
int rc2;
- pRtree->iReinsertHeight = -1;
rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0);
rc2 = nodeRelease(pRtree, pLeaf);
if( rc==SQLITE_OK ){
@@ -3478,8 +3331,11 @@
return 0;
}
+/* Forward declaration */
+static int rtreeIntegrity(sqlite3_vtab*, const char*, const char*, int, char**);
+
static sqlite3_module rtreeModule = {
- 3, /* iVersion */
+ 4, /* iVersion */
rtreeCreate, /* xCreate - create a table */
rtreeConnect, /* xConnect - connect to an existing table */
rtreeBestIndex, /* xBestIndex - Determine search strategy */
@@ -3502,7 +3358,8 @@
rtreeSavepoint, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
- rtreeShadowName /* xShadowName */
+ rtreeShadowName, /* xShadowName */
+ rtreeIntegrity /* xIntegrity */
};
static int rtreeSqlInit(
@@ -3758,22 +3615,27 @@
}
sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
+ sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
+
/* Allocate the sqlite3_vtab structure */
nDb = (int)strlen(argv[1]);
nName = (int)strlen(argv[2]);
- pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2);
+ pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName*2+8);
if( !pRtree ){
return SQLITE_NOMEM;
}
- memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2);
+ memset(pRtree, 0, sizeof(Rtree)+nDb+nName*2+8);
pRtree->nBusy = 1;
pRtree->base.pModule = &rtreeModule;
pRtree->zDb = (char *)&pRtree[1];
pRtree->zName = &pRtree->zDb[nDb+1];
+ pRtree->zNodeName = &pRtree->zName[nName+1];
pRtree->eCoordType = (u8)eCoordType;
memcpy(pRtree->zDb, argv[1], nDb);
memcpy(pRtree->zName, argv[2], nName);
+ memcpy(pRtree->zNodeName, argv[2], nName);
+ memcpy(&pRtree->zNodeName[nName], "_node", 6);
/* Create/Connect to the underlying relational database schema. If
@@ -4270,7 +4132,6 @@
){
RtreeCheck check; /* Common context for various routines */
sqlite3_stmt *pStmt = 0; /* Used to find column count of rtree table */
- int bEnd = 0; /* True if transaction should be closed */
int nAux = 0; /* Number of extra columns. */
/* Initialize the context object */
@@ -4279,14 +4140,6 @@
check.zDb = zDb;
check.zTab = zTab;
- /* If there is not already an open transaction, open one now. This is
- ** to ensure that the queries run as part of this integrity-check operate
- ** on a consistent snapshot. */
- if( sqlite3_get_autocommit(db) ){
- check.rc = sqlite3_exec(db, "BEGIN", 0, 0, 0);
- bEnd = 1;
- }
-
/* Find the number of auxiliary columns */
if( check.rc==SQLITE_OK ){
pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab);
@@ -4327,16 +4180,35 @@
sqlite3_finalize(check.aCheckMapping[0]);
sqlite3_finalize(check.aCheckMapping[1]);
- /* If one was opened, close the transaction */
- if( bEnd ){
- int rc = sqlite3_exec(db, "END", 0, 0, 0);
- if( check.rc==SQLITE_OK ) check.rc = rc;
- }
*pzReport = check.zReport;
return check.rc;
}
/*
+** Implementation of the xIntegrity method for Rtree.
+*/
+static int rtreeIntegrity(
+ sqlite3_vtab *pVtab, /* The virtual table to check */
+ const char *zSchema, /* Schema in which the virtual table lives */
+ const char *zName, /* Name of the virtual table */
+ int isQuick, /* True for a quick_check */
+ char **pzErr /* Write results here */
+){
+ Rtree *pRtree = (Rtree*)pVtab;
+ int rc;
+ assert( pzErr!=0 && *pzErr==0 );
+ UNUSED_PARAMETER(zSchema);
+ UNUSED_PARAMETER(zName);
+ UNUSED_PARAMETER(isQuick);
+ rc = rtreeCheckTable(pRtree->db, pRtree->zDb, pRtree->zName, pzErr);
+ if( rc==SQLITE_OK && *pzErr ){
+ *pzErr = sqlite3_mprintf("In RTree %s.%s:\n%z",
+ pRtree->zDb, pRtree->zName, *pzErr);
+ }
+ return rc;
+}
+
+/*
** Usage:
**
** rtreecheck();
diff -Nru sqlite3-3.42.0/ext/rtree/rtreecheck.test sqlite3-3.44.0-0/ext/rtree/rtreecheck.test
--- sqlite3-3.42.0/ext/rtree/rtreecheck.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtreecheck.test 2023-11-04 14:23:58.000000000 +0000
@@ -78,6 +78,11 @@
SELECT rtreecheck('r1')
} {{Dimension 0 of cell 0 on node 1 is corrupt
Dimension 1 of cell 3 on node 1 is corrupt}}
+do_execsql_test 2.3b {
+ PRAGMA integrity_check;
+} {{In RTree main.r1:
+Dimension 0 of cell 0 on node 1 is corrupt
+Dimension 1 of cell 3 on node 1 is corrupt}}
setup_simple_db
do_execsql_test 2.4 {
@@ -85,12 +90,21 @@
SELECT rtreecheck('r1')
} {{Mapping (3 -> 1) missing from %_rowid table
Wrong number of entries in %_rowid table - expected 5, actual 4}}
+do_execsql_test 2.4b {
+ PRAGMA integrity_check
+} {{In RTree main.r1:
+Mapping (3 -> 1) missing from %_rowid table
+Wrong number of entries in %_rowid table - expected 5, actual 4}}
setup_simple_db
do_execsql_test 2.5 {
UPDATE r1_rowid SET nodeno=2 WHERE rowid=3;
SELECT rtreecheck('r1')
} {{Found (3 -> 2) in %_rowid table, expected (3 -> 1)}}
+do_execsql_test 2.5b {
+ PRAGMA integrity_check
+} {{In RTree main.r1:
+Found (3 -> 2) in %_rowid table, expected (3 -> 1)}}
reset_db
do_execsql_test 3.0 {
@@ -104,14 +118,16 @@
INSERT INTO r1 VALUES(7, 5, 0x00000080);
INSERT INTO r1 VALUES(8, 5, 0x40490fdb);
INSERT INTO r1 VALUES(9, 0x7f800000, 0x7f900000);
- SELECT rtreecheck('r1')
-} {ok}
+ SELECT rtreecheck('r1');
+ PRAGMA integrity_check;
+} {ok ok}
do_execsql_test 3.1 {
CREATE VIRTUAL TABLE r2 USING rtree_i32(id, x1, x2);
INSERT INTO r2 VALUES(2, -1*(1<<31), -1*(1<<31)+5);
- SELECT rtreecheck('r2')
-} {ok}
+ SELECT rtreecheck('r2');
+ PRAGMA integrity_check;
+} {ok ok}
sqlite3_db_config db DEFENSIVE 0
do_execsql_test 3.2 {
@@ -125,6 +141,11 @@
UPDATE r2_node SET data = X'00001234';
SELECT rtreecheck('r2')!='ok';
} {1}
+do_execsql_test 3.4 {
+ PRAGMA integrity_check;
+} {{In RTree main.r2:
+Node 1 is too small for cell count of 4660 (4 bytes)
+Wrong number of entries in %_rowid table - expected 0, actual 1}}
do_execsql_test 4.0 {
CREATE TABLE notanrtree(i);
@@ -181,4 +202,3 @@
}
finish_test
-
diff -Nru sqlite3-3.42.0/ext/rtree/rtreedoc.test sqlite3-3.44.0-0/ext/rtree/rtreedoc.test
--- sqlite3-3.42.0/ext/rtree/rtreedoc.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtreedoc.test 2023-11-04 14:23:58.000000000 +0000
@@ -601,11 +601,21 @@
SELECT A.id FROM demo_index AS A, demo_index AS B
WHERE A.maxX>=B.minX AND A.minX<=B.maxX
AND A.maxY>=B.minY AND A.minY<=B.maxY
- AND B.id=28269;
+ AND B.id=28269 ORDER BY +A.id;
} {
- 28293 28216 28322 28286 28269
- 28215 28336 28262 28291 28320
- 28313 28298 28287
+ 28215
+ 28216
+ 28262
+ 28269
+ 28286
+ 28287
+ 28291
+ 28293
+ 28298
+ 28313
+ 28320
+ 28322
+ 28336
}
# EVIDENCE-OF: R-02723-34107 Note that it is not necessary for all
@@ -1575,7 +1585,7 @@
do_test 3.6 {
execsql { INSERT INTO rt2_parent VALUES(1000, 1000) }
execsql { SELECT rtreecheck('rt2') }
-} {{Wrong number of entries in %_parent table - expected 9, actual 10}}
+} {{Wrong number of entries in %_parent table - expected 10, actual 11}}
execsql ROLLBACK
diff -Nru sqlite3-3.42.0/ext/rtree/rtreefuzz001.test sqlite3-3.44.0-0/ext/rtree/rtreefuzz001.test
--- sqlite3-3.42.0/ext/rtree/rtreefuzz001.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtreefuzz001.test 2023-11-04 14:23:58.000000000 +0000
@@ -1043,7 +1043,7 @@
| end crash-2e81f5dce5cbd4.db}]
execsql { PRAGMA writable_schema = 1;}
catchsql {UPDATE t1 SET ex= ex ISNULL}
-} {1 {database disk image is malformed}}
+} {0 {}}
do_test rtreefuzz001-600 {
sqlite3 db {}
diff -Nru sqlite3-3.42.0/ext/rtree/rtree_util.tcl sqlite3-3.44.0-0/ext/rtree/rtree_util.tcl
--- sqlite3-3.42.0/ext/rtree/rtree_util.tcl 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/rtree/rtree_util.tcl 2023-11-04 14:23:58.000000000 +0000
@@ -192,6 +192,6 @@
}
proc do_rtree_integrity_test {tn tbl} {
- uplevel [list do_execsql_test $tn "SELECT rtreecheck('$tbl')" ok]
+ uplevel [list do_execsql_test $tn.1 "SELECT rtreecheck('$tbl')" ok]
+ uplevel [list do_execsql_test $tn.2 "PRAGMA integrity_check" ok]
}
-
diff -Nru sqlite3-3.42.0/ext/session/session3.test sqlite3-3.44.0-0/ext/session/session3.test
--- sqlite3-3.42.0/ext/session/session3.test 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/session3.test 2023-11-04 14:23:58.000000000 +0000
@@ -135,8 +135,8 @@
DROP TABLE t2;
CREATE TABLE t2(a, b PRIMARY KEY, c, d);
}
- list [catch { S changeset } msg] $msg
-} {1 SQLITE_SCHEMA}
+ catch { S changeset }
+} {0}
do_test 2.2.3 {
S delete
sqlite3session S db main
@@ -167,8 +167,8 @@
CREATE TABLE t2(a, b PRIMARY KEY, c, d);
INSERT INTO t2 VALUES(4, 5, 6, 7);
}
- list [catch { S changeset } msg] $msg
-} {1 SQLITE_SCHEMA}
+ catch { S changeset }
+} {0}
do_test 2.3 {
S delete
diff -Nru sqlite3-3.42.0/ext/session/sessionalter.test sqlite3-3.44.0-0/ext/session/sessionalter.test
--- sqlite3-3.42.0/ext/session/sessionalter.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionalter.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,238 @@
+# 2023 October 02
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements that the sessions module interacts well with
+# the ALTER TABLE ADD COLUMN command.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+
+ifcapable !session {finish_test; return}
+set testprefix sessionalter
+
+
+forcedelete test.db2
+sqlite3 db2 test.db2
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+}
+
+do_execsql_test -db db2 1.1 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c DEFAULT 1234);
+}
+
+do_then_apply_sql {
+ INSERT INTO t1 VALUES(1, 'one');
+ INSERT INTO t1 VALUES(2, 'two');
+}
+
+do_execsql_test -db db2 1.2 {
+ SELECT * FROM t1
+} {
+ 1 one 1234
+ 2 two 1234
+}
+
+do_then_apply_sql {
+ UPDATE t1 SET b='four' WHERE a=2;
+}
+
+do_execsql_test -db db2 1.3 {
+ SELECT * FROM t1
+} {
+ 1 one 1234
+ 2 four 1234
+}
+
+do_then_apply_sql {
+ DELETE FROM t1 WHERE a=1;
+}
+
+do_execsql_test -db db2 1.4 {
+ SELECT * FROM t1
+} {
+ 2 four 1234
+}
+
+db2 close
+
+#--------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 2.0 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+}
+
+do_test 2.1 {
+ sqlite3session S db main
+ S attach t1
+ set {} {}
+} {}
+do_execsql_test 2.2 {
+ INSERT INTO t1 VALUES(1, 2);
+ ALTER TABLE t1 ADD COLUMN c DEFAULT 'abcd';
+ INSERT INTO t1 VALUES(2, 3, 4);
+}
+do_changeset_test 2.3 S {
+ {INSERT t1 0 X.. {} {i 1 i 2 t abcd}}
+ {INSERT t1 0 X.. {} {i 2 i 3 i 4}}
+}
+
+do_iterator_test 2.4 {} {
+ DELETE FROM t1 WHERE a=2;
+ ALTER TABLE t1 ADD COLUMN d DEFAULT 'abcd';
+ ALTER TABLE t1 ADD COLUMN e DEFAULT 5;
+ ALTER TABLE t1 ADD COLUMN f DEFAULT 7.2;
+ -- INSERT INTO t1 VALUES(9, 9, 9, 9);
+} {
+ {DELETE t1 0 X..... {i 2 i 3 i 4 t abcd i 5 f 7.2} {}}
+}
+
+#-------------------------------------------------------------------------
+# Tests of the sqlite3changegroup_xxx() APIs.
+#
+reset_db
+do_execsql_test 3.0 {
+ CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
+ CREATE TABLE t2(x PRIMARY KEY, y);
+ CREATE TABLE t3(x, y);
+ CREATE TABLE t4(y PRIMARY KEY, x) WITHOUT ROWID;
+
+ INSERT INTO t1 VALUES(1, 2), (3, 4), (5, 6);
+ INSERT INTO t2 VALUES('one', 'two'), ('three', 'four'), ('five', 'six');
+ INSERT INTO t3 VALUES(1, 2), (3, 4), (5, 6);
+
+ INSERT INTO t4(x, y) VALUES(1, 2), (3, 4), (5, 6);
+}
+
+db_save_and_close
+foreach {tn sql1 at sql2} {
+ 1 {
+ INSERT INTO t1(x, y) VALUES(7, 8);
+ } {
+ ALTER TABLE t1 ADD COLUMN z DEFAULT 10;
+ } {
+ UPDATE t1 SET y=11 WHERE x=7;
+ }
+
+ 2 {
+ UPDATE t2 SET y='two.two' WHERE x='one';
+ DELETE FROM t2 WHERE x='five';
+ INSERT INTO t2(x, y) VALUES('seven', 'eight');
+ } {
+ ALTER TABLE t2 ADD COLUMN z;
+ ALTER TABLE t2 ADD COLUMN zz;
+ } {
+ }
+
+ 3 {
+ DELETE FROM t2 WHERE x='five';
+ } {
+ ALTER TABLE t2 ADD COLUMN z DEFAULT 'xyz';
+ } {
+ }
+
+ 4 {
+ UPDATE t2 SET y='two.two' WHERE x='three';
+ } {
+ ALTER TABLE t2 ADD COLUMN z;
+ } {
+ UPDATE t2 SET z='abc' WHERE x='one';
+ }
+
+ 5* {
+ UPDATE t2 SET y='two.two' WHERE x='three';
+ } {
+ ALTER TABLE t2 ADD COLUMN z DEFAULT 'defu1';
+ } {
+ }
+
+ 6* {
+ INSERT INTO t2(x, y) VALUES('nine', 'ten');
+ } {
+ ALTER TABLE t2 ADD COLUMN z;
+ ALTER TABLE t2 ADD COLUMN a DEFAULT 'eelve';
+ ALTER TABLE t2 ADD COLUMN b DEFAULT x'1234abcd';
+ ALTER TABLE t2 ADD COLUMN c DEFAULT 4.2;
+ ALTER TABLE t2 ADD COLUMN d DEFAULT NULL;
+ } {
+ }
+
+ 7 {
+ INSERT INTO t3(x, y) VALUES(7, 8);
+ UPDATE t3 SET y='fourteen' WHERE x=1;
+ DELETE FROM t3 WHERE x=3;
+ } {
+ ALTER TABLE t3 ADD COLUMN c;
+ } {
+ INSERT INTO t3(x, y, c) VALUES(9, 10, 11);
+ }
+
+ 8 {
+ INSERT INTO t4(x, y) VALUES(7, 8);
+ UPDATE t4 SET y='fourteen' WHERE x=1;
+ DELETE FROM t4 WHERE x=3;
+ } {
+ ALTER TABLE t4 ADD COLUMN c;
+ } {
+ INSERT INTO t4(x, y, c) VALUES(9, 10, 11);
+ }
+} {
+ foreach {tn2 cmd} {
+ 1 changeset_from_sql
+ 2 patchset_from_sql
+ } {
+ db_restore_and_reopen
+
+ set C1 [$cmd $sql1]
+ execsql $at
+ set C2 [$cmd $sql2]
+
+ sqlite3changegroup grp
+ grp schema db main
+ grp add $C1
+ grp add $C2
+ set T1 [grp output]
+ grp delete
+
+ db_restore_and_reopen
+ execsql $at
+ set T2 [$cmd "$sql1 ; $sql2"]
+
+ if {[string range $tn end end]!="*"} {
+ do_test 3.1.$tn.$tn2.1 { changeset_to_list $T1 } [changeset_to_list $T2]
+ set testname "$tn.$tn2"
+ } else {
+ set testname "[string range $tn 0 end-1].$tn2"
+ }
+
+ db_restore_and_reopen
+ proc xConflict {args} { return "REPLACE" }
+ sqlite3changeset_apply_v2 db $T1 xConflict
+ set S1 [scksum db main]
+
+ db_restore_and_reopen
+ sqlite3changeset_apply_v2 db $T2 xConflict
+ set S2 [scksum db main]
+
+ # if { $tn==7 } { puts [changeset_to_list $T1] }
+
+ do_test 3.1.$tn.2 { set S1 } $S2
+ }
+}
+
+
+finish_test
+
diff -Nru sqlite3-3.42.0/ext/session/sessionfault3.test sqlite3-3.44.0-0/ext/session/sessionfault3.test
--- sqlite3-3.42.0/ext/session/sessionfault3.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionfault3.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,59 @@
+# 2016 October 6
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the session module.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+ifcapable !session {finish_test; return}
+set testprefix sessionfault3
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a, b, PRIMARY KEY(a));
+ INSERT INTO t1 VALUES(1, 2);
+ INSERT INTO t1 VALUES(3, 4);
+ INSERT INTO t1 VALUES('five', 'six');
+}
+
+set C1 [changeset_from_sql {
+ INSERT INTO t1 VALUES('seven', 'eight');
+ UPDATE t1 SET b=6 WHERE a='five';
+ DELETE FROM t1 WHERE a=1;
+}]
+
+do_execsql_test 1.1 {
+ ALTER TABLE t1 ADD COLUMN d DEFAULT 123;
+ ALTER TABLE t1 ADD COLUMN e DEFAULT 'string';
+}
+
+set C2 [changeset_from_sql {
+ UPDATE t1 SET e='new value' WHERE a='seven';
+ INSERT INTO t1 VALUES(0, 0, 0, 0);
+}]
+
+do_faultsim_test 1 -faults oom* -prep {
+ sqlite3changegroup G
+} -body {
+ G schema db main
+ G add $::C1
+ G add $::C2
+ G output
+ set {} {}
+} -test {
+ catch { G delete }
+ faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
+}
+
+finish_test
diff -Nru sqlite3-3.42.0/ext/session/sessionnoact.test sqlite3-3.44.0-0/ext/session/sessionnoact.test
--- sqlite3-3.42.0/ext/session/sessionnoact.test 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sessionnoact.test 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,110 @@
+# 2023 October 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+ifcapable !session {finish_test; return}
+
+set testprefix sessionnoact
+
+do_execsql_test 1.0 {
+ CREATE TABLE p1(a INTEGER PRIMARY KEY, b, c UNIQUE);
+ INSERT INTO p1 VALUES(1, 1, 'one');
+ INSERT INTO p1 VALUES(2, 2, 'two');
+ INSERT INTO p1 VALUES(3, 3, 'three');
+ INSERT INTO p1 VALUES(4, 4, 'four');
+}
+
+db_save
+
+set C [changeset_from_sql {
+ DELETE FROM p1 WHERE a=2;
+ UPDATE p1 SET c='six' WHERE a=3;
+ INSERT INTO p1 VALUES(5, 5, 'two');
+ INSERT INTO p1 VALUES(6, 6, 'three');
+}]
+
+db_restore_and_reopen
+
+do_execsql_test 1.1 {
+ CREATE TABLE c1(x INTEGER PRIMARY KEY, y,
+ FOREIGN KEY(y) REFERENCES p1(c) ON DELETE CASCADE ON UPDATE SET NULL
+ );
+
+ INSERT INTO c1 VALUES(10, 'one');
+ INSERT INTO c1 VALUES(20, 'two');
+ INSERT INTO c1 VALUES(30, 'three');
+ INSERT INTO c1 VALUES(40, 'four');
+}
+
+db_save
+
+do_execsql_test 1.2 {
+ PRAGMA foreign_keys = 1;
+}
+
+set ::nConflict 0
+proc conflict {args} {
+ incr ::nConflict
+ return "OMIT"
+}
+
+sqlite3changeset_apply_v2 db $C conflict
+
+do_execsql_test 1.3 {
+ SELECT * FROM c1
+} {
+ 10 one
+ 30 {}
+ 40 four
+}
+
+db_restore_and_reopen
+
+do_execsql_test 1.4 {
+ PRAGMA foreign_keys = 1;
+}
+
+do_execsql_test 1.5 {
+ UPDATE p1 SET c=12345 WHERE a = 45;
+}
+
+sqlite3changeset_apply_v2 -noaction db $C conflict
+do_execsql_test 1.6 {
+ SELECT * FROM c1
+} {
+ 10 one
+ 20 two
+ 30 three
+ 40 four
+}
+
+do_execsql_test 1.7 {
+ PRAGMA foreign_keys = 1;
+ UPDATE p1 SET c = 'ten' WHERE c='two';
+ SELECT * FROM c1;
+} {
+ 10 one
+ 20 {}
+ 30 three
+ 40 four
+}
+
+do_execsql_test 1.8 {
+ PRAGMA foreign_key_check
+}
+
+finish_test
diff -Nru sqlite3-3.42.0/ext/session/sqlite3session.c sqlite3-3.44.0-0/ext/session/sqlite3session.c
--- sqlite3-3.42.0/ext/session/sqlite3session.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sqlite3session.c 2023-11-04 14:23:58.000000000 +0000
@@ -119,6 +119,18 @@
** The data associated with each hash-table entry is a structure containing
** a subset of the initial values that the modified row contained at the
** start of the session. Or no initial values if the row was inserted.
+**
+** pDfltStmt:
+** This is only used by the sqlite3changegroup_xxx() APIs, not by
+** regular sqlite3_session objects. It is a SELECT statement that
+** selects the default value for each table column. For example,
+** if the table is
+**
+** CREATE TABLE xx(a DEFAULT 1, b, c DEFAULT 'abc')
+**
+** then this variable is the compiled version of:
+**
+** SELECT 1, NULL, 'abc'
*/
struct SessionTable {
SessionTable *pNext;
@@ -127,10 +139,12 @@
int bStat1; /* True if this is sqlite_stat1 */
int bRowid; /* True if this table uses rowid for PK */
const char **azCol; /* Column names */
+ const char **azDflt; /* Default value expressions */
u8 *abPK; /* Array of primary key flags */
int nEntry; /* Total number of entries in hash table */
int nChange; /* Size of apChange[] array */
SessionChange **apChange; /* Hash table buckets */
+ sqlite3_stmt *pDfltStmt;
};
/*
@@ -299,6 +313,7 @@
struct SessionChange {
u8 op; /* One of UPDATE, DELETE, INSERT */
u8 bIndirect; /* True if this change is "indirect" */
+ u16 nRecordField; /* Number of fields in aRecord[] */
int nMaxSize; /* Max size of eventual changeset record */
int nRecord; /* Number of bytes in buffer aRecord[] */
u8 *aRecord; /* Buffer containing old.* record */
@@ -324,7 +339,7 @@
** Read a varint value from aBuf[] into *piVal. Return the number of
** bytes read.
*/
-static int sessionVarintGet(u8 *aBuf, int *piVal){
+static int sessionVarintGet(const u8 *aBuf, int *piVal){
return getVarint32(aBuf, *piVal);
}
@@ -587,9 +602,11 @@
** Return the number of bytes of space occupied by the value (including
** the type byte).
*/
-static int sessionSerialLen(u8 *a){
- int e = *a;
+static int sessionSerialLen(const u8 *a){
+ int e;
int n;
+ assert( a!=0 );
+ e = *a;
if( e==0 || e==0xFF ) return 1;
if( e==SQLITE_NULL ) return 1;
if( e==SQLITE_INTEGER || e==SQLITE_FLOAT ) return 9;
@@ -891,6 +908,7 @@
rc = pSession->hook.xOld(pSession->hook.pCtx, iCol, &pVal);
}
assert( rc==SQLITE_OK );
+ (void)rc; /* Suppress warning about unused variable */
if( sqlite3_value_type(pVal)!=eType ) return 0;
/* A SessionChange object never has a NULL value in a PK column */
@@ -993,13 +1011,14 @@
**
** For example, if the table is declared as:
**
-** CREATE TABLE tbl1(w, x, y, z, PRIMARY KEY(w, z));
+** CREATE TABLE tbl1(w, x DEFAULT 'abc', y, z, PRIMARY KEY(w, z));
**
-** Then the four output variables are populated as follows:
+** Then the five output variables are populated as follows:
**
** *pnCol = 4
** *pzTab = "tbl1"
** *pazCol = {"w", "x", "y", "z"}
+** *pazDflt = {NULL, 'abc', NULL, NULL}
** *pabPK = {1, 0, 0, 1}
**
** All returned buffers are part of the same single allocation, which must
@@ -1013,6 +1032,7 @@
int *pnCol, /* OUT: number of columns */
const char **pzTab, /* OUT: Copy of zThis */
const char ***pazCol, /* OUT: Array of column names for table */
+ const char ***pazDflt, /* OUT: Array of default value expressions */
u8 **pabPK, /* OUT: Array of booleans - true for PK col */
int *pbRowid /* OUT: True if only PK is a rowid */
){
@@ -1025,11 +1045,18 @@
int i;
u8 *pAlloc = 0;
char **azCol = 0;
+ char **azDflt = 0;
u8 *abPK = 0;
int bRowid = 0; /* Set to true to use rowid as PK */
assert( pazCol && pabPK );
+ *pazCol = 0;
+ *pabPK = 0;
+ *pnCol = 0;
+ if( pzTab ) *pzTab = 0;
+ if( pazDflt ) *pazDflt = 0;
+
nThis = sqlite3Strlen30(zThis);
if( nThis==12 && 0==sqlite3_stricmp("sqlite_stat1", zThis) ){
rc = sqlite3_table_column_metadata(db, zDb, zThis, 0, 0, 0, 0, 0, 0);
@@ -1043,39 +1070,28 @@
}else if( rc==SQLITE_ERROR ){
zPragma = sqlite3_mprintf("");
}else{
- *pazCol = 0;
- *pabPK = 0;
- *pnCol = 0;
- if( pzTab ) *pzTab = 0;
return rc;
}
}else{
zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis);
}
if( !zPragma ){
- *pazCol = 0;
- *pabPK = 0;
- *pnCol = 0;
- if( pzTab ) *pzTab = 0;
return SQLITE_NOMEM;
}
rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0);
sqlite3_free(zPragma);
if( rc!=SQLITE_OK ){
- *pazCol = 0;
- *pabPK = 0;
- *pnCol = 0;
- if( pzTab ) *pzTab = 0;
return rc;
}
nByte = nThis + 1;
bRowid = (pbRowid!=0);
while( SQLITE_ROW==sqlite3_step(pStmt) ){
- nByte += sqlite3_column_bytes(pStmt, 1);
+ nByte += sqlite3_column_bytes(pStmt, 1); /* name */
+ nByte += sqlite3_column_bytes(pStmt, 4); /* dflt_value */
nDbCol++;
- if( sqlite3_column_int(pStmt, 5) ) bRowid = 0;
+ if( sqlite3_column_int(pStmt, 5) ) bRowid = 0; /* pk */
}
if( nDbCol==0 ) bRowid = 0;
nDbCol += bRowid;
@@ -1083,15 +1099,18 @@
rc = sqlite3_reset(pStmt);
if( rc==SQLITE_OK ){
- nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1);
+ nByte += nDbCol * (sizeof(const char *)*2 + sizeof(u8) + 1 + 1);
pAlloc = sessionMalloc64(pSession, nByte);
if( pAlloc==0 ){
rc = SQLITE_NOMEM;
+ }else{
+ memset(pAlloc, 0, nByte);
}
}
if( rc==SQLITE_OK ){
azCol = (char **)pAlloc;
- pAlloc = (u8 *)&azCol[nDbCol];
+ azDflt = (char**)&azCol[nDbCol];
+ pAlloc = (u8 *)&azDflt[nDbCol];
abPK = (u8 *)pAlloc;
pAlloc = &abPK[nDbCol];
if( pzTab ){
@@ -1111,11 +1130,21 @@
}
while( SQLITE_ROW==sqlite3_step(pStmt) ){
int nName = sqlite3_column_bytes(pStmt, 1);
+ int nDflt = sqlite3_column_bytes(pStmt, 4);
const unsigned char *zName = sqlite3_column_text(pStmt, 1);
+ const unsigned char *zDflt = sqlite3_column_text(pStmt, 4);
+
if( zName==0 ) break;
memcpy(pAlloc, zName, nName+1);
azCol[i] = (char *)pAlloc;
pAlloc += nName+1;
+ if( zDflt ){
+ memcpy(pAlloc, zDflt, nDflt+1);
+ azDflt[i] = (char *)pAlloc;
+ pAlloc += nDflt+1;
+ }else{
+ azDflt[i] = 0;
+ }
abPK[i] = sqlite3_column_int(pStmt, 5);
i++;
}
@@ -1126,14 +1155,11 @@
** free any allocation made. An error code will be returned in this case.
*/
if( rc==SQLITE_OK ){
- *pazCol = (const char **)azCol;
+ *pazCol = (const char**)azCol;
+ if( pazDflt ) *pazDflt = (const char**)azDflt;
*pabPK = abPK;
*pnCol = nDbCol;
}else{
- *pazCol = 0;
- *pabPK = 0;
- *pnCol = 0;
- if( pzTab ) *pzTab = 0;
sessionFree(pSession, azCol);
}
if( pbRowid ) *pbRowid = bRowid;
@@ -1142,10 +1168,9 @@
}
/*
-** This function is only called from within a pre-update handler for a
-** write to table pTab, part of session pSession. If this is the first
-** write to this table, initalize the SessionTable.nCol, azCol[] and
-** abPK[] arrays accordingly.
+** This function is called to initialize the SessionTable.nCol, azCol[]
+** abPK[] and azDflt[] members of SessionTable object pTab. If these
+** fields are already initilialized, this function is a no-op.
**
** If an error occurs, an error code is stored in sqlite3_session.rc and
** non-zero returned. Or, if no error occurs but the table has no primary
@@ -1153,15 +1178,22 @@
** indicate that updates on this table should be ignored. SessionTable.abPK
** is set to NULL in this case.
*/
-static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
+static int sessionInitTable(
+ sqlite3_session *pSession, /* Optional session handle */
+ SessionTable *pTab, /* Table object to initialize */
+ sqlite3 *db, /* Database handle to read schema from */
+ const char *zDb /* Name of db - "main", "temp" etc. */
+){
+ int rc = SQLITE_OK;
+
if( pTab->nCol==0 ){
u8 *abPK;
assert( pTab->azCol==0 || pTab->abPK==0 );
- pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb,
- pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK,
- (pSession->bImplicitPK ? &pTab->bRowid : 0)
+ rc = sessionTableInfo(pSession, db, zDb,
+ pTab->zName, &pTab->nCol, 0, &pTab->azCol, &pTab->azDflt, &abPK,
+ ((pSession==0 || pSession->bImplicitPK) ? &pTab->bRowid : 0)
);
- if( pSession->rc==SQLITE_OK ){
+ if( rc==SQLITE_OK ){
int i;
for(i=0; inCol; i++){
if( abPK[i] ){
@@ -1173,14 +1205,321 @@
pTab->bStat1 = 1;
}
- if( pSession->bEnableSize ){
+ if( pSession && pSession->bEnableSize ){
pSession->nMaxChangesetSize += (
1 + sessionVarintLen(pTab->nCol) + pTab->nCol + strlen(pTab->zName)+1
);
}
}
}
- return (pSession->rc || pTab->abPK==0);
+
+ if( pSession ){
+ pSession->rc = rc;
+ return (rc || pTab->abPK==0);
+ }
+ return rc;
+}
+
+/*
+** Re-initialize table object pTab.
+*/
+static int sessionReinitTable(sqlite3_session *pSession, SessionTable *pTab){
+ int nCol = 0;
+ const char **azCol = 0;
+ const char **azDflt = 0;
+ u8 *abPK = 0;
+ int bRowid = 0;
+
+ assert( pSession->rc==SQLITE_OK );
+
+ pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb,
+ pTab->zName, &nCol, 0, &azCol, &azDflt, &abPK,
+ (pSession->bImplicitPK ? &bRowid : 0)
+ );
+ if( pSession->rc==SQLITE_OK ){
+ if( pTab->nCol>nCol || pTab->bRowid!=bRowid ){
+ pSession->rc = SQLITE_SCHEMA;
+ }else{
+ int ii;
+ int nOldCol = pTab->nCol;
+ for(ii=0; iinCol ){
+ if( pTab->abPK[ii]!=abPK[ii] ){
+ pSession->rc = SQLITE_SCHEMA;
+ }
+ }else if( abPK[ii] ){
+ pSession->rc = SQLITE_SCHEMA;
+ }
+ }
+
+ if( pSession->rc==SQLITE_OK ){
+ const char **a = pTab->azCol;
+ pTab->azCol = azCol;
+ pTab->nCol = nCol;
+ pTab->azDflt = azDflt;
+ pTab->abPK = abPK;
+ azCol = a;
+ }
+ if( pSession->bEnableSize ){
+ pSession->nMaxChangesetSize += (nCol - nOldCol);
+ pSession->nMaxChangesetSize += sessionVarintLen(nCol);
+ pSession->nMaxChangesetSize -= sessionVarintLen(nOldCol);
+ }
+ }
+ }
+
+ sqlite3_free((char*)azCol);
+ return pSession->rc;
+}
+
+/*
+** Session-change object (*pp) contains an old.* record with fewer than
+** nCol fields. This function updates it with the default values for
+** the missing fields.
+*/
+static void sessionUpdateOneChange(
+ sqlite3_session *pSession, /* For memory accounting */
+ int *pRc, /* IN/OUT: Error code */
+ SessionChange **pp, /* IN/OUT: Change object to update */
+ int nCol, /* Number of columns now in table */
+ sqlite3_stmt *pDflt /* SELECT */
+){
+ SessionChange *pOld = *pp;
+
+ while( pOld->nRecordFieldnRecordField;
+ int eType = sqlite3_column_type(pDflt, iField);
+ switch( eType ){
+ case SQLITE_NULL:
+ nIncr = 1;
+ break;
+ case SQLITE_INTEGER:
+ case SQLITE_FLOAT:
+ nIncr = 9;
+ break;
+ default: {
+ int n = sqlite3_column_bytes(pDflt, iField);
+ nIncr = 1 + sessionVarintLen(n) + n;
+ assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB );
+ break;
+ }
+ }
+
+ nByte = nIncr + (sizeof(SessionChange) + pOld->nRecord);
+ pNew = sessionMalloc64(pSession, nByte);
+ if( pNew==0 ){
+ *pRc = SQLITE_NOMEM;
+ return;
+ }else{
+ memcpy(pNew, pOld, sizeof(SessionChange));
+ pNew->aRecord = (u8*)&pNew[1];
+ memcpy(pNew->aRecord, pOld->aRecord, pOld->nRecord);
+ pNew->aRecord[pNew->nRecord++] = (u8)eType;
+ switch( eType ){
+ case SQLITE_INTEGER: {
+ i64 iVal = sqlite3_column_int64(pDflt, iField);
+ sessionPutI64(&pNew->aRecord[pNew->nRecord], iVal);
+ pNew->nRecord += 8;
+ break;
+ }
+
+ case SQLITE_FLOAT: {
+ double rVal = sqlite3_column_double(pDflt, iField);
+ i64 iVal = 0;
+ memcpy(&iVal, &rVal, sizeof(rVal));
+ sessionPutI64(&pNew->aRecord[pNew->nRecord], iVal);
+ pNew->nRecord += 8;
+ break;
+ }
+
+ case SQLITE_TEXT: {
+ int n = sqlite3_column_bytes(pDflt, iField);
+ const char *z = (const char*)sqlite3_column_text(pDflt, iField);
+ pNew->nRecord += sessionVarintPut(&pNew->aRecord[pNew->nRecord], n);
+ memcpy(&pNew->aRecord[pNew->nRecord], z, n);
+ pNew->nRecord += n;
+ break;
+ }
+
+ case SQLITE_BLOB: {
+ int n = sqlite3_column_bytes(pDflt, iField);
+ const u8 *z = (const u8*)sqlite3_column_blob(pDflt, iField);
+ pNew->nRecord += sessionVarintPut(&pNew->aRecord[pNew->nRecord], n);
+ memcpy(&pNew->aRecord[pNew->nRecord], z, n);
+ pNew->nRecord += n;
+ break;
+ }
+
+ default:
+ assert( eType==SQLITE_NULL );
+ break;
+ }
+
+ sessionFree(pSession, pOld);
+ *pp = pOld = pNew;
+ pNew->nRecordField++;
+ pNew->nMaxSize += nIncr;
+ if( pSession ){
+ pSession->nMaxChangesetSize += nIncr;
+ }
+ }
+ }
+}
+
+/*
+** Ensure that there is room in the buffer to append nByte bytes of data.
+** If not, use sqlite3_realloc() to grow the buffer so that there is.
+**
+** If successful, return zero. Otherwise, if an OOM condition is encountered,
+** set *pRc to SQLITE_NOMEM and return non-zero.
+*/
+static int sessionBufferGrow(SessionBuffer *p, i64 nByte, int *pRc){
+#define SESSION_MAX_BUFFER_SZ (0x7FFFFF00 - 1)
+ i64 nReq = p->nBuf + nByte;
+ if( *pRc==SQLITE_OK && nReq>p->nAlloc ){
+ u8 *aNew;
+ i64 nNew = p->nAlloc ? p->nAlloc : 128;
+
+ do {
+ nNew = nNew*2;
+ }while( nNewSESSION_MAX_BUFFER_SZ ){
+ nNew = SESSION_MAX_BUFFER_SZ;
+ if( nNewaBuf, nNew);
+ if( 0==aNew ){
+ *pRc = SQLITE_NOMEM;
+ }else{
+ p->aBuf = aNew;
+ p->nAlloc = nNew;
+ }
+ }
+ return (*pRc!=SQLITE_OK);
+}
+
+
+/*
+** This function is a no-op if *pRc is other than SQLITE_OK when it is
+** called. Otherwise, append a string to the buffer. All bytes in the string
+** up to (but not including) the nul-terminator are written to the buffer.
+**
+** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before
+** returning.
+*/
+static void sessionAppendStr(
+ SessionBuffer *p,
+ const char *zStr,
+ int *pRc
+){
+ int nStr = sqlite3Strlen30(zStr);
+ if( 0==sessionBufferGrow(p, nStr+1, pRc) ){
+ memcpy(&p->aBuf[p->nBuf], zStr, nStr);
+ p->nBuf += nStr;
+ p->aBuf[p->nBuf] = 0x00;
+ }
+}
+
+/*
+** Format a string using printf() style formatting and then append it to the
+** buffer using sessionAppendString().
+*/
+static void sessionAppendPrintf(
+ SessionBuffer *p, /* Buffer to append to */
+ int *pRc,
+ const char *zFmt,
+ ...
+){
+ if( *pRc==SQLITE_OK ){
+ char *zApp = 0;
+ va_list ap;
+ va_start(ap, zFmt);
+ zApp = sqlite3_vmprintf(zFmt, ap);
+ if( zApp==0 ){
+ *pRc = SQLITE_NOMEM;
+ }else{
+ sessionAppendStr(p, zApp, pRc);
+ }
+ va_end(ap);
+ sqlite3_free(zApp);
+ }
+}
+
+/*
+** Prepare a statement against database handle db that SELECTs a single
+** row containing the default values for each column in table pTab. For
+** example, if pTab is declared as:
+**
+** CREATE TABLE pTab(a PRIMARY KEY, b DEFAULT 123, c DEFAULT 'abcd');
+**
+** Then this function prepares and returns the SQL statement:
+**
+** SELECT NULL, 123, 'abcd';
+*/
+static int sessionPrepareDfltStmt(
+ sqlite3 *db, /* Database handle */
+ SessionTable *pTab, /* Table to prepare statement for */
+ sqlite3_stmt **ppStmt /* OUT: Statement handle */
+){
+ SessionBuffer sql = {0,0,0};
+ int rc = SQLITE_OK;
+ const char *zSep = " ";
+ int ii = 0;
+
+ *ppStmt = 0;
+ sessionAppendPrintf(&sql, &rc, "SELECT");
+ for(ii=0; iinCol; ii++){
+ const char *zDflt = pTab->azDflt[ii] ? pTab->azDflt[ii] : "NULL";
+ sessionAppendPrintf(&sql, &rc, "%s%s", zSep, zDflt);
+ zSep = ", ";
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_prepare_v2(db, (const char*)sql.aBuf, -1, ppStmt, 0);
+ }
+ sqlite3_free(sql.aBuf);
+
+ return rc;
+}
+
+/*
+** Table pTab has one or more existing change-records with old.* records
+** with fewer than pTab->nCol columns. This function updates all such
+** change-records with the default values for the missing columns.
+*/
+static int sessionUpdateChanges(sqlite3_session *pSession, SessionTable *pTab){
+ sqlite3_stmt *pStmt = 0;
+ int rc = pSession->rc;
+
+ rc = sessionPrepareDfltStmt(pSession->db, pTab, &pStmt);
+ if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+ int ii = 0;
+ SessionChange **pp = 0;
+ for(ii=0; iinChange; ii++){
+ for(pp=&pTab->apChange[ii]; *pp; pp=&((*pp)->pNext)){
+ if( (*pp)->nRecordField!=pTab->nCol ){
+ sessionUpdateOneChange(pSession, &rc, pp, pTab->nCol, pStmt);
+ }
+ }
+ }
+ }
+
+ pSession->rc = rc;
+ rc = sqlite3_finalize(pStmt);
+ if( pSession->rc==SQLITE_OK ) pSession->rc = rc;
+ return pSession->rc;
}
/*
@@ -1343,16 +1682,22 @@
int iHash;
int bNull = 0;
int rc = SQLITE_OK;
+ int nExpect = 0;
SessionStat1Ctx stat1 = {{0,0,0,0,0},0};
if( pSession->rc ) return;
/* Load table details if required */
- if( sessionInitTable(pSession, pTab) ) return;
+ if( sessionInitTable(pSession, pTab, pSession->db, pSession->zDb) ) return;
/* Check the number of columns in this xPreUpdate call matches the
** number of columns in the table. */
- if( (pTab->nCol-pTab->bRowid)!=pSession->hook.xCount(pSession->hook.pCtx) ){
+ nExpect = pSession->hook.xCount(pSession->hook.pCtx);
+ if( (pTab->nCol-pTab->bRowid)nCol-pTab->bRowid)!=nExpect ){
pSession->rc = SQLITE_SCHEMA;
return;
}
@@ -1429,7 +1774,7 @@
}
/* Allocate the change object */
- pC = (SessionChange *)sessionMalloc64(pSession, nByte);
+ pC = (SessionChange*)sessionMalloc64(pSession, nByte);
if( !pC ){
rc = SQLITE_NOMEM;
goto error_out;
@@ -1462,6 +1807,7 @@
if( pSession->bIndirect || pSession->hook.xDepth(pSession->hook.pCtx) ){
pC->bIndirect = 1;
}
+ pC->nRecordField = pTab->nCol;
pC->nRecord = nByte;
pC->op = op;
pC->pNext = pTab->apChange[iHash];
@@ -1841,7 +2187,7 @@
/* Locate and if necessary initialize the target table object */
rc = sessionFindTable(pSession, zTbl, &pTo);
if( pTo==0 ) goto diff_out;
- if( sessionInitTable(pSession, pTo) ){
+ if( sessionInitTable(pSession, pTo, pSession->db, pSession->zDb) ){
rc = pSession->rc;
goto diff_out;
}
@@ -1854,7 +2200,7 @@
int bRowid = 0;
u8 *abPK;
const char **azCol = 0;
- rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, &abPK,
+ rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, 0, &abPK,
pSession->bImplicitPK ? &bRowid : 0
);
if( rc==SQLITE_OK ){
@@ -1969,6 +2315,7 @@
sessionFree(pSession, p);
}
}
+ sqlite3_finalize(pTab->pDfltStmt);
sessionFree(pSession, (char*)pTab->azCol); /* cast works around VC++ bug */
sessionFree(pSession, pTab->apChange);
sessionFree(pSession, pTab);
@@ -2003,7 +2350,7 @@
/* Assert that all allocations have been freed and then free the
** session object itself. */
- assert( pSession->nMalloc==0 );
+ // assert( pSession->nMalloc==0 );
sqlite3_free(pSession);
}
@@ -2075,48 +2422,6 @@
}
/*
-** Ensure that there is room in the buffer to append nByte bytes of data.
-** If not, use sqlite3_realloc() to grow the buffer so that there is.
-**
-** If successful, return zero. Otherwise, if an OOM condition is encountered,
-** set *pRc to SQLITE_NOMEM and return non-zero.
-*/
-static int sessionBufferGrow(SessionBuffer *p, i64 nByte, int *pRc){
-#define SESSION_MAX_BUFFER_SZ (0x7FFFFF00 - 1)
- i64 nReq = p->nBuf + nByte;
- if( *pRc==SQLITE_OK && nReq>p->nAlloc ){
- u8 *aNew;
- i64 nNew = p->nAlloc ? p->nAlloc : 128;
-
- do {
- nNew = nNew*2;
- }while( nNewSESSION_MAX_BUFFER_SZ ){
- nNew = SESSION_MAX_BUFFER_SZ;
- if( nNewaBuf, nNew);
- if( 0==aNew ){
- *pRc = SQLITE_NOMEM;
- }else{
- p->aBuf = aNew;
- p->nAlloc = nNew;
- }
- }
- return (*pRc!=SQLITE_OK);
-}
-
-/*
** Append the value passed as the second argument to the buffer passed
** as the first.
**
@@ -2186,27 +2491,6 @@
/*
** This function is a no-op if *pRc is other than SQLITE_OK when it is
-** called. Otherwise, append a string to the buffer. All bytes in the string
-** up to (but not including) the nul-terminator are written to the buffer.
-**
-** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before
-** returning.
-*/
-static void sessionAppendStr(
- SessionBuffer *p,
- const char *zStr,
- int *pRc
-){
- int nStr = sqlite3Strlen30(zStr);
- if( 0==sessionBufferGrow(p, nStr+1, pRc) ){
- memcpy(&p->aBuf[p->nBuf], zStr, nStr);
- p->nBuf += nStr;
- p->aBuf[p->nBuf] = 0x00;
- }
-}
-
-/*
-** This function is a no-op if *pRc is other than SQLITE_OK when it is
** called. Otherwise, append the string representation of integer iVal
** to the buffer. No nul-terminator is written.
**
@@ -2223,27 +2507,6 @@
sessionAppendStr(p, aBuf, pRc);
}
-static void sessionAppendPrintf(
- SessionBuffer *p, /* Buffer to append to */
- int *pRc,
- const char *zFmt,
- ...
-){
- if( *pRc==SQLITE_OK ){
- char *zApp = 0;
- va_list ap;
- va_start(ap, zFmt);
- zApp = sqlite3_vmprintf(zFmt, ap);
- if( zApp==0 ){
- *pRc = SQLITE_NOMEM;
- }else{
- sessionAppendStr(p, zApp, pRc);
- }
- va_end(ap);
- sqlite3_free(zApp);
- }
-}
-
/*
** This function is a no-op if *pRc is other than SQLITE_OK when it is
** called. Otherwise, append the string zStr enclosed in quotes (") and
@@ -2734,26 +2997,16 @@
for(pTab=pSession->pTable; rc==SQLITE_OK && pTab; pTab=pTab->pNext){
if( pTab->nEntry ){
const char *zName = pTab->zName;
- int nCol = 0; /* Number of columns in table */
- u8 *abPK = 0; /* Primary key array */
- const char **azCol = 0; /* Table columns */
int i; /* Used to iterate through hash buckets */
sqlite3_stmt *pSel = 0; /* SELECT statement to query table pTab */
int nRewind = buf.nBuf; /* Initial size of write buffer */
int nNoop; /* Size of buffer after writing tbl header */
- int bRowid = 0;
+ int nOldCol = pTab->nCol;
/* Check the table schema is still Ok. */
- rc = sessionTableInfo(
- 0, db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK,
- (pSession->bImplicitPK ? &bRowid : 0)
- );
- if( rc==SQLITE_OK && (
- pTab->nCol!=nCol
- || pTab->bRowid!=bRowid
- || memcmp(abPK, pTab->abPK, nCol)
- )){
- rc = SQLITE_SCHEMA;
+ rc = sessionReinitTable(pSession, pTab);
+ if( rc==SQLITE_OK && pTab->nCol!=nOldCol ){
+ rc = sessionUpdateChanges(pSession, pTab);
}
/* Write a table header */
@@ -2761,8 +3014,8 @@
/* Build and compile a statement to execute: */
if( rc==SQLITE_OK ){
- rc = sessionSelectStmt(
- db, 0, pSession->zDb, zName, bRowid, nCol, azCol, abPK, &pSel
+ rc = sessionSelectStmt(db, 0, pSession->zDb,
+ zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel
);
}
@@ -2771,22 +3024,22 @@
SessionChange *p; /* Used to iterate through changes */
for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){
- rc = sessionSelectBind(pSel, nCol, abPK, p);
+ rc = sessionSelectBind(pSel, pTab->nCol, pTab->abPK, p);
if( rc!=SQLITE_OK ) continue;
if( sqlite3_step(pSel)==SQLITE_ROW ){
if( p->op==SQLITE_INSERT ){
int iCol;
sessionAppendByte(&buf, SQLITE_INSERT, &rc);
sessionAppendByte(&buf, p->bIndirect, &rc);
- for(iCol=0; iColnCol; iCol++){
sessionAppendCol(&buf, pSel, iCol, &rc);
}
}else{
- assert( abPK!=0 ); /* Because sessionSelectStmt() returned ok */
- rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, abPK);
+ assert( pTab->abPK!=0 );
+ rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, pTab->abPK);
}
}else if( p->op!=SQLITE_INSERT ){
- rc = sessionAppendDelete(&buf, bPatchset, p, nCol, abPK);
+ rc = sessionAppendDelete(&buf, bPatchset, p, pTab->nCol,pTab->abPK);
}
if( rc==SQLITE_OK ){
rc = sqlite3_reset(pSel);
@@ -2811,7 +3064,6 @@
if( buf.nBuf==nNoop ){
buf.nBuf = nRewind;
}
- sqlite3_free((char*)azCol); /* cast works around VC++ bug */
}
}
@@ -3235,15 +3487,19 @@
}
}
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
- sqlite3_int64 v = sessionGetI64(aVal);
- if( eType==SQLITE_INTEGER ){
- sqlite3VdbeMemSetInt64(apOut[i], v);
+ if( (pIn->nData-pIn->iNext)<8 ){
+ rc = SQLITE_CORRUPT_BKPT;
}else{
- double d;
- memcpy(&d, &v, 8);
- sqlite3VdbeMemSetDouble(apOut[i], d);
+ sqlite3_int64 v = sessionGetI64(aVal);
+ if( eType==SQLITE_INTEGER ){
+ sqlite3VdbeMemSetInt64(apOut[i], v);
+ }else{
+ double d;
+ memcpy(&d, &v, 8);
+ sqlite3VdbeMemSetDouble(apOut[i], d);
+ }
+ pIn->iNext += 8;
}
- pIn->iNext += 8;
}
}
}
@@ -4936,7 +5192,7 @@
sqlite3changeset_pk(pIter, &abPK, 0);
rc = sessionTableInfo(0, db, "main", zNew,
- &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK, &sApply.bRowid
+ &sApply.nCol, &zTab, &sApply.azCol, 0, &sApply.abPK, &sApply.bRowid
);
if( rc!=SQLITE_OK ) break;
for(i=0; iflags & SQLITE_FkNoAction;
+
+ if( flags & SQLITE_CHANGESETAPPLY_FKNOACTION ){
+ db->flags |= ((u64)SQLITE_FkNoAction);
+ db->aDb[0].pSchema->schema_cookie -= 32;
+ }
+
if( rc==SQLITE_OK ){
rc = sessionChangesetApply(
db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
);
}
+
+ if( (flags & SQLITE_CHANGESETAPPLY_FKNOACTION) && savedFlag==0 ){
+ assert( db->flags & SQLITE_FkNoAction );
+ db->flags &= ~((u64)SQLITE_FkNoAction);
+ db->aDb[0].pSchema->schema_cookie -= 32;
+ }
return rc;
}
@@ -5160,6 +5429,9 @@
int rc; /* Error code */
int bPatch; /* True to accumulate patchsets */
SessionTable *pList; /* List of tables in current patch */
+
+ sqlite3 *db; /* Configured by changegroup_schema() */
+ char *zDb; /* Configured by changegroup_schema() */
};
/*
@@ -5180,6 +5452,7 @@
){
SessionChange *pNew = 0;
int rc = SQLITE_OK;
+ assert( aRec!=0 );
if( !pExist ){
pNew = (SessionChange *)sqlite3_malloc64(sizeof(SessionChange) + nRec);
@@ -5346,6 +5619,114 @@
}
/*
+** Check if a changeset entry with nCol columns and the PK array passed
+** as the final argument to this function is compatible with SessionTable
+** pTab. If so, return 1. Otherwise, if they are incompatible in some way,
+** return 0.
+*/
+static int sessionChangesetCheckCompat(
+ SessionTable *pTab,
+ int nCol,
+ u8 *abPK
+){
+ if( pTab->azCol && nColnCol ){
+ int ii;
+ for(ii=0; iinCol; ii++){
+ u8 bPK = (ii < nCol) ? abPK[ii] : 0;
+ if( pTab->abPK[ii]!=bPK ) return 0;
+ }
+ return 1;
+ }
+ return (pTab->nCol==nCol && 0==memcmp(abPK, pTab->abPK, nCol));
+}
+
+static int sessionChangesetExtendRecord(
+ sqlite3_changegroup *pGrp,
+ SessionTable *pTab,
+ int nCol,
+ int op,
+ const u8 *aRec,
+ int nRec,
+ SessionBuffer *pOut
+){
+ int rc = SQLITE_OK;
+ int ii = 0;
+
+ assert( pTab->azCol );
+ assert( nColnCol );
+
+ pOut->nBuf = 0;
+ if( op==SQLITE_INSERT || (op==SQLITE_DELETE && pGrp->bPatch==0) ){
+ /* Append the missing default column values to the record. */
+ sessionAppendBlob(pOut, aRec, nRec, &rc);
+ if( rc==SQLITE_OK && pTab->pDfltStmt==0 ){
+ rc = sessionPrepareDfltStmt(pGrp->db, pTab, &pTab->pDfltStmt);
+ }
+ for(ii=nCol; rc==SQLITE_OK && iinCol; ii++){
+ int eType = sqlite3_column_type(pTab->pDfltStmt, ii);
+ sessionAppendByte(pOut, eType, &rc);
+ switch( eType ){
+ case SQLITE_FLOAT:
+ case SQLITE_INTEGER: {
+ i64 iVal;
+ if( eType==SQLITE_INTEGER ){
+ iVal = sqlite3_column_int64(pTab->pDfltStmt, ii);
+ }else{
+ double rVal = sqlite3_column_int64(pTab->pDfltStmt, ii);
+ memcpy(&iVal, &rVal, sizeof(i64));
+ }
+ if( SQLITE_OK==sessionBufferGrow(pOut, 8, &rc) ){
+ sessionPutI64(&pOut->aBuf[pOut->nBuf], iVal);
+ }
+ break;
+ }
+
+ case SQLITE_BLOB:
+ case SQLITE_TEXT: {
+ int n = sqlite3_column_bytes(pTab->pDfltStmt, ii);
+ sessionAppendVarint(pOut, n, &rc);
+ if( eType==SQLITE_TEXT ){
+ const u8 *z = (const u8*)sqlite3_column_text(pTab->pDfltStmt, ii);
+ sessionAppendBlob(pOut, z, n, &rc);
+ }else{
+ const u8 *z = (const u8*)sqlite3_column_blob(pTab->pDfltStmt, ii);
+ sessionAppendBlob(pOut, z, n, &rc);
+ }
+ break;
+ }
+
+ default:
+ assert( eType==SQLITE_NULL );
+ break;
+ }
+ }
+ }else if( op==SQLITE_UPDATE ){
+ /* Append missing "undefined" entries to the old.* record. And, if this
+ ** is an UPDATE, to the new.* record as well. */
+ int iOff = 0;
+ if( pGrp->bPatch==0 ){
+ for(ii=0; iinCol-nCol); ii++){
+ sessionAppendByte(pOut, 0x00, &rc);
+ }
+ }
+
+ sessionAppendBlob(pOut, &aRec[iOff], nRec-iOff, &rc);
+ for(ii=0; ii<(pTab->nCol-nCol); ii++){
+ sessionAppendByte(pOut, 0x00, &rc);
+ }
+ }else{
+ assert( op==SQLITE_DELETE && pGrp->bPatch );
+ sessionAppendBlob(pOut, aRec, nRec, &rc);
+ }
+
+ return rc;
+}
+
+/*
** Add all changes in the changeset traversed by the iterator passed as
** the first argument to the changegroup hash tables.
*/
@@ -5358,6 +5739,7 @@
int nRec;
int rc = SQLITE_OK;
SessionTable *pTab = 0;
+ SessionBuffer rec = {0, 0, 0};
while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){
const char *zNew;
@@ -5369,6 +5751,9 @@
SessionChange *pExist = 0;
SessionChange **pp;
+ /* Ensure that only changesets, or only patchsets, but not a mixture
+ ** of both, are being combined. It is an error to try to combine a
+ ** changeset and a patchset. */
if( pGrp->pList==0 ){
pGrp->bPatch = pIter->bPatchset;
}else if( pIter->bPatchset!=pGrp->bPatch ){
@@ -5401,18 +5786,38 @@
pTab->zName = (char*)&pTab->abPK[nCol];
memcpy(pTab->zName, zNew, nNew+1);
+ if( pGrp->db ){
+ pTab->nCol = 0;
+ rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb);
+ if( rc ){
+ assert( pTab->azCol==0 );
+ sqlite3_free(pTab);
+ break;
+ }
+ }
+
/* The new object must be linked on to the end of the list, not
** simply added to the start of it. This is to ensure that the
** tables within the output of sqlite3changegroup_output() are in
** the right order. */
for(ppTab=&pGrp->pList; *ppTab; ppTab=&(*ppTab)->pNext);
*ppTab = pTab;
- }else if( pTab->nCol!=nCol || memcmp(pTab->abPK, abPK, nCol) ){
+ }
+
+ if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){
rc = SQLITE_SCHEMA;
break;
}
}
+ if( nColnCol ){
+ assert( pGrp->db );
+ rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, &rec);
+ if( rc ) break;
+ aRec = rec.aBuf;
+ nRec = rec.nBuf;
+ }
+
if( sessionGrowHash(0, pIter->bPatchset, pTab) ){
rc = SQLITE_NOMEM;
break;
@@ -5450,6 +5855,7 @@
}
}
+ sqlite3_free(rec.aBuf);
if( rc==SQLITE_OK ) rc = pIter->rc;
return rc;
}
@@ -5537,6 +5943,31 @@
}
/*
+** Provide a database schema to the changegroup object.
+*/
+int sqlite3changegroup_schema(
+ sqlite3_changegroup *pGrp,
+ sqlite3 *db,
+ const char *zDb
+){
+ int rc = SQLITE_OK;
+
+ if( pGrp->pList || pGrp->db ){
+ /* Cannot add a schema after one or more calls to sqlite3changegroup_add(),
+ ** or after sqlite3changegroup_schema() has already been called. */
+ rc = SQLITE_MISUSE;
+ }else{
+ pGrp->zDb = sqlite3_mprintf("%s", zDb);
+ if( pGrp->zDb==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ pGrp->db = db;
+ }
+ }
+ return rc;
+}
+
+/*
** Add the changeset currently stored in buffer pData, size nData bytes,
** to changeset-group p.
*/
@@ -5599,6 +6030,7 @@
*/
void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){
if( pGrp ){
+ sqlite3_free(pGrp->zDb);
sessionDeleteTable(0, pGrp->pList);
sqlite3_free(pGrp);
}
diff -Nru sqlite3-3.42.0/ext/session/sqlite3session.h sqlite3-3.44.0-0/ext/session/sqlite3session.h
--- sqlite3-3.42.0/ext/session/sqlite3session.h 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/sqlite3session.h 2023-11-04 14:23:58.000000000 +0000
@@ -885,6 +885,18 @@
/*
+** CAPI3REF: Upgrade the Schema of a Changeset/Patchset
+*/
+int sqlite3changeset_upgrade(
+ sqlite3 *db,
+ const char *zDb,
+ int nIn, const void *pIn, /* Input changeset */
+ int *pnOut, void **ppOut /* OUT: Inverse of input */
+);
+
+
+
+/*
** CAPI3REF: Changegroup Handle
**
** A changegroup is an object used to combine two or more
@@ -931,6 +943,38 @@
int sqlite3changegroup_new(sqlite3_changegroup **pp);
/*
+** CAPI3REF: Add a Schema to a Changegroup
+** METHOD: sqlite3_changegroup_schema
+**
+** This method may be used to optionally enforce the rule that the changesets
+** added to the changegroup handle must match the schema of database zDb
+** ("main", "temp", or the name of an attached database). If
+** sqlite3changegroup_add() is called to add a changeset that is not compatible
+** with the configured schema, SQLITE_SCHEMA is returned and the changegroup
+** object is left in an undefined state.
+**
+** A changeset schema is considered compatible with the database schema in
+** the same way as for sqlite3changeset_apply(). Specifically, for each
+** table in the changeset, there exists a database table with:
+**
+**
+**
The name identified by the changeset, and
+**
at least as many columns as recorded in the changeset, and
+**
the primary key columns in the same position as recorded in
+** the changeset.
+**
+**
+** The output of the changegroup object always has the same schema as the
+** database nominated using this function. In cases where changesets passed
+** to sqlite3changegroup_add() have fewer columns than the corresponding table
+** in the database schema, these are filled in using the default column
+** values from the database schema. This makes it possible to combined
+** changesets that have different numbers of columns for a single table
+** within a changegroup, provided that they are otherwise compatible.
+*/
+int sqlite3changegroup_schema(sqlite3_changegroup*, sqlite3*, const char *zDb);
+
+/*
** CAPI3REF: Add A Changeset To A Changegroup
** METHOD: sqlite3_changegroup
**
@@ -998,13 +1042,18 @@
** If the new changeset contains changes to a table that is already present
** in the changegroup, then the number of columns and the position of the
** primary key columns for the table must be consistent. If this is not the
-** case, this function fails with SQLITE_SCHEMA. If the input changeset
-** appears to be corrupt and the corruption is detected, SQLITE_CORRUPT is
-** returned. Or, if an out-of-memory condition occurs during processing, this
-** function returns SQLITE_NOMEM. In all cases, if an error occurs the state
-** of the final contents of the changegroup is undefined.
+** case, this function fails with SQLITE_SCHEMA. Except, if the changegroup
+** object has been configured with a database schema using the
+** sqlite3changegroup_schema() API, then it is possible to combine changesets
+** with different numbers of columns for a single table, provided that
+** they are otherwise compatible.
+**
+** If the input changeset appears to be corrupt and the corruption is
+** detected, SQLITE_CORRUPT is returned. Or, if an out-of-memory condition
+** occurs during processing, this function returns SQLITE_NOMEM.
**
-** If no error occurs, SQLITE_OK is returned.
+** In all cases, if an error occurs the state of the final contents of the
+** changegroup is undefined. If no error occurs, SQLITE_OK is returned.
*/
int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
@@ -1269,10 +1318,17 @@
**
an insert change if all fields of the conflicting row match
** the row being inserted.
**
+**
+**
SQLITE_CHANGESETAPPLY_FKNOACTION
+** If this flag it set, then all foreign key constraints in the target
+** database behave as if they were declared with "ON UPDATE NO ACTION ON
+** DELETE NO ACTION", even if they are actually CASCADE, RESTRICT, SET NULL
+** or SET DEFAULT.
*/
#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001
#define SQLITE_CHANGESETAPPLY_INVERT 0x0002
#define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004
+#define SQLITE_CHANGESETAPPLY_FKNOACTION 0x0008
/*
** CAPI3REF: Constants Passed To The Conflict Handler
diff -Nru sqlite3-3.42.0/ext/session/test_session.c sqlite3-3.44.0-0/ext/session/test_session.c
--- sqlite3-3.42.0/ext/session/test_session.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/session/test_session.c 2023-11-04 14:23:58.000000000 +0000
@@ -392,7 +392,6 @@
};
size_t sz = sizeof(aOpt[0]);
- int rc;
int iArg;
int iOpt;
if( Tcl_GetIndexFromObjStruct(interp,objv[2],aOpt,sz,"option",0,&iOpt) ){
@@ -812,9 +811,12 @@
while( objc>1 ){
const char *z1 = Tcl_GetString(objv[1]);
int n = strlen(z1);
- if( n>1 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){
+ if( n>3 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){
flags |= SQLITE_CHANGESETAPPLY_NOSAVEPOINT;
}
+ else if( n>3 && n<=9 && 0==sqlite3_strnicmp("-noaction", z1, n) ){
+ flags |= SQLITE_CHANGESETAPPLY_FKNOACTION;
+ }
else if( n>2 && n<=7 && 0==sqlite3_strnicmp("-invert", z1, n) ){
flags |= SQLITE_CHANGESETAPPLY_INVERT;
}
@@ -1452,12 +1454,144 @@
return TCL_OK;
}
+typedef struct TestChangegroup TestChangegroup;
+struct TestChangegroup {
+ sqlite3_changegroup *pGrp;
+};
+
+/*
+** Destructor for Tcl changegroup command object.
+*/
+static void test_changegroup_del(void *clientData){
+ TestChangegroup *pGrp = (TestChangegroup*)clientData;
+ sqlite3changegroup_delete(pGrp->pGrp);
+ ckfree(pGrp);
+}
+
+/*
+** Tclcmd: $changegroup schema DB DBNAME
+** Tclcmd: $changegroup add CHANGESET
+** Tclcmd: $changegroup output
+** Tclcmd: $changegroup delete
+*/
+static int SQLITE_TCLAPI test_changegroup_cmd(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ TestChangegroup *p = (TestChangegroup*)clientData;
+ static struct ChangegroupCmd {
+ const char *zSub;
+ int nArg;
+ const char *zMsg;
+ int iSub;
+ } aSub[] = {
+ { "schema", 2, "DB DBNAME", }, /* 0 */
+ { "add", 1, "CHANGESET", }, /* 1 */
+ { "output", 0, "", }, /* 2 */
+ { "delete", 0, "", }, /* 3 */
+ { 0 }
+ };
+ int rc = TCL_OK;
+ int iSub = 0;
+
+ if( objc<2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
+ return TCL_ERROR;
+ }
+ rc = Tcl_GetIndexFromObjStruct(interp,
+ objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
+ );
+ if( rc!=TCL_OK ) return rc;
+ if( objc!=2+aSub[iSub].nArg ){
+ Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
+ return TCL_ERROR;
+ }
+
+ switch( iSub ){
+ case 0: { /* schema */
+ sqlite3 *db = 0;
+ const char *zDb = Tcl_GetString(objv[3]);
+ if( dbHandleFromObj(interp, objv[2], &db) ){
+ return TCL_ERROR;
+ }
+ rc = sqlite3changegroup_schema(p->pGrp, db, zDb);
+ if( rc!=SQLITE_OK ) rc = test_session_error(interp, rc, 0);
+ break;
+ };
+
+ case 1: { /* add */
+ int nByte = 0;
+ const u8 *aByte = Tcl_GetByteArrayFromObj(objv[2], &nByte);
+ rc = sqlite3changegroup_add(p->pGrp, nByte, (void*)aByte);
+ if( rc!=SQLITE_OK ) rc = test_session_error(interp, rc, 0);
+ break;
+ };
+
+ case 2: { /* output */
+ int nByte = 0;
+ u8 *aByte = 0;
+ rc = sqlite3changegroup_output(p->pGrp, &nByte, (void**)&aByte);
+ if( rc!=SQLITE_OK ){
+ rc = test_session_error(interp, rc, 0);
+ }else{
+ Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(aByte, nByte));
+ }
+ sqlite3_free(aByte);
+ break;
+ };
+
+ default: { /* delete */
+ assert( iSub==3 );
+ Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/*
+** Tclcmd: sqlite3changegroup CMD
+*/
+static int SQLITE_TCLAPI test_sqlite3changegroup(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ int rc; /* sqlite3changegroup_new() return code */
+ TestChangegroup *p; /* New wrapper object */
+
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "CMD");
+ return TCL_ERROR;
+ }
+
+ p = (TestChangegroup*)ckalloc(sizeof(TestChangegroup));
+ memset(p, 0, sizeof(TestChangegroup));
+ rc = sqlite3changegroup_new(&p->pGrp);
+ if( rc!=SQLITE_OK ){
+ ckfree((char*)p);
+ return test_session_error(interp, rc, 0);
+ }
+
+ Tcl_CreateObjCommand(
+ interp, Tcl_GetString(objv[1]), test_changegroup_cmd, (ClientData)p,
+ test_changegroup_del
+ );
+ Tcl_SetObjResult(interp, objv[1]);
+ return TCL_OK;
+}
+
int TestSession_Init(Tcl_Interp *interp){
struct Cmd {
const char *zCmd;
Tcl_ObjCmdProc *xProc;
} aCmd[] = {
{ "sqlite3session", test_sqlite3session },
+ { "sqlite3changegroup", test_sqlite3changegroup },
{ "sqlite3session_foreach", test_sqlite3session_foreach },
{ "sqlite3changeset_invert", test_sqlite3changeset_invert },
{ "sqlite3changeset_concat", test_sqlite3changeset_concat },
diff -Nru sqlite3-3.42.0/ext/wasm/api/extern-post-js.c-pp.js sqlite3-3.44.0-0/ext/wasm/api/extern-post-js.c-pp.js
--- sqlite3-3.42.0/ext/wasm/api/extern-post-js.c-pp.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/api/extern-post-js.c-pp.js 2023-11-04 14:23:58.000000000 +0000
@@ -23,10 +23,7 @@
impls which Emscripten installs at some point in the file above
this.
*/
- const originalInit =
- /* Maintenance reminder: DO NOT use `self.` here. It's correct
- for non-ES6 Module cases but wrong for ES6 modules because those
- resolve this symbol differently. */ sqlite3InitModule;
+ const originalInit = sqlite3InitModule;
if(!originalInit){
throw new Error("Expecting globalThis.sqlite3InitModule to be defined by the Emscripten build.");
}
@@ -65,19 +62,19 @@
globalThis.sqlite3InitModule = function ff(...args){
//console.warn("Using replaced sqlite3InitModule()",globalThis.location);
return originalInit(...args).then((EmscriptenModule)=>{
+//#if wasmfs
if('undefined'!==typeof WorkerGlobalScope &&
- (EmscriptenModule['ENVIRONMENT_IS_PTHREAD']
- || EmscriptenModule['_pthread_self']
- || 'function'===typeof threadAlert
- || globalThis?.location?.pathname?.endsWith?.('.worker.js')
- )){
+ EmscriptenModule['ENVIRONMENT_IS_PTHREAD']){
/** Workaround for wasmfs-generated worker, which calls this
routine from each individual thread and requires that its
- argument be returned. All of the criteria above are fragile,
- based solely on inspection of the offending code, not public
- Emscripten details. */
+ argument be returned. The conditional criteria above are
+ fragile, based solely on inspection of the offending code,
+ not public Emscripten details. */
+ //console.warn("sqlite3InitModule() returning E-module.",EmscriptenModule);
return EmscriptenModule;
}
+//#endif
+ //console.warn("sqlite3InitModule() returning sqlite3 object.");
const s = EmscriptenModule.sqlite3;
s.scriptInfo = initModuleState;
//console.warn("sqlite3.scriptInfo =",s.scriptInfo);
@@ -124,5 +121,6 @@
return globalThis.sqlite3InitModule /* required for ESM */;
})();
//#if target=es6-module
-export default toExportForESM;
+sqlite3InitModule = toExportForESM;
+export default sqlite3InitModule;
//#endif
diff -Nru sqlite3-3.42.0/ext/wasm/api/README.md sqlite3-3.44.0-0/ext/wasm/api/README.md
--- sqlite3-3.42.0/ext/wasm/api/README.md 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/api/README.md 2023-11-04 14:23:58.000000000 +0000
@@ -83,15 +83,18 @@
helpers for use by downstream code which creates `sqlite3_vfs`
and `sqlite3_module` implementations.
- **`sqlite3-vfs-opfs.c-pp.js`**\
- is an sqlite3 VFS implementation which supports Google Chrome's
- Origin-Private FileSystem (OPFS) as a storage layer to provide
- persistent storage for database files in a browser. It requires...
+ is an sqlite3 VFS implementation which supports the Origin-Private
+ FileSystem (OPFS) as a storage layer to provide persistent storage
+ for database files in a browser. It requires...
- **`sqlite3-opfs-async-proxy.js`**\
is the asynchronous backend part of the OPFS proxy. It speaks
directly to the (async) OPFS API and channels those results back
to its synchronous counterpart. This file, because it must be
started in its own Worker, is not part of the amalgamation.
-- **`api/sqlite3-api-cleanup.js`**\
+- **`sqlite3-vfs-opfs-sahpool.c-pp.js`**\
+ is another sqlite3 VFS supporting the OPFS, but uses a completely
+ different approach that the above-listed one.
+- **`sqlite3-api-cleanup.js`**\
The previous files do not immediately extend the library. Instead
they add callback functions to be called during its
bootstrapping. Some also temporarily create global objects in order
@@ -108,13 +111,15 @@
with `c-pp`](#c-pp), noting that such preprocessing may be applied
after all of the relevant files are concatenated. That extension is
used primarily to keep the code maintainers cognisant of the fact that
-those files contain constructs which will not run as-is in JavaScript.
+those files contain constructs which may not run as-is in any given
+JavaScript environment.
The build process glues those files together, resulting in
-`sqlite3-api.js`, which is everything except for the `post-js-*.js`
-files, and `sqlite3.js`, which is the Emscripten-generated amalgamated
-output and includes the `post-js-*.js` parts, as well as the
-Emscripten-provided module loading pieces.
+`sqlite3-api.js`, which is everything except for the
+`pre/post-js-*.js` files, and `sqlite3.js`, which is the
+Emscripten-generated amalgamated output and includes the
+`pre/post-js-*.js` parts, as well as the Emscripten-provided module
+loading pieces.
The non-JS outlier file is `sqlite3-wasm.c`: it is a proxy for
`sqlite3.c` which `#include`'s that file and adds a couple more
@@ -152,8 +157,8 @@
------------------------------------------------------------------------
Certain files in the build require preprocessing to filter in/out
-parts which differ between vanilla JS builds and ES6 Module
-(a.k.a. esm) builds. The preprocessor application itself is in
+parts which differ between vanilla JS, ES6 Modules, and node.js
+builds. The preprocessor application itself is in
[`c-pp.c`](/file/ext/wasm/c-pp.c) and the complete technical details
of such preprocessing are maintained in
[`GNUMakefile`](/file/ext/wasm/GNUmakefile).
diff -Nru sqlite3-3.42.0/ext/wasm/api/sqlite3-api-cleanup.js sqlite3-3.44.0-0/ext/wasm/api/sqlite3-api-cleanup.js
--- sqlite3-3.42.0/ext/wasm/api/sqlite3-api-cleanup.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/api/sqlite3-api-cleanup.js 2023-11-04 14:23:58.000000000 +0000
@@ -22,8 +22,10 @@
*/
const SABC = Object.assign(
Object.create(null), {
- exports: Module['asm'],
- memory: Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */
+ exports: ('undefined'===typeof wasmExports)
+ ? Module['asm']/* emscripten <=3.1.43 */
+ : wasmExports /* emscripten >=3.1.44 */,
+ memory: Module.wasmMemory /* gets set if built with -sIMPORTED_MEMORY */
},
globalThis.sqlite3ApiConfig || {}
);
diff -Nru sqlite3-3.42.0/ext/wasm/api/sqlite3-api-glue.js sqlite3-3.44.0-0/ext/wasm/api/sqlite3-api-glue.js
--- sqlite3-3.42.0/ext/wasm/api/sqlite3-api-glue.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/api/sqlite3-api-glue.js 2023-11-04 14:23:58.000000000 +0000
@@ -608,6 +608,7 @@
["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"],
["sqlite3_wasm_vfs_create_file", "int",
"sqlite3_vfs*","string","*", "int"],
+ ["sqlite3_wasm_posix_create_file", "int", "string","*", "int"],
["sqlite3_wasm_vfs_unlink", "int", "sqlite3_vfs*","string"]
];
@@ -728,6 +729,15 @@
Populate api object with sqlite3_...() by binding the "raw" wasm
exports into type-converting proxies using wasm.xWrap().
*/
+ if(0 === wasm.exports.sqlite3_step.length){
+ /* This environment wraps exports in nullary functions, which means
+ we must disable the arg-count validation we otherwise perform
+ on the wrappers. */
+ wasm.xWrap.doArgcCheck = false;
+ sqlite3.config.warn(
+ "Disabling sqlite3.wasm.xWrap.doArgcCheck due to environmental quirks."
+ );
+ }
for(const e of wasm.bindingSignatures){
capi[e[0]] = wasm.xWrap.apply(null, e);
}
@@ -878,9 +888,9 @@
consistency with non-special-case wrappings.
*/
const __dbArgcMismatch = (pDb,f,n)=>{
- return sqlite3.util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE,
- f+"() requires "+n+" argument"+
- (1===n?"":'s')+".");
+ return util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE,
+ f+"() requires "+n+" argument"+
+ (1===n?"":'s')+".");
};
/** Code duplication reducer for functions which take an encoding
diff -Nru sqlite3-3.42.0/ext/wasm/api/sqlite3-api-oo1.js sqlite3-3.44.0-0/ext/wasm/api/sqlite3-api-oo1.js
--- sqlite3-3.42.0/ext/wasm/api/sqlite3-api-oo1.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/api/sqlite3-api-oo1.js 2023-11-04 14:23:58.000000000 +0000
@@ -55,6 +55,7 @@
if(sqliteResultCode){
if(dbPtr instanceof DB) dbPtr = dbPtr.pointer;
toss3(
+ sqliteResultCode,
"sqlite3 result code",sqliteResultCode+":",
(dbPtr
? capi.sqlite3_errmsg(dbPtr)
@@ -330,10 +331,15 @@
- `db`: the DB object which created the statement.
- - `columnCount`: the number of result columns in the query, or 0 for
- queries which cannot return results.
+ - `columnCount`: the number of result columns in the query, or 0
+ for queries which cannot return results. This property is a proxy
+ for sqlite3_column_count() and its use in loops should be avoided
+ because of the call overhead associated with that. The
+ `columnCount` is not cached when the Stmt is created because a
+ schema change made via a separate db connection between this
+ statement's preparation and when it is stepped may invalidate it.
- - `parameterCount`: the number of bindable paramters in the query.
+ - `parameterCount`: the number of bindable parameters in the query.
*/
const Stmt = function(){
if(BindTypes!==arguments[2]){
@@ -341,7 +347,6 @@
}
this.db = arguments[0];
__ptrMap.set(this, arguments[1]);
- this.columnCount = capi.sqlite3_column_count(this.pointer);
this.parameterCount = capi.sqlite3_bind_parameter_count(this.pointer);
};
@@ -473,7 +478,9 @@
const __selectFirstRow = (db, sql, bind, ...getArgs)=>{
const stmt = db.prepare(sql);
try {
- return stmt.bind(bind).step() ? stmt.get(...getArgs) : undefined;
+ const rc = stmt.bind(bind).step() ? stmt.get(...getArgs) : undefined;
+ stmt.reset(/*for INSERT...RETURNING locking case*/);
+ return rc;
}finally{
stmt.finalize();
}
@@ -499,6 +506,9 @@
"Not an error." The various non-0 non-error codes need to be
checked for in client code where they are expected.
+ The thrown exception's `resultCode` property will be the value of
+ the second argument to this function.
+
If it does not throw, it returns its first argument.
*/
DB.checkRc = (db,resultCode)=>checkSqlite3Rc(db,resultCode);
@@ -546,7 +556,10 @@
}
const pDb = this.pointer;
Object.keys(__stmtMap.get(this)).forEach((k,s)=>{
- if(s && s.pointer) s.finalize();
+ if(s && s.pointer){
+ try{s.finalize()}
+ catch(e){/*ignore*/}
+ }
});
__ptrMap.delete(this);
__stmtMap.delete(this);
@@ -701,18 +714,18 @@
with identical names.
- `callback` = a function which gets called for each row of the
- result set, but only if that statement has any result
- _rows_. The callback's "this" is the options object, noting
- that this function synthesizes one if the caller does not pass
- one to exec(). The second argument passed to the callback is
- always the current Stmt object, as it's needed if the caller
- wants to fetch the column names or some such (noting that they
- could also be fetched via `this.columnNames`, if the client
- provides the `columnNames` option). If the callback returns a
- literal `false` (as opposed to any other falsy value, e.g. an
- implicit `undefined` return), any ongoing statement-`step()`
- iteration stops without an error. The return value of the
- callback is otherwise ignored.
+ result set, but only if that statement has any result rows. The
+ callback's "this" is the options object, noting that this
+ function synthesizes one if the caller does not pass one to
+ exec(). The second argument passed to the callback is always
+ the current Stmt object, as it's needed if the caller wants to
+ fetch the column names or some such (noting that they could
+ also be fetched via `this.columnNames`, if the client provides
+ the `columnNames` option). If the callback returns a literal
+ `false` (as opposed to any other falsy value, e.g. an implicit
+ `undefined` return), any ongoing statement-`step()` iteration
+ stops without an error. The return value of the callback is
+ otherwise ignored.
ACHTUNG: The callback MUST NOT modify the Stmt object. Calling
any of the Stmt.get() variants, Stmt.getColumnName(), or
@@ -733,7 +746,7 @@
A.1) `'array'` (the default) causes the results of
`stmt.get([])` to be passed to the `callback` and/or appended
- to `resultRows`
+ to `resultRows`.
A.2) `'object'` causes the results of
`stmt.get(Object.create(null))` to be passed to the
@@ -744,8 +757,8 @@
A.3) `'stmt'` causes the current Stmt to be passed to the
callback, but this mode will trigger an exception if
- `resultRows` is an array because appending the statement to
- the array would be downright unhelpful.
+ `resultRows` is an array because appending the transient
+ statement to the array would be downright unhelpful.
B) An integer, indicating a zero-based column in the result
row. Only that one single value will be passed on.
@@ -775,7 +788,7 @@
should return:
A) The default value is (usually) `"this"`, meaning that the
- DB object itself should be returned. The exceptions is if
+ DB object itself should be returned. The exception is if
the caller passes neither of `callback` nor `returnValue`
but does pass an explicit `rowMode` then the default
`returnValue` is `"resultRows"`, described below.
@@ -857,38 +870,53 @@
bind = null;
}
if(evalFirstResult && stmt.columnCount){
- /* Only forward SELECT results for the FIRST query
+ /* Only forward SELECT-style results for the FIRST query
in the SQL which potentially has them. */
+ let gotColNames = Array.isArray(
+ opt.columnNames
+ /* As reported in
+ https://sqlite.org/forum/forumpost/7774b773937cbe0a
+ we need to delay fetching of the column names until
+ after the first step() (if we step() at all) because
+ a schema change between the prepare() and step(), via
+ another connection, may invalidate the column count
+ and names. */) ? 0 : 1;
evalFirstResult = false;
- if(Array.isArray(opt.columnNames)){
- stmt.getColumnNames(opt.columnNames);
- }
if(arg.cbArg || resultRows){
- for(; stmt.step(); stmt._isLocked = false){
- stmt._isLocked = true;
+ for(; stmt.step(); stmt._lockedByExec = false){
+ if(0===gotColNames++) stmt.getColumnNames(opt.columnNames);
+ stmt._lockedByExec = true;
const row = arg.cbArg(stmt);
if(resultRows) resultRows.push(row);
if(callback && false === callback.call(opt, row, stmt)){
break;
}
}
- stmt._isLocked = false;
+ stmt._lockedByExec = false;
+ }
+ if(0===gotColNames){
+ /* opt.columnNames was provided but we visited no result rows */
+ stmt.getColumnNames(opt.columnNames);
}
}else{
stmt.step();
}
- stmt.finalize();
+ stmt.reset(
+ /* In order to trigger an exception in the
+ INSERT...RETURNING locking scenario:
+ https://sqlite.org/forum/forumpost/36f7a2e7494897df
+ */).finalize();
stmt = null;
- }
+ }/*prepare() loop*/
}/*catch(e){
sqlite3.config.warn("DB.exec() is propagating exception",opt,e);
throw e;
}*/finally{
+ wasm.scopedAllocPop(stack);
if(stmt){
- delete stmt._isLocked;
+ delete stmt._lockedByExec;
stmt.finalize();
}
- wasm.scopedAllocPop(stack);
}
return arg.returnVal();
}/*exec()*/,
@@ -1107,6 +1135,7 @@
try {
stmt.bind(bind);
while(stmt.step()) rc.push(stmt.get(0,asType));
+ stmt.reset(/*for INSERT...RETURNING locking case*/);
}finally{
stmt.finalize();
}
@@ -1241,7 +1270,7 @@
not throw, it returns this object.
*/
checkRc: function(resultCode){
- return DB.checkRc(this, resultCode);
+ return checkSqlite3Rc(this, resultCode);
}
}/*DB.prototype*/;
@@ -1302,15 +1331,15 @@
};
/**
- If stmt._isLocked is truthy, this throws an exception
+ If stmt._lockedByExec is truthy, this throws an exception
complaining that the 2nd argument (an operation name,
e.g. "bind()") is not legal while the statement is "locked".
Locking happens before an exec()-like callback is passed a
statement, to ensure that the callback does not mutate or
finalize the statement. If it does not throw, it returns stmt.
*/
- const affirmUnlocked = function(stmt,currentOpName){
- if(stmt._isLocked){
+ const affirmNotLockedByExec = function(stmt,currentOpName){
+ if(stmt._lockedByExec){
toss3("Operation is illegal when statement is locked:",currentOpName);
}
return stmt;
@@ -1323,14 +1352,11 @@
success.
*/
const bindOne = function f(stmt,ndx,bindType,val){
- affirmUnlocked(affirmStmtOpen(stmt), 'bind()');
+ affirmNotLockedByExec(affirmStmtOpen(stmt), 'bind()');
if(!f._){
f._tooBigInt = (v)=>toss3(
"BigInt value is too big to store without precision loss:", v
);
- /* Reminder: when not in BigInt mode, it's impossible for
- JS to represent a number out of the range we can bind,
- so we have no range checking. */
f._ = {
string: function(stmt, ndx, val, asBlob){
const [pStr, n] = wasm.allocCString(val, true);
@@ -1404,46 +1430,67 @@
Stmt.prototype = {
/**
- "Finalizes" this statement. This is a no-op if the
- statement has already been finalizes. Returns
- undefined. Most methods in this class will throw if called
- after this is.
+ "Finalizes" this statement. This is a no-op if the statement
+ has already been finalized. Returns the result of
+ sqlite3_finalize() (0 on success, non-0 on error), or the
+ undefined value if the statement has already been
+ finalized. Regardless of success or failure, most methods in
+ this class will throw if called after this is.
+
+ This method always throws if called when it is illegal to do
+ so. Namely, when triggered via a per-row callback handler of a
+ DB.exec() call.
*/
finalize: function(){
if(this.pointer){
- affirmUnlocked(this,'finalize()');
+ affirmNotLockedByExec(this,'finalize()');
+ const rc = capi.sqlite3_finalize(this.pointer);
delete __stmtMap.get(this.db)[this.pointer];
- capi.sqlite3_finalize(this.pointer);
__ptrMap.delete(this);
delete this._mayGet;
- delete this.columnCount;
delete this.parameterCount;
+ delete this._lockedByExec;
delete this.db;
- delete this._isLocked;
+ return rc;
}
},
- /** Clears all bound values. Returns this object.
- Throws if this statement has been finalized. */
+ /**
+ Clears all bound values. Returns this object. Throws if this
+ statement has been finalized or if modification of the
+ statement is currently illegal (e.g. in the per-row callback of
+ a DB.exec() call).
+ */
clearBindings: function(){
- affirmUnlocked(affirmStmtOpen(this), 'clearBindings()')
+ affirmNotLockedByExec(affirmStmtOpen(this), 'clearBindings()')
capi.sqlite3_clear_bindings(this.pointer);
this._mayGet = false;
return this;
},
/**
- Resets this statement so that it may be step()ed again
- from the beginning. Returns this object. Throws if this
- statement has been finalized.
+ Resets this statement so that it may be step()ed again from the
+ beginning. Returns this object. Throws if this statement has
+ been finalized, if it may not legally be reset because it is
+ currently being used from a DB.exec() callback, or if the
+ underlying call to sqlite3_reset() returns non-0.
If passed a truthy argument then this.clearBindings() is
also called, otherwise any existing bindings, along with
any memory allocated for them, are retained.
+
+ In versions 3.42.0 and earlier, this function did not throw if
+ sqlite3_reset() returns non-0, but it was discovered that
+ throwing (or significant extra client-side code) is necessary
+ in order to avoid certain silent failure scenarios, as
+ discussed at:
+
+ https://sqlite.org/forum/forumpost/36f7a2e7494897df
*/
reset: function(alsoClearBinds){
- affirmUnlocked(this,'reset()');
+ affirmNotLockedByExec(this,'reset()');
if(alsoClearBinds) this.clearBindings();
- capi.sqlite3_reset(affirmStmtOpen(this).pointer);
+ const rc = capi.sqlite3_reset(affirmStmtOpen(this).pointer);
this._mayGet = false;
+ checkSqlite3Rc(this.db, rc);
return this;
},
/**
@@ -1592,7 +1639,7 @@
value is returned. Throws on error.
*/
step: function(){
- affirmUnlocked(this, 'step()');
+ affirmNotLockedByExec(this, 'step()');
const rc = capi.sqlite3_step(affirmStmtOpen(this).pointer);
switch(rc){
case capi.SQLITE_DONE: return this._mayGet = false;
@@ -1627,11 +1674,9 @@
return this.reset();
},
/**
- Functions like step() except that it finalizes this statement
- immediately after stepping unless the step cannot be performed
- because the statement is locked. Throws on error, but any error
- other than the statement-is-locked case will also trigger
- finalization of this statement.
+ Functions like step() except that it calls finalize() on this
+ statement immediately after stepping, even if the step() call
+ throws.
On success, it returns true if the step indicated that a row of
data was available, else it returns false.
@@ -1643,9 +1688,14 @@
```
*/
stepFinalize: function(){
- const rc = this.step();
- this.finalize();
- return rc;
+ try{
+ const rc = this.step();
+ this.reset(/*for INSERT...RETURNING locking case*/);
+ return rc;
+ }finally{
+ try{this.finalize()}
+ catch(e){/*ignored*/}
+ }
},
/**
Fetches the value from the given 0-based column index of
@@ -1686,13 +1736,15 @@
}
if(Array.isArray(ndx)){
let i = 0;
- while(itoss3("The columnCount property is read-only.")
+ });
/** The OO API's public namespace. */
sqlite3.oo1 = {
diff -Nru sqlite3-3.42.0/ext/wasm/api/sqlite3-api-prologue.js sqlite3-3.44.0-0/ext/wasm/api/sqlite3-api-prologue.js
--- sqlite3-3.42.0/ext/wasm/api/sqlite3-api-prologue.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/api/sqlite3-api-prologue.js 2023-11-04 14:23:58.000000000 +0000
@@ -53,7 +53,7 @@
- `memory`[^1]: optional WebAssembly.Memory object, defaulting to
`exports.memory`. In Emscripten environments this should be set
- to `Module.wasmMemory` if the build uses `-sIMPORT_MEMORY`, or be
+ to `Module.wasmMemory` if the build uses `-sIMPORTED_MEMORY`, or be
left undefined/falsy to default to `exports.memory` when using
WASM-exported memory.
@@ -88,12 +88,12 @@
can be replaced with (e.g.) empty functions to squelch all such
output.
- - `wasmfsOpfsDir`[^1]: As of 2022-12-17, this feature does not
- currently work due to incompatible Emscripten-side changes made
- in the WASMFS+OPFS combination. This option is currently ignored.
+ - `wasmfsOpfsDir`[^1]: Specifies the "mount point" of the OPFS-backed
+ filesystem in WASMFS-capable builds.
- [^1] = This property may optionally be a function, in which case this
- function re-assigns calls that function to fetch the value,
+
+ [^1] = This property may optionally be a function, in which case
+ this function calls that function to fetch the value,
enabling delayed evaluation.
The returned object is the top-level sqlite3 namespace object.
@@ -125,11 +125,11 @@
log: console.log.bind(console),
wasmfsOpfsDir: '/opfs',
/**
- useStdAlloc is just for testing an allocator discrepancy. The
+ useStdAlloc is just for testing allocator discrepancies. The
docs guarantee that this is false in the canonical builds. For
99% of purposes it doesn't matter which allocators we use, but
- it becomes significant with, e.g., sqlite3_deserialize()
- and certain wasm.xWrap.resultAdapter()s.
+ it becomes significant with, e.g., sqlite3_deserialize() and
+ certain wasm.xWrap.resultAdapter()s.
*/
useStdAlloc: false
}, apiConfig || {});
@@ -149,11 +149,6 @@
config[k] = config[k]();
}
});
- config.wasmOpfsDir =
- /* 2022-12-17: WASMFS+OPFS can no longer be activated from the
- main thread (aborts via a failed assert() if it's attempted),
- which eliminates any(?) benefit to supporting it. */ false;
-
/**
The main sqlite3 binding API gets installed into this object,
mimicking the C API as closely as we can. The numerous members
@@ -777,8 +772,43 @@
isSharedTypedArray,
toss: function(...args){throw new Error(args.join(' '))},
toss3,
- typedArrayPart
- };
+ typedArrayPart,
+ /**
+ Given a byte array or ArrayBuffer, this function throws if the
+ lead bytes of that buffer do not hold a SQLite3 database header,
+ else it returns without side effects.
+
+ Added in 3.44.
+ */
+ affirmDbHeader: function(bytes){
+ if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes);
+ const header = "SQLite format 3";
+ if( header.length > bytes.byteLength ){
+ toss3("Input does not contain an SQLite3 database header.");
+ }
+ for(let i = 0; i < header.length; ++i){
+ if( header.charCodeAt(i) !== bytes[i] ){
+ toss3("Input does not contain an SQLite3 database header.");
+ }
+ }
+ },
+ /**
+ Given a byte array or ArrayBuffer, this function throws if the
+ database does not, at a cursory glance, appear to be an SQLite3
+ database. It only examines the size and header, but further
+ checks may be added in the future.
+
+ Added in 3.44.
+ */
+ affirmIsDb: function(bytes){
+ if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes);
+ const n = bytes.byteLength;
+ if(n<512 || n%512!==0) {
+ toss3("Byte array size",n,"is invalid for an SQLite3 db.");
+ }
+ util.affirmDbHeader(bytes);
+ }
+ }/*util*/;
Object.assign(wasm, {
/**
@@ -809,7 +839,7 @@
|| toss3("Missing API config.exports (WASM module exports)."),
/**
- When Emscripten compiles with `-sIMPORT_MEMORY`, it
+ When Emscripten compiles with `-sIMPORTED_MEMORY`, it
initalizes the heap and imports it into wasm, as opposed to
the other way around. In this case, the memory is not
available via this.exports.memory.
@@ -1105,7 +1135,23 @@
return 1===n
? wasm.pstack.alloc(safePtrSize ? 8 : wasm.ptrSizeof)
: wasm.pstack.allocChunks(n, safePtrSize ? 8 : wasm.ptrSizeof);
+ },
+
+ /**
+ Records the current pstack position, calls the given function,
+ passing it the sqlite3 object, then restores the pstack
+ regardless of whether the function throws. Returns the result
+ of the call or propagates an exception on error.
+
+ Added in 3.44.
+ */
+ call: function(f){
+ const stackPos = wasm.pstack.pointer;
+ try{ return f(sqlite3) } finally{
+ wasm.pstack.restore(stackPos);
+ }
}
+
})/*wasm.pstack*/;
Object.defineProperties(wasm.pstack, {
/**
@@ -1177,31 +1223,31 @@
/** State for sqlite3_wasmfs_opfs_dir(). */
let __wasmfsOpfsDir = undefined;
/**
- 2022-12-17: incompatible WASMFS changes have made WASMFS+OPFS
- unavailable from the main thread, which eliminates the most
- significant benefit of supporting WASMFS. This function is now a
- no-op which always returns a falsy value. Before that change,
- this function behaved as documented below (and how it will again
- if we can find a compelling reason to support it).
-
If the wasm environment has a WASMFS/OPFS-backed persistent
storage directory, its path is returned by this function. If it
does not then it returns "" (noting that "" is a falsy value).
The first time this is called, this function inspects the current
environment to determine whether persistence support is available
- and, if it is, enables it (if needed).
+ and, if it is, enables it (if needed). After the first call it
+ always returns the cached result.
- This function currently only recognizes the WASMFS/OPFS storage
- combination and its path refers to storage rooted in the
- Emscripten-managed virtual filesystem.
+ If the returned string is not empty, any files stored under the
+ given path (recursively) are housed in OPFS storage. If the
+ returned string is empty, this particular persistent storage
+ option is not available on the client.
+
+ Though the mount point name returned by this function is intended
+ to remain stable, clients should not hard-coded it anywhere. Always call this function to get the path.
+
+ Note that this function is a no-op in must builds of this
+ library, as the WASMFS capability requires a custom
+ build.
*/
capi.sqlite3_wasmfs_opfs_dir = function(){
if(undefined !== __wasmfsOpfsDir) return __wasmfsOpfsDir;
// If we have no OPFS, there is no persistent dir
const pdir = config.wasmfsOpfsDir;
- console.error("sqlite3_wasmfs_opfs_dir() can no longer work due "+
- "to incompatible WASMFS changes. It will be removed.");
if(!pdir
|| !globalThis.FileSystemHandle
|| !globalThis.FileSystemDirectoryHandle
@@ -1223,8 +1269,6 @@
};
/**
- Experimental and subject to change or removal.
-
Returns true if sqlite3.capi.sqlite3_wasmfs_opfs_dir() is a
non-empty string and the given name starts with (that string +
'/'), else returns false.
@@ -1234,13 +1278,6 @@
return (p && name) ? name.startsWith(p+'/') : false;
};
- // This bit is highly arguable and is incompatible with the fiddle shell.
- if(false && 0===wasm.exports.sqlite3_vfs_find(0)){
- /* Assume that sqlite3_initialize() has not yet been called.
- This will be the case in an SQLITE_OS_KV build. */
- wasm.exports.sqlite3_initialize();
- }
-
/**
Given an `sqlite3*`, an sqlite3_vfs name, and an optional db name
(defaulting to "main"), returns a truthy value (see below) if
@@ -1371,6 +1408,74 @@
};
/**
+ If the current environment supports the POSIX file APIs, this routine
+ creates (or overwrites) the given file using those APIs. This is
+ primarily intended for use in Emscripten-based builds where the POSIX
+ APIs are transparently proxied by an in-memory virtual filesystem.
+ It may behave diffrently in other environments.
+
+ The first argument must be either a JS string or WASM C-string
+ holding the filename. Note that this routine does _not_ create
+ intermediary directories if the filename has a directory part.
+
+ The 2nd argument may either a valid WASM memory pointer, an
+ ArrayBuffer, or a Uint8Array. The 3rd must be the length, in
+ bytes, of the data array to copy. If the 2nd argument is an
+ ArrayBuffer or Uint8Array and the 3rd is not a positive integer
+ then the 3rd defaults to the array's byteLength value.
+
+ Results are undefined if data is a WASM pointer and dataLen is
+ exceeds data's bounds.
+
+ Throws if any arguments are invalid or if creating or writing to
+ the file fails.
+
+ Added in 3.43 as an alternative for the deprecated
+ sqlite3_js_vfs_create_file().
+ */
+ capi.sqlite3_js_posix_create_file = function(filename, data, dataLen){
+ let pData;
+ if(data && wasm.isPtr(data)){
+ pData = data;
+ }else if(data instanceof ArrayBuffer || data instanceof Uint8Array){
+ pData = wasm.allocFromTypedArray(data);
+ if(arguments.length<3 || !util.isInt32(dataLen) || dataLen<0){
+ dataLen = data.byteLength;
+ }
+ }else{
+ SQLite3Error.toss("Invalid 2nd argument for sqlite3_js_posix_create_file().");
+ }
+ try{
+ if(!util.isInt32(dataLen) || dataLen<0){
+ SQLite3Error.toss("Invalid 3rd argument for sqlite3_js_posix_create_file().");
+ }
+ const rc = wasm.sqlite3_wasm_posix_create_file(filename, pData, dataLen);
+ if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code",
+ capi.sqlite3_js_rc_str(rc));
+ }finally{
+ wasm.dealloc(pData);
+ }
+ };
+
+ /**
+ Deprecation warning: this function does not work properly in
+ debug builds of sqlite3 because its out-of-scope use of the
+ sqlite3_vfs API triggers assertions in the core library. That
+ was unfortunately not discovered until 2023-08-11. This function
+ is now deprecated and should not be used in new code.
+
+ Alternative options:
+
+ - "unix" VFS and its variants can get equivalent functionality
+ with sqlite3_js_posix_create_file().
+
+ - OPFS: use either sqlite3.oo1.OpfsDb.importDb(), for the "opfs"
+ VFS, or the importDb() method of the PoolUtil object provided
+ by the "opfs-sahpool" OPFS (noting that its VFS name may differ
+ depending on client-side configuration). We cannot proxy those
+ from here because the former is necessarily asynchronous and
+ the latter requires information not available to this function.
+
Creates a file using the storage appropriate for the given
sqlite3_vfs. The first argument may be a VFS name (JS string
only, NOT a WASM C-string), WASM-managed `sqlite3_vfs*`, or
@@ -1416,9 +1521,13 @@
VFS nor the WASM environment imposes requirements which break it.
- "opfs": uses OPFS storage and creates directory parts of the
- filename.
+ filename. It can only be used to import an SQLite3 database
+ file and will fail if given anything else.
*/
capi.sqlite3_js_vfs_create_file = function(vfs, filename, data, dataLen){
+ config.warn("sqlite3_js_vfs_create_file() is deprecated and",
+ "should be avoided because it can lead to C-level crashes.",
+ "See its documentation for alternative options.");
let pData;
if(data){
if(wasm.isPtr(data)){
@@ -1446,10 +1555,30 @@
if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code",
capi.sqlite3_js_rc_str(rc));
}finally{
- wasm.dealloc(pData);
+ wasm.dealloc(pData);
}
};
+ /**
+ Converts SQL input from a variety of convenient formats
+ to plain strings.
+
+ If v is a string, it is returned as-is. If it is-a Array, its
+ join("") result is returned. If is is a Uint8Array, Int8Array,
+ or ArrayBuffer, it is assumed to hold UTF-8-encoded text and is
+ decoded to a string. If it looks like a WASM pointer,
+ wasm.cstrToJs(sql) is returned. Else undefined is returned.
+
+ Added in 3.44
+ */
+ capi.sqlite3_js_sql_to_string = (sql)=>{
+ if('string' === typeof sql){
+ return sql;
+ }
+ const x = flexibleString(v);
+ return x===v ? undefined : x;
+ }
+
if( util.isUIThread() ){
/* Features specific to the main window thread... */
@@ -1659,7 +1788,8 @@
do not.
*/
tgt.push(capi.sqlite3_value_to_js(
- wasm.peekPtr(pArgv + (wasm.ptrSizeof * i))
+ wasm.peekPtr(pArgv + (wasm.ptrSizeof * i)),
+ throwIfCannotConvert
));
}
return tgt;
@@ -1875,6 +2005,9 @@
client: undefined,
/**
+ This function is not part of the public interface, but a
+ piece of internal bootstrapping infrastructure.
+
Performs any optional asynchronous library-level initialization
which might be required. This function returns a Promise which
resolves to the sqlite3 namespace object. Any error in the
@@ -1890,27 +2023,19 @@
then it must be called by client-level code, which must not use
the library until the returned promise resolves.
- Bug: if called while a prior call is still resolving, the 2nd
- call will resolve prematurely, before the 1st call has finished
- resolving. The current build setup precludes that possibility,
- so it's only a hypothetical problem if/when this function
- ever needs to be invoked by clients.
+ If called multiple times it will return the same promise on
+ subsequent calls. The current build setup precludes that
+ possibility, so it's only a hypothetical problem if/when this
+ function ever needs to be invoked by clients.
In Emscripten-based builds, this function is called
automatically and deleted from this object.
*/
- asyncPostInit: async function(){
- let lip = sqlite3ApiBootstrap.initializersAsync;
+ asyncPostInit: async function ff(){
+ if(ff.isReady instanceof Promise) return ff.isReady;
+ let lia = sqlite3ApiBootstrap.initializersAsync;
delete sqlite3ApiBootstrap.initializersAsync;
- if(!lip || !lip.length) return Promise.resolve(sqlite3);
- lip = lip.map((f)=>{
- const p = (f instanceof Promise) ? f : f(sqlite3);
- return p.catch((e)=>{
- console.error("an async sqlite3 initializer failed:",e);
- throw e;
- });
- });
- const postInit = ()=>{
+ const postInit = async ()=>{
if(!sqlite3.__isUnderTest){
/* Delete references to internal-only APIs which are used by
some initializers. Retain them when running in test mode
@@ -1919,23 +2044,25 @@
/* It's conceivable that we might want to expose
StructBinder to client-side code, but it's only useful if
clients build their own sqlite3.wasm which contains their
- one C struct types. */
+ own C struct types. */
delete sqlite3.StructBinder;
}
return sqlite3;
};
- if(1){
- /* Run all initializers in sequence. The advantage is that it
- allows us to have post-init cleanup defined outside of this
- routine at the end of the list and have it run at a
- well-defined time. */
- let p = lip.shift();
- while(lip.length) p = p.then(lip.shift());
- return p.then(postInit);
- }else{
- /* Run them in an arbitrary order. */
- return Promise.all(lip).then(postInit);
+ const catcher = (e)=>{
+ config.error("an async sqlite3 initializer failed:",e);
+ throw e;
+ };
+ if(!lia || !lia.length){
+ return ff.isReady = postInit().catch(catcher);
}
+ lia = lia.map((f)=>{
+ return (f instanceof Function) ? async x=>f(sqlite3) : f;
+ });
+ lia.push(postInit);
+ let p = Promise.resolve(sqlite3);
+ while(lia.length) p = p.then(lia.shift());
+ return ff.isReady = p.catch(catcher);
},
/**
scriptInfo ideally gets injected into this object by the
@@ -1995,7 +2122,7 @@
specifically for initializers which are asynchronous. All entries in
this list must be either async functions, non-async functions which
return a Promise, or a Promise. Each function in the list is called
- with the sqlite3 ojbect as its only argument.
+ with the sqlite3 object as its only argument.
The resolved value of any Promise is ignored and rejection will kill
the asyncPostInit() process (at an indeterminate point because all
diff -Nru sqlite3-3.42.0/ext/wasm/api/sqlite3-api-worker1.js sqlite3-3.44.0-0/ext/wasm/api/sqlite3-api-worker1.js
--- sqlite3-3.42.0/ext/wasm/api/sqlite3-api-worker1.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/api/sqlite3-api-worker1.js 2023-11-04 14:23:58.000000000 +0000
@@ -278,6 +278,19 @@
The arguments are in the same form accepted by oo1.DB.exec(), with
the exceptions noted below.
+ If the `countChanges` arguments property (added in version 3.43) is
+ truthy then the `result` property contained by the returned object
+ will have a `changeCount` property which holds the number of changes
+ made by the provided SQL. Because the SQL may contain an arbitrary
+ number of statements, the `changeCount` is calculated by calling
+ `sqlite3_total_changes()` before and after the SQL is evaluated. If
+ the value of `countChanges` is 64 then the `changeCount` property
+ will be returned as a 64-bit integer in the form of a BigInt (noting
+ that that will trigger an exception if used in a BigInt-incapable
+ build). In the latter case, the number of changes is calculated by
+ calling `sqlite3_total_changes64()` before and after the SQL is
+ evaluated.
+
A function-type args.callback property cannot cross
the window/Worker boundary, so is not useful here. If
args.callback is a string then it is assumed to be a
@@ -320,7 +333,6 @@
if(!(globalThis.WorkerGlobalScope instanceof Function)){
toss("initWorker1API() must be run from a Worker thread.");
}
- const self = this.self;
const sqlite3 = this.sqlite3 || toss("Missing this.sqlite3 object.");
const DB = sqlite3.oo1.DB;
@@ -523,7 +535,13 @@
}
}
try {
+ const changeCount = !!rc.countChanges
+ ? db.changes(true,(64===rc.countChanges))
+ : undefined;
db.exec(rc);
+ if(undefined !== changeCount){
+ rc.changeCount = db.changes(true,64===rc.countChanges) - changeCount;
+ }
if(rc.callback instanceof Function){
rc.callback = theCallback;
/* Post a sentinel message to tell the client that the end
@@ -638,5 +656,5 @@
}, wState.xfer);
};
globalThis.postMessage({type:'sqlite3-api',result:'worker1-ready'});
-}.bind({self, sqlite3});
+}.bind({sqlite3});
});
diff -Nru sqlite3-3.42.0/ext/wasm/api/sqlite3-opfs-async-proxy.js sqlite3-3.44.0-0/ext/wasm/api/sqlite3-opfs-async-proxy.js
--- sqlite3-3.42.0/ext/wasm/api/sqlite3-opfs-async-proxy.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/api/sqlite3-opfs-async-proxy.js 2023-11-04 14:23:58.000000000 +0000
@@ -35,6 +35,9 @@
https://developer.chrome.com/blog/sync-methods-for-accesshandles/
+ Firefox v111 and Safari 16.4, both released in March 2023, also
+ include this.
+
We cannot change to the sync forms at this point without breaking
clients who use Chrome v104-ish or higher. truncate(), getSize(),
flush(), and close() are now (as of v108) synchronous. Calling them
@@ -818,9 +821,24 @@
}
while(!flagAsyncShutdown){
try {
- if('timed-out'===Atomics.wait(
+ if('not-equal'!==Atomics.wait(
state.sabOPView, state.opIds.whichOp, 0, state.asyncIdleWaitTime
)){
+ /* Maintenance note: we compare against 'not-equal' because
+
+ https://github.com/tomayac/sqlite-wasm/issues/12
+
+ is reporting that this occassionally, under high loads,
+ returns 'ok', which leads to the whichOp being 0 (which
+ isn't a valid operation ID and leads to an exception,
+ along with a corresponding ugly console log
+ message). Unfortunately, the conditions for that cannot
+ be reliably reproduced. The only place in our code which
+ writes a 0 to the state.opIds.whichOp SharedArrayBuffer
+ index is a few lines down from here, and that instance
+ is required in order for clear communication between
+ the sync half of this proxy and this half.
+ */
await releaseImplicitLocks();
continue;
}
diff -Nru sqlite3-3.42.0/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js sqlite3-3.44.0-0/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
--- sqlite3-3.42.0/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 2023-11-04 14:23:58.000000000 +0000
@@ -1,3 +1,4 @@
+//#ifnot target=node
/*
2022-09-18
@@ -23,7 +24,7 @@
installOpfsVfs() returns a Promise which, on success, installs an
sqlite3_vfs named "opfs", suitable for use with all sqlite3 APIs
which accept a VFS. It is intended to be called via
- sqlite3ApiBootstrap.initializersAsync or an equivalent mechanism.
+ sqlite3ApiBootstrap.initializers or an equivalent mechanism.
The installed VFS uses the Origin-Private FileSystem API for
all file storage. On error it is rejected with an exception
@@ -101,6 +102,10 @@
options = Object.create(null);
}
const urlParams = new URL(globalThis.location.href).searchParams;
+ if(urlParams.has('opfs-disable')){
+ //sqlite3.config.warn('Explicitly not installing "opfs" VFS due to opfs-disable flag.');
+ return Promise.resolve(sqlite3);
+ }
if(undefined===options.verbose){
options.verbose = urlParams.has('opfs-verbose')
? (+urlParams.get('opfs-verbose') || 2) : 1;
@@ -118,11 +123,11 @@
options.proxyUri = options.proxyUri();
}
const thePromise = new Promise(function(promiseResolve_, promiseReject_){
- const loggers = {
- 0:sqlite3.config.error,
- 1:sqlite3.config.warn,
- 2:sqlite3.config.log
- };
+ const loggers = [
+ sqlite3.config.error,
+ sqlite3.config.warn,
+ sqlite3.config.log
+ ];
const logImpl = (level,...args)=>{
if(options.verbose>level) loggers[level]("OPFS syncer:",...args);
};
@@ -131,6 +136,7 @@
const error = (...args)=>logImpl(0, ...args);
const toss = sqlite3.util.toss;
const capi = sqlite3.capi;
+ const util = sqlite3.util;
const wasm = sqlite3.wasm;
const sqlite3_vfs = capi.sqlite3_vfs;
const sqlite3_file = capi.sqlite3_file;
@@ -191,17 +197,18 @@
s.count = s.time = 0;
}
}/*metrics*/;
- const opfsVfs = new sqlite3_vfs();
const opfsIoMethods = new sqlite3_io_methods();
+ const opfsVfs = new sqlite3_vfs()
+ .addOnDispose( ()=>opfsIoMethods.dispose());
let promiseWasRejected = undefined;
const promiseReject = (err)=>{
promiseWasRejected = true;
opfsVfs.dispose();
return promiseReject_(err);
};
- const promiseResolve = (value)=>{
+ const promiseResolve = ()=>{
promiseWasRejected = false;
- return promiseResolve_(value);
+ return promiseResolve_(sqlite3);
};
const W =
//#if target=es6-bundler-friendly
@@ -235,17 +242,17 @@
? new sqlite3_vfs(pDVfs)
: null /* dVfs will be null when sqlite3 is built with
SQLITE_OS_OTHER. */;
+ opfsIoMethods.$iVersion = 1;
opfsVfs.$iVersion = 2/*yes, two*/;
opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
opfsVfs.$mxPathname = 1024/*sure, why not?*/;
opfsVfs.$zName = wasm.allocCString("opfs");
// All C-side memory of opfsVfs is zeroed out, but just to be explicit:
opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null;
- opfsVfs.ondispose = [
+ opfsVfs.addOnDispose(
'$zName', opfsVfs.$zName,
- 'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null),
- 'cleanup opfsIoMethods', ()=>opfsIoMethods.dispose()
- ];
+ 'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null)
+ );
/**
Pedantic sidebar about opfsVfs.ondispose: the entries in that array
are items to clean up when opfsVfs.dispose() is called, but in this
@@ -298,6 +305,7 @@
lock contention to free up.
*/
state.asyncIdleWaitTime = 150;
+
/**
Whether the async counterpart should log exceptions to
the serialization channel. That produces a great deal of
@@ -484,7 +492,8 @@
This proxy de/serializes cross-thread function arguments and
output-pointer values via the state.sabIO SharedArrayBuffer,
using the region defined by (state.sabS11nOffset,
- state.sabS11nOffset]. Only one dataset is recorded at a time.
+ state.sabS11nOffset + state.sabS11nSize]. Only one dataset is
+ recorded at a time.
This is not a general-purpose format. It only supports the
range of operations, and data sizes, needed by the
@@ -831,22 +840,19 @@
/* If it turns out that we need to adjust for timezone, see:
https://stackoverflow.com/a/11760121/1458521 */
wasm.poke(pOut, 2440587.5 + (new Date().getTime()/86400000),
- 'double');
+ 'double');
return 0;
},
xCurrentTimeInt64: function(pVfs,pOut){
- // TODO: confirm that this calculation is correct
wasm.poke(pOut, (2440587.5 * 86400000) + new Date().getTime(),
- 'i64');
+ 'i64');
return 0;
},
xDelete: function(pVfs, zName, doSyncDir){
mTimeStart('xDelete');
- opRun('xDelete', wasm.cstrToJs(zName), doSyncDir, false);
- /* We're ignoring errors because we cannot yet differentiate
- between harmless and non-harmless failures. */
+ const rc = opRun('xDelete', wasm.cstrToJs(zName), doSyncDir, false);
mTimeEnd();
- return 0;
+ return rc;
},
xFullPathname: function(pVfs,zName,nOut,pOut){
/* Until/unless we have some notion of "current dir"
@@ -1088,7 +1094,7 @@
propagate any exception on error, rather than returning false.
*/
opfsUtil.unlink = async function(fsEntryName, recursive = false,
- throwOnError = false){
+ throwOnError = false){
try {
const [hDir, filenamePart] =
await opfsUtil.getDirForFilename(fsEntryName, false);
@@ -1163,11 +1169,102 @@
doDir(opt.directory, 0);
};
- //TODO to support fiddle and worker1 db upload:
- //opfsUtil.createFile = function(absName, content=undefined){...}
- //We have sqlite3.wasm.sqlite3_wasm_vfs_create_file() for this
- //purpose but its interface and name are still under
- //consideration.
+ /**
+ impl of importDb() when it's given a function as its second
+ argument.
+ */
+ const importDbChunked = async function(filename, callback){
+ const [hDir, fnamePart] = await opfsUtil.getDirForFilename(filename, true);
+ const hFile = await hDir.getFileHandle(fnamePart, {create:true});
+ let sah = await hFile.createSyncAccessHandle();
+ let nWrote = 0, chunk, checkedHeader = false, err = false;
+ try{
+ sah.truncate(0);
+ while( undefined !== (chunk = await callback()) ){
+ if(chunk instanceof ArrayBuffer) chunk = new Uint8Array(chunk);
+ if( 0===nWrote && chunk.byteLength>=15 ){
+ util.affirmDbHeader(chunk);
+ checkedHeader = true;
+ }
+ sah.write(chunk, {at: nWrote});
+ nWrote += chunk.byteLength;
+ }
+ if( nWrote < 512 || 0!==nWrote % 512 ){
+ toss("Input size",nWrote,"is not correct for an SQLite database.");
+ }
+ if( !checkedHeader ){
+ const header = new Uint8Array(20);
+ sah.read( header, {at: 0} );
+ util.affirmDbHeader( header );
+ }
+ sah.write(new Uint8Array([1,1]), {at: 18}/*force db out of WAL mode*/);
+ return nWrote;
+ }catch(e){
+ await sah.close();
+ sah = undefined;
+ await hDir.removeEntry( fnamePart ).catch(()=>{});
+ throw e;
+ }finally {
+ if( sah ) await sah.close();
+ }
+ };
+
+ /**
+ Asynchronously imports the given bytes (a byte array or
+ ArrayBuffer) into the given database file.
+
+ If passed a function for its second argument, its behaviour
+ changes to async and it imports its data in chunks fed to it by
+ the given callback function. It calls the callback (which may
+ be async) repeatedly, expecting either a Uint8Array or
+ ArrayBuffer (to denote new input) or undefined (to denote
+ EOF). For so long as the callback continues to return
+ non-undefined, it will append incoming data to the given
+ VFS-hosted database file. When called this way, the resolved
+ value of the returned Promise is the number of bytes written to
+ the target file.
+
+ It very specifically requires the input to be an SQLite3
+ database and throws if that's not the case. It does so in
+ order to prevent this function from taking on a larger scope
+ than it is specifically intended to. i.e. we do not want it to
+ become a convenience for importing arbitrary files into OPFS.
+
+ This routine rewrites the database header bytes in the output
+ file (not the input array) to force disabling of WAL mode.
+
+ On error this throws and the state of the input file is
+ undefined (it depends on where the exception was triggered).
+
+ On success, resolves to the number of bytes written.
+ */
+ opfsUtil.importDb = async function(filename, bytes){
+ if( bytes instanceof Function ){
+ return importDbChunked(filename, bytes);
+ }
+ if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes);
+ util.affirmIsDb(bytes);
+ const n = bytes.byteLength;
+ const [hDir, fnamePart] = await opfsUtil.getDirForFilename(filename, true);
+ let sah, err, nWrote = 0;
+ try {
+ const hFile = await hDir.getFileHandle(fnamePart, {create:true});
+ sah = await hFile.createSyncAccessHandle();
+ sah.truncate(0);
+ nWrote = sah.write(bytes, {at: 0});
+ if(nWrote != n){
+ toss("Expected to write "+n+" bytes but wrote "+nWrote+".");
+ }
+ sah.write(new Uint8Array([1,1]), {at: 18}) /* force db out of WAL mode */;
+ return nWrote;
+ }catch(e){
+ if( sah ){ await sah.close(); sah = undefined; }
+ await hDir.removeEntry( fnamePart ).catch(()=>{});
+ throw e;
+ }finally{
+ if( sah ) await sah.close();
+ }
+ };
if(sqlite3.oo1){
const OpfsDb = function(...args){
@@ -1177,6 +1274,7 @@
};
OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
sqlite3.oo1.OpfsDb = OpfsDb;
+ OpfsDb.importDb = opfsUtil.importDb;
sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql(
opfsVfs.pointer,
function(oo1Db, sqlite3){
@@ -1185,19 +1283,23 @@
contention. */
sqlite3.capi.sqlite3_busy_timeout(oo1Db, 10000);
sqlite3.capi.sqlite3_exec(oo1Db, [
- /* Truncate journal mode is faster than delete for
- this vfs, per speedtest1. That gap seems to have closed with
- Chrome version 108 or 109, but "persist" is very roughly 5-6%
- faster than truncate in initial tests.
-
- For later analysis: Roy Hashimoto notes that TRUNCATE
- and PERSIST modes may decrease OPFS concurrency because
- multiple connections can open the journal file in those
- modes:
+ /* As of July 2023, the PERSIST journal mode on OPFS is
+ somewhat slower than DELETE or TRUNCATE (it was faster
+ before Chrome version 108 or 109). TRUNCATE and DELETE
+ have very similar performance on OPFS.
+
+ Roy Hashimoto notes that TRUNCATE and PERSIST modes may
+ decrease OPFS concurrency because multiple connections
+ can open the journal file in those modes:
https://github.com/rhashimoto/wa-sqlite/issues/68
+
+ Given that, and the fact that testing has not revealed
+ any appreciable difference between performance of
+ TRUNCATE and DELETE modes on OPFS, we currently (as of
+ 2023-07-13) default to DELETE mode.
*/
- "pragma journal_mode=persist;",
+ "pragma journal_mode=DELETE;",
/*
This vfs benefits hugely from cache on moderate/large
speedtest1 --size 50 and --size 100 workloads. We
@@ -1318,10 +1420,10 @@
sqlite3.opfs = opfsUtil;
opfsUtil.rootDirectory = d;
log("End of OPFS sqlite3_vfs setup.", opfsVfs);
- promiseResolve(sqlite3);
+ promiseResolve();
}).catch(promiseReject);
}else{
- promiseResolve(sqlite3);
+ promiseResolve();
}
}catch(e){
error(e);
@@ -1358,7 +1460,10 @@
});
}catch(e){
sqlite3.config.error("installOpfsVfs() exception:",e);
- throw e;
+ return Promise.reject(e);
}
});
}/*sqlite3ApiBootstrap.initializers.push()*/);
+//#else
+/* The OPFS VFS parts are elided from builds targeting node.js. */
+//#endif target=node
diff -Nru sqlite3-3.42.0/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js sqlite3-3.44.0-0/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js
--- sqlite3-3.42.0/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,1287 @@
+//#ifnot target=node
+/*
+ 2023-07-14
+
+ The author disclaims copyright to this source code. In place of a
+ legal notice, here is a blessing:
+
+ * May you do good and not evil.
+ * May you find forgiveness for yourself and forgive others.
+ * May you share freely, never taking more than you give.
+
+ ***********************************************************************
+
+ This file holds a sqlite3_vfs backed by OPFS storage which uses a
+ different implementation strategy than the "opfs" VFS. This one is a
+ port of Roy Hashimoto's OPFS SyncAccessHandle pool:
+
+ https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/AccessHandlePoolVFS.js
+
+ As described at:
+
+ https://github.com/rhashimoto/wa-sqlite/discussions/67
+
+ with Roy's explicit permission to permit us to port his to our
+ infrastructure rather than having to clean-room reverse-engineer it:
+
+ https://sqlite.org/forum/forumpost/e140d84e71
+
+ Primary differences from the "opfs" VFS include:
+
+ - This one avoids the need for a sub-worker to synchronize
+ communication between the synchronous C API and the
+ only-partly-synchronous OPFS API.
+
+ - It does so by opening a fixed number of OPFS files at
+ library-level initialization time, obtaining SyncAccessHandles to
+ each, and manipulating those handles via the synchronous sqlite3_vfs
+ interface. If it cannot open them (e.g. they are already opened by
+ another tab) then the VFS will not be installed.
+
+ - Because of that, this one lacks all library-level concurrency
+ support.
+
+ - Also because of that, it does not require the SharedArrayBuffer,
+ so can function without the COOP/COEP HTTP response headers.
+
+ - It can hypothetically support Safari 16.4+, whereas the "opfs" VFS
+ requires v17 due to a subworker/storage bug in 16.x which makes it
+ incompatible with that VFS.
+
+ - This VFS requires the "semi-fully-sync" FileSystemSyncAccessHandle
+ (hereafter "SAH") APIs released with Chrome v108 (and all other
+ major browsers released since March 2023). If that API is not
+ detected, the VFS is not registered.
+*/
+globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
+ 'use strict';
+ const toss = sqlite3.util.toss;
+ const toss3 = sqlite3.util.toss3;
+ const initPromises = Object.create(null);
+ const capi = sqlite3.capi;
+ const util = sqlite3.util;
+ const wasm = sqlite3.wasm;
+ // Config opts for the VFS...
+ const SECTOR_SIZE = 4096;
+ const HEADER_MAX_PATH_SIZE = 512;
+ const HEADER_FLAGS_SIZE = 4;
+ const HEADER_DIGEST_SIZE = 8;
+ const HEADER_CORPUS_SIZE = HEADER_MAX_PATH_SIZE + HEADER_FLAGS_SIZE;
+ const HEADER_OFFSET_FLAGS = HEADER_MAX_PATH_SIZE;
+ const HEADER_OFFSET_DIGEST = HEADER_CORPUS_SIZE;
+ const HEADER_OFFSET_DATA = SECTOR_SIZE;
+ /* Bitmask of file types which may persist across sessions.
+ SQLITE_OPEN_xyz types not listed here may be inadvertently
+ left in OPFS but are treated as transient by this VFS and
+ they will be cleaned up during VFS init. */
+ const PERSISTENT_FILE_TYPES =
+ capi.SQLITE_OPEN_MAIN_DB |
+ capi.SQLITE_OPEN_MAIN_JOURNAL |
+ capi.SQLITE_OPEN_SUPER_JOURNAL |
+ capi.SQLITE_OPEN_WAL /* noting that WAL support is
+ unavailable in the WASM build.*/;
+
+ /** Subdirectory of the VFS's space where "opaque" (randomly-named)
+ files are stored. Changing this effectively invalidates the data
+ stored under older names (orphaning it), so don't do that. */
+ const OPAQUE_DIR_NAME = ".opaque";
+
+ /**
+ Returns short a string of random alphanumeric characters
+ suitable for use as a random filename.
+ */
+ const getRandomName = ()=>Math.random().toString(36).slice(2);
+
+ const textDecoder = new TextDecoder();
+ const textEncoder = new TextEncoder();
+
+ const optionDefaults = Object.assign(Object.create(null),{
+ name: 'opfs-sahpool',
+ directory: undefined /* derived from .name */,
+ initialCapacity: 6,
+ clearOnInit: false,
+ /* Logging verbosity 3+ == everything, 2 == warnings+errors, 1 ==
+ errors only. */
+ verbosity: 2
+ });
+
+ /** Logging routines, from most to least serious. */
+ const loggers = [
+ sqlite3.config.error,
+ sqlite3.config.warn,
+ sqlite3.config.log
+ ];
+ const log = sqlite3.config.log;
+ const warn = sqlite3.config.warn;
+ const error = sqlite3.config.error;
+
+ /* Maps (sqlite3_vfs*) to OpfsSAHPool instances */
+ const __mapVfsToPool = new Map();
+ const getPoolForVfs = (pVfs)=>__mapVfsToPool.get(pVfs);
+ const setPoolForVfs = (pVfs,pool)=>{
+ if(pool) __mapVfsToPool.set(pVfs, pool);
+ else __mapVfsToPool.delete(pVfs);
+ };
+ /* Maps (sqlite3_file*) to OpfsSAHPool instances */
+ const __mapSqlite3File = new Map();
+ const getPoolForPFile = (pFile)=>__mapSqlite3File.get(pFile);
+ const setPoolForPFile = (pFile,pool)=>{
+ if(pool) __mapSqlite3File.set(pFile, pool);
+ else __mapSqlite3File.delete(pFile);
+ };
+
+ /**
+ Impls for the sqlite3_io_methods methods. Maintenance reminder:
+ members are in alphabetical order to simplify finding them.
+ */
+ const ioMethods = {
+ xCheckReservedLock: function(pFile,pOut){
+ const pool = getPoolForPFile(pFile);
+ pool.log('xCheckReservedLock');
+ pool.storeErr();
+ wasm.poke32(pOut, 1);
+ return 0;
+ },
+ xClose: function(pFile){
+ const pool = getPoolForPFile(pFile);
+ pool.storeErr();
+ const file = pool.getOFileForS3File(pFile);
+ if(file) {
+ try{
+ pool.log(`xClose ${file.path}`);
+ pool.mapS3FileToOFile(pFile, false);
+ file.sah.flush();
+ if(file.flags & capi.SQLITE_OPEN_DELETEONCLOSE){
+ pool.deletePath(file.path);
+ }
+ }catch(e){
+ return pool.storeErr(e, capi.SQLITE_IOERR);
+ }
+ }
+ return 0;
+ },
+ xDeviceCharacteristics: function(pFile){
+ return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
+ },
+ xFileControl: function(pFile, opId, pArg){
+ return capi.SQLITE_NOTFOUND;
+ },
+ xFileSize: function(pFile,pSz64){
+ const pool = getPoolForPFile(pFile);
+ pool.log(`xFileSize`);
+ const file = pool.getOFileForS3File(pFile);
+ const size = file.sah.getSize() - HEADER_OFFSET_DATA;
+ //log(`xFileSize ${file.path} ${size}`);
+ wasm.poke64(pSz64, BigInt(size));
+ return 0;
+ },
+ xLock: function(pFile,lockType){
+ const pool = getPoolForPFile(pFile);
+ pool.log(`xLock ${lockType}`);
+ pool.storeErr();
+ const file = pool.getOFileForS3File(pFile);
+ file.lockType = lockType;
+ return 0;
+ },
+ xRead: function(pFile,pDest,n,offset64){
+ const pool = getPoolForPFile(pFile);
+ pool.storeErr();
+ const file = pool.getOFileForS3File(pFile);
+ pool.log(`xRead ${file.path} ${n} @ ${offset64}`);
+ try {
+ const nRead = file.sah.read(
+ wasm.heap8u().subarray(pDest, pDest+n),
+ {at: HEADER_OFFSET_DATA + Number(offset64)}
+ );
+ if(nRead < n){
+ wasm.heap8u().fill(0, pDest + nRead, pDest + n);
+ return capi.SQLITE_IOERR_SHORT_READ;
+ }
+ return 0;
+ }catch(e){
+ return pool.storeErr(e, capi.SQLITE_IOERR);
+ }
+ },
+ xSectorSize: function(pFile){
+ return SECTOR_SIZE;
+ },
+ xSync: function(pFile,flags){
+ const pool = getPoolForPFile(pFile);
+ pool.log(`xSync ${flags}`);
+ pool.storeErr();
+ const file = pool.getOFileForS3File(pFile);
+ //log(`xSync ${file.path} ${flags}`);
+ try{
+ file.sah.flush();
+ return 0;
+ }catch(e){
+ return pool.storeErr(e, capi.SQLITE_IOERR);
+ }
+ },
+ xTruncate: function(pFile,sz64){
+ const pool = getPoolForPFile(pFile);
+ pool.log(`xTruncate ${sz64}`);
+ pool.storeErr();
+ const file = pool.getOFileForS3File(pFile);
+ //log(`xTruncate ${file.path} ${iSize}`);
+ try{
+ file.sah.truncate(HEADER_OFFSET_DATA + Number(sz64));
+ return 0;
+ }catch(e){
+ return pool.storeErr(e, capi.SQLITE_IOERR);
+ }
+ },
+ xUnlock: function(pFile,lockType){
+ const pool = getPoolForPFile(pFile);
+ pool.log('xUnlock');
+ const file = pool.getOFileForS3File(pFile);
+ file.lockType = lockType;
+ return 0;
+ },
+ xWrite: function(pFile,pSrc,n,offset64){
+ const pool = getPoolForPFile(pFile);
+ pool.storeErr();
+ const file = pool.getOFileForS3File(pFile);
+ pool.log(`xWrite ${file.path} ${n} ${offset64}`);
+ try{
+ const nBytes = file.sah.write(
+ wasm.heap8u().subarray(pSrc, pSrc+n),
+ { at: HEADER_OFFSET_DATA + Number(offset64) }
+ );
+ return n===nBytes ? 0 : toss("Unknown write() failure.");
+ }catch(e){
+ return pool.storeErr(e, capi.SQLITE_IOERR);
+ }
+ }
+ }/*ioMethods*/;
+
+ const opfsIoMethods = new capi.sqlite3_io_methods();
+ opfsIoMethods.$iVersion = 1;
+ sqlite3.vfs.installVfs({
+ io: {struct: opfsIoMethods, methods: ioMethods}
+ });
+
+ /**
+ Impls for the sqlite3_vfs methods. Maintenance reminder: members
+ are in alphabetical order to simplify finding them.
+ */
+ const vfsMethods = {
+ xAccess: function(pVfs,zName,flags,pOut){
+ //log(`xAccess ${wasm.cstrToJs(zName)}`);
+ const pool = getPoolForVfs(pVfs);
+ pool.storeErr();
+ try{
+ const name = pool.getPath(zName);
+ wasm.poke32(pOut, pool.hasFilename(name) ? 1 : 0);
+ }catch(e){
+ /*ignored*/
+ wasm.poke32(pOut, 0);
+ }
+ return 0;
+ },
+ xCurrentTime: function(pVfs,pOut){
+ wasm.poke(pOut, 2440587.5 + (new Date().getTime()/86400000),
+ 'double');
+ return 0;
+ },
+ xCurrentTimeInt64: function(pVfs,pOut){
+ wasm.poke(pOut, (2440587.5 * 86400000) + new Date().getTime(),
+ 'i64');
+ return 0;
+ },
+ xDelete: function(pVfs, zName, doSyncDir){
+ const pool = getPoolForVfs(pVfs);
+ pool.log(`xDelete ${wasm.cstrToJs(zName)}`);
+ pool.storeErr();
+ try{
+ pool.deletePath(pool.getPath(zName));
+ return 0;
+ }catch(e){
+ pool.storeErr(e);
+ return capi.SQLITE_IOERR_DELETE;
+ }
+ },
+ xFullPathname: function(pVfs,zName,nOut,pOut){
+ //const pool = getPoolForVfs(pVfs);
+ //pool.log(`xFullPathname ${wasm.cstrToJs(zName)}`);
+ const i = wasm.cstrncpy(pOut, zName, nOut);
+ return i nOut) wasm.poke8(pOut + nOut - 1, 0);
+ }catch(e){
+ return capi.SQLITE_NOMEM;
+ }finally{
+ wasm.scopedAllocPop(scope);
+ }
+ }
+ return e ? (e.sqlite3Rc || capi.SQLITE_IOERR) : 0;
+ },
+ //xSleep is optionally defined below
+ xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
+ const pool = getPoolForVfs(pVfs);
+ try{
+ pool.log(`xOpen ${wasm.cstrToJs(zName)} ${flags}`);
+ // First try to open a path that already exists in the file system.
+ const path = (zName && wasm.peek8(zName))
+ ? pool.getPath(zName)
+ : getRandomName();
+ let sah = pool.getSAHForPath(path);
+ if(!sah && (flags & capi.SQLITE_OPEN_CREATE)) {
+ // File not found so try to create it.
+ if(pool.getFileCount() < pool.getCapacity()) {
+ // Choose an unassociated OPFS file from the pool.
+ sah = pool.nextAvailableSAH();
+ pool.setAssociatedPath(sah, path, flags);
+ }else{
+ // File pool is full.
+ toss('SAH pool is full. Cannot create file',path);
+ }
+ }
+ if(!sah){
+ toss('file not found:',path);
+ }
+ // Subsequent I/O methods are only passed the sqlite3_file
+ // pointer, so map the relevant info we need to that pointer.
+ const file = {path, flags, sah};
+ pool.mapS3FileToOFile(pFile, file);
+ file.lockType = capi.SQLITE_LOCK_NONE;
+ const sq3File = new capi.sqlite3_file(pFile);
+ sq3File.$pMethods = opfsIoMethods.pointer;
+ sq3File.dispose();
+ wasm.poke32(pOutFlags, flags);
+ return 0;
+ }catch(e){
+ pool.storeErr(e);
+ return capi.SQLITE_CANTOPEN;
+ }
+ }/*xOpen()*/
+ }/*vfsMethods*/;
+
+ /**
+ Creates and initializes an sqlite3_vfs instance for an
+ OpfsSAHPool. The argument is the VFS's name (JS string).
+
+ Throws if the VFS name is already registered or if something
+ goes terribly wrong via sqlite3.vfs.installVfs().
+
+ Maintenance reminder: the only detail about the returned object
+ which is specific to any given OpfsSAHPool instance is the $zName
+ member. All other state is identical.
+ */
+ const createOpfsVfs = function(vfsName){
+ if( sqlite3.capi.sqlite3_vfs_find(vfsName)){
+ toss3("VFS name is already registered:", vfsName);
+ }
+ const opfsVfs = new capi.sqlite3_vfs();
+ /* We fetch the default VFS so that we can inherit some
+ methods from it. */
+ const pDVfs = capi.sqlite3_vfs_find(null);
+ const dVfs = pDVfs
+ ? new capi.sqlite3_vfs(pDVfs)
+ : null /* dVfs will be null when sqlite3 is built with
+ SQLITE_OS_OTHER. */;
+ opfsVfs.$iVersion = 2/*yes, two*/;
+ opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
+ opfsVfs.$mxPathname = HEADER_MAX_PATH_SIZE;
+ opfsVfs.addOnDispose(
+ opfsVfs.$zName = wasm.allocCString(vfsName),
+ ()=>setPoolForVfs(opfsVfs.pointer, 0)
+ );
+
+ if(dVfs){
+ /* Inherit certain VFS members from the default VFS,
+ if available. */
+ opfsVfs.$xRandomness = dVfs.$xRandomness;
+ opfsVfs.$xSleep = dVfs.$xSleep;
+ dVfs.dispose();
+ }
+ if(!opfsVfs.$xRandomness && !vfsMethods.xRandomness){
+ /* If the default VFS has no xRandomness(), add a basic JS impl... */
+ vfsMethods.xRandomness = function(pVfs, nOut, pOut){
+ const heap = wasm.heap8u();
+ let i = 0;
+ for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
+ return i;
+ };
+ }
+ if(!opfsVfs.$xSleep && !vfsMethods.xSleep){
+ vfsMethods.xSleep = (pVfs,ms)=>0;
+ }
+ sqlite3.vfs.installVfs({
+ vfs: {struct: opfsVfs, methods: vfsMethods}
+ });
+ return opfsVfs;
+ };
+
+ /**
+ Class for managing OPFS-related state for the
+ OPFS SharedAccessHandle Pool sqlite3_vfs.
+ */
+ class OpfsSAHPool {
+ /* OPFS dir in which VFS metadata is stored. */
+ vfsDir;
+ /* Directory handle to this.vfsDir. */
+ #dhVfsRoot;
+ /* Directory handle to the subdir of this.#dhVfsRoot which holds
+ the randomly-named "opaque" files. This subdir exists in the
+ hope that we can eventually support client-created files in
+ this.#dhVfsRoot. */
+ #dhOpaque;
+ /* Directory handle to this.dhVfsRoot's parent dir. Needed
+ for a VFS-wipe op. */
+ #dhVfsParent;
+ /* Maps SAHs to their opaque file names. */
+ #mapSAHToName = new Map();
+ /* Maps client-side file names to SAHs. */
+ #mapFilenameToSAH = new Map();
+ /* Set of currently-unused SAHs. */
+ #availableSAH = new Set();
+ /* Maps (sqlite3_file*) to xOpen's file objects. */
+ #mapS3FileToOFile_ = new Map();
+
+ /* Maps SAH to an abstract File Object which contains
+ various metadata about that handle. */
+ //#mapSAHToMeta = new Map();
+
+ /** Buffer used by [sg]etAssociatedPath(). */
+ #apBody = new Uint8Array(HEADER_CORPUS_SIZE);
+ // DataView for this.#apBody
+ #dvBody;
+
+ // associated sqlite3_vfs instance
+ #cVfs;
+
+ // Logging verbosity. See optionDefaults.verbosity.
+ #verbosity;
+
+ constructor(options = Object.create(null)){
+ this.#verbosity = options.verbosity ?? optionDefaults.verbosity;
+ this.vfsName = options.name || optionDefaults.name;
+ this.#cVfs = createOpfsVfs(this.vfsName);
+ setPoolForVfs(this.#cVfs.pointer, this);
+ this.vfsDir = options.directory || ("."+this.vfsName);
+ this.#dvBody =
+ new DataView(this.#apBody.buffer, this.#apBody.byteOffset);
+ this.isReady = this
+ .reset(!!(options.clearOnInit ?? optionDefaults.clearOnInit))
+ .then(()=>{
+ if(this.$error) throw this.$error;
+ return this.getCapacity()
+ ? Promise.resolve(undefined)
+ : this.addCapacity(options.initialCapacity
+ || optionDefaults.initialCapacity);
+ });
+ }
+
+ #logImpl(level,...args){
+ if(this.#verbosity>level) loggers[level](this.vfsName+":",...args);
+ };
+ log(...args){this.#logImpl(2, ...args)};
+ warn(...args){this.#logImpl(1, ...args)};
+ error(...args){this.#logImpl(0, ...args)};
+
+ getVfs(){return this.#cVfs}
+
+ /* Current pool capacity. */
+ getCapacity(){return this.#mapSAHToName.size}
+
+ /* Current number of in-use files from pool. */
+ getFileCount(){return this.#mapFilenameToSAH.size}
+
+ /* Returns an array of the names of all
+ currently-opened client-specified filenames. */
+ getFileNames(){
+ const rc = [];
+ const iter = this.#mapFilenameToSAH.keys();
+ for(const n of iter) rc.push(n);
+ return rc;
+ }
+
+// #createFileObject(sah,clientName,opaqueName){
+// const f = Object.assign(Object.create(null),{
+// clientName, opaqueName
+// });
+// this.#mapSAHToMeta.set(sah, f);
+// return f;
+// }
+// #unmapFileObject(sah){
+// this.#mapSAHToMeta.delete(sah);
+// }
+
+ /**
+ Adds n files to the pool's capacity. This change is
+ persistent across settings. Returns a Promise which resolves
+ to the new capacity.
+ */
+ async addCapacity(n){
+ for(let i = 0; i < n; ++i){
+ const name = getRandomName();
+ const h = await this.#dhOpaque.getFileHandle(name, {create:true});
+ const ah = await h.createSyncAccessHandle();
+ this.#mapSAHToName.set(ah,name);
+ this.setAssociatedPath(ah, '', 0);
+ //this.#createFileObject(ah,undefined,name);
+ }
+ return this.getCapacity();
+ }
+
+ /**
+ Reduce capacity by n, but can only reduce up to the limit
+ of currently-available SAHs. Returns a Promise which resolves
+ to the number of slots really removed.
+ */
+ async reduceCapacity(n){
+ let nRm = 0;
+ for(const ah of Array.from(this.#availableSAH)){
+ if(nRm === n || this.getFileCount() === this.getCapacity()){
+ break;
+ }
+ const name = this.#mapSAHToName.get(ah);
+ //this.#unmapFileObject(ah);
+ ah.close();
+ await this.#dhOpaque.removeEntry(name);
+ this.#mapSAHToName.delete(ah);
+ this.#availableSAH.delete(ah);
+ ++nRm;
+ }
+ return nRm;
+ }
+
+ /**
+ Releases all currently-opened SAHs. The only legal
+ operation after this is acquireAccessHandles().
+ */
+ releaseAccessHandles(){
+ for(const ah of this.#mapSAHToName.keys()) ah.close();
+ this.#mapSAHToName.clear();
+ this.#mapFilenameToSAH.clear();
+ this.#availableSAH.clear();
+ }
+
+ /**
+ Opens all files under this.vfsDir/this.#dhOpaque and acquires
+ a SAH for each. returns a Promise which resolves to no value
+ but completes once all SAHs are acquired. If acquiring an SAH
+ throws, SAHPool.$error will contain the corresponding
+ exception.
+
+ If clearFiles is true, the client-stored state of each file is
+ cleared when its handle is acquired, including its name, flags,
+ and any data stored after the metadata block.
+ */
+ async acquireAccessHandles(clearFiles){
+ const files = [];
+ for await (const [name,h] of this.#dhOpaque){
+ if('file'===h.kind){
+ files.push([name,h]);
+ }
+ }
+ return Promise.all(files.map(async([name,h])=>{
+ try{
+ const ah = await h.createSyncAccessHandle()
+ this.#mapSAHToName.set(ah, name);
+ if(clearFiles){
+ ah.truncate(HEADER_OFFSET_DATA);
+ this.setAssociatedPath(ah, '', 0);
+ }else{
+ const path = this.getAssociatedPath(ah);
+ if(path){
+ this.#mapFilenameToSAH.set(path, ah);
+ }else{
+ this.#availableSAH.add(ah);
+ }
+ }
+ }catch(e){
+ this.storeErr(e);
+ this.releaseAccessHandles();
+ throw e;
+ }
+ }));
+ }
+
+ /**
+ Given an SAH, returns the client-specified name of
+ that file by extracting it from the SAH's header.
+
+ On error, it disassociates SAH from the pool and
+ returns an empty string.
+ */
+ getAssociatedPath(sah){
+ sah.read(this.#apBody, {at: 0});
+ // Delete any unexpected files left over by previous
+ // untimely errors...
+ const flags = this.#dvBody.getUint32(HEADER_OFFSET_FLAGS);
+ if(this.#apBody[0] &&
+ ((flags & capi.SQLITE_OPEN_DELETEONCLOSE) ||
+ (flags & PERSISTENT_FILE_TYPES)===0)){
+ warn(`Removing file with unexpected flags ${flags.toString(16)}`,
+ this.#apBody);
+ this.setAssociatedPath(sah, '', 0);
+ return '';
+ }
+
+ const fileDigest = new Uint32Array(HEADER_DIGEST_SIZE / 4);
+ sah.read(fileDigest, {at: HEADER_OFFSET_DIGEST});
+ const compDigest = this.computeDigest(this.#apBody);
+ if(fileDigest.every((v,i) => v===compDigest[i])){
+ // Valid digest
+ const pathBytes = this.#apBody.findIndex((v)=>0===v);
+ if(0===pathBytes){
+ // This file is unassociated, so truncate it to avoid
+ // leaving stale db data laying around.
+ sah.truncate(HEADER_OFFSET_DATA);
+ }
+ return pathBytes
+ ? textDecoder.decode(this.#apBody.subarray(0,pathBytes))
+ : '';
+ }else{
+ // Invalid digest
+ warn('Disassociating file with bad digest.');
+ this.setAssociatedPath(sah, '', 0);
+ return '';
+ }
+ }
+
+ /**
+ Stores the given client-defined path and SQLITE_OPEN_xyz flags
+ into the given SAH. If path is an empty string then the file is
+ disassociated from the pool but its previous name is preserved
+ in the metadata.
+ */
+ setAssociatedPath(sah, path, flags){
+ const enc = textEncoder.encodeInto(path, this.#apBody);
+ if(HEADER_MAX_PATH_SIZE <= enc.written + 1/*NUL byte*/){
+ toss("Path too long:",path);
+ }
+ this.#apBody.fill(0, enc.written, HEADER_MAX_PATH_SIZE);
+ this.#dvBody.setUint32(HEADER_OFFSET_FLAGS, flags);
+
+ const digest = this.computeDigest(this.#apBody);
+ sah.write(this.#apBody, {at: 0});
+ sah.write(digest, {at: HEADER_OFFSET_DIGEST});
+ sah.flush();
+
+ if(path){
+ this.#mapFilenameToSAH.set(path, sah);
+ this.#availableSAH.delete(sah);
+ }else{
+ // This is not a persistent file, so eliminate the contents.
+ sah.truncate(HEADER_OFFSET_DATA);
+ this.#availableSAH.add(sah);
+ }
+ }
+
+ /**
+ Computes a digest for the given byte array and returns it as a
+ two-element Uint32Array. This digest gets stored in the
+ metadata for each file as a validation check. Changing this
+ algorithm invalidates all existing databases for this VFS, so
+ don't do that.
+ */
+ computeDigest(byteArray){
+ let h1 = 0xdeadbeef;
+ let h2 = 0x41c6ce57;
+ for(const v of byteArray){
+ h1 = 31 * h1 + (v * 307);
+ h2 = 31 * h2 + (v * 307);
+ }
+ return new Uint32Array([h1>>>0, h2>>>0]);
+ }
+
+ /**
+ Re-initializes the state of the SAH pool, releasing and
+ re-acquiring all handles.
+
+ See acquireAccessHandles() for the specifics of the clearFiles
+ argument.
+ */
+ async reset(clearFiles){
+ await this.isReady;
+ let h = await navigator.storage.getDirectory();
+ let prev, prevName;
+ for(const d of this.vfsDir.split('/')){
+ if(d){
+ prev = h;
+ h = await h.getDirectoryHandle(d,{create:true});
+ }
+ }
+ this.#dhVfsRoot = h;
+ this.#dhVfsParent = prev;
+ this.#dhOpaque = await this.#dhVfsRoot.getDirectoryHandle(
+ OPAQUE_DIR_NAME,{create:true}
+ );
+ this.releaseAccessHandles();
+ return this.acquireAccessHandles(clearFiles);
+ }
+
+ /**
+ Returns the pathname part of the given argument,
+ which may be any of:
+
+ - a URL object
+ - A JS string representing a file name
+ - Wasm C-string representing a file name
+
+ All "../" parts and duplicate slashes are resolve/removed from
+ the returned result.
+ */
+ getPath(arg) {
+ if(wasm.isPtr(arg)) arg = wasm.cstrToJs(arg);
+ return ((arg instanceof URL)
+ ? arg
+ : new URL(arg, 'file://localhost/')).pathname;
+ }
+
+ /**
+ Removes the association of the given client-specified file
+ name (JS string) from the pool. Returns true if a mapping
+ is found, else false.
+ */
+ deletePath(path) {
+ const sah = this.#mapFilenameToSAH.get(path);
+ if(sah) {
+ // Un-associate the name from the SAH.
+ this.#mapFilenameToSAH.delete(path);
+ this.setAssociatedPath(sah, '', 0);
+ }
+ return !!sah;
+ }
+
+ /**
+ Sets e (an Error object) as this object's current error. Pass a
+ falsy (or no) value to clear it. If code is truthy it is
+ assumed to be an SQLITE_xxx result code, defaulting to
+ SQLITE_IOERR if code is falsy.
+
+ Returns the 2nd argument.
+ */
+ storeErr(e,code){
+ if(e){
+ e.sqlite3Rc = code || capi.SQLITE_IOERR;
+ this.error(e);
+ }
+ this.$error = e;
+ return code;
+ }
+ /**
+ Pops this object's Error object and returns
+ it (a falsy value if no error is set).
+ */
+ popErr(){
+ const rc = this.$error;
+ this.$error = undefined;
+ return rc;
+ }
+
+ /**
+ Returns the next available SAH without removing
+ it from the set.
+ */
+ nextAvailableSAH(){
+ const [rc] = this.#availableSAH.keys();
+ return rc;
+ }
+
+ /**
+ Given an (sqlite3_file*), returns the mapped
+ xOpen file object.
+ */
+ getOFileForS3File(pFile){
+ return this.#mapS3FileToOFile_.get(pFile);
+ }
+ /**
+ Maps or unmaps (if file is falsy) the given (sqlite3_file*)
+ to an xOpen file object and to this pool object.
+ */
+ mapS3FileToOFile(pFile,file){
+ if(file){
+ this.#mapS3FileToOFile_.set(pFile, file);
+ setPoolForPFile(pFile, this);
+ }else{
+ this.#mapS3FileToOFile_.delete(pFile);
+ setPoolForPFile(pFile, false);
+ }
+ }
+
+ /**
+ Returns true if the given client-defined file name is in this
+ object's name-to-SAH map.
+ */
+ hasFilename(name){
+ return this.#mapFilenameToSAH.has(name)
+ }
+
+ /**
+ Returns the SAH associated with the given
+ client-defined file name.
+ */
+ getSAHForPath(path){
+ return this.#mapFilenameToSAH.get(path);
+ }
+
+ /**
+ Removes this object's sqlite3_vfs registration and shuts down
+ this object, releasing all handles, mappings, and whatnot,
+ including deleting its data directory. There is currently no
+ way to "revive" the object and reaquire its resources.
+
+ This function is intended primarily for testing.
+
+ Resolves to true if it did its job, false if the
+ VFS has already been shut down.
+ */
+ async removeVfs(){
+ if(!this.#cVfs.pointer || !this.#dhOpaque) return false;
+ capi.sqlite3_vfs_unregister(this.#cVfs.pointer);
+ this.#cVfs.dispose();
+ try{
+ this.releaseAccessHandles();
+ await this.#dhVfsRoot.removeEntry(OPAQUE_DIR_NAME, {recursive: true});
+ this.#dhOpaque = undefined;
+ await this.#dhVfsParent.removeEntry(
+ this.#dhVfsRoot.name, {recursive: true}
+ );
+ this.#dhVfsRoot = this.#dhVfsParent = undefined;
+ }catch(e){
+ sqlite3.config.error(this.vfsName,"removeVfs() failed:",e);
+ /*otherwise ignored - there is no recovery strategy*/
+ }
+ return true;
+ }
+
+
+ //! Documented elsewhere in this file.
+ exportFile(name){
+ const sah = this.#mapFilenameToSAH.get(name) || toss("File not found:",name);
+ const n = sah.getSize() - HEADER_OFFSET_DATA;
+ const b = new Uint8Array(n>0 ? n : 0);
+ if(n>0){
+ const nRead = sah.read(b, {at: HEADER_OFFSET_DATA});
+ if(nRead != n){
+ toss("Expected to read "+n+" bytes but read "+nRead+".");
+ }
+ }
+ return b;
+ }
+
+ //! Impl for importDb() when its 2nd arg is a function.
+ async importDbChunked(name, callback){
+ const sah = this.#mapFilenameToSAH.get(name)
+ || this.nextAvailableSAH()
+ || toss("No available handles to import to.");
+ sah.truncate(0);
+ let nWrote = 0, chunk, checkedHeader = false, err = false;
+ try{
+ while( undefined !== (chunk = await callback()) ){
+ if(chunk instanceof ArrayBuffer) chunk = new Uint8Array(chunk);
+ if( 0===nWrote && chunk.byteLength>=15 ){
+ util.affirmDbHeader(chunk);
+ checkedHeader = true;
+ }
+ sah.write(chunk, {at: HEADER_OFFSET_DATA + nWrote});
+ nWrote += chunk.byteLength;
+ }
+ if( nWrote < 512 || 0!==nWrote % 512 ){
+ toss("Input size",nWrote,"is not correct for an SQLite database.");
+ }
+ if( !checkedHeader ){
+ const header = new Uint8Array(20);
+ sah.read( header, {at: 0} );
+ util.affirmDbHeader( header );
+ }
+ sah.write(new Uint8Array([1,1]), {
+ at: HEADER_OFFSET_DATA + 18
+ }/*force db out of WAL mode*/);
+ }catch(e){
+ this.setAssociatedPath(sah, '', 0);
+ throw e;
+ }
+ this.setAssociatedPath(sah, name, capi.SQLITE_OPEN_MAIN_DB);
+ return nWrote;
+ }
+
+ //! Documented elsewhere in this file.
+ importDb(name, bytes){
+ if( bytes instanceof ArrayBuffer ) bytes = new Uint8Array(bytes);
+ else if( bytes instanceof Function ) return this.importDbChunked(name, bytes);
+ const sah = this.#mapFilenameToSAH.get(name)
+ || this.nextAvailableSAH()
+ || toss("No available handles to import to.");
+ const n = bytes.byteLength;
+ if(n<512 || n%512!=0){
+ toss("Byte array size is invalid for an SQLite db.");
+ }
+ const header = "SQLite format 3";
+ for(let i = 0; i < header.length; ++i){
+ if( header.charCodeAt(i) !== bytes[i] ){
+ toss("Input does not contain an SQLite database header.");
+ }
+ }
+ const nWrote = sah.write(bytes, {at: HEADER_OFFSET_DATA});
+ if(nWrote != n){
+ this.setAssociatedPath(sah, '', 0);
+ toss("Expected to write "+n+" bytes but wrote "+nWrote+".");
+ }else{
+ sah.write(new Uint8Array([1,1]), {at: HEADER_OFFSET_DATA+18}
+ /* force db out of WAL mode */);
+ this.setAssociatedPath(sah, name, capi.SQLITE_OPEN_MAIN_DB);
+ }
+ return nWrote;
+ }
+
+ }/*class OpfsSAHPool*/;
+
+
+ /**
+ A OpfsSAHPoolUtil instance is exposed to clients in order to
+ manipulate an OpfsSAHPool object without directly exposing that
+ object and allowing for some semantic changes compared to that
+ class.
+
+ Class docs are in the client-level docs for
+ installOpfsSAHPoolVfs().
+ */
+ class OpfsSAHPoolUtil {
+ /* This object's associated OpfsSAHPool. */
+ #p;
+
+ constructor(sahPool){
+ this.#p = sahPool;
+ this.vfsName = sahPool.vfsName;
+ }
+
+ async addCapacity(n){ return this.#p.addCapacity(n) }
+
+ async reduceCapacity(n){ return this.#p.reduceCapacity(n) }
+
+ getCapacity(){ return this.#p.getCapacity(this.#p) }
+
+ getFileCount(){ return this.#p.getFileCount() }
+ getFileNames(){ return this.#p.getFileNames() }
+
+ async reserveMinimumCapacity(min){
+ const c = this.#p.getCapacity();
+ return (c < min) ? this.#p.addCapacity(min - c) : c;
+ }
+
+ exportFile(name){ return this.#p.exportFile(name) }
+
+ importDb(name, bytes){ return this.#p.importDb(name,bytes) }
+
+ async wipeFiles(){ return this.#p.reset(true) }
+
+ unlink(filename){ return this.#p.deletePath(filename) }
+
+ async removeVfs(){ return this.#p.removeVfs() }
+
+ }/* class OpfsSAHPoolUtil */;
+
+ /**
+ Returns a resolved Promise if the current environment
+ has a "fully-sync" SAH impl, else a rejected Promise.
+ */
+ const apiVersionCheck = async ()=>{
+ const dh = await navigator.storage.getDirectory();
+ const fn = '.opfs-sahpool-sync-check-'+getRandomName();
+ const fh = await dh.getFileHandle(fn, { create: true });
+ const ah = await fh.createSyncAccessHandle();
+ const close = ah.close();
+ await close;
+ await dh.removeEntry(fn);
+ if(close?.then){
+ toss("The local OPFS API is too old for opfs-sahpool:",
+ "it has an async FileSystemSyncAccessHandle.close() method.");
+ }
+ return true;
+ };
+
+ /** Only for testing a rejection case. */
+ let instanceCounter = 0;
+
+ /**
+ installOpfsSAHPoolVfs() asynchronously initializes the OPFS
+ SyncAccessHandle (a.k.a. SAH) Pool VFS. It returns a Promise which
+ either resolves to a utility object described below or rejects with
+ an Error value.
+
+ Initialization of this VFS is not automatic because its
+ registration requires that it lock all resources it
+ will potentially use, even if client code does not want
+ to use them. That, in turn, can lead to locking errors
+ when, for example, one page in a given origin has loaded
+ this VFS but does not use it, then another page in that
+ origin tries to use the VFS. If the VFS were automatically
+ registered, the second page would fail to load the VFS
+ due to OPFS locking errors.
+
+ If this function is called more than once with a given "name"
+ option (see below), it will return the same Promise. Calls for
+ different names will return different Promises which resolve to
+ independent objects and refer to different VFS registrations.
+
+ On success, the resulting Promise resolves to a utility object
+ which can be used to query and manipulate the pool. Its API is
+ described at the end of these docs.
+
+ This function accepts an options object to configure certain
+ parts but it is only acknowledged for the very first call and
+ ignored for all subsequent calls.
+
+ The options, in alphabetical order:
+
+ - `clearOnInit`: (default=false) if truthy, contents and filename
+ mapping are removed from each SAH it is acquired during
+ initalization of the VFS, leaving the VFS's storage in a pristine
+ state. Use this only for databases which need not survive a page
+ reload.
+
+ - `initialCapacity`: (default=6) Specifies the default capacity of
+ the VFS. This should not be set unduly high because the VFS has
+ to open (and keep open) a file for each entry in the pool. This
+ setting only has an effect when the pool is initially empty. It
+ does not have any effect if a pool already exists.
+
+ - `directory`: (default="."+`name`) Specifies the OPFS directory
+ name in which to store metadata for the `"opfs-sahpool"`
+ sqlite3_vfs. Only one instance of this VFS can be installed per
+ JavaScript engine, and any two engines with the same storage
+ directory name will collide with each other, leading to locking
+ errors and the inability to register the VFS in the second and
+ subsequent engine. Using a different directory name for each
+ application enables different engines in the same HTTP origin to
+ co-exist, but their data are invisible to each other. Changing
+ this name will effectively orphan any databases stored under
+ previous names. The default is unspecified but descriptive. This
+ option may contain multiple path elements, e.g. "foo/bar/baz",
+ and they are created automatically. In practice there should be
+ no driving need to change this. ACHTUNG: all files in this
+ directory are assumed to be managed by the VFS. Do not place
+ other files in that directory, as they may be deleted or
+ otherwise modified by the VFS.
+
+ - `name`: (default="opfs-sahpool") sets the name to register this
+ VFS under. Normally this should not be changed, but it is
+ possible to register this VFS under multiple names so long as
+ each has its own separate directory to work from. The storage for
+ each is invisible to all others. The name must be a string
+ compatible with `sqlite3_vfs_register()` and friends and suitable
+ for use in URI-style database file names.
+
+ Achtung: if a custom `name` is provided, a custom `directory`
+ must also be provided if any other instance is registered with
+ the default directory. If no directory is explicitly provided
+ then a directory name is synthesized from the `name` option.
+
+ Peculiarities of this VFS:
+
+ - Paths given to it _must_ be absolute. Relative paths will not
+ be properly recognized. This is arguably a bug but correcting it
+ requires some hoop-jumping in routines which have no business
+ doing tricks.
+
+ - It is possible to install multiple instances under different
+ names, each sandboxed from one another inside their own private
+ directory. This feature exists primarily as a way for disparate
+ applications within a given HTTP origin to use this VFS without
+ introducing locking issues between them.
+
+
+ The API for the utility object passed on by this function's
+ Promise, in alphabetical order...
+
+ - [async] number addCapacity(n)
+
+ Adds `n` entries to the current pool. This change is persistent
+ across sessions so should not be called automatically at each app
+ startup (but see `reserveMinimumCapacity()`). Its returned Promise
+ resolves to the new capacity. Because this operation is necessarily
+ asynchronous, the C-level VFS API cannot call this on its own as
+ needed.
+
+ - byteArray exportFile(name)
+
+ Synchronously reads the contents of the given file into a Uint8Array
+ and returns it. This will throw if the given name is not currently
+ in active use or on I/O error. Note that the given name is _not_
+ visible directly in OPFS (or, if it is, it's not from this VFS).
+
+ - number getCapacity()
+
+ Returns the number of files currently contained
+ in the SAH pool. The default capacity is only large enough for one
+ or two databases and their associated temp files.
+
+ - number getFileCount()
+
+ Returns the number of files from the pool currently allocated to
+ slots. This is not the same as the files being "opened".
+
+ - array getFileNames()
+
+ Returns an array of the names of the files currently allocated to
+ slots. This list is the same length as getFileCount().
+
+ - void importDb(name, bytes)
+
+ Imports the contents of an SQLite database, provided as a byte
+ array or ArrayBuffer, under the given name, overwriting any
+ existing content. Throws if the pool has no available file slots,
+ on I/O error, or if the input does not appear to be a
+ database. In the latter case, only a cursory examination is made.
+ Note that this routine is _only_ for importing database files,
+ not arbitrary files, the reason being that this VFS will
+ automatically clean up any non-database files so importing them
+ is pointless.
+
+ If passed a function for its second argument, its behavior
+ changes to asynchronous and it imports its data in chunks fed to
+ it by the given callback function. It calls the callback (which
+ may be async) repeatedly, expecting either a Uint8Array or
+ ArrayBuffer (to denote new input) or undefined (to denote
+ EOF). For so long as the callback continues to return
+ non-undefined, it will append incoming data to the given
+ VFS-hosted database file. The result of the resolved Promise when
+ called this way is the size of the resulting database.
+
+ On succes this routine rewrites the database header bytes in the
+ output file (not the input array) to force disabling of WAL mode.
+
+ On a write error, the handle is removed from the pool and made
+ available for re-use.
+
+ - [async] number reduceCapacity(n)
+
+ Removes up to `n` entries from the pool, with the caveat that it can
+ only remove currently-unused entries. It returns a Promise which
+ resolves to the number of entries actually removed.
+
+ - [async] boolean removeVfs()
+
+ Unregisters the opfs-sahpool VFS and removes its directory from OPFS
+ (which means that _all client content_ is removed). After calling
+ this, the VFS may no longer be used and there is no way to re-add it
+ aside from reloading the current JavaScript context.
+
+ Results are undefined if a database is currently in use with this
+ VFS.
+
+ The returned Promise resolves to true if it performed the removal
+ and false if the VFS was not installed.
+
+ If the VFS has a multi-level directory, e.g. "/foo/bar/baz", _only_
+ the bottom-most directory is removed because this VFS cannot know for
+ certain whether the higher-level directories contain data which
+ should be removed.
+
+ - [async] number reserveMinimumCapacity(min)
+
+ If the current capacity is less than `min`, the capacity is
+ increased to `min`, else this returns with no side effects. The
+ resulting Promise resolves to the new capacity.
+
+ - boolean unlink(filename)
+
+ If a virtual file exists with the given name, disassociates it from
+ the pool and returns true, else returns false without side
+ effects. Results are undefined if the file is currently in active
+ use.
+
+ - string vfsName
+
+ The SQLite VFS name under which this pool's VFS is registered.
+
+ - [async] void wipeFiles()
+
+ Clears all client-defined state of all SAHs and makes all of them
+ available for re-use by the pool. Results are undefined if any such
+ handles are currently in use, e.g. by an sqlite3 db.
+ */
+ sqlite3.installOpfsSAHPoolVfs = async function(options=Object.create(null)){
+ const vfsName = options.name || optionDefaults.name;
+ if(0 && 2===++instanceCounter){
+ throw new Error("Just testing rejection.");
+ }
+ if(initPromises[vfsName]){
+ //console.warn("Returning same OpfsSAHPool result",options,vfsName,initPromises[vfsName]);
+ return initPromises[vfsName];
+ }
+ if(!globalThis.FileSystemHandle ||
+ !globalThis.FileSystemDirectoryHandle ||
+ !globalThis.FileSystemFileHandle ||
+ !globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle ||
+ !navigator?.storage?.getDirectory){
+ return (initPromises[vfsName] = Promise.reject(new Error("Missing required OPFS APIs.")));
+ }
+
+ /**
+ Maintenance reminder: the order of ASYNC ops in this function
+ is significant. We need to have them all chained at the very
+ end in order to be able to catch a race condition where
+ installOpfsSAHPoolVfs() is called twice in rapid succession,
+ e.g.:
+
+ installOpfsSAHPoolVfs().then(console.warn.bind(console));
+ installOpfsSAHPoolVfs().then(console.warn.bind(console));
+
+ If the timing of the async calls is not "just right" then that
+ second call can end up triggering the init a second time and chaos
+ ensues.
+ */
+ return initPromises[vfsName] = apiVersionCheck().then(async function(){
+ if(options.$testThrowInInit){
+ throw options.$testThrowInInit;
+ }
+ const thePool = new OpfsSAHPool(options);
+ return thePool.isReady.then(async()=>{
+ /** The poolUtil object will be the result of the
+ resolved Promise. */
+ const poolUtil = new OpfsSAHPoolUtil(thePool);
+ if(sqlite3.oo1){
+ const oo1 = sqlite3.oo1;
+ const theVfs = thePool.getVfs();
+ const OpfsSAHPoolDb = function(...args){
+ const opt = oo1.DB.dbCtorHelper.normalizeArgs(...args);
+ opt.vfs = theVfs.$zName;
+ oo1.DB.dbCtorHelper.call(this, opt);
+ };
+ OpfsSAHPoolDb.prototype = Object.create(oo1.DB.prototype);
+ // yes or no? OpfsSAHPoolDb.PoolUtil = poolUtil;
+ poolUtil.OpfsSAHPoolDb = OpfsSAHPoolDb;
+ oo1.DB.dbCtorHelper.setVfsPostOpenSql(
+ theVfs.pointer,
+ function(oo1Db, sqlite3){
+ sqlite3.capi.sqlite3_exec(oo1Db, [
+ /* See notes in sqlite3-vfs-opfs.js */
+ "pragma journal_mode=DELETE;",
+ "pragma cache_size=-16384;"
+ ], 0, 0, 0);
+ }
+ );
+ }/*extend sqlite3.oo1*/
+ thePool.log("VFS initialized.");
+ return poolUtil;
+ }).catch(async (e)=>{
+ await thePool.removeVfs().catch(()=>{});
+ return e;
+ });
+ }).catch((err)=>{
+ //error("rejecting promise:",err);
+ return initPromises[vfsName] = Promise.reject(err);
+ });
+ }/*installOpfsSAHPoolVfs()*/;
+}/*sqlite3ApiBootstrap.initializers*/);
+//#else
+/*
+ The OPFS SAH Pool VFS parts are elided from builds targeting
+ node.js.
+*/
+//#endif target=node
diff -Nru sqlite3-3.42.0/ext/wasm/api/sqlite3-v-helper.js sqlite3-3.44.0-0/ext/wasm/api/sqlite3-v-helper.js
--- sqlite3-3.42.0/ext/wasm/api/sqlite3-v-helper.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/api/sqlite3-v-helper.js 2023-11-04 14:23:58.000000000 +0000
@@ -100,7 +100,7 @@
ACHTUNG: because we cannot generically know how to transform JS
exceptions into result codes, the installed functions do no
- automatic catching of exceptions. It is critical, to avoid
+ automatic catching of exceptions. It is critical, to avoid
undefined behavior in the C layer, that methods mapped via
this function do not throw. The exception, as it were, to that
rule is...
@@ -295,7 +295,8 @@
- If `struct.$zName` is falsy and the entry has a string-type
`name` property, `struct.$zName` is set to the C-string form of
- that `name` value before registerVfs() is called.
+ that `name` value before registerVfs() is called. That string
+ gets added to the on-dispose state of the struct.
On success returns this object. Throws on error.
*/
@@ -608,7 +609,7 @@
This is to facilitate creation of those methods inline in the
passed-in object without requiring the client to explicitly get a
reference to one of them in order to assign it to the other
- one.
+ one.
The `catchExceptions`-installed handlers will account for
identical references to the above functions and will install the
diff -Nru sqlite3-3.42.0/ext/wasm/api/sqlite3-wasi.h sqlite3-3.44.0-0/ext/wasm/api/sqlite3-wasi.h
--- sqlite3-3.42.0/ext/wasm/api/sqlite3-wasi.h 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/api/sqlite3-wasi.h 1970-01-01 00:00:00.000000000 +0000
@@ -1,69 +0,0 @@
-/**
- Dummy function stubs to get sqlite3.c compiling with
- wasi-sdk. This requires, in addition:
-
- -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_GETPID
-
- -lwasi-emulated-getpid
-*/
-typedef unsigned mode_t;
-int fchmod(int fd, mode_t mode);
-int fchmod(int fd, mode_t mode){
- return (fd && mode) ? 0 : 0;
-}
-typedef unsigned uid_t;
-typedef uid_t gid_t;
-int fchown(int fd, uid_t owner, gid_t group);
-int fchown(int fd, uid_t owner, gid_t group){
- return (fd && owner && group) ? 0 : 0;
-}
-uid_t geteuid(void);
-uid_t geteuid(void){return 0;}
-#if !defined(F_WRLCK)
-enum {
-F_WRLCK,
-F_RDLCK,
-F_GETLK,
-F_SETLK,
-F_UNLCK
-};
-#endif
-
-#undef HAVE_PREAD
-
-#include
-#define WASM__KEEP __attribute__((used))
-
-#if 0
-/**
- wasi-sdk cannot build sqlite3's default VFS without at least the following
- functions. They are apparently syscalls which clients have to implement or
- otherwise obtain.
-
- https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md
-*/
-environ_get
-environ_sizes_get
-clock_time_get
-fd_close
-fd_fdstat_get
-fd_fdstat_set_flags
-fd_filestat_get
-fd_filestat_set_size
-fd_pread
-fd_prestat_get
-fd_prestat_dir_name
-fd_read
-fd_seek
-fd_sync
-fd_write
-path_create_directory
-path_filestat_get
-path_filestat_set_times
-path_open
-path_readlink
-path_remove_directory
-path_unlink_file
-poll_oneoff
-proc_exit
-#endif
diff -Nru sqlite3-3.42.0/ext/wasm/api/sqlite3-wasm.c sqlite3-3.44.0-0/ext/wasm/api/sqlite3-wasm.c
--- sqlite3-3.42.0/ext/wasm/api/sqlite3-wasm.c 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/api/sqlite3-wasm.c 2023-11-04 14:23:58.000000000 +0000
@@ -84,6 +84,14 @@
/**********************************************************************/
/* SQLITE_ENABLE_... */
+/*
+** Unconditionally enable API_ARMOR in the WASM build. It ensures that
+** public APIs behave predictable in the face of passing illegal NULLs
+** or ranges which might otherwise invoke undefined behavior.
+*/
+#undef SQLITE_ENABLE_API_ARMOR
+#define SQLITE_ENABLE_API_ARMOR 1
+
#ifndef SQLITE_ENABLE_BYTECODE_VTAB
# define SQLITE_ENABLE_BYTECODE_VTAB 1
#endif
@@ -122,12 +130,6 @@
#endif
/**********************************************************************/
-/* SQLITE_M... */
-#ifndef SQLITE_MAX_ALLOCATION_SIZE
-# define SQLITE_MAX_ALLOCATION_SIZE 0x1fffffff
-#endif
-
-/**********************************************************************/
/* SQLITE_O... */
#ifndef SQLITE_OMIT_DEPRECATED
# define SQLITE_OMIT_DEPRECATED 1
@@ -141,9 +143,6 @@
#ifndef SQLITE_OMIT_UTF16
# define SQLITE_OMIT_UTF16 1
#endif
-#ifndef SQLITE_OMIT_WAL
-# define SQLITE_OMIT_WAL 1
-#endif
#ifndef SQLITE_OS_KV_OPTIONAL
# define SQLITE_OS_KV_OPTIONAL 1
#endif
@@ -151,7 +150,7 @@
/**********************************************************************/
/* SQLITE_T... */
#ifndef SQLITE_TEMP_STORE
-# define SQLITE_TEMP_STORE 3
+# define SQLITE_TEMP_STORE 2
#endif
#ifndef SQLITE_THREADSAFE
# define SQLITE_THREADSAFE 0
@@ -295,7 +294,7 @@
*/
SQLITE_WASM_EXPORT void sqlite3_wasm_pstack_restore(unsigned char * p){
assert(p>=PStack.pBegin && p<=PStack.pEnd && p>=PStack.pPos);
- assert(0==(p & 0x7));
+ assert(0==((unsigned long long)p & 0x7));
if(p>=PStack.pBegin && p<=PStack.pEnd /*&& p>=PStack.pPos*/){
PStack.pPos = p;
}
@@ -355,7 +354,9 @@
if( db!=0 ){
if( 0!=zMsg ){
const int nMsg = sqlite3Strlen30(zMsg);
+ sqlite3_mutex_enter(sqlite3_db_mutex(db));
sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg);
+ sqlite3_mutex_leave(sqlite3_db_mutex(db));
}else{
sqlite3ErrorWithMsg(db, err_code, NULL);
}
@@ -1353,6 +1354,13 @@
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings.
**
+** ACHTUNG: it was discovered on 2023-08-11 that, with SQLITE_DEBUG,
+** this function's out-of-scope use of the sqlite3_vfs/file/io_methods
+** APIs leads to triggering of assertions in the core library. Its use
+** is now deprecated and VFS-specific APIs for importing files need to
+** be found to replace it. sqlite3_wasm_posix_create_file() is
+** suitable for the "unix" family of VFSes.
+**
** Creates a new file using the I/O API of the given VFS, containing
** the given number of bytes of the given data. If the file exists, it
** is truncated to the given length and populated with the given
@@ -1398,7 +1406,14 @@
int rc;
sqlite3_file *pFile = 0;
sqlite3_io_methods const *pIo;
- const int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
+ const int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE
+#if 0 && defined(SQLITE_DEBUG)
+ | SQLITE_OPEN_MAIN_JOURNAL
+ /* ^^^^ This is for testing a horrible workaround to avoid
+ triggering a specific assert() in os_unix.c:unixOpen(). Please
+ do not enable this in real builds. */
+#endif
+ ;
int flagsOut = 0;
int fileExisted = 0;
int doUnlock = 0;
@@ -1464,6 +1479,34 @@
return rc;
}
+/**
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own JS/WASM bindings.
+**
+** Creates or overwrites a file using the POSIX file API,
+** i.e. Emscripten's virtual filesystem. Creates or truncates
+** zFilename, appends pData bytes to it, and returns 0 on success or
+** SQLITE_IOERR on error.
+*/
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_posix_create_file( const char *zFilename,
+ const unsigned char * pData,
+ int nData ){
+ int rc;
+ FILE * pFile = 0;
+ int fileExisted = 0;
+ size_t nWrote = 1;
+
+ if( !zFilename || nData<0 || (pData==0 && nData>0) ) return SQLITE_MISUSE;
+ pFile = fopen(zFilename, "w");
+ if( 0==pFile ) return SQLITE_IOERR;
+ if( nData>0 ){
+ nWrote = fwrite(pData, (size_t)nData, 1, pFile);
+ }
+ fclose(pFile);
+ return 1==nWrote ? 0 : SQLITE_IOERR;
+}
+
/*
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings.
@@ -1760,6 +1803,118 @@
}
return s;
}
+
+/*
+** For testing using SQLTester scripts.
+**
+** Return non-zero if string z matches glob pattern zGlob and zero if the
+** pattern does not match.
+**
+** To repeat:
+**
+** zero == no match
+** non-zero == match
+**
+** Globbing rules:
+**
+** '*' Matches any sequence of zero or more characters.
+**
+** '?' Matches exactly one character.
+**
+** [...] Matches one character from the enclosed list of
+** characters.
+**
+** [^...] Matches one character not in the enclosed list.
+**
+** '#' Matches any sequence of one or more digits with an
+** optional + or - sign in front, or a hexadecimal
+** literal of the form 0x...
+*/
+static int sqlite3_wasm_SQLTester_strnotglob(const char *zGlob, const char *z){
+ int c, c2;
+ int invert;
+ int seen;
+ typedef int (*recurse_f)(const char *,const char *);
+ static const recurse_f recurse = sqlite3_wasm_SQLTester_strnotglob;
+
+ while( (c = (*(zGlob++)))!=0 ){
+ if( c=='*' ){
+ while( (c=(*(zGlob++))) == '*' || c=='?' ){
+ if( c=='?' && (*(z++))==0 ) return 0;
+ }
+ if( c==0 ){
+ return 1;
+ }else if( c=='[' ){
+ while( *z && recurse(zGlob-1,z)==0 ){
+ z++;
+ }
+ return (*z)!=0;
+ }
+ while( (c2 = (*(z++)))!=0 ){
+ while( c2!=c ){
+ c2 = *(z++);
+ if( c2==0 ) return 0;
+ }
+ if( recurse(zGlob,z) ) return 1;
+ }
+ return 0;
+ }else if( c=='?' ){
+ if( (*(z++))==0 ) return 0;
+ }else if( c=='[' ){
+ int prior_c = 0;
+ seen = 0;
+ invert = 0;
+ c = *(z++);
+ if( c==0 ) return 0;
+ c2 = *(zGlob++);
+ if( c2=='^' ){
+ invert = 1;
+ c2 = *(zGlob++);
+ }
+ if( c2==']' ){
+ if( c==']' ) seen = 1;
+ c2 = *(zGlob++);
+ }
+ while( c2 && c2!=']' ){
+ if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
+ c2 = *(zGlob++);
+ if( c>=prior_c && c<=c2 ) seen = 1;
+ prior_c = 0;
+ }else{
+ if( c==c2 ){
+ seen = 1;
+ }
+ prior_c = c2;
+ }
+ c2 = *(zGlob++);
+ }
+ if( c2==0 || (seen ^ invert)==0 ) return 0;
+ }else if( c=='#' ){
+ if( z[0]=='0'
+ && (z[1]=='x' || z[1]=='X')
+ && sqlite3Isxdigit(z[2])
+ ){
+ z += 3;
+ while( sqlite3Isxdigit(z[0]) ){ z++; }
+ }else{
+ if( (z[0]=='-' || z[0]=='+') && sqlite3Isdigit(z[1]) ) z++;
+ if( !sqlite3Isdigit(z[0]) ) return 0;
+ z++;
+ while( sqlite3Isdigit(z[0]) ){ z++; }
+ }
+ }else{
+ if( c!=(*(z++)) ) return 0;
+ }
+ }
+ return *z==0;
+}
+
+SQLITE_WASM_EXPORT
+int sqlite3_wasm_SQLTester_strglob(const char *zGlob, const char *z){
+ return !sqlite3_wasm_SQLTester_strnotglob(zGlob, z);
+}
+
+
#endif /* SQLITE_WASM_TESTS */
#undef SQLITE_WASM_EXPORT
diff -Nru sqlite3-3.42.0/ext/wasm/api/sqlite3-worker1.c-pp.js sqlite3-3.44.0-0/ext/wasm/api/sqlite3-worker1.c-pp.js
--- sqlite3-3.42.0/ext/wasm/api/sqlite3-worker1.c-pp.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/api/sqlite3-worker1.c-pp.js 2023-11-04 14:23:58.000000000 +0000
@@ -37,7 +37,7 @@
"use strict";
{
const urlParams = globalThis.location
- ? new URL(self.location.href).searchParams
+ ? new URL(globalThis.location.href).searchParams
: new URLSearchParams();
let theJs = 'sqlite3.js';
if(urlParams.has('sqlite3.dir')){
diff -Nru sqlite3-3.42.0/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js sqlite3-3.44.0-0/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js
--- sqlite3-3.42.0/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js 2023-11-04 14:23:58.000000000 +0000
@@ -196,10 +196,9 @@
if(1===arguments.length){
msg = arguments[0];
}else if(2===arguments.length){
- msg = {
- type: arguments[0],
- args: arguments[1]
- };
+ msg = Object.create(null);
+ msg.type = arguments[0];
+ msg.args = arguments[1];
}else{
toss("Invalid arugments for sqlite3Worker1Promiser()-created factory.");
}
diff -Nru sqlite3-3.42.0/ext/wasm/common/SqliteTestUtil.js sqlite3-3.44.0-0/ext/wasm/common/SqliteTestUtil.js
--- sqlite3-3.42.0/ext/wasm/common/SqliteTestUtil.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/common/SqliteTestUtil.js 2023-11-04 14:23:58.000000000 +0000
@@ -156,7 +156,6 @@
}
};
-
/**
This is a module object for use with the emscripten-installed
sqlite3InitModule() factory function.
diff -Nru sqlite3-3.42.0/ext/wasm/common/testing.css sqlite3-3.44.0-0/ext/wasm/common/testing.css
--- sqlite3-3.42.0/ext/wasm/common/testing.css 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/common/testing.css 2023-11-04 14:23:58.000000000 +0000
@@ -40,8 +40,41 @@
.tests-pass { background-color: green; color: white }
.tests-fail { background-color: red; color: yellow }
.faded { opacity: 0.5; }
-.group-start { color: blue; }
-.group-end { color: blue; }
+.group-start {
+ color: blue;
+ background-color: skyblue;
+ font-weight: bold;
+ border-top: 1px dotted blue;
+ padding: 0.5em;
+ margin-top: 0.5em;
+}
+.group-end {
+ padding: 0.5em;
+ margin-bottom: 0.25em;
+ /*border-bottom: 1px dotted blue;*/
+}
+.group-end.green {
+ background: lightgreen;
+ border-bottom: 1px dotted green;
+}
+.one-test-line, .skipping-group {
+ margin-left: 3em;
+}
+.skipping-test, .skipping-group {
+ padding: 0.25em 0.5em;
+ background-color: #ffff73;
+}
+.skipping-test {
+ margin-left: 6em;
+}
+.one-test-summary {
+ margin-left: 6em;
+}
+.full-test-summary {
+ padding-bottom: 0.5em;
+ padding-top: 0.5em;
+ border-top: 1px solid black;
+}
.input-wrapper {
white-space: nowrap;
display: flex;
diff -Nru sqlite3-3.42.0/ext/wasm/common/whwasmutil.js sqlite3-3.44.0-0/ext/wasm/common/whwasmutil.js
--- sqlite3-3.42.0/ext/wasm/common/whwasmutil.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/common/whwasmutil.js 2023-11-04 14:23:58.000000000 +0000
@@ -111,7 +111,8 @@
of `target.instance` (a WebAssembly.Module instance) and it must
contain the symbols exported by the WASM module associated with
this code. In an Enscripten environment it must be set to
- `Module['asm']`. The exports object must contain a minimum of the
+ `Module['asm']` (versions <=3.1.43) or `wasmExports` (versions
+ >=3.1.44). The exports object must contain a minimum of the
following symbols:
- `memory`: a WebAssembly.Memory object representing the WASM
@@ -174,7 +175,7 @@
globalThis.WhWasmUtilInstaller = function(target){
'use strict';
if(undefined===target.bigIntEnabled){
- target.bigIntEnabled = !!self['BigInt64Array'];
+ target.bigIntEnabled = !!globalThis['BigInt64Array'];
}
/** Throws a new Error, the message of which is the concatenation of
@@ -355,8 +356,8 @@
break;
default:
if(target.bigIntEnabled){
- if(n===self['BigUint64Array']) return c.HEAP64U;
- else if(n===self['BigInt64Array']) return c.HEAP64;
+ if(n===globalThis['BigUint64Array']) return c.HEAP64U;
+ else if(n===globalThis['BigInt64Array']) return c.HEAP64;
break;
}
}
@@ -612,8 +613,6 @@
target.installFunction = (func, sig)=>__installFunction(func, sig, false);
/**
- EXPERIMENTAL! DO NOT USE IN CLIENT CODE!
-
Works exactly like installFunction() but requires that a
scopedAllocPush() is active and uninstalls the given function
when that alloc scope is popped via scopedAllocPop().
@@ -1179,7 +1178,7 @@
cache.scopedAlloc.splice(n,1);
for(let p; (p = state.pop()); ){
if(target.functionEntry(p)){
- //console.warn("scopedAllocPop() uninstalling transient function",p);
+ //console.warn("scopedAllocPop() uninstalling function",p);
target.uninstallFunction(p);
}
else target.dealloc(p);
@@ -1636,6 +1635,7 @@
'and is not intended to be invoked from',
'client-level code. Invoked with:',opt);
}
+ this.name = opt.name || "unnamed";
this.signature = opt.signature;
if(opt.contextKey instanceof Function){
this.contextKey = opt.contextKey;
@@ -1656,25 +1656,11 @@
? opt.callProxy : undefined;
}
- /** If true, the constructor emits a warning. The intent is that
- this be set to true after bootstrapping of the higher-level
- client library is complete, to warn downstream clients that
- they shouldn't be relying on this implemenation detail which
- does not have a stable interface. */
- static warnOnUse = false;
-
- /** If true, convertArg() will FuncPtrAdapter.debugOut() when it
- (un)installs a function binding to/from WASM. Note that
- deinstallation of bindScope=transient bindings happens
- via scopedAllocPop() so will not be output. */
- static debugFuncInstall = false;
-
- /** Function used for debug output. */
- static debugOut = console.debug.bind(console);
-
- static bindScopes = [
- 'transient', 'context', 'singleton', 'permanent'
- ];
+ /**
+ Note that static class members are defined outside of the class
+ to work around an emcc toolchain build problem: one of the
+ tools in emsdk v3.1.42 does not support the static keyword.
+ */
/* Dummy impl. Overwritten per-instance as needed. */
contextKey(argv,argIndex){
@@ -1711,14 +1697,16 @@
exactly the 2nd and 3rd arguments are.
*/
convertArg(v,argv,argIndex){
- //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.signature,this.transient,v);
+ //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v);
let pair = this.singleton;
if(!pair && this.isContext){
pair = this.contextMap(this.contextKey(argv,argIndex));
+ //FuncPtrAdapter.debugOut(this.name, this.signature, "contextKey() =",this.contextKey(argv,argIndex), pair);
}
if(pair && pair[0]===v) return pair[1];
if(v instanceof Function){
/* Install a WASM binding and return its pointer. */
+ //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v,pair);
if(this.callProxy) v = this.callProxy(v);
const fp = __installFunction(v, this.signature, this.isTransient);
if(FuncPtrAdapter.debugFuncInstall){
@@ -1732,7 +1720,18 @@
FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this,
this.contextKey(argv,argIndex), '@'+pair[1], v);
}
- try{target.uninstallFunction(pair[1])}
+ try{
+ /* Because the pending native call might rely on the
+ pointer we're replacing, e.g. as is normally the case
+ with sqlite3's xDestroy() methods, we don't
+ immediately uninstall but instead add its pointer to
+ the scopedAlloc stack, which will be cleared when the
+ xWrap() mechanism is done calling the native
+ function. We're relying very much here on xWrap()
+ having pushed an alloc scope.
+ */
+ cache.scopedAlloc[cache.scopedAlloc.length-1].push(pair[1]);
+ }
catch(e){/*ignored*/}
}
pair[0] = v;
@@ -1740,13 +1739,14 @@
}
return fp;
}else if(target.isPtr(v) || null===v || undefined===v){
+ //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.name,this.signature,this.transient,v,pair);
if(pair && pair[1] && pair[1]!==v){
/* uninstall stashed mapping and replace stashed mapping with v. */
if(FuncPtrAdapter.debugFuncInstall){
FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this,
this.contextKey(argv,argIndex), '@'+pair[1], v);
}
- try{target.uninstallFunction(pair[1])}
+ try{ cache.scopedAlloc[cache.scopedAlloc.length-1].push(pair[1]) }
catch(e){/*ignored*/}
pair[0] = pair[1] = (v | 0);
}
@@ -1761,6 +1761,26 @@
}/*convertArg()*/
}/*FuncPtrAdapter*/;
+ /** If true, the constructor emits a warning. The intent is that
+ this be set to true after bootstrapping of the higher-level
+ client library is complete, to warn downstream clients that
+ they shouldn't be relying on this implemenation detail which
+ does not have a stable interface. */
+ xArg.FuncPtrAdapter.warnOnUse = false;
+
+ /** If true, convertArg() will FuncPtrAdapter.debugOut() when it
+ (un)installs a function binding to/from WASM. Note that
+ deinstallation of bindScope=transient bindings happens
+ via scopedAllocPop() so will not be output. */
+ xArg.FuncPtrAdapter.debugFuncInstall = false;
+
+ /** Function used for debug output. */
+ xArg.FuncPtrAdapter.debugOut = console.debug.bind(console);
+
+ xArg.FuncPtrAdapter.bindScopes = [
+ 'transient', 'context', 'singleton', 'permanent'
+ ];
+
const __xArgAdapterCheck =
(t)=>xArg.get(t) || toss("Argument adapter not found:",t);
diff -Nru sqlite3-3.42.0/ext/wasm/demo-123.js sqlite3-3.44.0-0/ext/wasm/demo-123.js
--- sqlite3-3.42.0/ext/wasm/demo-123.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/demo-123.js 2023-11-04 14:23:58.000000000 +0000
@@ -235,10 +235,10 @@
- get change count (total or statement-local, 32- or 64-bit)
- get a DB's file name
-
+
Misc. Stmt features:
- - Various forms of bind()
+ - Various forms of bind()
- clearBindings()
- reset()
- Various forms of step()
diff -Nru sqlite3-3.42.0/ext/wasm/demo-worker1.js sqlite3-3.44.0-0/ext/wasm/demo-worker1.js
--- sqlite3-3.42.0/ext/wasm/demo-worker1.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/demo-worker1.js 2023-11-04 14:23:58.000000000 +0000
@@ -209,8 +209,8 @@
// ^^^ arbitrary query with no result columns
"select a, b from t order by a desc;",
"select a from t;"
- // multi-statement exec only honors results from the first
- // statement with result columns (regardless of whether)
+ // exec() only honors SELECT results from the first
+ // statement with result columns (regardless of whether
// it has any rows).
],
rowMode: 1,
diff -Nru sqlite3-3.42.0/ext/wasm/demo-worker1-promiser.js sqlite3-3.44.0-0/ext/wasm/demo-worker1-promiser.js
--- sqlite3-3.42.0/ext/wasm/demo-worker1-promiser.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/demo-worker1-promiser.js 2023-11-04 14:23:58.000000000 +0000
@@ -64,15 +64,17 @@
callback = msgArgs;
msgArgs = undefined;
}
- const p = workerPromise({type: msgType, args:msgArgs});
+ const p = 1
+ ? workerPromise({type: msgType, args:msgArgs})
+ : workerPromise(msgType, msgArgs);
return callback ? p.then(callback).finally(testCount) : p;
};
+ let sqConfig;
const runTests = async function(){
const dbFilename = '/testing2.sqlite3';
startTime = performance.now();
- let sqConfig;
await wtest('config-get', (ev)=>{
const r = ev.result;
log('sqlite3.config subset:', r);
@@ -102,12 +104,15 @@
sql: ["create table t(a,b)",
"insert into t(a,b) values(1,2),(3,4),(5,6)"
].join(';'),
- multi: true,
- resultRows: [], columnNames: []
+ resultRows: [], columnNames: [],
+ countChanges: sqConfig.bigIntEnabled ? 64 : true
}, function(ev){
ev = ev.result;
T.assert(0===ev.resultRows.length)
- .assert(0===ev.columnNames.length);
+ .assert(0===ev.columnNames.length)
+ .assert(sqConfig.bigIntEnabled
+ ? (3n===ev.changeCount)
+ : (3===ev.changeCount));
});
await wtest('exec',{
@@ -125,12 +130,14 @@
await wtest('exec',{
sql: 'select a a, b b from t order by a',
resultRows: [], columnNames: [],
- rowMode: 'object'
+ rowMode: 'object',
+ countChanges: true
}, function(ev){
ev = ev.result;
T.assert(3===ev.resultRows.length)
.assert(1===ev.resultRows[0].a)
.assert(6===ev.resultRows[2].b)
+ .assert(0===ev.changeCount);
});
await wtest(
@@ -143,12 +150,13 @@
await wtest('exec',{
sql:'select 1 union all select 3',
- resultRows: [],
+ resultRows: []
}, function(ev){
ev = ev.result;
T.assert(2 === ev.resultRows.length)
.assert(1 === ev.resultRows[0][0])
- .assert(3 === ev.resultRows[1][0]);
+ .assert(3 === ev.resultRows[1][0])
+ .assert(undefined === ev.changeCount);
});
const resultRowTest1 = function f(ev){
@@ -218,13 +226,12 @@
});
await wtest('exec',{
- multi: true,
sql:[
'pragma foreign_keys=0;',
// ^^^ arbitrary query with no result columns
'select a, b from t order by a desc; select a from t;'
- // multi-exec only honors results from the first
- // statement with result columns (regardless of whether)
+ // exec() only honors SELECT results from the first
+ // statement with result columns (regardless of whether
// it has any rows).
],
rowMode: 1,
diff -Nru sqlite3-3.42.0/ext/wasm/dist.make sqlite3-3.44.0-0/ext/wasm/dist.make
--- sqlite3-3.42.0/ext/wasm/dist.make 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/dist.make 2023-11-04 14:23:58.000000000 +0000
@@ -38,7 +38,7 @@
# date. Our general policy is that we want the smallest binaries for
# dist zip files, so use the oz build unless there is a compelling
# reason not to.
-dist.build ?= qoz
+dist.build ?= oz
dist-dir.top := $(dist-name)
dist-dir.jswasm := $(dist-dir.top)/$(notdir $(dir.dout))
diff -Nru sqlite3-3.42.0/ext/wasm/fiddle/fiddle-worker.js sqlite3-3.44.0-0/ext/wasm/fiddle/fiddle-worker.js
--- sqlite3-3.42.0/ext/wasm/fiddle/fiddle-worker.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/fiddle/fiddle-worker.js 2023-11-04 14:23:58.000000000 +0000
@@ -221,7 +221,7 @@
f._();
}
};
-
+
self.onmessage = function f(ev){
ev = ev.data;
if(!f.cache){
@@ -371,12 +371,14 @@
sqlite3InitModule(fiddleModule).then((_sqlite3)=>{
sqlite3 = _sqlite3;
console.warn("Installing sqlite3 module globally (in Worker)",
- "for use in the dev console.");
- self.sqlite3 = sqlite3;
+ "for use in the dev console.", sqlite3);
+ globalThis.sqlite3 = sqlite3;
const dbVfs = sqlite3.wasm.xWrap('fiddle_db_vfs', "*", ['string']);
fiddleModule.fsUnlink = (fn)=>{
return sqlite3.wasm.sqlite3_wasm_vfs_unlink(dbVfs(0), fn);
};
wMsg('fiddle-ready');
- })/*then()*/;
+ }).catch(e=>{
+ console.error("Fiddle worker init failed:",e);
+ });
})();
diff -Nru sqlite3-3.42.0/ext/wasm/fiddle.make sqlite3-3.44.0-0/ext/wasm/fiddle.make
--- sqlite3-3.42.0/ext/wasm/fiddle.make 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/fiddle.make 2023-11-04 14:23:58.000000000 +0000
@@ -18,7 +18,7 @@
ifeq (,$(SHELL_SRC))
$(error Could not parse SHELL_SRC from $(dir.top)/Makefile.)
endif
-$(dir.top)/shell.c: $(SHELL_SRC) $(dir.top)/tool/mkshellc.tcl
+$(dir.top)/shell.c: $(SHELL_SRC) $(dir.top)/tool/mkshellc.tcl $(sqlite3.c)
$(MAKE) -C $(dir.top) shell.c
# /shell.c
########################################################################
@@ -61,9 +61,9 @@
$(eval $(call call-make-pre-post,fiddle-module,vanilla))
$(fiddle-module.js): $(MAKEFILE) $(MAKEFILE.fiddle) \
$(EXPORTED_FUNCTIONS.fiddle) \
- $(fiddle.cses) $(pre-post-fiddle-module.deps.vanilla) $(fiddle.SOAP.js)
+ $(fiddle.cses) $(pre-post-fiddle-module-vanilla.deps) $(fiddle.SOAP.js)
$(emcc.bin) -o $@ $(fiddle.emcc-flags) \
- $(pre-post-fiddle-module.flags.vanilla) \
+ $(pre-post-fiddle-module-vanilla.flags) \
$(fiddle.cses)
$(maybe-wasm-strip) $(fiddle-module.wasm)
gzip < $@ > $@.gz
diff -Nru sqlite3-3.42.0/ext/wasm/GNUmakefile sqlite3-3.44.0-0/ext/wasm/GNUmakefile
--- sqlite3-3.42.0/ext/wasm/GNUmakefile 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/GNUmakefile 2023-11-04 14:23:58.000000000 +0000
@@ -18,8 +18,6 @@
# quick, q = do just a minimal build (sqlite3.js/wasm, tester1) for
# faster development-mode turnaround.
#
-# qo2, qoz = a combination of quick+o2/oz.
-#
# dist = create end user deliverables. Add dist.build=oX to build
# with a specific optimization level, where oX is one of the
# above-listed o? or qo? target names.
@@ -46,11 +44,12 @@
# $(eval), or at least centralize the setup of the numerous vars
# related to each build variant $(JS_BUILD_MODES).
#
+default: all
+#default: quick
SHELL := $(shell which bash 2>/dev/null)
MAKEFILE := $(lastword $(MAKEFILE_LIST))
CLEAN_FILES :=
DISTCLEAN_FILES := ./--dummy--
-default: all
release: oz
# JS_BUILD_MODES exists solely to reduce repetition in documentation
# below.
@@ -68,13 +67,6 @@
else
$(info using emcc version [$(emcc.version)])
endif
-emcc.version := $(shell "$(emcc.bin)" --version | sed -n 1p \
- | sed -e 's/^.* \([3-9][^ ]*\) .*$$/\1/;')
-ifeq (,$(emcc.version))
- $(warning Cannot determine emcc version. This might unduly impact build flags.)
-else
- $(info using emcc version [$(emcc.version)])
-endif
wasm-strip ?= $(shell which wasm-strip 2>/dev/null)
ifeq (,$(filter clean,$(MAKECMDGOALS)))
@@ -159,6 +151,9 @@
# bundle.
#
# A custom sqlite3.c must not have any spaces in its name.
+# $(sqlite3.canonical.c) must point to the sqlite3.c in
+# the sqlite3 canonical source tree, as that source file
+# is required for certain utility and test code.
sqlite3.canonical.c := $(dir.top)/sqlite3.c
sqlite3.c ?= $(firstword $(wildcard $(dir.top)/sqlite3-see.c) $(sqlite3.canonical.c))
sqlite3.h := $(dir.top)/sqlite3.h
@@ -184,14 +179,19 @@
-DSQLITE_OMIT_DEPRECATED \
-DSQLITE_OMIT_UTF16 \
-DSQLITE_OMIT_SHARED_CACHE \
- -DSQLITE_OMIT_WAL \
-DSQLITE_THREADSAFE=0 \
- -DSQLITE_TEMP_STORE=3 \
+ -DSQLITE_TEMP_STORE=2 \
-DSQLITE_OS_KV_OPTIONAL=1 \
'-DSQLITE_DEFAULT_UNIX_VFS="unix-none"' \
-DSQLITE_USE_URI=1 \
-DSQLITE_WASM_ENABLE_C_TESTS \
-DSQLITE_C=$(sqlite3.c)
+#SQLITE_OPT += -DSQLITE_DEBUG
+# Enabling SQLITE_DEBUG will break sqlite3_wasm_vfs_create_file()
+# (and thus sqlite3_js_vfs_create_file()). Those functions are
+# deprecated and alternatives are in place, but this crash behavior
+# can be used to find errant uses of sqlite3_js_vfs_create_file()
+# in client code.
.NOTPARALLEL: $(sqlite3.h)
$(sqlite3.h):
@@ -246,10 +246,10 @@
# embedding in the JS files and in building the distribution zip file.
# It must NOT be in $(dir.tmp) because we need it to survive the
# cleanup process for the dist build to work properly.
-bin.version-info := $(dir.wasm)/version-info
-$(bin.version-info): $(dir.wasm)/version-info.c $(sqlite3.h) $(MAKEFILE)
- $(CC) -O0 -I$(dir $(sqlite3.c)) -o $@ $<
-DISTCLEAN_FILES += $(bin.version-info)
+bin.version-info := $(dir.top)/version-info
+.NOTPARALLEL: $(bin.version-info)
+$(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile
+ $(MAKE) -C $(dir.top) version-info
# bin.stripcomments is used for stripping C/C++-style comments from JS
# files. The JS files contain large chunks of documentation which we
@@ -379,20 +379,17 @@
sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js
sqlite3-api.jses += $(dir.api)/sqlite3-v-helper.js
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js
+sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs-sahpool.c-pp.js
sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js
-# "External" API files which are part of our distribution
+# SOAP.js is an external API file which is part of our distribution
# but not part of the sqlite3-api.js amalgamation.
SOAP.js := $(dir.api)/sqlite3-opfs-async-proxy.js
-# COPY_XAPI = a $(call)able function to copy $1 to $(dir.dout), where
-# $1 must be one of the "external" JS API files.
-define COPY_XAPI
-sqlite3-api.ext.jses += $$(dir.dout)/$$(notdir $(1))
-$$(dir.dout)/$$(notdir $(1)): $(1) $$(MAKEFILE)
- cp $$< $$@
-endef
-$(foreach X,$(SOAP.js),\
- $(eval $(call COPY_XAPI,$(X))))
+SOAP.js.bld := $(dir.dout)/$(notdir $(SOAP.js))
+sqlite3-api.ext.jses += $(SOAP.js.bld)
+$(SOAP.js.bld): $(SOAP.js)
+ cp $< $@
+
all quick: $(sqlite3-api.ext.jses)
q: quick
@@ -615,26 +612,40 @@
########################################################################
# call-make-pre-post is a $(call)able which creates rules for
-# pre-js-$(1).js. $1 = the base name of the JS file on whose behalf
-# this pre-js is for (one of: sqlite3, sqlite3-wasmfs). $2 is the
-# build mode: one of $(JS_BUILD_MODES). This
-# sets up --[extern-][pre/post]-js flags in
-# $(pre-post-$(1).flags.$(2)) and dependencies in
-# $(pre-post-$(1).deps.$(2)).
+# pre-js-$(1)-$(2).js. $1 = the base name of the JS file on whose
+# behalf this pre-js is for (one of: sqlite3, sqlite3-wasmfs). $2 is
+# the build mode: one of $(JS_BUILD_MODES). This sets up
+# --[extern-][pre/post]-js flags in $(pre-post-$(1)-$(2).flags) and
+# dependencies in $(pre-post-$(1)-$(2).deps). The resulting files get
+# filtered using $(C-PP.FILTER). Any flags necessary for such
+# filtering need to be set in $(c-pp.D.$(1)-$(2)) before $(call)ing
+# this.
define call-make-pre-post
-pre-post-$(1).flags.$(2) ?=
-$$(dir.tmp)/pre-js-$(1)-$(2).js: $$(pre-js.js.$(2)) $$(MAKEFILE)
- cp $$(pre-js.js.$(2)) $$@
+pre-post-$(1)-$(2).flags ?=
+pre-js.js.$(1)-$(2) := $$(dir.tmp)/pre-js.$(1)-$(2).intermediary.js
+$$(eval $$(call C-PP.FILTER,$$(pre-js.js.in),$$(pre-js.js.$(1)-$(2)),$$(c-pp.D.$(1)-$(2))))
+post-js.js.$(1)-$(2) := $$(dir.tmp)/post-js.$(1)-$(2).js
+$$(eval $$(call C-PP.FILTER,$$(post-js.js.in),$$(post-js.js.$(1)-$(2)),$$(c-pp.D.$(1)-$(2))))
+extern-post-js.js.$(1)-$(2) := $$(dir.tmp)/extern-post-js.$(1)-$(2).js
+$$(eval $$(call C-PP.FILTER,$$(extern-post-js.js.in),$$(extern-post-js.js.$(1)-$(2)),$$(c-pp.D.$(1)-$(2))))
+pre-post-common.flags.$(1)-$(2) := \
+ $$(pre-post-common.flags) \
+ --post-js=$$(post-js.js.$(1)-$(2)) \
+ --extern-post-js=$$(extern-post-js.js.$(1)-$(2))
+pre-post-jses.$(1)-$(2).deps := $$(pre-post-jses.deps.common) \
+ $$(post-js.js.$(1)-$(2)) $$(extern-post-js.js.$(1)-$(2))
+$$(dir.tmp)/pre-js-$(1)-$(2).js: $$(pre-js.js.$(1)-$(2)) $$(MAKEFILE)
+ cp $$(pre-js.js.$(1)-$(2)) $$@
@if [ sqlite3-wasmfs = $(1) ]; then \
echo "delete Module[xNameOfInstantiateWasm] /*for WASMFS build*/;"; \
elif [ sqlite3 != $(1) ]; then \
echo "Module[xNameOfInstantiateWasm].uri = '$(1).wasm';"; \
fi >> $$@
-pre-post-$(1).deps.$(2) := \
- $$(pre-post-jses.deps.$(2)) \
+pre-post-$(1)-$(2).deps := \
+ $$(pre-post-jses.$(1)-$(2).deps) \
$$(dir.tmp)/pre-js-$(1)-$(2).js
-pre-post-$(1).flags.$(2) += \
- $$(pre-post-common.flags.$(2)) \
+pre-post-$(1)-$(2).flags += \
+ $$(pre-post-common.flags.$(1)-$(2)) \
--pre-js=$$(dir.tmp)/pre-js-$(1)-$(2).js
endef
# /post-js and pre-js
@@ -645,7 +656,8 @@
# https://github.com/emscripten-core/emscripten/issues/14383
sqlite3.wasm := $(dir.dout)/sqlite3.wasm
sqlite3-wasm.c := $(dir.api)/sqlite3-wasm.c
-sqlite3-wasm.cses := $(sqlite3-wasm.c) $(sqlite3_wasm_extra_init.c)
+sqlite3-wasm.cfiles := $(sqlite3-wasm.c) $(sqlite3_wasm_extra_init.c)
+sqlite3-wasmfs.cfiles := $(sqlite3-wasm.cfiles)
# sqlite3-wasm.o vs sqlite3-wasm.c: building against the latter
# (predictably) results in a slightly faster binary. We're close
# enough to the target speed requirements that the 500ms makes a
@@ -655,7 +667,8 @@
# SQLITE3.xJS.EXPORT-DEFAULT is part of SQLITE3-WASMFS.xJS.RECIPE and
# SETUP_LIB_BUILD_MODE, factored into a separate piece to avoid code
# duplication. $1 is 1 if the build mode needs this workaround (esm,
-# bundler-friendly) and 0 if not (vanilla).
+# bundler-friendly, node) and 0 if not (vanilla). $2 must be empty for
+# all builds except sqlite3-wasmfs.mjs, in which case it must be 1.
#
# Reminder for ESM builds: even if we use -sEXPORT_ES6=0, emcc _still_
# adds:
@@ -669,13 +682,20 @@
#
# Upstream RFE:
# https://github.com/emscripten-core/emscripten/issues/18237
+#
+# Maintenance reminder: Mac sed works differently than GNU sed, so
+# don't use sed for this.
define SQLITE3.xJS.ESM-EXPORT-DEFAULT
if [ x1 = x$(1) ]; then \
- echo "Fragile workaround for an Emscripten annoyance. See SQLITE3.xJS.RECIPE."; \
- sed -i -e '0,/^export default/{/^export default/d;}' $@ || exit $$?; \
- if ! grep -q '^export default' $@; then \
- echo "Cannot find export default." 1>&2; \
- exit 1; \
+ echo "Fragile workaround for emscripten/issues/18237. See SQLITE3.xJS.RECIPE."; \
+ {\
+ awk '/^export default/ && !f{f=1; next} 1' $@ > $@.tmp && mv $@.tmp $@; \
+ } || exit $$?; \
+ if [ x != x$(2) ]; then \
+ if ! grep -q '^export default' $@; then \
+ echo "Cannot find export default." 1>&2; \
+ exit 1; \
+ fi; \
fi; \
fi
endef
@@ -695,53 +715,57 @@
# SETUP_LIB_BUILD_MODE is a $(call)'able which sets up numerous pieces
# for one of the build modes.
#
-# $1 = build mode name: one of $(JS_BUILD_MODES)
-# $2 = 1 for ESM build mode, else 0
-# $3 = resulting sqlite-api JS/MJS file
-# $4 = resulting JS/MJS file
-# $5 = -D... flags for $(bin.c-pp)
-# $6 = emcc -sXYZ flags (CURRENTLY UNUSED - was factored out)
+# $1 = one of: sqlite3, sqlite3-wasmfs
+# $2 = build mode name: one of $(JS_BUILD_MODES)
+# $3 = 1 for ESM build mode, else 0
+# $4 = resulting sqlite-api JS/MJS file
+# $5 = resulting JS/MJS file
+# $6 = -D... flags for $(bin.c-pp)
+# $7 = emcc -sXYZ flags (CURRENTLY UNUSED - was factored out)
+#
+# Maintenance reminder: be careful not to introduce spaces around args
+# ($1, $2), otherwise string concatenation will malfunction.
+#
+# emcc.environment.$(2) must be set to a value for the -sENVIRONMENT flag.
#
-# emcc.environment.$(1) must be set to a value for the -sENVIRONMENT flag.
+# $(cflags.$(1)) and $(cflags.$(1).$(2)) may be defined to append
+# CFLAGS to a given build mode.
+#
+# $(emcc.flags.$(1)) and $(emcc.flags.$(1).$(2)) may be defined to
+# append emcc-specific flags to a given build mode.
define SETUP_LIB_BUILD_MODE
-$(info Setting up build [$(1)]: $(4))
-c-pp.D.$(1) := $(5)
-pre-js.js.$(1) := $$(dir.tmp)/pre-js.$(1).js
-$$(eval $$(call C-PP.FILTER,$$(pre-js.js.in),$$(pre-js.js.$(1)),$$(c-pp.D.$(1))))
-post-js.js.$(1) := $$(dir.tmp)/post-js.$(1).js
-$$(eval $$(call C-PP.FILTER,$$(post-js.js.in),$$(post-js.js.$(1)),$$(c-pp.D.$(1))))
-extern-post-js.js.$(1) := $$(dir.tmp)/extern-post-js.$(1).js
-$$(eval $$(call C-PP.FILTER,$$(extern-post-js.js.in),$$(extern-post-js.js.$(1)),$$(c-pp.D.$(1))))
-pre-post-common.flags.$(1) := \
- $$(pre-post-common.flags) \
- --post-js=$$(post-js.js.$(1)) \
- --extern-post-js=$$(extern-post-js.js.$(1))
-pre-post-jses.deps.$(1) := $$(pre-post-jses.deps.common) \
- $$(post-js.js.$(1)) $$(extern-post-js.js.$(1))
-$$(eval $$(call call-make-pre-post,sqlite3,$(1)))
-emcc.flags.sqlite3.$(1) := $(6)
-$$(eval $$(call C-PP.FILTER, $$(sqlite3-api.js.in), $(3), $(5)))
-$(4): $(3) $$(MAKEFILE) $$(sqlite3-wasm.cses) $$(EXPORTED_FUNCTIONS.api) $$(pre-post-sqlite3.deps.$(1))
+$(info Setting up build [$(1)-$(2)]: $(5))
+c-pp.D.$(1)-$(2) := $(6)
+$$(eval $$(call call-make-pre-post,$(1),$(2)))
+emcc.flags.$(1).$(2) ?=
+emcc.flags.$(1).$(2) += $(7)
+$$(eval $$(call C-PP.FILTER, $$(sqlite3-api.js.in), $(4), $(6)))
+$(5): $(4) $$(MAKEFILE) $$(sqlite3-wasm.cfiles) $$(EXPORTED_FUNCTIONS.api) $$(pre-post-$(1)-$(2).deps)
@echo "Building $$@ ..."
$$(emcc.bin) -o $$@ $$(emcc_opt_full) $$(emcc.flags) \
$$(emcc.jsflags) \
- -sENVIRONMENT=$$(emcc.environment.$(1)) \
- $$(pre-post-sqlite3.flags.$(1)) $$(emcc.flags.sqlite3.$(1)) \
- $$(cflags.common) $$(SQLITE_OPT) $$(cflags.wasm_extra_init) $$(sqlite3-wasm.cses)
- @$$(call SQLITE3.xJS.ESM-EXPORT-DEFAULT,$(2))
- @case $(1) in \
+ -sENVIRONMENT=$$(emcc.environment.$(2)) \
+ $$(pre-post-$(1)-$(2).flags) \
+ $$(emcc.flags.$(1)) $$(emcc.flags.$(1).$(2)) \
+ $$(cflags.common) $$(SQLITE_OPT) \
+ $$(cflags.$(1)) $$(cflags.$(1).$(2)) \
+ $$(cflags.wasm_extra_init) $$(sqlite3-wasm.cfiles)
+ @$$(call SQLITE3.xJS.ESM-EXPORT-DEFAULT,$(3))
+ @dotwasm=$$(basename $$@).wasm; \
+ chmod -x $$$$dotwasm; \
+ $(maybe-wasm-strip) $$$$dotwasm; \
+ case $(2) in \
bundler-friendly|node) \
- echo "Patching $(3) for sqlite3.wasm..."; \
- rm -f $$(dir.dout)/sqlite3-$(1).wasm; \
- sed -i -e 's/sqlite3-$(1).wasm/sqlite3.wasm/g' $$@ || exit $$$$?; \
+ echo "Patching $$@ for $(1).wasm..."; \
+ rm -f $$$$dotwasm; \
+ dotwasm=; \
+ sed -i -e 's/$(1)-$(2).wasm/$(1).wasm/g' $$@ || exit $$$$?; \
;; \
- esac
- chmod -x $$(sqlite3.wasm)
- $$(maybe-wasm-strip) $$(sqlite3.wasm)
- @ls -la $@ $$(sqlite3.wasm)
-all: $(4)
-quick: $(4)
-CLEAN_FILES += $(3) $(4)
+ esac; \
+ ls -la $$$$dotwasm $$@
+all: $(5)
+#quick: $(5)
+CLEAN_FILES += $(4) $(5)
endef
# ^^^ /SETUP_LIB_BUILD_MODE
########################################################################
@@ -753,21 +777,24 @@
sqlite3-bundler-friendly.mjs := $(dir.dout)/sqlite3-bundler-friendly.mjs
sqlite3-api-node.mjs := $(dir.dout)/sqlite3-api-node.mjs
sqlite3-node.mjs := $(dir.dout)/sqlite3-node.mjs
-# Maintenance reminder: careful not to introduce spaces around args $1, $2
-#$(info $(call SETUP_LIB_BUILD_MODE,vanilla,0, $(sqlite3-api.js), $(sqlite3.js)))
-$(eval $(call SETUP_LIB_BUILD_MODE,vanilla,0, $(sqlite3-api.js), $(sqlite3.js)))
-$(eval $(call SETUP_LIB_BUILD_MODE,esm,1, $(sqlite3-api.mjs), $(sqlite3.mjs), \
+#$(info $(call SETUP_LIB_BUILD_MODE,sqlite3,vanilla,0, $(sqlite3-api.js), $(sqlite3.js)))
+$(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,vanilla,0,\
+ $(sqlite3-api.js), $(sqlite3.js)))
+$(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,esm,1,\
+ $(sqlite3-api.mjs), $(sqlite3.mjs), \
-Dtarget=es6-module, -sEXPORT_ES6 -sUSE_ES6_IMPORT_META))
-$(eval $(call SETUP_LIB_BUILD_MODE,bundler-friendly,1,\
+$(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,bundler-friendly,1,\
$(sqlite3-api-bundler-friendly.mjs),$(sqlite3-bundler-friendly.mjs),\
- $(c-pp.D.esm) -Dtarget=es6-bundler-friendly))
-$(eval $(call SETUP_LIB_BUILD_MODE,node,1,\
+ $(c-pp.D.sqlite3-esm) -Dtarget=es6-bundler-friendly))
+$(eval $(call SETUP_LIB_BUILD_MODE,sqlite3,node,1,\
$(sqlite3-api-node.mjs),$(sqlite3-node.mjs),\
- $(c-pp.D.bundler-friendly) -Dtarget=node))
+ $(c-pp.D.sqlite3-bundler-friendly) -Dtarget=node))
# The various -D... values used by *.c-pp.js include:
#
# -Dtarget=es6-module: for all ESM module builds
#
+# -Dtarget=node: for node.js builds
+#
# -Dtarget=es6-module -Dtarget=es6-bundler-friendly: intended for
# "bundler-friendly" ESM module build. These have some restrictions
# on how URL() objects are constructed in some contexts: URLs which
@@ -811,11 +838,11 @@
sqlite3-worker1-promiser-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-promiser-bundler-friendly.js
$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1.js)))
$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.js),\
- $(c-pp.D.bundler-friendly)))
+ $(c-pp.D.sqlite3-bundler-friendly)))
$(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.js)))
$(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),\
$(sqlite3-worker1-promiser-bundler-friendly.js),\
- $(c-pp.D.bundler-friendly)))
+ $(c-pp.D.sqlite3-bundler-friendly)))
$(sqlite3-bundler-friendly.mjs): $(sqlite3-worker1-bundler-friendly.js) \
$(sqlite3-worker1-promiser-bundler-friendly.js)
$(sqlite3.js) $(sqlite3.mjs): $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js)
@@ -841,7 +868,7 @@
# a regular basis with different -Ox flags and rebuilding the batch
# pieces each time is an unnecessary time sink.
batch: batch-runner.list
-all: batch
+#all: batch
# end batch-runner.js
########################################################################
# Wasmified speedtest1 is our primary benchmarking tool.
@@ -849,7 +876,7 @@
# emcc.speedtest1.common = emcc flags used by multiple builds of speedtest1
# emcc.speedtest1 = emcc flags used by main build of speedtest1
emcc.speedtest1.common := $(emcc_opt_full)
-emcc.speedtest1 :=
+emcc.speedtest1 := -I. -I$(dir $(sqlite3.canonical.c))
emcc.speedtest1 += -sENVIRONMENT=web
emcc.speedtest1 += -sALLOW_MEMORY_GROWTH
emcc.speedtest1 += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY))
@@ -892,22 +919,24 @@
@{ echo _wasm_main; cat $(EXPORTED_FUNCTIONS.api.main); } > $@
speedtest1.js := $(dir.dout)/speedtest1.js
speedtest1.wasm := $(dir.dout)/speedtest1.wasm
-cflags.speedtest1 := $(cflags.common) -DSQLITE_SPEEDTEST1_WASM
-speedtest1.cses := $(speedtest1.c) $(sqlite3-wasm.c)
+emcc.flags.speedtest1-vanilla := $(cflags.common) -DSQLITE_SPEEDTEST1_WASM
+
+speedtest1.cfiles := $(speedtest1.c) $(sqlite3-wasm.c)
$(eval $(call call-make-pre-post,speedtest1,vanilla))
-$(speedtest1.js): $(MAKEFILE) $(speedtest1.cses) \
- $(pre-post-speedtest1.deps.vanilla) \
+$(speedtest1.js): $(MAKEFILE) $(speedtest1.cfiles) \
+ $(pre-post-speedtest1-vanilla.deps) \
$(EXPORTED_FUNCTIONS.speedtest1)
@echo "Building $@ ..."
$(emcc.bin) \
- $(emcc.speedtest1) -I$(dir $(sqlite3.canonical.c)) \
+ $(emcc.speedtest1) \
$(emcc.speedtest1.common) \
- $(cflags.speedtest1) $(pre-post-speedtest1.flags.vanilla) \
+ $(emcc.flags.speedtest1-vanilla) $(pre-post-speedtest1-vanilla.flags) \
$(SQLITE_OPT) \
-USQLITE_C -DSQLITE_C=$(sqlite3.canonical.c) \
$(speedtest1.exit-runtime0) \
- -o $@ $(speedtest1.cses) -lm
+ -o $@ $(speedtest1.cfiles) -lm
$(maybe-wasm-strip) $(speedtest1.wasm)
+ chmod -x $(speedtest1.wasm)
ls -la $@ $(speedtest1.wasm)
speedtest1: $(speedtest1.js)
@@ -933,13 +962,14 @@
#
# To create those, we filter tester1.c-pp.js with $(bin.c-pp)...
$(eval $(call C-PP.FILTER,tester1.c-pp.js,tester1.js))
-$(eval $(call C-PP.FILTER,tester1.c-pp.js,tester1.mjs,$(c-pp.D.esm)))
+$(eval $(call C-PP.FILTER,tester1.c-pp.js,tester1.mjs,$(c-pp.D.sqlite3-esm)))
$(eval $(call C-PP.FILTER,tester1.c-pp.html,tester1.html))
-$(eval $(call C-PP.FILTER,tester1.c-pp.html,tester1-esm.html,$(c-pp.D.esm)))
+$(eval $(call C-PP.FILTER,tester1.c-pp.html,tester1-esm.html,$(c-pp.D.sqlite3-esm)))
tester1: tester1.js tester1.mjs tester1.html tester1-esm.html
# Note that we do not include $(sqlite3-bundler-friendly.mjs) in this
# because bundlers are client-specific.
all quick: tester1
+quick: $(sqlite3.js)
########################################################################
# Convenience rules to rebuild with various -Ox levels. Much
@@ -959,8 +989,6 @@
$(MAKE) -e "emcc_opt=-O1 $(o-xtra)"
o2: clean
$(MAKE) -j2 -e "emcc_opt=-O2 $(o-xtra)"
-qo2: clean
- $(MAKE) -j2 -e "emcc_opt=-O2 $(o-xtra)" quick
o3: clean
$(MAKE) -e "emcc_opt=-O3 $(o-xtra)"
os: clean
@@ -968,8 +996,6 @@
$(MAKE) -e "emcc_opt=-Os $(o-xtra)"
oz: clean
$(MAKE) -j2 -e "emcc_opt=-Oz $(o-xtra)"
-qoz: clean
- $(MAKE) -j2 -e "emcc_opt=-Oz $(o-xtra)" quick
########################################################################
# Sub-makes...
@@ -981,6 +1007,8 @@
ifneq (,$(filter wasmfs,$(MAKECMDGOALS)))
wasmfs.enable ?= 1
else
+# Unconditionally enable wasmfs for [dist]clean so that the wasmfs
+# sub-make can clean up.
wasmfs.enable ?= $(if $(filter %clean,$(MAKECMDGOALS)),1,0)
endif
ifeq (1,$(wasmfs.enable))
@@ -1011,8 +1039,7 @@
# Push files to public wasm-testing.sqlite.org server
wasm-testing.include = *.js *.mjs *.html \
./tests \
- batch-runner.list \
- $(dir.dout) $(dir.sql) $(dir.common) $(dir.fiddle) $(dir.jacc)
+ $(dir.dout) $(dir.common) $(dir.fiddle) $(dir.jacc)
wasm-testing.exclude = sql/speedtest1.sql
wasm-testing.dir = /jail/sites/wasm-testing
wasm-testing.dest ?= wasm-testing:$(wasm-testing.dir)
diff -Nru sqlite3-3.42.0/ext/wasm/index.html sqlite3-3.44.0-0/ext/wasm/index.html
--- sqlite3-3.42.0/ext/wasm/index.html 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/index.html 2023-11-04 14:23:58.000000000 +0000
@@ -93,9 +93,13 @@
speedtest1: a main-thread WASM build of speedtest1.
-
+
diff -Nru sqlite3-3.42.0/ext/wasm/jaccwabyt/jaccwabyt.js sqlite3-3.44.0-0/ext/wasm/jaccwabyt/jaccwabyt.js
--- sqlite3-3.42.0/ext/wasm/jaccwabyt/jaccwabyt.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/jaccwabyt/jaccwabyt.js 2023-11-04 14:23:58.000000000 +0000
@@ -60,9 +60,9 @@
memberPrefix = (config.memberPrefix || ""),
memberSuffix = (config.memberSuffix || ""),
bigIntEnabled = (undefined===config.bigIntEnabled
- ? !!self['BigInt64Array'] : !!config.bigIntEnabled),
- BigInt = self['BigInt'],
- BigInt64Array = self['BigInt64Array'],
+ ? !!globalThis['BigInt64Array'] : !!config.bigIntEnabled),
+ BigInt = globalThis['BigInt'],
+ BigInt64Array = globalThis['BigInt64Array'],
/* Undocumented (on purpose) config options: */
ptrSizeof = config.ptrSizeof || 4,
ptrIR = config.ptrIR || 'i32'
diff -Nru sqlite3-3.42.0/ext/wasm/module-symbols.html sqlite3-3.44.0-0/ext/wasm/module-symbols.html
--- sqlite3-3.42.0/ext/wasm/module-symbols.html 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/module-symbols.html 2023-11-04 14:23:58.000000000 +0000
@@ -355,6 +355,7 @@
sqlite3_js_db_vfs: 'wasm:/api-c-style.md#sqlite3_js_db_vfs',
sqlite3_js_kvvfs_clear: 'wasm:/api-c-style.md#sqlite3_js_kvvfs',
sqlite3_js_kvvfs_size: 'wasm:/api-c-style.md#sqlite3_js_kvvfs',
+ sqlite3_js_posix_create_file: 'wasm:/api-c-style.md#sqlite3_js_posix_create_file',
sqlite3_js_rc_str: 'wasm:/api-c-style.md#sqlite3_js_rc_str',
sqlite3_js_vfs_create_file: 'wasm:/api-c-style.md#sqlite3_js_vfs_create_file',
sqlite3_js_vfs_list: 'wasm:/api-c-style.md#sqlite3_js_vfs_list',
diff -Nru sqlite3-3.42.0/ext/wasm/README.md sqlite3-3.44.0-0/ext/wasm/README.md
--- sqlite3-3.42.0/ext/wasm/README.md 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/README.md 2023-11-04 14:23:58.000000000 +0000
@@ -82,7 +82,7 @@
# Testing on a remote machine that is accessed via SSH
-*NB: The following are developer notes, last validated on 2022-08-18*
+*NB: The following are developer notes, last validated on 2023-07-19*
* Remote: Install git, emsdk, and althttpd
* Use a [version of althttpd][althttpd] from
@@ -93,13 +93,14 @@
* Local: `ssh -L 8180:localhost:8080 remote`
* Local: Point your web-browser at http://localhost:8180/index.html
-In order to enable [SharedArrayBuffers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer),
-the web-browser requires that the two extra Cross-Origin lines be present
-in HTTP reply headers and that the request must come from "localhost".
-Since the web-server is on a different machine from
-the web-broser, the localhost requirement means that the connection must be tunneled
-using SSH.
+In order to enable [SharedArrayBuffer][], the web-browser requires
+that the two extra Cross-Origin lines be present in HTTP reply headers
+and that the request must come from "localhost" (_or_ over an SSL
+connection). Since the web-server is on a different machine from the
+web-broser, the localhost requirement means that the connection must
+be tunneled using SSH.
[emscripten]: https://emscripten.org
[althttpd]: https://sqlite.org/althttpd
+[SharedArrayBuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer
diff -Nru sqlite3-3.42.0/ext/wasm/scratchpad-wasmfs.html sqlite3-3.44.0-0/ext/wasm/scratchpad-wasmfs.html
--- sqlite3-3.42.0/ext/wasm/scratchpad-wasmfs.html 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/scratchpad-wasmfs.html 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+ sqlite3 WASMFS/OPFS Main-thread Scratchpad
+
+
+ sqlite3 WASMFS/OPFS Main-thread Scratchpad
+
Scratchpad/test app for the WASMF/OPFS integration in the
+ main window thread. This page requires that the sqlite3 API have
+ been built with WASMFS support. If OPFS support is available then
+ it "should" persist a database across reloads (watch the dev console
+ output), otherwise it will not.
+
+
All stuff on this page happens in the dev console.
Scratchpad/test app for the WASMF/OPFS integration in the
- main window thread. This page requires that the sqlite3 API have
- been built with WASMFS support. If OPFS support is available then
- it "should" persist a database across reloads (watch the dev console
- output), otherwise it will not.
-
-
All stuff on this page happens in the dev console.
-
-
-
-
-
-
-
diff -Nru sqlite3-3.42.0/ext/wasm/scratchpad-wasmfs-main.js sqlite3-3.44.0-0/ext/wasm/scratchpad-wasmfs-main.js
--- sqlite3-3.42.0/ext/wasm/scratchpad-wasmfs-main.js 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/scratchpad-wasmfs-main.js 1970-01-01 00:00:00.000000000 +0000
@@ -1,70 +0,0 @@
-/*
- 2022-05-22
-
- The author disclaims copyright to this source code. In place of a
- legal notice, here is a blessing:
-
- * May you do good and not evil.
- * May you find forgiveness for yourself and forgive others.
- * May you share freely, never taking more than you give.
-
- ***********************************************************************
-
- A basic test script for sqlite3-api.js. This file must be run in
- main JS thread and sqlite3.js must have been loaded before it.
-*/
-'use strict';
-(function(){
- const toss = function(...args){throw new Error(args.join(' '))};
- const log = console.log.bind(console),
- warn = console.warn.bind(console),
- error = console.error.bind(console);
-
- const stdout = log;
- const stderr = error;
-
- const test1 = function(db){
- db.exec("create table if not exists t(a);")
- .transaction(function(db){
- db.prepare("insert into t(a) values(?)")
- .bind(new Date().getTime())
- .stepFinalize();
- stdout("Number of values in table t:",
- db.selectValue("select count(*) from t"));
- });
- };
-
- const runTests = function(sqlite3){
- const capi = sqlite3.capi,
- oo = sqlite3.oo1,
- wasm = sqlite3.wasm;
- stdout("Loaded sqlite3:",capi.sqlite3_libversion(), capi.sqlite3_sourceid());
- const persistentDir = capi.sqlite3_wasmfs_opfs_dir();
- if(persistentDir){
- stdout("Persistent storage dir:",persistentDir);
- }else{
- stderr("No persistent storage available.");
- }
- const startTime = performance.now();
- let db;
- try {
- db = new oo.DB(persistentDir+'/foo.db');
- stdout("DB filename:",db.filename);
- const banner1 = '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>',
- banner2 = '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<';
- [
- test1
- ].forEach((f)=>{
- const n = performance.now();
- stdout(banner1,"Running",f.name+"()...");
- f(db, sqlite3);
- stdout(banner2,f.name+"() took ",(performance.now() - n),"ms");
- });
- }finally{
- if(db) db.close();
- }
- stdout("Total test time:",(performance.now() - startTime),"ms");
- };
-
- sqlite3InitModule(self.sqlite3TestModule).then(runTests);
-})();
diff -Nru sqlite3-3.42.0/ext/wasm/scratchpad-wasmfs.mjs sqlite3-3.44.0-0/ext/wasm/scratchpad-wasmfs.mjs
--- sqlite3-3.42.0/ext/wasm/scratchpad-wasmfs.mjs 1970-01-01 00:00:00.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/scratchpad-wasmfs.mjs 2023-11-04 14:23:58.000000000 +0000
@@ -0,0 +1,70 @@
+/*
+ 2022-05-22
+
+ The author disclaims copyright to this source code. In place of a
+ legal notice, here is a blessing:
+
+ * May you do good and not evil.
+ * May you find forgiveness for yourself and forgive others.
+ * May you share freely, never taking more than you give.
+
+ ***********************************************************************
+
+ A basic test script for sqlite3-api.js. This file must be run in
+ main JS thread and sqlite3.js must have been loaded before it.
+*/
+import sqlite3InitModule from './jswasm/sqlite3-wasmfs.mjs';
+//console.log('sqlite3InitModule =',sqlite3InitModule);
+const toss = function(...args){throw new Error(args.join(' '))};
+const log = console.log.bind(console),
+ warn = console.warn.bind(console),
+ error = console.error.bind(console);
+
+const stdout = log;
+const stderr = error;
+
+const test1 = function(db){
+ db.exec("create table if not exists t(a);")
+ .transaction(function(db){
+ db.prepare("insert into t(a) values(?)")
+ .bind(new Date().getTime())
+ .stepFinalize();
+ stdout("Number of values in table t:",
+ db.selectValue("select count(*) from t"));
+ });
+};
+
+const runTests = function(sqlite3){
+ const capi = sqlite3.capi,
+ oo = sqlite3.oo1,
+ wasm = sqlite3.wasm;
+ stdout("Loaded module:",sqlite3);
+ stdout("Loaded sqlite3:",capi.sqlite3_libversion(), capi.sqlite3_sourceid());
+ const persistentDir = capi.sqlite3_wasmfs_opfs_dir();
+ if(persistentDir){
+ stdout("Persistent storage dir:",persistentDir);
+ }else{
+ stderr("No persistent storage available.");
+ }
+ const startTime = performance.now();
+ let db;
+ try {
+ db = new oo.DB(persistentDir+'/foo.db');
+ stdout("DB filename:",db.filename);
+ const banner1 = '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>',
+ banner2 = '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<';
+ [
+ test1
+ ].forEach((f)=>{
+ const n = performance.now();
+ stdout(banner1,"Running",f.name+"()...");
+ f(db, sqlite3);
+ stdout(banner2,f.name+"() took ",(performance.now() - n),"ms");
+ });
+ }finally{
+ if(db) db.close();
+ }
+ stdout("Total test time:",(performance.now() - startTime),"ms");
+};
+
+sqlite3InitModule().then(runTests);
diff -Nru sqlite3-3.42.0/ext/wasm/speedtest1-wasmfs.html sqlite3-3.44.0-0/ext/wasm/speedtest1-wasmfs.html
--- sqlite3-3.42.0/ext/wasm/speedtest1-wasmfs.html 2023-05-16 13:45:19.000000000 +0000
+++ sqlite3-3.44.0-0/ext/wasm/speedtest1-wasmfs.html 2023-11-04 14:23:58.000000000 +0000
@@ -10,141 +10,46 @@
speedtest1-wasmfs.wasm
-
This page starts running the main exe when it loads, which will
- block the UI until it finishes! Adding UI controls to manually configure and start it
- are TODO.