%start_symbol
@@ -1200,6 +1202,21 @@ match any input token.
the wildcard token and some other token, the other token is always used.
The wildcard token is only matched if there are no alternatives.
+
+
4.4.26 The %realloc and %free directives
+
+
The %realloc and %free directives defines function
+that allocate and free heap memory. The signatures of these functions
+should be the same as the realloc() and free() functions from the standard
+C library.
+
+
If both of these functions are defined
+then these functions are used to allocate and free
+memory for supplemental parser stack space, if the initial
+parse stack space is exceeded. The initial parser stack size
+is specified by either %stack_size or the
+-DYYSTACKDEPTH compile-time flag.
+
5.0 Error Processing
@@ -1224,6 +1241,7 @@ to begin parsing a new file. This is what will happen at the very
first syntax error, of course, if there are no instances of the
"error" non-terminal in your grammar.
+
5. Controlling CPU Core Utilization
@@ -29,12 +28,18 @@ 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].
+ * Tests run with `make` commands. Examples:
+ - `make mdevtest`
+ - `make releasetest`
+ - `make sdevtest`
+ - `make testrunner`
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.log**, created in the current working directory. Search this
+file to find details of errors. Suggested search commands:
+
+ * `grep "^!" testrunner.log`
+ * `grep failed testrunner.log`
testrunner.tcl also populates SQLite database **testrunner.db**. This database
contains details of all tests run, running and to be run. A useful query
@@ -60,7 +65,7 @@ Running:
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
+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").
@@ -68,9 +73,9 @@ 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
+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
+`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
@@ -193,7 +198,7 @@ TODO: ./configure + Makefile.msc build systems.
## 3.1. 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
+the `make fuzztest` target once for each of two --enable-all builds - one
with debugging enabled and one without:
```
@@ -283,7 +288,7 @@ a dos \*.bat file on windows. For example:
```
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,
+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.
@@ -310,6 +315,16 @@ would normally execute into the testrunner.log file. Example:
tclsh $TESTDIR/testrunner.tcl --dryrun mdevtest"
```
+The **--explain** option is similar to --dryrun in that it prevents testrunner.tcl
+from building any binaries or running any tests. The difference is that --explain
+prints on standard output a human-readable summary of all the builds and tests that
+would have been run.
+
+```
+ # Show what builds and tests would have been run
+ tclsh $TESTDIR/testrunner.tcl --explain mdevtest
+```
+
# 5. Controlling CPU Core Utilization
@@ -339,6 +354,3 @@ testrunner.log and testrunner.db files:
```
$ ./testfixture $TESTDIR/testrunner.tcl njob $NEW_NUMBER_OF_JOBS
```
-
-
-
diff --git a/ext/consio/console_io.c b/ext/consio/console_io.c
index 3acb0daa27..3e2f556f52 100755
--- a/ext/consio/console_io.c
+++ b/ext/consio/console_io.c
@@ -29,6 +29,9 @@
#ifndef HAVE_CONSOLE_IO_H
# include "console_io.h"
#endif
+#if defined(_MSC_VER)
+# pragma warning(disable : 4204)
+#endif
#ifndef SQLITE_CIO_NO_TRANSLATE
# if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT
@@ -127,6 +130,10 @@ static short streamOfConsole(FILE *pf, /* out */ PerStreamTags *ppst){
# endif
}
+# ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
+# define ENABLE_VIRTUAL_TERMINAL_PROCESSING (0x4)
+# endif
+
# if CIO_WIN_WC_XLATE
/* Define console modes for use with the Windows Console API. */
# define SHELL_CONI_MODE \
@@ -677,4 +684,8 @@ SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn){
}
#endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */
+#if defined(_MSC_VER)
+# pragma warning(default : 4204)
+#endif
+
#undef SHELL_INVALID_FILE_PTR
diff --git a/ext/expert/expert1.test b/ext/expert/expert1.test
index 453334234d..c456c30c52 100644
--- a/ext/expert/expert1.test
+++ b/ext/expert/expert1.test
@@ -8,6 +8,7 @@
# May you share freely, never taking more than you give.
#
#***********************************************************************
+# TESTRUNNER: shell
#
# The focus of this file is testing the CLI shell tool. Specifically,
# the ".recommend" command.
diff --git a/ext/expert/sqlite3expert.c b/ext/expert/sqlite3expert.c
index 33d62226f0..276c2cc9fe 100644
--- a/ext/expert/sqlite3expert.c
+++ b/ext/expert/sqlite3expert.c
@@ -1948,7 +1948,7 @@ sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErrmsg){
sqlite3_stmt *pSql = 0;
rc = idxPrintfPrepareStmt(pNew->db, &pSql, pzErrmsg,
"SELECT sql FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%%'"
- " AND sql NOT LIKE 'CREATE VIRTUAL %%'"
+ " AND sql NOT LIKE 'CREATE VIRTUAL %%' ORDER BY rowid"
);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
const char *zSql = (const char*)sqlite3_column_text(pSql, 0);
diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c
index 65852804a1..f977aabfbc 100644
--- a/ext/fts3/fts3.c
+++ b/ext/fts3/fts3.c
@@ -4006,7 +4006,7 @@ static int fts3ShadowName(const char *zName){
** Implementation of the xIntegrity() method on the FTS3/FTS4 virtual
** table.
*/
-static int fts3Integrity(
+static int fts3IntegrityMethod(
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 */
@@ -4014,31 +4014,24 @@ static int fts3Integrity(
char **pzErr /* Write error message here */
){
Fts3Table *p = (Fts3Table*)pVtab;
- char *zSql;
- int rc;
- char *zErr = 0;
+ int rc = SQLITE_OK;
+ int bOk = 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 ){
+ rc = sqlite3Fts3IntegrityCheck(p, &bOk);
+ assert( rc!=SQLITE_CORRUPT_VTAB );
+ if( rc==SQLITE_ERROR || (rc&0xFF)==SQLITE_CORRUPT ){
*pzErr = sqlite3_mprintf("unable to validate the inverted index for"
" FTS%d table %s.%s: %s",
- p->bFts4 ? 4 : 3, zSchema, zTabname, zErr);
+ p->bFts4 ? 4 : 3, zSchema, zTabname, sqlite3_errstr(rc));
+ if( *pzErr ) rc = SQLITE_OK;
+ }else if( rc==SQLITE_OK && bOk==0 ){
+ *pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s",
+ p->bFts4 ? 4 : 3, zSchema, zTabname);
+ if( *pzErr==0 ) rc = SQLITE_NOMEM;
}
- sqlite3_free(zErr);
- return SQLITE_OK;
+ sqlite3Fts3SegmentsClose(p);
+ return rc;
}
@@ -4068,7 +4061,7 @@ static const sqlite3_module fts3Module = {
/* xRelease */ fts3ReleaseMethod,
/* xRollbackTo */ fts3RollbackToMethod,
/* xShadowName */ fts3ShadowName,
- /* xIntegrity */ fts3Integrity,
+ /* xIntegrity */ fts3IntegrityMethod,
};
/*
diff --git a/ext/fts3/fts3Int.h b/ext/fts3/fts3Int.h
index 5a5b123b4c..3b236faf49 100644
--- a/ext/fts3/fts3Int.h
+++ b/ext/fts3/fts3Int.h
@@ -653,5 +653,7 @@ int sqlite3FtsUnicodeIsdiacritic(int);
int sqlite3Fts3ExprIterate(Fts3Expr*, int (*x)(Fts3Expr*,int,void*), void*);
+int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk);
+
#endif /* !SQLITE_CORE || SQLITE_ENABLE_FTS3 */
#endif /* _FTSINT_H */
diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c
index 0f894943dd..5a449dec1e 100644
--- a/ext/fts3/fts3_write.c
+++ b/ext/fts3/fts3_write.c
@@ -5294,7 +5294,7 @@ static u64 fts3ChecksumIndex(
** If an error occurs (e.g. an OOM or IO error), return an SQLite error
** code. The final value of *pbOk is undefined in this case.
*/
-static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){
+int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk){
int rc = SQLITE_OK; /* Return code */
u64 cksum1 = 0; /* Checksum based on FTS index contents */
u64 cksum2 = 0; /* Checksum based on %_content contents */
@@ -5372,7 +5372,12 @@ static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){
sqlite3_finalize(pStmt);
}
- *pbOk = (cksum1==cksum2);
+ if( rc==SQLITE_CORRUPT_VTAB ){
+ rc = SQLITE_OK;
+ *pbOk = 0;
+ }else{
+ *pbOk = (rc==SQLITE_OK && cksum1==cksum2);
+ }
return rc;
}
@@ -5412,7 +5417,7 @@ static int fts3DoIntegrityCheck(
){
int rc;
int bOk = 0;
- rc = fts3IntegrityCheck(p, &bOk);
+ rc = sqlite3Fts3IntegrityCheck(p, &bOk);
if( rc==SQLITE_OK && bOk==0 ) rc = FTS_CORRUPT_VTAB;
return rc;
}
diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c
index 8eb8f328f6..333fefa2d3 100644
--- a/ext/fts5/fts5_index.c
+++ b/ext/fts5/fts5_index.c
@@ -6837,23 +6837,26 @@ static void fts5IterSetOutputsTokendata(Fts5Iter *pIter){
static void fts5TokendataIterNext(Fts5Iter *pIter, int bFrom, i64 iFrom){
int ii;
Fts5TokenDataIter *pT = pIter->pTokenDataIter;
+ Fts5Index *pIndex = pIter->pIndex;
for(ii=0; iinIter; ii++){
Fts5Iter *p = pT->apIter[ii];
if( p->base.bEof==0
&& (p->base.iRowid==pIter->base.iRowid || (bFrom && p->base.iRowidpIndex, p, bFrom, iFrom);
+ fts5MultiIterNext(pIndex, p, bFrom, iFrom);
while( bFrom && p->base.bEof==0
&& p->base.iRowidpIndex->rc==SQLITE_OK
+ && pIndex->rc==SQLITE_OK
){
- fts5MultiIterNext(p->pIndex, p, 0, 0);
+ fts5MultiIterNext(pIndex, p, 0, 0);
}
}
}
- fts5IterSetOutputsTokendata(pIter);
+ if( pIndex->rc==SQLITE_OK ){
+ fts5IterSetOutputsTokendata(pIter);
+ }
}
/*
diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c
index eb7ee74087..f609f7f34a 100644
--- a/ext/fts5/fts5_main.c
+++ b/ext/fts5/fts5_main.c
@@ -2971,28 +2971,23 @@ static int fts5IntegrityMethod(
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);
+ rc = sqlite3Fts5StorageIntegrity(pTab->pStorage, 0);
if( (rc&0xff)==SQLITE_CORRUPT ){
*pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s",
zSchema, zTabname);
+ rc = (*pzErr) ? SQLITE_OK : SQLITE_NOMEM;
}else if( rc!=SQLITE_OK ){
*pzErr = sqlite3_mprintf("unable to validate the inverted index for"
" FTS5 table %s.%s: %s",
- zSchema, zTabname, zErr);
+ zSchema, zTabname, sqlite3_errstr(rc));
}
- sqlite3_free(zErr);
- return SQLITE_OK;
+ sqlite3Fts5IndexCloseReader(pTab->p.pIndex);
+
+ return rc;
}
static int fts5Init(sqlite3 *db){
diff --git a/ext/fts5/fts5_tcl.c b/ext/fts5/fts5_tcl.c
index 853a41865e..c5b5f41f83 100644
--- a/ext/fts5/fts5_tcl.c
+++ b/ext/fts5/fts5_tcl.c
@@ -1169,7 +1169,7 @@ struct OriginTextTokenizer {
*/
static void f5tOrigintextTokenizerDelete(void *pCtx){
OriginTextCtx *p = (OriginTextCtx*)pCtx;
- ckfree(p);
+ ckfree((char*)p);
}
static int f5tOrigintextCreate(
diff --git a/ext/fts5/test/fts5fault8.test b/ext/fts5/test/fts5fault8.test
index 5afab77541..dc060a1592 100644
--- a/ext/fts5/test/fts5fault8.test
+++ b/ext/fts5/test/fts5fault8.test
@@ -57,7 +57,6 @@ foreach_detail_mode $testprefix {
} ;# foreach_detail_mode...
-
do_execsql_test 4.0 {
CREATE VIRTUAL TABLE x2 USING fts5(a);
INSERT INTO x2(x2, rank) VALUES('crisismerge', 2);
@@ -80,5 +79,18 @@ do_faultsim_test 4 -faults oom-* -prep {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
}
+set TMPDBERROR {1 {unable to open a temporary database file for storing temporary tables}}
+
+do_faultsim_test 5 -faults oom-t* -prep {
+ faultsim_restore_and_reopen
+ execsql { PRAGMA temp_store = memory }
+} -body {
+ execsql { PRAGMA integrity_check }
+} -test {
+ if {[string match {*error code=7*} $testresult]==0} {
+ faultsim_test_result {0 ok} {1 SQLITE_NOMEM} $::TMPDBERROR
+ }
+}
+
finish_test
diff --git a/ext/fts5/test/fts5faultH.test b/ext/fts5/test/fts5faultH.test
index 540b889f37..df430f20fa 100644
--- a/ext/fts5/test/fts5faultH.test
+++ b/ext/fts5/test/fts5faultH.test
@@ -127,7 +127,7 @@ do_execsql_test 3.0 {
COMMIT;
}
-do_faultsim_test 3 -faults oom* -prep {
+do_faultsim_test 3.1 -faults oom* -prep {
} -body {
execsql {
SELECT rowid FROM t1('BBB AND AAA');
@@ -136,6 +136,15 @@ do_faultsim_test 3 -faults oom* -prep {
faultsim_integrity_check
faultsim_test_result {0 {10 35}}
}
+do_faultsim_test 3.2 -faults oom* -prep {
+} -body {
+ execsql {
+ SELECT count(*) FROM t1('BBB');
+ }
+} -test {
+ faultsim_integrity_check
+ faultsim_test_result {0 27}
+}
finish_test
diff --git a/ext/fts5/test/fts5integrity.test b/ext/fts5/test/fts5integrity.test
index f9851dc15a..1bb367538d 100644
--- a/ext/fts5/test/fts5integrity.test
+++ b/ext/fts5/test/fts5integrity.test
@@ -355,4 +355,30 @@ do_execsql_test 11.4 {
PRAGMA integrity_check(t2);
} {ok}
+#-------------------------------------------------------------------
+reset_db
+
+do_execsql_test 12.1 {
+ CREATE VIRTUAL TABLE x1 USING fts5(a, b);
+ INSERT INTO x1 VALUES('one', 'two');
+ INSERT INTO x1 VALUES('three', 'four');
+ INSERT INTO x1 VALUES('five', 'six');
+}
+
+do_execsql_test 12.2 {
+ PRAGMA integrity_check
+} {ok}
+
+db close
+sqlite3 db test.db -readonly 1
+
+explain_i {
+ PRAGMA integrity_check
+ }
+do_execsql_test 12.3 {
+ PRAGMA integrity_check
+} {ok}
+
+
+
finish_test
diff --git a/ext/intck/intck1.test b/ext/intck/intck1.test
new file mode 100644
index 0000000000..187132f766
--- /dev/null
+++ b/ext/intck/intck1.test
@@ -0,0 +1,332 @@
+# 2008 Feb 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the incremental integrity check
+# (intck) extension.
+#
+
+source [file join [file dirname [info script]] intck_common.tcl]
+set testprefix intck1
+return_if_no_intck
+
+foreach {tn sql} {
+ 1 "CREATE TABLE t1(a PRIMARY KEY, b)"
+ 2 "CREATE TABLE t2(a PRIMARY KEY, b) WITHOUT ROWID "
+ 3 "CREATE TABLE t3(a PRIMARY KEY, b) WITHOUT rowID;"
+ 4 "CREATE TABLE t4(a PRIMARY KEY, ROWID)"
+ 5 {CREATE TABLE t5(a PRIMARY KEY, ROWID) WITHOUT ROWID
+ }
+} {
+ do_test 1.1.$tn {
+ db eval $sql
+ set {} {}
+ } {}
+}
+
+set space " \n\v\t\r\f"
+
+do_execsql_test 1.2 {
+ SELECT name, (rtrim(sql, $space) LIKE '%rowid')
+ FROM sqlite_schema WHERE type='table'
+ ORDER BY 1
+} {
+ t1 0
+ t2 1
+ t3 1
+ t4 0
+ t5 1
+}
+
+do_execsql_test 1.3 {
+ CREATE TABLE x1(a COLLATE nocase, b INTEGER, c BLOB);
+ INSERT INTO x1 VALUES('lEtTeRs', 1234, 1234);
+}
+do_execsql_test 1.3.1 {
+ WITH wrapper(c1, c2, c3) AS (
+ SELECT a, b, c FROM x1
+ )
+ SELECT * FROM wrapper WHERE c1='letters';
+} {lEtTeRs 1234 1234}
+do_execsql_test 1.3.2 {
+ WITH wrapper(c1, c2, c3) AS (
+ SELECT a, b, c FROM x1
+ )
+ SELECT * FROM wrapper WHERE c2='1234';
+} {lEtTeRs 1234 1234}
+do_execsql_test 1.3.2 {
+ WITH wrapper(c1, c2, c3) AS (
+ SELECT a, b, c FROM x1
+ )
+ SELECT * FROM wrapper WHERE c3='1234';
+} {}
+
+do_execsql_test 1.4 {
+ CREATE TABLE z1(a, b);
+ CREATE INDEX z1ab ON z1(a+b COLLATE nocase);
+}
+do_execsql_test 1.4.1 {
+ SELECT * FROM z1 INDEXED BY z1ab
+}
+
+do_catchsql_test 1.5.1 {
+ CREATE INDEX z1b ON z1(b ASC NULLS LAST);
+} {1 {unsupported use of NULLS LAST}}
+do_catchsql_test 1.5.2 {
+ CREATE INDEX z1b ON z1(b DESC NULLS LAST);
+} {1 {unsupported use of NULLS LAST}}
+do_catchsql_test 1.5.3 {
+ CREATE INDEX z1b ON z1(b ASC NULLS FIRST);
+} {1 {unsupported use of NULLS FIRST}}
+do_catchsql_test 1.5.4 {
+ CREATE INDEX z1b ON z1(b DESC NULLS FIRST);
+} {1 {unsupported use of NULLS FIRST}}
+
+
+reset_db
+do_execsql_test 1.6.1 {
+ CREATE TABLE t1(i INTEGER PRIMARY KEY, b, c);
+ CREATE INDEX i1 ON t1(b);
+ ANALYZE;
+ INSERT INTO sqlite_stat1 VALUES('t1', 'i1', '10000 10000');
+ ANALYZE sqlite_schema;
+} {}
+do_eqp_test 1.6.2 {
+ SELECT 1 FROM t1 INDEXED BY i1 WHERE (b, i) IS (?, ?);
+} {SEARCH}
+
+
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_test 2.0 {
+ set ic [sqlite3_intck db main]
+ $ic close
+} {}
+
+do_execsql_test 2.1 {
+ CREATE TABLE t1(a, b);
+ INSERT INTO t1 VALUES(1, 2);
+ INSERT INTO t1 VALUES(3, 4);
+
+ CREATE INDEX i1 ON t1(a COLLATE nocase);
+ CREATE INDEX i2 ON t1(b, a);
+ CREATE INDEX i3 ON t1(b + a COLLATE nocase) WHERE a!=1;
+}
+
+do_intck_test 2.2 {
+}
+
+# Delete a row from each of the i1 and i2 indexes using the imposter
+# table interface.
+#
+do_test 2.3 {
+ db eval {SELECT name, rootpage FROM sqlite_schema} {
+ set R($name) $rootpage
+ }
+ sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $R(i1)
+ db eval { CREATE TABLE imp1(a PRIMARY KEY, rowid) WITHOUT ROWID; }
+ sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $R(i2)
+ db eval { CREATE TABLE imp2(b, a, rowid, PRIMARY KEY(b, a)) WITHOUT ROWID; }
+ sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 0
+
+ db eval {
+ DELETE FROM imp1 WHERE rowid=1;
+ DELETE FROM imp2 WHERE rowid=2;
+ }
+
+ db close
+ sqlite3 db test.db
+} {}
+
+do_intck_test 2.4 {
+ {entry (1,1) missing from index i1}
+ {entry (4,3,2) missing from index i2}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 3.0 {
+ CREATE TABLE x1(a, b, c, PRIMARY KEY(c, b)) WITHOUT ROWID;
+ CREATE INDEX x1a ON x1(a COLLATE nocase);
+
+ INSERT INTO x1 VALUES(1, 2, 'three');
+ INSERT INTO x1 VALUES(4, 5, 'six');
+ INSERT INTO x1 VALUES(7, 8, 'nine');
+}
+
+do_intck_test 3.1 { }
+
+do_test 3.2 {
+ db eval {SELECT name, rootpage FROM sqlite_schema} {
+ set R($name) $rootpage
+ }
+ sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $R(x1a)
+ db eval { CREATE TABLE imp1(c, b, a, PRIMARY KEY(c, b)) WITHOUT ROWID }
+ sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 0
+
+ db eval {
+ DELETE FROM imp1 WHERE a=5;
+ }
+ execsql_pp {
+ }
+
+ db close
+ sqlite3 db test.db
+} {}
+
+do_intck_test 3.3 {
+ {entry (4,'six',5) missing from index x1a}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 4.0 {
+ CREATE TABLE www(x, y, z);
+ CREATE INDEX w1 ON www( (x+1), z );
+ INSERT INTO www VALUES(1, 1, 1), (2, 2, 2);
+}
+
+do_intck_test 4.1 { }
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 5.0 {
+ CREATE TABLE t1(a, b);
+ CREATE INDEX i1 ON t1(a COLLATE NOCASE);
+ INSERT INTO t1 VALUES(1, 1);
+ INSERT INTO t1 VALUES(2, 2);
+}
+
+do_test 5.1 {
+ set ic [sqlite3_intck db nosuchdb]
+ $ic step
+} {SQLITE_ERROR}
+
+do_test 5.2 {
+ $ic close
+ set ic [sqlite3_intck db {}]
+ while {[$ic step]=="SQLITE_OK"} {}
+ set res [$ic error]
+ $ic close
+ set res
+} {SQLITE_OK {}}
+
+do_test 5.3 { test_do_intck db "main" } {}
+
+do_test 5.4 {
+ set ret {}
+ set ic [sqlite3_intck db main]
+ db eval [$ic test_sql t1] {
+ if {$error_message!=""} { lappend ret $error_message }
+ }
+ $ic close
+ set ret
+} {}
+
+do_test 5.5 {
+ set ret {}
+ set ic [sqlite3_intck db main]
+ db eval [$ic test_sql {}] {
+ if {$error_message!=""} { lappend ret $error_message }
+ }
+ $ic close
+ set ret
+} {}
+
+db cache flush
+
+do_test 5.6 {
+ set ret {}
+ set ic [sqlite3_intck db main]
+ $ic step
+ db eval [$ic test_sql {}] {
+ if {$error_message!=""} { lappend ret $error_message }
+ }
+ $ic close
+ set ret
+} {}
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 6.0 {
+ CREATE TABLE t1(x, y, PRIMARY KEY(x)) WITHOUT ROWID;
+ CREATE INDEX i1 ON t1(y, x);
+ INSERT INTO t1 VALUES(X'0000', X'1111');
+}
+
+do_intck_test 6.1 {}
+
+do_execsql_test 6.2.1 {
+ PRAGMA writable_schema = 1;
+ UPDATE sqlite_schema SET sql = 'CREATE INDEX i1' WHERE name='i1';
+} {}
+do_intck_test 6.2.2 {}
+
+do_execsql_test 6.3.1 {
+ UPDATE sqlite_schema SET sql = 'CREATE INDEX i1(y' WHERE name='i1';
+} {}
+do_intck_test 6.3.2 {}
+
+do_execsql_test 6.4.1 {
+ UPDATE sqlite_schema
+ SET sql = 'CREATE INDEX i1(y) hello world'
+ WHERE name='i1';
+} {}
+do_intck_test 6.4.2 {}
+
+do_execsql_test 6.5.1 {
+ UPDATE sqlite_schema
+ SET sql = 'CREATE INDEX i1(y, x) WHERE 1 '
+ WHERE name='i1';
+} {}
+do_intck_test 6.5.2 {}
+
+do_execsql_test 6.6.1 {
+ UPDATE sqlite_schema
+ SET sql = 'CREATE INDEX i1( , ) WHERE 1 '
+ WHERE name='i1';
+} {}
+
+do_test 6.7.2 {
+ set ic [sqlite3_intck db main]
+ $ic step
+} {SQLITE_ERROR}
+do_test 6.5.3 {
+ $ic error
+} {SQLITE_ERROR {near "AS": syntax error}}
+$ic close
+
+do_execsql_test 6.6.1 {
+ UPDATE sqlite_schema
+ SET sql = 'CREATE INDEX i1([y'
+ WHERE name='i1';
+} {}
+do_intck_test 6.6.2 {}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 7.0 {
+ CREATE TABLE x1("1", "22", "3333", four);
+ CREATE INDEX i1 ON x1( "1" , "22", NULL);
+ INSERT INTO x1 VALUES(1, 22, 3333, NULL);
+ INSERT INTO x1 VALUES(1, 22, 3333, NULL);
+}
+do_execsql_test 7.1 " CREATE INDEX i2 ON x1( \"1\"\r\n\t ) "
+do_execsql_test 7.2 { CREATE INDEX i3 ON x1( "22" || 'abc''def' || `1` ) }
+do_execsql_test 7.3 { CREATE INDEX i4 ON x1( [22] + [1] ) }
+do_execsql_test 7.4 { CREATE INDEX i5 ON x1( four||'hello' ) }
+
+do_intck_test 7.5 {}
+
+
+finish_test
diff --git a/ext/intck/intck2.test b/ext/intck/intck2.test
new file mode 100644
index 0000000000..23b241b5a9
--- /dev/null
+++ b/ext/intck/intck2.test
@@ -0,0 +1,177 @@
+# 2024 Feb 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the incremental integrity check
+# (intck) extension.
+#
+
+source [file join [file dirname [info script]] intck_common.tcl]
+set testprefix intck2
+return_if_no_intck
+
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);
+ INSERT INTO t1 VALUES(1, 'one');
+ INSERT INTO t1 VALUES(2, 'two');
+ INSERT INTO t1 VALUES(3, 'three');
+ CREATE INDEX i1 ON t1(b);
+}
+
+proc imposter_edit {obj create sql} {
+ sqlite3 xdb test.db
+ set pgno [xdb one {SELECT rootpage FROM sqlite_schema WHERE name=$obj}]
+
+ sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER xdb main 1 $pgno
+ xdb eval $create
+ sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER xdb main 0 0
+ xdb eval $sql
+ xdb close
+}
+
+imposter_edit i1 {
+ CREATE TABLE imp(b, a, PRIMARY KEY(b)) WITHOUT ROWID;
+} {
+ DELETE FROM imp WHERE b='two';
+ INSERT INTO imp(b, a) VALUES('four', 4);
+}
+
+do_intck_test 1.1 {
+ {surplus entry ('four',4) in index i1}
+ {entry ('two',2) missing from index i1}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 2.0 {
+ CREATE TABLE x1(a, b, "c d");
+ CREATE INDEX x1a ON x1(a COLLATE nocase DESC , b ASC);
+ CREATE INDEX x1b ON x1( a || b || ' "''" ' COLLATE binary ASC );
+ CREATE INDEX x1c ON x1( format('%s', a)ASC, format('%d', "c d" ) );
+ INSERT INTO x1 VALUES('one', 2, 3);
+ INSERT INTO x1 VALUES('One', 4, 5);
+ INSERT INTO x1 VALUES('ONE', 6, 7);
+ INSERT INTO x1 VALUES(NULL, NULL, NULL);
+}
+
+do_intck_test 2.1 {}
+
+imposter_edit x1 {
+ CREATE TABLE imp(a, b, c);
+} {
+ DELETE FROM imp WHERE c=7;
+}
+do_intck_test 2.2 {
+ {surplus entry ('ONE',6,3) in index x1a}
+ {surplus entry ('ONE6 "''" ',3) in index x1b}
+ {surplus entry ('ONE','7',3) in index x1c}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 3.0 {
+ CREATE TABLE x1(a, b, c);
+ CREATE INDEX x1all ON x1(a DESC, b ASC, c DESC);
+ INSERT INTO x1 VALUES(2, 1, 2);
+ INSERT INTO x1 VALUES(2, 1, 1);
+ INSERT INTO x1 VALUES(2, 2, 2);
+ INSERT INTO x1 VALUES(2, 2, 1);
+ INSERT INTO x1 VALUES(1, 1, 2);
+ INSERT INTO x1 VALUES(1, 1, 1);
+ INSERT INTO x1 VALUES(1, 2, 2);
+ INSERT INTO x1 VALUES(1, 2, 1);
+}
+
+do_intck_test 3.1 {
+}
+
+imposter_edit x1 {
+ CREATE TABLE imp(a, b, c);
+} {
+ DELETE FROM imp WHERE 1;
+}
+
+db close
+sqlite3 db test.db
+
+do_intck_test 3.2 {
+ {surplus entry (2,1,2,1) in index x1all}
+ {surplus entry (2,1,1,2) in index x1all}
+ {surplus entry (2,2,2,3) in index x1all}
+ {surplus entry (2,2,1,4) in index x1all}
+ {surplus entry (1,1,2,5) in index x1all}
+ {surplus entry (1,1,1,6) in index x1all}
+ {surplus entry (1,2,2,7) in index x1all}
+ {surplus entry (1,2,1,8) in index x1all}
+}
+
+do_execsql_test 3.3 {
+ DELETE FROM x1;
+ INSERT INTO x1 VALUES(NULL, NULL, NULL);
+ INSERT INTO x1 VALUES(NULL, NULL, NULL);
+ INSERT INTO x1 VALUES(NULL, NULL, NULL);
+ INSERT INTO x1 VALUES(NULL, NULL, NULL);
+}
+
+do_intck_test 3.4 {
+}
+
+imposter_edit x1 {
+ CREATE TABLE imp(a, b, c);
+} {
+ DELETE FROM imp WHERE 1;
+ INSERT INTO imp(rowid) VALUES(-123);
+ INSERT INTO imp(rowid) VALUES(456);
+}
+
+db close
+sqlite3 db test.db
+
+do_intck_test 3.5 {
+ {entry (NULL,NULL,NULL,-123) missing from index x1all}
+ {entry (NULL,NULL,NULL,456) missing from index x1all}
+ {surplus entry (NULL,NULL,NULL,1) in index x1all}
+ {surplus entry (NULL,NULL,NULL,2) in index x1all}
+ {surplus entry (NULL,NULL,NULL,3) in index x1all}
+ {surplus entry (NULL,NULL,NULL,4) in index x1all}
+}
+
+reset_db
+
+do_execsql_test 3.6 {
+ CREATE TABLE w1(a PRIMARY KEY, b, c);
+ INSERT INTO w1 VALUES(1.0, NULL, NULL);
+ INSERT INTO w1 VALUES(33.0, NULL, NULL);
+ INSERT INTO w1 VALUES(100.0, NULL, NULL);
+ CREATE INDEX w1bc ON w1(b, c);
+}
+
+do_intck_test 3.7 {
+}
+
+imposter_edit w1 {
+ CREATE TABLE imp(a, b, c);
+} {
+ DELETE FROM imp WHERE a=33;
+ INSERT INTO imp(a) VALUES(1234.5);
+ INSERT INTO imp(a) VALUES(-1234.5);
+}
+
+do_intck_test 3.8 {
+ {surplus entry (33.0,2) in index sqlite_autoindex_w1_1}
+ {entry (1234.5,4) missing from index sqlite_autoindex_w1_1}
+ {entry (NULL,NULL,4) missing from index w1bc}
+ {entry (-1234.5,5) missing from index sqlite_autoindex_w1_1}
+ {entry (NULL,NULL,5) missing from index w1bc}
+ {surplus entry (NULL,NULL,2) in index w1bc}
+}
+
+finish_test
diff --git a/ext/intck/intck_common.tcl b/ext/intck/intck_common.tcl
new file mode 100644
index 0000000000..1e216b59f3
--- /dev/null
+++ b/ext/intck/intck_common.tcl
@@ -0,0 +1,66 @@
+# 2024 Feb 18
+#
+# 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.
+#
+#***********************************************************************
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source $testdir/tester.tcl
+
+ifcapable !vtab||!pragma {
+ proc return_if_no_intck {} {
+ finish_test
+ return -code return
+ }
+ return
+} else {
+ proc return_if_no_intck {} {}
+}
+
+proc do_intck {db {bSuspend 0}} {
+ set ic [sqlite3_intck $db main]
+
+ set ret [list]
+ while {"SQLITE_OK"==[$ic step]} {
+ set msg [$ic message]
+ if {$msg!=""} {
+ lappend ret $msg
+ }
+ if {$bSuspend} {
+ $ic unlock
+ #puts "SQL: [$ic test_sql {}]"
+ #execsql_pp "EXPLAIN query plan [$ic test_sql {}]"
+ #explain_i [$ic test_sql {}]
+ }
+ }
+
+ set err [$ic error]
+ if {[lindex $err 0]!="SQLITE_OK"} {
+ error $err
+ }
+ $ic close
+
+ return $ret
+}
+
+proc intck_sql {db tbl} {
+ set ic [sqlite3_intck $db main]
+ set sql [$ic test_sql $tbl]
+ $ic close
+ return $sql
+}
+
+proc do_intck_test {tn expect} {
+ uplevel [list do_test $tn.a [list do_intck db] [list {*}$expect]]
+ uplevel [list do_test $tn.b [list do_intck db 1] [list {*}$expect]]
+}
+
+
diff --git a/ext/intck/intckbusy.test b/ext/intck/intckbusy.test
new file mode 100644
index 0000000000..7c65b686bb
--- /dev/null
+++ b/ext/intck/intckbusy.test
@@ -0,0 +1,49 @@
+# 2024 February 24
+#
+# 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]] intck_common.tcl]
+set testprefix intckbusy
+return_if_no_intck
+
+
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+ INSERT INTO t1 VALUES(1, 2, 3);
+ INSERT INTO t1 VALUES(2, 'two', 'three');
+ INSERT INTO t1 VALUES(3, NULL, NULL);
+ CREATE INDEX i1 ON t1(b, c);
+}
+
+sqlite3 db2 test.db
+
+do_execsql_test -db db2 1.1 {
+ BEGIN EXCLUSIVE;
+ INSERT INTO t1 VALUES(4, 5, 6);
+}
+
+do_test 1.2 {
+ set ic [sqlite3_intck db main]
+ $ic step
+} {SQLITE_BUSY}
+do_test 1.3 {
+ $ic unlock
+} {SQLITE_BUSY}
+do_test 1.4 {
+ $ic error
+} {SQLITE_BUSY {database is locked}}
+do_test 1.4 {
+ $ic close
+} {}
+
+finish_test
+
diff --git a/ext/intck/intckcorrupt.test b/ext/intck/intckcorrupt.test
new file mode 100644
index 0000000000..eee63b32f0
--- /dev/null
+++ b/ext/intck/intckcorrupt.test
@@ -0,0 +1,236 @@
+# 2024 Feb 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the intck extensions response
+# to corruption at the b-tree level.
+#
+
+source [file join [file dirname [info script]] intck_common.tcl]
+set testprefix intckcorrupt
+return_if_no_intck
+
+#-------------------------------------------------------------------------
+reset_db
+do_test 1.0 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+| size 356352 pagesize 4096 filename crash-acaae0347204ae.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 d0 00 00 00 .....@ ........
+| 32: 40 00 ea 00 00 00 00 00 00 40 00 00 00 40 00 00 @........@...@..
+| 96: 00 00 00 00 0d 00 00 00 04 0e 9c 00 0f ad 0f 4f ...............O
+| 112: 0e fc 0e 9c 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 3728: 00 00 00 00 00 00 00 00 00 00 00 00 5e 04 07 17 ............^...
+| 3744: 1f 1f 01 81 0b 74 61 62 6c 65 74 31 5f 70 61 72 .....tablet1_par
+| 3760: 65 6e 74 74 31 5f 70 61 72 65 6e 74 04 43 52 45 entt1_parent.CRE
+| 3776: 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 70 61 ATE TABLE .t1_pa
+| 3792: 72 65 6e 74 22 28 6e 6f 64 65 6e 6f 20 49 4e 54 rent.(nodeno INT
+| 3808: 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 EGER PRIMARY KEY
+| 3824: 2c 70 61 72 65 6e 74 6e 6f 64 65 29 51 03 06 17 ,parentnode)Q...
+| 3840: 1b 1b 01 7b 74 61 62 6c 65 74 31 5f 6e 6f 64 65 ....tablet1_node
+| 3856: 74 31 5f 6e 6f 64 65 03 43 52 45 41 54 45 20 54 t1_node.CREATE T
+| 3872: 41 42 4c 45 20 22 74 31 5f 6e 6f 64 65 22 28 6e ABLE .t1_node.(n
+| 3888: 6f 64 65 6e 6f 20 49 4e 54 45 47 45 52 20 50 52 odeno INTEGER PR
+| 3904: 49 4d 41 52 59 20 4b 45 59 2c 64 61 74 61 29 5c IMARY KEY,data).
+| 3920: 02 07 17 1d 1d 01 81 0b 74 61 62 6c 65 74 31 5f ........tablet1_
+| 3936: 72 6f 77 69 64 74 31 5f 72 6f 77 69 64 02 43 52 rowidt1_rowid.CR
+| 3952: 45 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 72 EATE TABLE .t1_r
+| 3968: 6f 77 69 64 22 28 72 6f 77 69 64 20 49 4e 54 45 owid.(rowid INTE
+| 3984: 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c GER PRIMARY KEY,
+| 4000: 6e 6f 64 65 6e 6f 2c 61 30 2c 61 31 29 51 01 07 nodeno,a0,a1)Q..
+| 4016: 17 11 11 08 81 0f 74 61 62 6c 65 74 31 74 31 43 ......tablet1t1C
+| 4032: 52 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 REATE VIRTUAL TA
+| 4048: 42 4c 45 20 74 31 20 55 53 49 4e 47 20 72 74 72 BLE t1 USING rtr
+| 4064: 65 65 28 69 64 2c 78 30 20 50 52 49 4d 41 52 59 ee(id,x0 PRIMARY
+| 4080: 20 4b 45 59 2c 70 61 72 65 6e 74 6e 6f 64 65 29 KEY,parentnode)
+| page 2 offset 4096
+| 0: 51 03 06 17 1b 1b 01 7b 74 61 62 6c 65 74 31 5f Q.......tablet1_
+| 16: 6e 6f 64 65 74 31 5f 6e 6f 64 65 03 43 52 45 41 nodet1_node.CREA
+| 32: 54 45 20 54 41 42 4c 45 20 22 74 31 5f 6e 6f 64 TE TABLE .t1_nod
+| 48: 65 22 28 6e 6f 64 65 6e 6f 20 49 4e 54 45 47 45 e.(nodeno INTEGE
+| 64: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 64 61 R PRIMARY KEY,da
+| 80: 74 61 29 5c 02 07 17 1d 1d 01 81 0b 74 61 62 6c ta).........tabl
+| 96: 65 74 31 5f 72 6f 77 69 64 74 31 5f 72 6f 77 69 et1_rowidt1_rowi
+| 112: 64 02 43 52 45 41 54 45 20 54 41 42 4c 45 00 00 d.CREATE TABLE..
+| 128: 01 0a 02 00 00 00 01 0e 0d 00 00 00 00 24 0e 0d .............$..
+| 144: 0c 1a 06 85 50 46 60 27 70 08 00 00 00 00 00 00 ....PF`'p.......
+| 3824: 00 00 00 00 00 00 00 0d 0e 05 00 09 1d 00 74 6f ..............to
+| 3840: 79 20 68 61 6c 66 10 0d 05 00 09 23 00 62 6f 74 y half.....#.bot
+| 3856: 74 6f 6d 20 68 61 6c 66 0f 0c 05 00 09 21 00 72 tom half.....!.r
+| 3872: 69 67 68 74 20 68 61 6c 66 0e 0b 05 00 09 1f 00 ight half.......
+| 3888: 6c 65 66 74 20 43 15 f6 e6 f6 46 50 34 35 24 54 left C....FP45$T
+| 3904: 15 44 52 05 44 14 24 c4 52 02 27 43 15 f6 e6 f6 .DR.D.$.R.'C....
+| 3920: 46 52 22 8e 6f 64 65 6e 6f 20 49 4e 54 45 47 45 FR..odeno INTEGE
+| 3936: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 64 61 R PRIMARY KEY,da
+| 3952: 74 61 29 5c 02 07 17 1d 1d 01 81 0b 74 61 62 6c ta).........tabl
+| 3968: 65 74 31 5f 72 6f 74 74 6f 6d 20 65 64 67 65 0f et1_rottom edge.
+| 3984: 07 05 00 09 21 00 72 69 67 68 74 20 65 64 67 65 ....!.right edge
+| 4000: 0e 06 05 00 09 1f 00 6c 65 66 74 20 65 64 67 65 .......left edge
+| 4016: 0b 05 05 00 09 19 00 63 65 6e 74 65 72 17 04 05 .......center...
+| 4032: 00 09 31 00 75 70 70 65 72 2d 72 69 67 68 74 20 ..1.upper-right
+| 4048: 63 6f 72 6e 65 72 17 03 05 00 09 31 00 6c 6f 77 corner.....1.low
+| 4064: 65 72 2d 72 69 67 68 74 20 63 6f 72 6e 65 72 16 er-right corner.
+| 4080: 02 05 00 09 2f 00 75 70 70 65 72 2d 6c 65 66 74 ..../.upper-left
+| page 3 offset 8192
+| 0: 20 63 6f 72 6e 65 72 16 01 05 00 09 2f 01 8c 6f corner...../..o
+| 16: 77 65 72 2d 6c 53 51 4c 69 74 65 20 66 6f 72 6d wer-lSQLite form
+| 32: 61 74 20 33 00 10 00 01 01 00 40 20 20 00 00 00 at 3......@ ...
+| 48: 00 00 00 00 2f 00 00 0d eb 13 00 00 00 03 00 00 ..../...........
+| 64: 00 04 00 00 00 00 00 00 00 06 00 00 00 01 00 00 ................
+| 80: 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 ................
+| page 6 offset 20480
+| 128: 00 00 00 00 00 00 00 00 97 3d 04 ae 7c 01 00 00 .........=..|...
+| 624: 00 00 00 00 00 00 21 97 3d 04 ae 7c 01 00 00 00 ......!.=..|....
+| 1120: 00 00 00 00 00 20 97 3d 04 ae 7c 01 00 00 00 00 ..... .=..|.....
+| 1616: 00 00 00 00 1f 97 3d 04 ae 7c 01 00 00 00 00 00 ......=..|......
+| 2112: 00 00 00 1e 97 3d 04 ae 7c 01 00 00 00 00 00 00 .....=..|.......
+| 2608: 00 00 1d 97 d3 d0 4a e7 c0 00 00 00 00 00 00 00 ......J.........
+| 3088: 00 00 00 00 00 00 00 00 00 00 00 00 01 f3 00 00 ................
+| 3600: 23 97 3d 04 ae 7c 01 00 00 00 00 00 00 00 00 00 #.=..|..........
+| 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 26 ...............&
+| page 8 offset 28672
+| 0: 0d 00 00 00 01 04 30 00 04 30 00 00 00 00 00 00 ......0..0......
+| 1072: 97 4d 1e 14 00 ae 7c 00 00 00 00 00 00 00 00 00 .M....|.........
+| 1088: 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 ................
+| 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 ................
+| page 10 offset 36864
+| 0: 0d 00 00 00 01 04 30 00 04 30 00 00 00 00 00 00 ......0..0......
+| 1072: 9a ee c1 80 fd 78 1f ce 1b ae eb b4 00 00 00 00 .....x..........
+| 1088: 13 20 ff 20 00 70 00 00 00 60 50 00 00 00 11 e0 . . .p...`P.....
+| 1104: 00 00 00 70 00 00 00 60 50 05 35 14 c6 97 46 52 ...p...`P.5...FR
+| 1120: 06 66 f7 26 d6 17 42 03 30 01 00 00 10 10 04 02 .f.&..B.0.......
+| 1136: 02 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 .........@......
+| 1152: 00 00 00 00 00 40 00 00 00 40 00 00 00 00 00 00 .....@...@......
+| 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 05 ................
+| page 12 offset 45056
+| 0: 0d 00 00 00 01 04 30 00 04 30 e1 b4 30 97 4d 46 ......0..0..0.MF
+| 16: 14 00 ae 7c 00 00 00 00 00 00 00 03 00 00 43 00 ...|..........C.
+| page 47 offset 188416
+| 2512: 00 00 00 00 00 00 00 00 be 00 00 00 00 00 00 00 ................
+| page 87 offset 352256
+| 2512: 00 00 00 00 00 00 00 00 aa 00 00 00 00 00 00 00 ................
+| end crash-acaae0347204ae.db
+}]} {}
+
+do_intck_test 1.1 {
+ {corruption found while reading database schema}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_test 2.0 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+| size 28672 pagesize 4096 filename crash-3afa1ca9e9c1bd.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 06 00 00 00 04 ................
+| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
+| 96: 00 00 00 00 0d 00 00 00 06 0e 88 00 0f b8 0f 6d ...............m
+| 112: 0f 3a 0f 0b 0e d5 0e 88 01 00 00 00 00 00 00 00 .:..............
+| 3712: 00 00 00 00 00 00 00 00 4b 06 06 17 25 25 01 5b ........K...%%.[
+| 3728: 74 61 62 6c 65 73 71 6c 69 74 65 5f 73 74 61 74 tablesqlite_stat
+| 3744: 31 73 71 6c 69 74 65 5f 73 74 61 74 31 07 43 52 1sqlite_stat1.CR
+| 3760: 45 41 54 45 20 54 41 42 4c 45 20 73 71 6c 69 74 EATE TABLE sqlit
+| 3776: 65 5f 73 74 61 74 31 28 74 62 6c 2c 69 64 78 2c e_stat1(tbl,idx,
+| 3792: 73 74 61 74 29 34 05 06 17 13 11 01 53 69 6e 64 stat)4......Sind
+| 3808: 65 78 63 31 63 63 31 06 43 52 45 41 54 45 20 55 exc1cc1.CREATE U
+| 3824: 4e 49 51 55 45 20 49 4e 44 45 58 20 63 31 63 20 NIQUE INDEX c1c
+| 3840: 4f 4e 20 63 31 28 63 2c 20 62 29 2d 04 06 17 13 ON c1(c, b)-....
+| 3856: 11 01 45 69 6e 64 65 78 63 31 64 63 31 05 43 52 ..Eindexc1dc1.CR
+| 3872: 45 41 54 45 20 49 4e 44 45 58 20 63 31 64 20 4f EATE INDEX c1d O
+| 3888: 4e 20 63 31 28 64 2c 20 62 29 31 03 06 17 13 11 N c1(d, b)1.....
+| 3904: 01 4d 69 6e 64 65 78 62 31 63 62 31 05 43 52 45 .Mindexb1cb1.CRE
+| 3920: 41 54 45 20 55 4e 49 51 55 45 20 49 4e 44 45 58 ATE UNIQUE INDEX
+| 3936: 20 62 31 63 20 4f 4e 20 62 31 28 63 29 49 02 06 b1c ON b1(c)I..
+| 3952: 17 11 11 0f 7f 74 61 62 6c 65 63 31 63 31 03 43 .....tablec1c1.C
+| 3968: 52 45 41 54 45 20 54 41 42 4c 45 20 63 31 28 61 REATE TABLE c1(a
+| 3984: 20 49 4e 54 20 50 52 49 4d 41 52 59 20 4b 45 59 INT PRIMARY KEY
+| 4000: 2c 20 62 2c 20 63 2c 20 64 29 20 57 49 54 48 4f , b, c, d) WITHO
+| 4016: 55 54 20 52 4f 57 49 44 46 01 06 17 11 11 01 79 UT ROWIDF......y
+| 4032: 74 61 62 6c 65 62 31 62 31 02 43 52 45 41 54 45 tableb1b1.CREATE
+| 4048: 20 54 41 42 4c 45 20 62 31 28 61 20 49 4e 54 20 TABLE b1(a INT
+| 4064: 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 2c 20 PRIMARY KEY, b,
+| 4080: 63 29 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 c) WITHOUT ROWID
+| page 2 offset 4096
+| 0: 0a 00 00 00 07 0f ca 00 0f fa 0f f2 0f ea 0f e2 ................
+| 16: 0f da 00 00 00 01 00 00 00 00 00 00 00 00 00 00 ................
+| 4032: 00 00 00 00 00 00 00 00 00 00 07 04 01 0f 01 06 ................
+| 4048: 67 07 07 04 01 0f 01 06 66 06 07 04 01 0f 01 05 g.......f.......
+| 4064: 65 05 07 04 01 0f 01 04 64 04 07 04 01 0f 01 03 e.......d.......
+| 4080: 63 03 07 04 01 0f 01 02 62 0f 05 04 09 0f 09 61 c.......b......a
+| page 3 offset 8192
+| 0: 0a 00 00 00 07 0f bd 00 0f f9 0f ef 0f e5 0f db ................
+| 16: 0f d1 0f c7 0f bd 00 00 00 00 01 00 00 00 00 00 ................
+| 4016: 00 00 00 00 00 00 00 00 00 00 00 00 00 09 05 01 ................
+| 4032: 0f 01 01 07 61 07 07 09 05 01 0f 01 01 06 61 06 ....a.........a.
+| 4048: 06 09 05 01 0f 01 01 05 61 05 05 09 05 01 0f 01 ........a.......
+| 4064: 01 04 61 04 04 09 05 01 0f 01 01 03 61 03 03 09 ..a.........a...
+| 4080: 05 01 0f 01 01 02 61 0f 02 06 05 09 0f 09 09 61 ......a........a
+| page 4 offset 12288
+| 0: 0a 00 00 00 07 0f d8 00 0f fc 0f f0 0f ea 0f e4 ................
+| 16: 0f de 0f d8 0f f6 00 00 00 00 00 00 00 00 00 00 ................
+| 4048: 00 00 00 00 00 00 00 00 05 03 01 01 07 07 05 03 ................
+| 4064: 01 01 06 06 05 03 01 01 05 05 05 03 01 01 04 04 ................
+| 4080: 05 03 01 01 03 03 05 03 01 01 0f 02 03 03 09 09 ................
+| page 5 offset 16384
+| 0: 0a 00 00 00 07 0f ca 00 0f fa 0f f2 0f ea 0f 00 ................
+| 4032: 00 00 00 00 00 00 00 00 00 00 07 04 01 0f 01 07 ................
+| 4048: 61 07 07 04 01 0f 01 06 61 06 07 04 01 0f 01 05 a.......a.......
+| 4064: 61 05 07 04 01 1f 01 04 61 04 07 04 01 0f 01 03 a.......a.......
+| 4080: 61 03 07 04 01 0f 01 02 61 02 05 04 09 0f 09 61 a.......a......a
+| page 6 offset 20480
+| 0: 0a 00 00 00 07 0f ca 00 0f fa 0f ea 0f e2 00 00 ................
+| 4032: 00 00 00 00 00 00 00 00 00 00 07 04 01 0f 01 07 ................
+| 4048: 61 07 07 04 01 0f 01 06 61 06 07 04 01 0f 01 05 a.......a.......
+| 4064: 61 05 07 04 01 0f 01 04 61 04 07 04 01 0f 01 03 a.......a.......
+| 4080: 61 03 07 04 01 0f 01 0f 61 02 05 04 09 0f 09 61 a.......a......a
+| page 7 offset 24576
+| 0: 0d 00 00 00 05 0f 1c 00 0f f0 0f e0 0f d3 0f c5 ................
+| 16: 0f b8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 4016: 00 00 00 00 00 00 00 00 0b 05 04 11 11 13 62 31 ..............b1
+| 4032: 62 31 37 20 31 0c 04 04 11 13 13 62 31 62 31 63 b17 1......b1b1c
+| 4048: 37 20 31 0b 03 04 11 11 13 63 31 63 31 37 20 31 7 1......c1c17 1
+| 4064: 0e 02 04 11 13 07 63 31 63 31 64 37 20 31 20 31 ......c1c1d7 1 1
+| 4080: 0e 01 04 11 13 17 63 31 63 31 63 37 20 31 00 00 ......c1c1c7 1..
+| end crash-3afa1ca9e9c1bd.db
+}]} {}
+
+do_intck_test 2.1 {
+ {corruption found while reading database schema}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 3.0 {
+ PRAGMA page_size = 1024;
+ CREATE TABLE t1(a, b);
+ CREATE INDEX i1 ON t1(a);
+ INSERT INTO t1 VALUES(1, 1), (2, 2), (3, 3);
+}
+
+do_test 3.1 {
+ set pgno [db one {SELECT rootpage FROM sqlite_schema WHERE name='t1'}]
+ db close
+ hexio_write test.db [expr ($pgno-1)*1024] 0000
+} {2}
+
+sqlite3 db test.db
+do_intck_test 3.2 {
+ {corruption found while scanning database object i1}
+ {corruption found while scanning database object t1}
+}
+
+finish_test
+
+
diff --git a/ext/intck/intckfault.test b/ext/intck/intckfault.test
new file mode 100644
index 0000000000..0bc06e584a
--- /dev/null
+++ b/ext/intck/intckfault.test
@@ -0,0 +1,42 @@
+# 2024 February 24
+#
+# 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]] intck_common.tcl]
+set testprefix intckfault
+return_if_no_intck
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+ INSERT INTO t1 VALUES(1, 2, 3);
+ INSERT INTO t1 VALUES(2, 'two', 'three');
+ INSERT INTO t1 VALUES(3, NULL, NULL);
+ CREATE INDEX i1 ON t1(b, c);
+}
+
+do_faultsim_test 1 -faults oom-t* -prep {
+} -body {
+ set ::ic [sqlite3_intck db main]
+ set nStep 0
+ while {"SQLITE_OK"==[$::ic step]} {
+ incr nStep
+ if {$nStep==3} { $::ic unlock }
+ }
+ set res [$::ic error]
+ $::ic close
+ set res
+} -test {
+ catch { $::ic close }
+ faultsim_test_result {0 {SQLITE_OK {}}} {0 {SQLITE_NOMEM {}}} {0 {SQLITE_NOMEM {out of memory}}}
+}
+
+finish_test
+
diff --git a/ext/intck/sqlite3intck.c b/ext/intck/sqlite3intck.c
new file mode 100644
index 0000000000..ed169a2664
--- /dev/null
+++ b/ext/intck/sqlite3intck.c
@@ -0,0 +1,940 @@
+/*
+** 2024-02-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.
+**
+*************************************************************************
+*/
+
+#include "sqlite3intck.h"
+#include
+#include
+
+#include
+#include
+
+/*
+** nKeyVal:
+** The number of values that make up the 'key' for the current pCheck
+** statement.
+**
+** rc:
+** Error code returned by most recent sqlite3_intck_step() or
+** sqlite3_intck_unlock() call. This is set to SQLITE_DONE when
+** the integrity-check operation is finished.
+**
+** zErr:
+** If the object has entered the error state, this is the error message.
+** Is freed using sqlite3_free() when the object is deleted.
+**
+** zTestSql:
+** The value returned by the most recent call to sqlite3_intck_testsql().
+** Each call to testsql() frees the previous zTestSql value (using
+** sqlite3_free()) and replaces it with the new value it will return.
+*/
+struct sqlite3_intck {
+ sqlite3 *db;
+ const char *zDb; /* Copy of zDb parameter to _open() */
+ char *zObj; /* Current object. Or NULL. */
+
+ sqlite3_stmt *pCheck; /* Current check statement */
+ char *zKey;
+ int nKeyVal;
+
+ char *zMessage;
+ int bCorruptSchema;
+
+ int rc; /* Error code */
+ char *zErr; /* Error message */
+ char *zTestSql; /* Returned by sqlite3_intck_test_sql() */
+};
+
+
+/*
+** Some error has occurred while using database p->db. Save the error message
+** and error code currently held by the database handle in p->rc and p->zErr.
+*/
+static void intckSaveErrmsg(sqlite3_intck *p){
+ p->rc = sqlite3_errcode(p->db);
+ sqlite3_free(p->zErr);
+ p->zErr = sqlite3_mprintf("%s", sqlite3_errmsg(p->db));
+}
+
+/*
+** If the handle passed as the first argument is already in the error state,
+** then this function is a no-op (returns NULL immediately). Otherwise, if an
+** error occurs within this function, it leaves an error in said handle.
+**
+** Otherwise, this function attempts to prepare SQL statement zSql and
+** return the resulting statement handle to the user.
+*/
+static sqlite3_stmt *intckPrepare(sqlite3_intck *p, const char *zSql){
+ sqlite3_stmt *pRet = 0;
+ if( p->rc==SQLITE_OK ){
+ p->rc = sqlite3_prepare_v2(p->db, zSql, -1, &pRet, 0);
+ if( p->rc!=SQLITE_OK ){
+ intckSaveErrmsg(p);
+ assert( pRet==0 );
+ }
+ }
+ return pRet;
+}
+
+/*
+** If the handle passed as the first argument is already in the error state,
+** then this function is a no-op (returns NULL immediately). Otherwise, if an
+** error occurs within this function, it leaves an error in said handle.
+**
+** Otherwise, this function treats argument zFmt as a printf() style format
+** string. It formats it according to the trailing arguments and then
+** attempts to prepare the results and return the resulting prepared
+** statement.
+*/
+static sqlite3_stmt *intckPrepareFmt(sqlite3_intck *p, const char *zFmt, ...){
+ sqlite3_stmt *pRet = 0;
+ va_list ap;
+ char *zSql = 0;
+ va_start(ap, zFmt);
+ zSql = sqlite3_vmprintf(zFmt, ap);
+ if( p->rc==SQLITE_OK && zSql==0 ){
+ p->rc = SQLITE_NOMEM;
+ }
+ pRet = intckPrepare(p, zSql);
+ sqlite3_free(zSql);
+ va_end(ap);
+ return pRet;
+}
+
+/*
+** Finalize SQL statement pStmt. If an error occurs and the handle passed
+** as the first argument does not already contain an error, store the
+** error in the handle.
+*/
+static void intckFinalize(sqlite3_intck *p, sqlite3_stmt *pStmt){
+ int rc = sqlite3_finalize(pStmt);
+ if( p->rc==SQLITE_OK && rc!=SQLITE_OK ){
+ intckSaveErrmsg(p);
+ }
+}
+
+/*
+** If there is already an error in handle p, return it. Otherwise, call
+** sqlite3_step() on the statement handle and return that value.
+*/
+static int intckStep(sqlite3_intck *p, sqlite3_stmt *pStmt){
+ if( p->rc ) return p->rc;
+ return sqlite3_step(pStmt);
+}
+
+/*
+** Execute SQL statement zSql. There is no way to obtain any results
+** returned by the statement. This function uses the sqlite3_intck error
+** code convention.
+*/
+static void intckExec(sqlite3_intck *p, const char *zSql){
+ sqlite3_stmt *pStmt = 0;
+ pStmt = intckPrepare(p, zSql);
+ intckStep(p, pStmt);
+ intckFinalize(p, pStmt);
+}
+
+/*
+** A wrapper around sqlite3_mprintf() that uses the sqlite3_intck error
+** code convention.
+*/
+static char *intckMprintf(sqlite3_intck *p, const char *zFmt, ...){
+ va_list ap;
+ char *zRet = 0;
+ va_start(ap, zFmt);
+ zRet = sqlite3_vmprintf(zFmt, ap);
+ if( p->rc==SQLITE_OK ){
+ if( zRet==0 ){
+ p->rc = SQLITE_NOMEM;
+ }
+ }else{
+ sqlite3_free(zRet);
+ zRet = 0;
+ }
+ return zRet;
+}
+
+/*
+** This is used by sqlite3_intck_unlock() to save the vector key value
+** required to restart the current pCheck query as a nul-terminated string
+** in p->zKey.
+*/
+static void intckSaveKey(sqlite3_intck *p){
+ int ii;
+ char *zSql = 0;
+ sqlite3_stmt *pStmt = 0;
+ sqlite3_stmt *pXinfo = 0;
+ const char *zDir = 0;
+
+ assert( p->pCheck );
+ assert( p->zKey==0 );
+
+ pXinfo = intckPrepareFmt(p,
+ "SELECT group_concat(desc, '') FROM %Q.sqlite_schema s, "
+ "pragma_index_xinfo(%Q, %Q) "
+ "WHERE s.type='index' AND s.name=%Q",
+ p->zDb, p->zObj, p->zDb, p->zObj
+ );
+ if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXinfo) ){
+ zDir = (const char*)sqlite3_column_text(pXinfo, 0);
+ }
+
+ if( zDir==0 ){
+ /* Object is a table, not an index. This is the easy case,as there are
+ ** no DESC columns or NULL values in a primary key. */
+ const char *zSep = "SELECT '(' || ";
+ for(ii=0; iinKeyVal; ii++){
+ zSql = intckMprintf(p, "%z%squote(?)", zSql, zSep);
+ zSep = " || ', ' || ";
+ }
+ zSql = intckMprintf(p, "%z || ')'", zSql);
+ }else{
+
+ /* Object is an index. */
+ assert( p->nKeyVal>1 );
+ for(ii=p->nKeyVal; ii>0; ii--){
+ int bLastIsDesc = zDir[ii-1]=='1';
+ int bLastIsNull = sqlite3_column_type(p->pCheck, ii)==SQLITE_NULL;
+ const char *zLast = sqlite3_column_name(p->pCheck, ii);
+ char *zLhs = 0;
+ char *zRhs = 0;
+ char *zWhere = 0;
+
+ if( bLastIsNull ){
+ if( bLastIsDesc ) continue;
+ zWhere = intckMprintf(p, "'%s IS NOT NULL'", zLast);
+ }else{
+ const char *zOp = bLastIsDesc ? "<" : ">";
+ zWhere = intckMprintf(p, "'%s %s ' || quote(?%d)", zLast, zOp, ii);
+ }
+
+ if( ii>1 ){
+ const char *zLhsSep = "";
+ const char *zRhsSep = "";
+ int jj;
+ for(jj=0; jjpCheck,jj+1);
+ zLhs = intckMprintf(p, "%z%s%s", zLhs, zLhsSep, zAlias);
+ zRhs = intckMprintf(p, "%z%squote(?%d)", zRhs, zRhsSep, jj+1);
+ zLhsSep = ",";
+ zRhsSep = " || ',' || ";
+ }
+
+ zWhere = intckMprintf(p,
+ "'(%z) IS (' || %z || ') AND ' || %z",
+ zLhs, zRhs, zWhere);
+ }
+ zWhere = intckMprintf(p, "'WHERE ' || %z", zWhere);
+
+ zSql = intckMprintf(p, "%z%s(quote( %z ) )",
+ zSql,
+ (zSql==0 ? "VALUES" : ",\n "),
+ zWhere
+ );
+ }
+ zSql = intckMprintf(p,
+ "WITH wc(q) AS (\n%z\n)"
+ "SELECT 'VALUES' || group_concat('(' || q || ')', ',\n ') FROM wc"
+ , zSql
+ );
+ }
+
+ pStmt = intckPrepare(p, zSql);
+ if( p->rc==SQLITE_OK ){
+ for(ii=0; iinKeyVal; ii++){
+ sqlite3_bind_value(pStmt, ii+1, sqlite3_column_value(p->pCheck, ii+1));
+ }
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){
+ p->zKey = intckMprintf(p,"%s",(const char*)sqlite3_column_text(pStmt, 0));
+ }
+ intckFinalize(p, pStmt);
+ }
+
+ sqlite3_free(zSql);
+ intckFinalize(p, pXinfo);
+}
+
+/*
+** Find the next database object (table or index) to check. If successful,
+** set sqlite3_intck.zObj to point to a nul-terminated buffer containing
+** the object's name before returning.
+*/
+static void intckFindObject(sqlite3_intck *p){
+ sqlite3_stmt *pStmt = 0;
+ char *zPrev = p->zObj;
+ p->zObj = 0;
+
+ assert( p->rc==SQLITE_OK );
+ assert( p->pCheck==0 );
+
+ pStmt = intckPrepareFmt(p,
+ "WITH tables(table_name) AS ("
+ " SELECT name"
+ " FROM %Q.sqlite_schema WHERE (type='table' OR type='index') AND rootpage"
+ " UNION ALL "
+ " SELECT 'sqlite_schema'"
+ ")"
+ "SELECT table_name FROM tables "
+ "WHERE ?1 IS NULL OR table_name%s?1 "
+ "ORDER BY 1"
+ , p->zDb, (p->zKey ? ">=" : ">")
+ );
+
+ if( p->rc==SQLITE_OK ){
+ sqlite3_bind_text(pStmt, 1, zPrev, -1, SQLITE_TRANSIENT);
+ if( sqlite3_step(pStmt)==SQLITE_ROW ){
+ p->zObj = intckMprintf(p,"%s",(const char*)sqlite3_column_text(pStmt, 0));
+ }
+ }
+ intckFinalize(p, pStmt);
+
+ /* If this is a new object, ensure the previous key value is cleared. */
+ if( sqlite3_stricmp(p->zObj, zPrev) ){
+ sqlite3_free(p->zKey);
+ p->zKey = 0;
+ }
+
+ sqlite3_free(zPrev);
+}
+
+/*
+** Return the size in bytes of the first token in nul-terminated buffer z.
+** For the purposes of this call, a token is either:
+**
+** * a quoted SQL string,
+* * a contiguous series of ascii alphabet characters, or
+* * any other single byte.
+*/
+static int intckGetToken(const char *z){
+ char c = z[0];
+ int iRet = 1;
+ if( c=='\'' || c=='"' || c=='`' ){
+ while( 1 ){
+ if( z[iRet]==c ){
+ iRet++;
+ if( z[iRet]!=c ) break;
+ }
+ iRet++;
+ }
+ }
+ else if( c=='[' ){
+ while( z[iRet++]!=']' && z[iRet] );
+ }
+ else if( (c>='A' && c<='Z') || (c>='a' && c<='z') ){
+ while( (z[iRet]>='A' && z[iRet]<='Z') || (z[iRet]>='a' && z[iRet]<='z') ){
+ iRet++;
+ }
+ }
+
+ return iRet;
+}
+
+/*
+** Return true if argument c is an ascii whitespace character.
+*/
+static int intckIsSpace(char c){
+ return (c==' ' || c=='\t' || c=='\n' || c=='\r');
+}
+
+/*
+** Argument z points to the text of a CREATE INDEX statement. This function
+** identifies the part of the text that contains either the index WHERE
+** clause (if iCol<0) or the iCol'th column of the index.
+**
+** If (iCol<0), the identified fragment does not include the "WHERE" keyword,
+** only the expression that follows it. If (iCol>=0) then the identified
+** fragment does not include any trailing sort-order keywords - "ASC" or
+** "DESC".
+**
+** If the CREATE INDEX statement does not contain the requested field or
+** clause, NULL is returned and (*pnByte) is set to 0. Otherwise, a pointer to
+** the identified fragment is returned and output parameter (*pnByte) set
+** to its size in bytes.
+*/
+static const char *intckParseCreateIndex(const char *z, int iCol, int *pnByte){
+ int iOff = 0;
+ int iThisCol = 0;
+ int iStart = 0;
+ int nOpen = 0;
+
+ const char *zRet = 0;
+ int nRet = 0;
+
+ int iEndOfCol = 0;
+
+ /* Skip forward until the first "(" token */
+ while( z[iOff]!='(' ){
+ iOff += intckGetToken(&z[iOff]);
+ if( z[iOff]=='\0' ) return 0;
+ }
+ assert( z[iOff]=='(' );
+
+ nOpen = 1;
+ iOff++;
+ iStart = iOff;
+ while( z[iOff] ){
+ const char *zToken = &z[iOff];
+ int nToken = 0;
+
+ /* Check if this is the end of the current column - either a "," or ")"
+ ** when nOpen==1. */
+ if( nOpen==1 ){
+ if( z[iOff]==',' || z[iOff]==')' ){
+ if( iCol==iThisCol ){
+ int iEnd = iEndOfCol ? iEndOfCol : iOff;
+ nRet = (iEnd - iStart);
+ zRet = &z[iStart];
+ break;
+ }
+ iStart = iOff+1;
+ while( intckIsSpace(z[iStart]) ) iStart++;
+ iThisCol++;
+ }
+ if( z[iOff]==')' ) break;
+ }
+ if( z[iOff]=='(' ) nOpen++;
+ if( z[iOff]==')' ) nOpen--;
+ nToken = intckGetToken(zToken);
+
+ if( (nToken==3 && 0==sqlite3_strnicmp(zToken, "ASC", nToken))
+ || (nToken==4 && 0==sqlite3_strnicmp(zToken, "DESC", nToken))
+ ){
+ iEndOfCol = iOff;
+ }else if( 0==intckIsSpace(zToken[0]) ){
+ iEndOfCol = 0;
+ }
+
+ iOff += nToken;
+ }
+
+ /* iStart is now the byte offset of 1 byte passed the final ')' in the
+ ** CREATE INDEX statement. Try to find a WHERE clause to return. */
+ while( zRet==0 && z[iOff] ){
+ int n = intckGetToken(&z[iOff]);
+ if( n==5 && 0==sqlite3_strnicmp(&z[iOff], "where", 5) ){
+ zRet = &z[iOff+5];
+ nRet = (int)strlen(zRet);
+ }
+ iOff += n;
+ }
+
+ /* Trim any whitespace from the start and end of the returned string. */
+ if( zRet ){
+ while( intckIsSpace(zRet[0]) ){
+ nRet--;
+ zRet++;
+ }
+ while( nRet>0 && intckIsSpace(zRet[nRet-1]) ) nRet--;
+ }
+
+ *pnByte = nRet;
+ return zRet;
+}
+
+/*
+** User-defined SQL function wrapper for intckParseCreateIndex():
+**
+** SELECT parse_create_index(, );
+*/
+static void intckParseCreateIndexFunc(
+ sqlite3_context *pCtx,
+ int nVal,
+ sqlite3_value **apVal
+){
+ const char *zSql = (const char*)sqlite3_value_text(apVal[0]);
+ int idx = sqlite3_value_int(apVal[1]);
+ const char *zRes = 0;
+ int nRes = 0;
+
+ assert( nVal==2 );
+ if( zSql ){
+ zRes = intckParseCreateIndex(zSql, idx, &nRes);
+ }
+ sqlite3_result_text(pCtx, zRes, nRes, SQLITE_TRANSIENT);
+}
+
+/*
+** Return true if sqlite3_intck.db has automatic indexes enabled, false
+** otherwise.
+*/
+static int intckGetAutoIndex(sqlite3_intck *p){
+ int bRet = 0;
+ sqlite3_stmt *pStmt = 0;
+ pStmt = intckPrepare(p, "PRAGMA automatic_index");
+ if( SQLITE_ROW==intckStep(p, pStmt) ){
+ bRet = sqlite3_column_int(pStmt, 0);
+ }
+ intckFinalize(p, pStmt);
+ return bRet;
+}
+
+/*
+** Return true if zObj is an index, or false otherwise.
+*/
+static int intckIsIndex(sqlite3_intck *p, const char *zObj){
+ int bRet = 0;
+ sqlite3_stmt *pStmt = 0;
+ pStmt = intckPrepareFmt(p,
+ "SELECT 1 FROM %Q.sqlite_schema WHERE name=%Q AND type='index'",
+ p->zDb, zObj
+ );
+ if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+ bRet = 1;
+ }
+ intckFinalize(p, pStmt);
+ return bRet;
+}
+
+/*
+** Return a pointer to a nul-terminated buffer containing the SQL statement
+** used to check database object zObj (a table or index) for corruption.
+** If parameter zPrev is not NULL, then it must be a string containing the
+** vector key required to restart the check where it left off last time.
+** If pnKeyVal is not NULL, then (*pnKeyVal) is set to the number of
+** columns in the vector key value for the specified object.
+**
+** This function uses the sqlite3_intck error code convention.
+*/
+static char *intckCheckObjectSql(
+ sqlite3_intck *p, /* Integrity check object */
+ const char *zObj, /* Object (table or index) to scan */
+ const char *zPrev, /* Restart key vector, if any */
+ int *pnKeyVal /* OUT: Number of key-values for this scan */
+){
+ char *zRet = 0;
+ sqlite3_stmt *pStmt = 0;
+ int bAutoIndex = 0;
+ int bIsIndex = 0;
+
+ const char *zCommon =
+ /* Relation without_rowid also contains just one row. Column "b" is
+ ** set to true if the table being examined is a WITHOUT ROWID table,
+ ** or false otherwise. */
+ ", without_rowid(b) AS ("
+ " SELECT EXISTS ("
+ " SELECT 1 FROM tabname, pragma_index_list(tab, db) AS l"
+ " WHERE origin='pk' "
+ " AND NOT EXISTS (SELECT 1 FROM sqlite_schema WHERE name=l.name)"
+ " )"
+ ")"
+ ""
+ /* Table idx_cols contains 1 row for each column in each index on the
+ ** table being checked. Columns are:
+ **
+ ** idx_name: Name of the index.
+ ** idx_ispk: True if this index is the PK of a WITHOUT ROWID table.
+ ** col_name: Name of indexed column, or NULL for index on expression.
+ ** col_expr: Indexed expression, including COLLATE clause.
+ ** col_alias: Alias used for column in 'intck_wrapper' table.
+ */
+ ", idx_cols(idx_name, idx_ispk, col_name, col_expr, col_alias) AS ("
+ " SELECT l.name, (l.origin=='pk' AND w.b), i.name, COALESCE(("
+ " SELECT parse_create_index(sql, i.seqno) FROM "
+ " sqlite_schema WHERE name = l.name"
+ " ), format('\"%w\"', i.name) || ' COLLATE ' || quote(i.coll)),"
+ " 'c' || row_number() OVER ()"
+ " FROM "
+ " tabname t,"
+ " without_rowid w,"
+ " pragma_index_list(t.tab, t.db) l,"
+ " pragma_index_xinfo(l.name) i"
+ " WHERE i.key"
+ " UNION ALL"
+ " SELECT '', 1, '_rowid_', '_rowid_', 'r1' FROM without_rowid WHERE b=0"
+ ")"
+ ""
+ ""
+ /*
+ ** For a PK declared as "PRIMARY KEY(a, b) ... WITHOUT ROWID", where
+ ** the intck_wrapper aliases of "a" and "b" are "c1" and "c2":
+ **
+ ** o_pk: "o.c1, o.c2"
+ ** i_pk: "i.'a', i.'b'"
+ ** ...
+ ** n_pk: 2
+ */
+ ", tabpk(db, tab, idx, o_pk, i_pk, q_pk, eq_pk, ps_pk, pk_pk, n_pk) AS ("
+ " WITH pkfields(f, a) AS ("
+ " SELECT i.col_name, i.col_alias FROM idx_cols i WHERE i.idx_ispk"
+ " )"
+ " SELECT t.db, t.tab, t.idx, "
+ " group_concat(a, ', '), "
+ " group_concat('i.'||quote(f), ', '), "
+ " group_concat('quote(o.'||a||')', ' || '','' || '), "
+ " format('(%s)==(%s)',"
+ " group_concat('o.'||a, ', '), "
+ " group_concat(format('\"%w\"', f), ', ')"
+ " ),"
+ " group_concat('%s', ','),"
+ " group_concat('quote('||a||')', ', '), "
+ " count(*)"
+ " FROM tabname t, pkfields"
+ ")"
+ ""
+ ", idx(name, match_expr, partial, partial_alias, idx_ps, idx_idx) AS ("
+ " SELECT idx_name,"
+ " format('(%s,%s) IS (%s,%s)', "
+ " group_concat(i.col_expr, ', '), i_pk,"
+ " group_concat('o.'||i.col_alias, ', '), o_pk"
+ " ), "
+ " parse_create_index("
+ " (SELECT sql FROM sqlite_schema WHERE name=idx_name), -1"
+ " ),"
+ " 'cond' || row_number() OVER ()"
+ " , group_concat('%s', ',')"
+ " , group_concat('quote('||i.col_alias||')', ', ')"
+ " FROM tabpk t, "
+ " without_rowid w,"
+ " idx_cols i"
+ " WHERE i.idx_ispk==0 "
+ " GROUP BY idx_name"
+ ")"
+ ""
+ ", wrapper_with(s) AS ("
+ " SELECT 'intck_wrapper AS (\n SELECT\n ' || ("
+ " WITH f(a, b) AS ("
+ " SELECT col_expr, col_alias FROM idx_cols"
+ " UNION ALL "
+ " SELECT partial, partial_alias FROM idx WHERE partial IS NOT NULL"
+ " )"
+ " SELECT group_concat(format('%s AS %s', a, b), ',\n ') FROM f"
+ " )"
+ " || format('\n FROM %Q.%Q ', t.db, t.tab)"
+ /* If the object being checked is a table, append "NOT INDEXED".
+ ** Otherwise, append "INDEXED BY ", and then, if the index
+ ** is a partial index " WHERE ". */
+ " || CASE WHEN t.idx IS NULL THEN "
+ " 'NOT INDEXED'"
+ " ELSE"
+ " format('INDEXED BY %Q%s', t.idx, ' WHERE '||i.partial)"
+ " END"
+ " || '\n)'"
+ " FROM tabname t LEFT JOIN idx i ON (i.name=t.idx)"
+ ")"
+ ""
+ ;
+
+ bAutoIndex = intckGetAutoIndex(p);
+ if( bAutoIndex ) intckExec(p, "PRAGMA automatic_index = 0");
+
+ bIsIndex = intckIsIndex(p, zObj);
+ if( bIsIndex ){
+ pStmt = intckPrepareFmt(p,
+ /* Table idxname contains a single row. The first column, "db", contains
+ ** the name of the db containing the table (e.g. "main") and the second,
+ ** "tab", the name of the table itself. */
+ "WITH tabname(db, tab, idx) AS ("
+ " SELECT %Q, (SELECT tbl_name FROM %Q.sqlite_schema WHERE name=%Q), %Q "
+ ")"
+ ""
+ ", whereclause(w_c) AS (%s)"
+ ""
+ "%s" /* zCommon */
+ ""
+ ", case_statement(c) AS ("
+ " SELECT "
+ " 'CASE WHEN (' || group_concat(col_alias, ', ') || ', 1) IS (\n' "
+ " || ' SELECT ' || group_concat(col_expr, ', ') || ', 1 FROM '"
+ " || format('%%Q.%%Q NOT INDEXED WHERE %%s\n', t.db, t.tab, p.eq_pk)"
+ " || ' )\n THEN NULL\n '"
+ " || 'ELSE format(''surplus entry ('"
+ " || group_concat('%%s', ',') || ',' || p.ps_pk"
+ " || ') in index ' || t.idx || ''', ' "
+ " || group_concat('quote('||i.col_alias||')', ', ') || ', ' || p.pk_pk"
+ " || ')'"
+ " || '\n END AS error_message'"
+ " FROM tabname t, tabpk p, idx_cols i WHERE i.idx_name=t.idx"
+ ")"
+ ""
+ ", thiskey(k, n) AS ("
+ " SELECT group_concat(i.col_alias, ', ') || ', ' || p.o_pk, "
+ " count(*) + p.n_pk "
+ " FROM tabpk p, idx_cols i WHERE i.idx_name=p.idx"
+ ")"
+ ""
+ ", main_select(m, n) AS ("
+ " SELECT format("
+ " 'WITH %%s\n' ||"
+ " ', idx_checker AS (\n' ||"
+ " ' SELECT %%s,\n' ||"
+ " ' %%s\n' || "
+ " ' FROM intck_wrapper AS o\n' ||"
+ " ')\n',"
+ " ww.s, c, t.k"
+ " ), t.n"
+ " FROM case_statement, wrapper_with ww, thiskey t"
+ ")"
+
+ "SELECT m || "
+ " group_concat('SELECT * FROM idx_checker ' || w_c, ' UNION ALL '), n"
+ " FROM "
+ "main_select, whereclause "
+ , p->zDb, p->zDb, zObj, zObj
+ , zPrev ? zPrev : "VALUES('')", zCommon
+ );
+ }else{
+ pStmt = intckPrepareFmt(p,
+ /* Table tabname contains a single row. The first column, "db", contains
+ ** the name of the db containing the table (e.g. "main") and the second,
+ ** "tab", the name of the table itself. */
+ "WITH tabname(db, tab, idx, prev) AS (SELECT %Q, %Q, NULL, %Q)"
+ ""
+ "%s" /* zCommon */
+
+ /* expr(e) contains one row for each index on table zObj. Value e
+ ** is set to an expression that evaluates to NULL if the required
+ ** entry is present in the index, or an error message otherwise. */
+ ", expr(e, p) AS ("
+ " SELECT format('CASE WHEN EXISTS \n"
+ " (SELECT 1 FROM %%Q.%%Q AS i INDEXED BY %%Q WHERE %%s%%s)\n"
+ " THEN NULL\n"
+ " ELSE format(''entry (%%s,%%s) missing from index %%s'', %%s, %%s)\n"
+ " END\n'"
+ " , t.db, t.tab, i.name, i.match_expr, ' AND (' || partial || ')',"
+ " i.idx_ps, t.ps_pk, i.name, i.idx_idx, t.pk_pk),"
+ " CASE WHEN partial IS NULL THEN NULL ELSE i.partial_alias END"
+ " FROM tabpk t, idx i"
+ ")"
+
+ ", numbered(ii, cond, e) AS ("
+ " SELECT 0, 'n.ii=0', 'NULL'"
+ " UNION ALL "
+ " SELECT row_number() OVER (),"
+ " '(n.ii='||row_number() OVER ()||COALESCE(' AND '||p||')', ')'), e"
+ " FROM expr"
+ ")"
+
+ ", counter_with(w) AS ("
+ " SELECT 'WITH intck_counter(ii) AS (\n ' || "
+ " group_concat('SELECT '||ii, ' UNION ALL\n ') "
+ " || '\n)' FROM numbered"
+ ")"
+ ""
+ ", case_statement(c) AS ("
+ " SELECT 'CASE ' || "
+ " group_concat(format('\n WHEN %%s THEN (%%s)', cond, e), '') ||"
+ " '\nEND AS error_message'"
+ " FROM numbered"
+ ")"
+ ""
+
+ /* This table contains a single row consisting of a single value -
+ ** the text of an SQL expression that may be used by the main SQL
+ ** statement to output an SQL literal that can be used to resume
+ ** the scan if it is suspended. e.g. for a rowid table, an expression
+ ** like:
+ **
+ ** format('(%d,%d)', _rowid_, n.ii)
+ */
+ ", thiskey(k, n) AS ("
+ " SELECT o_pk || ', ii', n_pk+1 FROM tabpk"
+ ")"
+ ""
+ ", whereclause(w_c) AS ("
+ " SELECT CASE WHEN prev!='' THEN "
+ " '\nWHERE (' || o_pk ||', n.ii) > ' || prev"
+ " ELSE ''"
+ " END"
+ " FROM tabpk, tabname"
+ ")"
+ ""
+ ", main_select(m, n) AS ("
+ " SELECT format("
+ " '%%s, %%s\nSELECT %%s,\n%%s\nFROM intck_wrapper AS o"
+ ", intck_counter AS n%%s\nORDER BY %%s', "
+ " w, ww.s, c, thiskey.k, whereclause.w_c, t.o_pk"
+ " ), thiskey.n"
+ " FROM case_statement, tabpk t, counter_with, "
+ " wrapper_with ww, thiskey, whereclause"
+ ")"
+
+ "SELECT m, n FROM main_select",
+ p->zDb, zObj, zPrev, zCommon
+ );
+ }
+
+ while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+ zRet = intckMprintf(p, "%s", (const char*)sqlite3_column_text(pStmt, 0));
+ if( pnKeyVal ){
+ *pnKeyVal = sqlite3_column_int(pStmt, 1);
+ }
+ }
+ intckFinalize(p, pStmt);
+
+ if( bAutoIndex ) intckExec(p, "PRAGMA automatic_index = 1");
+ return zRet;
+}
+
+/*
+** Open a new integrity-check object.
+*/
+int sqlite3_intck_open(
+ sqlite3 *db, /* Database handle to operate on */
+ const char *zDbArg, /* "main", "temp" etc. */
+ sqlite3_intck **ppOut /* OUT: New integrity-check handle */
+){
+ sqlite3_intck *pNew = 0;
+ int rc = SQLITE_OK;
+ const char *zDb = zDbArg ? zDbArg : "main";
+ int nDb = (int)strlen(zDb);
+
+ pNew = (sqlite3_intck*)sqlite3_malloc(sizeof(*pNew) + nDb + 1);
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ memset(pNew, 0, sizeof(*pNew));
+ pNew->db = db;
+ pNew->zDb = (const char*)&pNew[1];
+ memcpy(&pNew[1], zDb, nDb+1);
+ rc = sqlite3_create_function(db, "parse_create_index",
+ 2, SQLITE_UTF8, 0, intckParseCreateIndexFunc, 0, 0
+ );
+ if( rc!=SQLITE_OK ){
+ sqlite3_intck_close(pNew);
+ pNew = 0;
+ }
+ }
+
+ *ppOut = pNew;
+ return rc;
+}
+
+/*
+** Free the integrity-check object.
+*/
+void sqlite3_intck_close(sqlite3_intck *p){
+ if( p ){
+ sqlite3_finalize(p->pCheck);
+ sqlite3_create_function(
+ p->db, "parse_create_index", 1, SQLITE_UTF8, 0, 0, 0, 0
+ );
+ sqlite3_free(p->zObj);
+ sqlite3_free(p->zKey);
+ sqlite3_free(p->zTestSql);
+ sqlite3_free(p->zErr);
+ sqlite3_free(p->zMessage);
+ sqlite3_free(p);
+ }
+}
+
+/*
+** Step the integrity-check object.
+*/
+int sqlite3_intck_step(sqlite3_intck *p){
+ if( p->rc==SQLITE_OK ){
+
+ if( p->zMessage ){
+ sqlite3_free(p->zMessage);
+ p->zMessage = 0;
+ }
+
+ if( p->bCorruptSchema ){
+ p->rc = SQLITE_DONE;
+ }else
+ if( p->pCheck==0 ){
+ intckFindObject(p);
+ if( p->rc==SQLITE_OK ){
+ if( p->zObj ){
+ char *zSql = 0;
+ zSql = intckCheckObjectSql(p, p->zObj, p->zKey, &p->nKeyVal);
+ p->pCheck = intckPrepare(p, zSql);
+ sqlite3_free(zSql);
+ sqlite3_free(p->zKey);
+ p->zKey = 0;
+ }else{
+ p->rc = SQLITE_DONE;
+ }
+ }else if( p->rc==SQLITE_CORRUPT ){
+ p->rc = SQLITE_OK;
+ p->zMessage = intckMprintf(p, "%s",
+ "corruption found while reading database schema"
+ );
+ p->bCorruptSchema = 1;
+ }
+ }
+
+ if( p->pCheck ){
+ assert( p->rc==SQLITE_OK );
+ if( sqlite3_step(p->pCheck)==SQLITE_ROW ){
+ /* Normal case, do nothing. */
+ }else{
+ intckFinalize(p, p->pCheck);
+ p->pCheck = 0;
+ p->nKeyVal = 0;
+ if( p->rc==SQLITE_CORRUPT ){
+ p->rc = SQLITE_OK;
+ p->zMessage = intckMprintf(p,
+ "corruption found while scanning database object %s", p->zObj
+ );
+ }
+ }
+ }
+ }
+
+ return p->rc;
+}
+
+/*
+** Return a message describing the corruption encountered by the most recent
+** call to sqlite3_intck_step(), or NULL if no corruption was encountered.
+*/
+const char *sqlite3_intck_message(sqlite3_intck *p){
+ assert( p->pCheck==0 || p->zMessage==0 );
+ if( p->zMessage ){
+ return p->zMessage;
+ }
+ if( p->pCheck ){
+ return (const char*)sqlite3_column_text(p->pCheck, 0);
+ }
+ return 0;
+}
+
+/*
+** Return the error code and message.
+*/
+int sqlite3_intck_error(sqlite3_intck *p, const char **pzErr){
+ if( pzErr ) *pzErr = p->zErr;
+ return (p->rc==SQLITE_DONE ? SQLITE_OK : p->rc);
+}
+
+/*
+** Close any read transaction the integrity-check object is holding open
+** on the database.
+*/
+int sqlite3_intck_unlock(sqlite3_intck *p){
+ if( p->rc==SQLITE_OK && p->pCheck ){
+ assert( p->zKey==0 && p->nKeyVal>0 );
+ intckSaveKey(p);
+ intckFinalize(p, p->pCheck);
+ p->pCheck = 0;
+ }
+ return p->rc;
+}
+
+/*
+** Return the SQL statement used to check object zObj. Or, if zObj is
+** NULL, the current SQL statement.
+*/
+const char *sqlite3_intck_test_sql(sqlite3_intck *p, const char *zObj){
+ sqlite3_free(p->zTestSql);
+ if( zObj ){
+ p->zTestSql = intckCheckObjectSql(p, zObj, 0, 0);
+ }else{
+ if( p->zObj ){
+ p->zTestSql = intckCheckObjectSql(p, p->zObj, p->zKey, 0);
+ }else{
+ sqlite3_free(p->zTestSql);
+ p->zTestSql = 0;
+ }
+ }
+ return p->zTestSql;
+}
diff --git a/ext/intck/sqlite3intck.h b/ext/intck/sqlite3intck.h
new file mode 100644
index 0000000000..e08a86f289
--- /dev/null
+++ b/ext/intck/sqlite3intck.h
@@ -0,0 +1,171 @@
+/*
+** 2024-02-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.
+**
+*************************************************************************
+*/
+
+/*
+** Incremental Integrity-Check Extension
+** -------------------------------------
+**
+** This module contains code to check whether or not an SQLite database
+** is well-formed or corrupt. This is the same task as performed by SQLite's
+** built-in "PRAGMA integrity_check" command. This module differs from
+** "PRAGMA integrity_check" in that:
+**
+** + It is less thorough - this module does not detect certain types
+** of corruption that are detected by the PRAGMA command. However,
+** it does detect all kinds of corruption that are likely to cause
+** errors in SQLite applications.
+**
+** + It is slower. Sometimes up to three times slower.
+**
+** + It allows integrity-check operations to be split into multiple
+** transactions, so that the database does not need to be read-locked
+** for the duration of the integrity-check.
+**
+** One way to use the API to run integrity-check on the "main" database
+** of handle db is:
+**
+** int rc = SQLITE_OK;
+** sqlite3_intck *p = 0;
+**
+** sqlite3_intck_open(db, "main", &p);
+** while( SQLITE_OK==sqlite3_intck_step(p) ){
+** const char *zMsg = sqlite3_intck_message(p);
+** if( zMsg ) printf("corruption: %s\n", zMsg);
+** }
+** rc = sqlite3_intck_error(p, &zErr);
+** if( rc!=SQLITE_OK ){
+** printf("error occured (rc=%d), (errmsg=%s)\n", rc, zErr);
+** }
+** sqlite3_intck_close(p);
+**
+** Usually, the sqlite3_intck object opens a read transaction within the
+** first call to sqlite3_intck_step() and holds it open until the
+** integrity-check is complete. However, if sqlite3_intck_unlock() is
+** called, the read transaction is ended and a new read transaction opened
+** by the subsequent call to sqlite3_intck_step().
+*/
+
+#ifndef _SQLITE_INTCK_H
+#define _SQLITE_INTCK_H
+
+#include "sqlite3.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+** An ongoing incremental integrity-check operation is represented by an
+** opaque pointer of the following type.
+*/
+typedef struct sqlite3_intck sqlite3_intck;
+
+/*
+** Open a new incremental integrity-check object. If successful, populate
+** output variable (*ppOut) with the new object handle and return SQLITE_OK.
+** Or, if an error occurs, set (*ppOut) to NULL and return an SQLite error
+** code (e.g. SQLITE_NOMEM).
+**
+** The integrity-check will be conducted on database zDb (which must be "main",
+** "temp", or the name of an attached database) of database handle db. Once
+** this function has been called successfully, the caller should not use
+** database handle db until the integrity-check object has been destroyed
+** using sqlite3_intck_close().
+*/
+int sqlite3_intck_open(
+ sqlite3 *db, /* Database handle */
+ const char *zDb, /* Database name ("main", "temp" etc.) */
+ sqlite3_intck **ppOut /* OUT: New sqlite3_intck handle */
+);
+
+/*
+** Close and release all resources associated with a handle opened by an
+** earlier call to sqlite3_intck_open(). The results of using an
+** integrity-check handle after it has been passed to this function are
+** undefined.
+*/
+void sqlite3_intck_close(sqlite3_intck *pCk);
+
+/*
+** Do the next step of the integrity-check operation specified by the handle
+** passed as the only argument. This function returns SQLITE_DONE if the
+** integrity-check operation is finished, or an SQLite error code if
+** an error occurs, or SQLITE_OK if no error occurs but the integrity-check
+** is not finished. It is not considered an error if database corruption
+** is encountered.
+**
+** Following a successful call to sqlite3_intck_step() (one that returns
+** SQLITE_OK), sqlite3_intck_message() returns a non-NULL value if
+** corruption was detected in the db.
+**
+** If an error occurs and a value other than SQLITE_OK or SQLITE_DONE is
+** returned, then the integrity-check handle is placed in an error state.
+** In this state all subsequent calls to sqlite3_intck_step() or
+** sqlite3_intck_unlock() will immediately return the same error. The
+** sqlite3_intck_error() method may be used to obtain an English language
+** error message in this case.
+*/
+int sqlite3_intck_step(sqlite3_intck *pCk);
+
+/*
+** If the previous call to sqlite3_intck_step() encountered corruption
+** within the database, then this function returns a pointer to a buffer
+** containing a nul-terminated string describing the corruption in
+** English. If the previous call to sqlite3_intck_step() did not encounter
+** corruption, or if there was no previous call, this function returns
+** NULL.
+*/
+const char *sqlite3_intck_message(sqlite3_intck *pCk);
+
+/*
+** Close any read-transaction opened by an earlier call to
+** sqlite3_intck_step(). Any subsequent call to sqlite3_intck_step() will
+** open a new transaction. Return SQLITE_OK if successful, or an SQLite error
+** code otherwise.
+**
+** If an error occurs, then the integrity-check handle is placed in an error
+** state. In this state all subsequent calls to sqlite3_intck_step() or
+** sqlite3_intck_unlock() will immediately return the same error. The
+** sqlite3_intck_error() method may be used to obtain an English language
+** error message in this case.
+*/
+int sqlite3_intck_unlock(sqlite3_intck *pCk);
+
+/*
+** If an error has occurred in an earlier call to sqlite3_intck_step()
+** or sqlite3_intck_unlock(), then this method returns the associated
+** SQLite error code. Additionally, if pzErr is not NULL, then (*pzErr)
+** may be set to point to a nul-terminated string containing an English
+** language error message. Or, if no error message is available, to
+** NULL.
+**
+** If no error has occurred within sqlite3_intck_step() or
+** sqlite_intck_unlock() calls on the handle passed as the first argument,
+** then SQLITE_OK is returned and (*pzErr) set to NULL.
+*/
+int sqlite3_intck_error(sqlite3_intck *pCk, const char **pzErr);
+
+/*
+** This API is used for testing only. It returns the full-text of an SQL
+** statement used to test object zObj, which may be a table or index.
+** The returned buffer is valid until the next call to either this function
+** or sqlite3_intck_close() on the same sqlite3_intck handle.
+*/
+const char *sqlite3_intck_test_sql(sqlite3_intck *pCk, const char *zObj);
+
+
+#ifdef __cplusplus
+} /* end of the 'extern "C"' block */
+#endif
+
+#endif /* ifndef _SQLITE_INTCK_H */
diff --git a/ext/intck/test_intck.c b/ext/intck/test_intck.c
new file mode 100644
index 0000000000..84008fb07f
--- /dev/null
+++ b/ext/intck/test_intck.c
@@ -0,0 +1,238 @@
+/*
+** 2010 August 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.
+**
+*************************************************************************
+** Code for testing all sorts of SQLite interfaces. This code
+** is not included in the SQLite library.
+*/
+
+#include "sqlite3.h"
+#include "sqlite3intck.h"
+
+#if defined(INCLUDE_SQLITE_TCL_H)
+# include "sqlite_tcl.h"
+#else
+# include "tcl.h"
+#endif
+
+#include
+#include
+
+/* In test1.c */
+int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb);
+const char *sqlite3ErrName(int);
+
+typedef struct TestIntck TestIntck;
+struct TestIntck {
+ sqlite3_intck *intck;
+};
+
+static int testIntckCmd(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ struct Subcmd {
+ const char *zName;
+ int nArg;
+ const char *zExpect;
+ } aCmd[] = {
+ {"close", 0, ""}, /* 0 */
+ {"step", 0, ""}, /* 1 */
+ {"message", 0, ""}, /* 2 */
+ {"error", 0, ""}, /* 3 */
+ {"unlock", 0, ""}, /* 4 */
+ {"test_sql", 1, ""}, /* 5 */
+ {0 , 0}
+ };
+ int rc = TCL_OK;
+ int iIdx = -1;
+ TestIntck *p = (TestIntck*)clientData;
+
+ if( objc<2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND ...");
+ return TCL_ERROR;
+ }
+
+ rc = Tcl_GetIndexFromObjStruct(
+ interp, objv[1], aCmd, sizeof(aCmd[0]), "SUB-COMMAND", 0, &iIdx
+ );
+ if( rc ) return rc;
+
+ if( objc!=2+aCmd[iIdx].nArg ){
+ Tcl_WrongNumArgs(interp, 2, objv, aCmd[iIdx].zExpect);
+ return TCL_ERROR;
+ }
+
+ switch( iIdx ){
+ case 0: assert( 0==strcmp("close", aCmd[iIdx].zName) ); {
+ Tcl_DeleteCommand(interp, Tcl_GetStringFromObj(objv[0], 0));
+ break;
+ }
+
+ case 1: assert( 0==strcmp("step", aCmd[iIdx].zName) ); {
+ rc = sqlite3_intck_step(p->intck);
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ break;
+ }
+
+ case 2: assert( 0==strcmp("message", aCmd[iIdx].zName) ); {
+ const char *z = sqlite3_intck_message(p->intck);
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(z ? z : "", -1));
+ break;
+ }
+
+ case 3: assert( 0==strcmp("error", aCmd[iIdx].zName) ); {
+ const char *zErr = 0;
+ rc = sqlite3_intck_error(p->intck, 0);
+ Tcl_Obj *pRes = Tcl_NewObj();
+ Tcl_ListObjAppendElement(
+ interp, pRes, Tcl_NewStringObj(sqlite3ErrName(rc), -1)
+ );
+ sqlite3_intck_error(p->intck, &zErr);
+ Tcl_ListObjAppendElement(
+ interp, pRes, Tcl_NewStringObj(zErr ? zErr : 0, -1)
+ );
+ Tcl_SetObjResult(interp, pRes);
+ break;
+ }
+
+ case 4: assert( 0==strcmp("unlock", aCmd[iIdx].zName) ); {
+ rc = sqlite3_intck_unlock(p->intck);
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ break;
+ }
+
+ case 5: assert( 0==strcmp("test_sql", aCmd[iIdx].zName) ); {
+ const char *zObj = Tcl_GetString(objv[2]);
+ const char *zSql = sqlite3_intck_test_sql(p->intck, zObj[0] ? zObj : 0);
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(zSql, -1));
+ break;
+ }
+ }
+
+ return TCL_OK;
+}
+
+/*
+** Destructor for commands created by test_sqlite3_intck().
+*/
+static void testIntckFree(void *clientData){
+ TestIntck *p = (TestIntck*)clientData;
+ sqlite3_intck_close(p->intck);
+ ckfree(p);
+}
+
+/*
+** tclcmd: sqlite3_intck DB DBNAME
+*/
+static int test_sqlite3_intck(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ char zName[64];
+ int iName = 0;
+ Tcl_CmdInfo info;
+ TestIntck *p = 0;
+ sqlite3 *db = 0;
+ const char *zDb = 0;
+ int rc = SQLITE_OK;
+
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
+ return TCL_ERROR;
+ }
+
+ p = (TestIntck*)ckalloc(sizeof(TestIntck));
+ memset(p, 0, sizeof(TestIntck));
+
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+ return TCL_ERROR;
+ }
+ zDb = Tcl_GetString(objv[2]);
+ if( zDb[0]=='\0' ) zDb = 0;
+
+ rc = sqlite3_intck_open(db, zDb, &p->intck);
+ if( rc!=SQLITE_OK ){
+ ckfree(p);
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3_errstr(rc), -1));
+ return TCL_ERROR;
+ }
+
+ do {
+ sprintf(zName, "intck%d", iName++);
+ }while( Tcl_GetCommandInfo(interp, zName, &info)!=0 );
+ Tcl_CreateObjCommand(interp, zName, testIntckCmd, (void*)p, testIntckFree);
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(zName, -1));
+
+ return TCL_OK;
+}
+
+/*
+** tclcmd: test_do_intck DB DBNAME
+*/
+static int test_do_intck(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ sqlite3 *db = 0;
+ const char *zDb = 0;
+ int rc = SQLITE_OK;
+ sqlite3_intck *pCk = 0;
+ Tcl_Obj *pRet = 0;
+ const char *zErr = 0;
+
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
+ return TCL_ERROR;
+ }
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+ return TCL_ERROR;
+ }
+ zDb = Tcl_GetString(objv[2]);
+
+ pRet = Tcl_NewObj();
+ Tcl_IncrRefCount(pRet);
+
+ rc = sqlite3_intck_open(db, zDb, &pCk);
+ if( rc==SQLITE_OK ){
+ while( sqlite3_intck_step(pCk)==SQLITE_OK ){
+ const char *zMsg = sqlite3_intck_message(pCk);
+ if( zMsg ){
+ Tcl_ListObjAppendElement(interp, pRet, Tcl_NewStringObj(zMsg, -1));
+ }
+ }
+ rc = sqlite3_intck_error(pCk, &zErr);
+ }
+ if( rc!=SQLITE_OK ){
+ if( zErr ){
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1));
+ }else{
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ }
+ }else{
+ Tcl_SetObjResult(interp, pRet);
+ }
+ Tcl_DecrRefCount(pRet);
+ sqlite3_intck_close(pCk);
+ sqlite3_intck_close(0);
+ return rc ? TCL_ERROR : TCL_OK;
+}
+
+int Sqlitetestintck_Init(Tcl_Interp *interp){
+ Tcl_CreateObjCommand(interp, "sqlite3_intck", test_sqlite3_intck, 0, 0);
+ Tcl_CreateObjCommand(interp, "test_do_intck", test_do_intck, 0, 0);
+ return TCL_OK;
+}
diff --git a/ext/misc/noop.c b/ext/misc/noop.c
index d3a58670c4..18c25e10f7 100644
--- a/ext/misc/noop.c
+++ b/ext/misc/noop.c
@@ -38,6 +38,24 @@ static void noopfunc(
sqlite3_result_value(context, argv[0]);
}
+/*
+** Implementation of the multitype_text() function.
+**
+** The function returns its argument. The result will always have a
+** TEXT value. But if the original input is numeric, it will also
+** have that numeric value.
+*/
+static void multitypeTextFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ assert( argc==1 );
+ (void)argc;
+ (void)sqlite3_value_text(argv[0]);
+ sqlite3_result_value(context, argv[0]);
+}
+
#ifdef _WIN32
__declspec(dllexport)
#endif
@@ -64,5 +82,9 @@ int sqlite3_noop_init(
rc = sqlite3_create_function(db, "noop_nd", 1,
SQLITE_UTF8,
0, noopfunc, 0, 0);
+ if( rc ) return rc;
+ rc = sqlite3_create_function(db, "multitype_text", 1,
+ SQLITE_UTF8,
+ 0, multitypeTextFunc, 0, 0);
return rc;
}
diff --git a/ext/misc/series.c b/ext/misc/series.c
index abd6af7ad6..3e859a8f23 100644
--- a/ext/misc/series.c
+++ b/ext/misc/series.c
@@ -103,16 +103,20 @@ SQLITE_EXTENSION_INIT1
** index is ix. The 0th member is given by smBase. The sequence members
** progress per ix increment by smStep.
*/
-static sqlite3_int64 genSeqMember(sqlite3_int64 smBase,
- sqlite3_int64 smStep,
- sqlite3_uint64 ix){
- if( ix>=(sqlite3_uint64)LLONG_MAX ){
+static sqlite3_int64 genSeqMember(
+ sqlite3_int64 smBase,
+ sqlite3_int64 smStep,
+ sqlite3_uint64 ix
+){
+ static const sqlite3_uint64 mxI64 =
+ ((sqlite3_uint64)0x7fffffff)<<32 | 0xffffffff;
+ if( ix>=mxI64 ){
/* Get ix into signed i64 range. */
- ix -= (sqlite3_uint64)LLONG_MAX;
+ ix -= mxI64;
/* With 2's complement ALU, this next can be 1 step, but is split into
* 2 for UBSAN's satisfaction (and hypothetical 1's complement ALUs.) */
- smBase += (LLONG_MAX/2) * smStep;
- smBase += (LLONG_MAX - LLONG_MAX/2) * smStep;
+ smBase += (mxI64/2) * smStep;
+ smBase += (mxI64 - mxI64/2) * smStep;
}
/* Under UBSAN (or on 1's complement machines), must do this last term
* in steps to avoid the dreaded (and harmless) signed multiply overlow. */
diff --git a/ext/recover/test_recover.c b/ext/recover/test_recover.c
index 1c333df8e0..ba8ef6da11 100644
--- a/ext/recover/test_recover.c
+++ b/ext/recover/test_recover.c
@@ -236,7 +236,7 @@ static int test_sqlite3_recover_init(
zDb = Tcl_GetString(objv[2]);
if( zDb[0]=='\0' ) zDb = 0;
- pNew = ckalloc(sizeof(TestRecover));
+ pNew = (TestRecover*)ckalloc(sizeof(TestRecover));
if( bSql==0 ){
zUri = Tcl_GetString(objv[3]);
pNew->p = sqlite3_recover_init(db, zDb, zUri);
diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c
index 013bb0b5b8..299b5b54b9 100644
--- a/ext/rtree/rtree.c
+++ b/ext/rtree/rtree.c
@@ -694,11 +694,9 @@ static RtreeNode *nodeNew(Rtree *pRtree, RtreeNode *pParent){
** Clear the Rtree.pNodeBlob object
*/
static void nodeBlobReset(Rtree *pRtree){
- if( pRtree->pNodeBlob && pRtree->inWrTrans==0 && pRtree->nCursor==0 ){
- sqlite3_blob *pBlob = pRtree->pNodeBlob;
- pRtree->pNodeBlob = 0;
- sqlite3_blob_close(pBlob);
- }
+ sqlite3_blob *pBlob = pRtree->pNodeBlob;
+ pRtree->pNodeBlob = 0;
+ sqlite3_blob_close(pBlob);
}
/*
@@ -742,7 +740,6 @@ static int nodeAcquire(
&pRtree->pNodeBlob);
}
if( rc ){
- nodeBlobReset(pRtree);
*ppNode = 0;
/* If unable to open an sqlite3_blob on the desired row, that can only
** be because the shadow tables hold erroneous data. */
@@ -802,6 +799,7 @@ static int nodeAcquire(
}
*ppNode = pNode;
}else{
+ nodeBlobReset(pRtree);
if( pNode ){
pRtree->nNodeRef--;
sqlite3_free(pNode);
@@ -946,6 +944,7 @@ static void nodeGetCoord(
int iCoord, /* Which coordinate to extract */
RtreeCoord *pCoord /* OUT: Space to write result to */
){
+ assert( iCellzData[12 + pRtree->nBytesPerCell*iCell + 4*iCoord], pCoord);
}
@@ -1135,7 +1134,9 @@ static int rtreeClose(sqlite3_vtab_cursor *cur){
sqlite3_finalize(pCsr->pReadAux);
sqlite3_free(pCsr);
pRtree->nCursor--;
- nodeBlobReset(pRtree);
+ if( pRtree->nCursor==0 && pRtree->inWrTrans==0 ){
+ nodeBlobReset(pRtree);
+ }
return SQLITE_OK;
}
@@ -1720,7 +1721,11 @@ static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){
int rc = SQLITE_OK;
RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc);
if( rc==SQLITE_OK && ALWAYS(p) ){
- *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell);
+ if( p->iCell>=NCELL(pNode) ){
+ rc = SQLITE_ABORT;
+ }else{
+ *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell);
+ }
}
return rc;
}
@@ -1738,6 +1743,7 @@ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
if( rc ) return rc;
if( NEVER(p==0) ) return SQLITE_OK;
+ if( p->iCell>=NCELL(pNode) ) return SQLITE_ABORT;
if( i==0 ){
sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell));
}else if( i<=pRtree->nDim2 ){
@@ -1835,6 +1841,8 @@ static int deserializeGeometry(sqlite3_value *pValue, RtreeConstraint *pCons){
return SQLITE_OK;
}
+int sqlite3IntFloatCompare(i64,double);
+
/*
** Rtree virtual table module xFilter method.
*/
@@ -1864,7 +1872,8 @@ static int rtreeFilter(
i64 iNode = 0;
int eType = sqlite3_value_numeric_type(argv[0]);
if( eType==SQLITE_INTEGER
- || (eType==SQLITE_FLOAT && sqlite3_value_double(argv[0])==iRowid)
+ || (eType==SQLITE_FLOAT
+ && 0==sqlite3IntFloatCompare(iRowid,sqlite3_value_double(argv[0])))
){
rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode);
}else{
@@ -3220,7 +3229,7 @@ constraint:
static int rtreeBeginTransaction(sqlite3_vtab *pVtab){
Rtree *pRtree = (Rtree *)pVtab;
assert( pRtree->inWrTrans==0 );
- pRtree->inWrTrans++;
+ pRtree->inWrTrans = 1;
return SQLITE_OK;
}
@@ -3234,6 +3243,9 @@ static int rtreeEndTransaction(sqlite3_vtab *pVtab){
nodeBlobReset(pRtree);
return SQLITE_OK;
}
+static int rtreeRollback(sqlite3_vtab *pVtab){
+ return rtreeEndTransaction(pVtab);
+}
/*
** The xRename method for rtree module virtual tables.
@@ -3352,7 +3364,7 @@ static sqlite3_module rtreeModule = {
rtreeBeginTransaction, /* xBegin - begin transaction */
rtreeEndTransaction, /* xSync - sync transaction */
rtreeEndTransaction, /* xCommit - commit transaction */
- rtreeEndTransaction, /* xRollback - rollback transaction */
+ rtreeRollback, /* xRollback - rollback transaction */
0, /* xFindFunction - function overloading */
rtreeRename, /* xRename - rename the table */
rtreeSavepoint, /* xSavepoint */
diff --git a/ext/rtree/rtree1.test b/ext/rtree/rtree1.test
index 61664e1529..e596df71d5 100644
--- a/ext/rtree/rtree1.test
+++ b/ext/rtree/rtree1.test
@@ -797,4 +797,22 @@ do_test 23.0 {
db eval {PRAGMA integrity_check;}
} {ok}
+reset_db
+do_execsql_test 24.0 {
+ CREATE VIRTUAL TABLE rt1 USING rtree_i32(rid, c1, c2);
+ INSERT INTO rt1(rid, c1, c2) VALUES (9223372036854775807, 10, 18);
+}
+
+do_execsql_test 24.1 {
+ SELECT (rid = (CAST (9223372036854775807 AS REAL)))
+ FROM rt1 WHERE
+ (rid = (CAST (9223372036854775807 AS REAL)));
+}
+
+do_execsql_test 24.2 {
+ DELETE FROM rt1;
+ INSERT INTO rt1(rid, c1, c2) VALUES(1,2,3);
+ SELECT * FROM rt1 WHERE rid=1.005;
+} {}
+
finish_test
diff --git a/ext/rtree/rtreeJ.test b/ext/rtree/rtreeJ.test
new file mode 100644
index 0000000000..b091d2c686
--- /dev/null
+++ b/ext/rtree/rtreeJ.test
@@ -0,0 +1,273 @@
+# 2024-02-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.
+#
+#***********************************************************************
+#
+# ROLLBACK in the middle of an RTREE query
+#
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source $testdir/tester.tcl
+set testprefix rtreeJ
+ifcapable !rtree { finish_test ; return }
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING rtree(id, x1, x2);
+ INSERT INTO t1 VALUES(1, 1, 1), (2, 2, 2);
+} {}
+
+do_execsql_test 1.1 {
+ SELECT * FROM t1
+} {1 1.0 1.0 2 2.0 2.0}
+
+# If a ROLLBACK occurs that backs out changes to the RTREE, then
+# all pending queries to the RTREE are aborted.
+#
+do_test 1.2 {
+ db eval {
+ BEGIN;
+ INSERT INTO t1 VALUES(3, 3, 3);
+ INSERT INTO t1 VALUES(4, 4, 4);
+ }
+ set rc [catch {
+ db eval { SELECT * FROM t1 } {
+ if {$id==1} {
+ db eval { ROLLBACK }
+ }
+ lappend res $id $x1 $x2
+ }
+ } msg]
+ list $rc $msg
+} {1 {query aborted}}
+
+do_execsql_test 1.3 {
+ SELECT * FROM t1;
+} {1 1.0 1.0 2 2.0 2.0}
+
+# A COMMIT of changes to the RTREE does not affect pending queries
+#
+do_test 1.4 {
+ set res {}
+ db eval {
+ BEGIN;
+ INSERT INTO t1 VALUES(5, 5, 5);
+ INSERT INTO t1 VALUES(6, 6, 6);
+ }
+ db eval { SELECT * FROM t1 } {
+ if {$id==1} {
+ db eval { COMMIT }
+ }
+ lappend res $id $x1 $x2
+ }
+ set res
+} {1 1.0 1.0 2 2.0 2.0 5 5.0 5.0 6 6.0 6.0}
+
+do_execsql_test 1.5 {
+ SELECT * FROM t1;
+} {1 1.0 1.0 2 2.0 2.0 5 5.0 5.0 6 6.0 6.0}
+
+do_execsql_test 1.6 {
+ DELETE FROM t1;
+ INSERT INTO t1 VALUES(1,1,1),(2,2,2),(3,3,3),(4,4,4);
+ CREATE TABLE t2(x);
+ SELECT * FROM t1;
+} {1 1.0 1.0 2 2.0 2.0 3 3.0 3.0 4 4.0 4.0}
+
+# A rollback that does not affect the rtree table because
+# the rtree table has not been written to does not cause
+# a query abort.
+#
+do_test 1.7 {
+ set res {}
+ db eval {
+ BEGIN;
+ INSERT INTO t2(x) VALUES(12345);
+ }
+ db eval { SELECT * FROM t1 } {
+ if {$id==1} {
+ db eval { ROLLBACK }
+ }
+ lappend res $id $x1 $x2
+ }
+ set res
+} {1 1.0 1.0 2 2.0 2.0 3 3.0 3.0 4 4.0 4.0}
+
+# ROLLBACK TO that affects the RTREE does cause a query abort.
+#
+do_test 1.8 {
+ db eval {
+ DELETE FROM t1 WHERE rowid>1;
+ BEGIN;
+ DELETE FROM t2;
+ INSERT INTO t2(x) VALUES(23456);
+ SAVEPOINT 'one';
+ INSERT INTO t1 VALUES(2,2,2),(3,3,3);
+ }
+ set rc [catch {
+ db eval { SELECT * FROM t1 } {
+ if {$id==1} {
+ db eval { ROLLBACK TO 'one'; }
+ }
+ lappend res $id $x1 $x2
+ }
+ } msg]
+ list $rc $msg
+} {1 {query aborted}}
+
+do_execsql_test 1.9 {
+ COMMIT;
+ SELECT * FROM t1;
+} {1 1.0 1.0}
+
+# ROLLBACK TO that does not affect the RTREE does not cause a query abort.
+#
+do_execsql_test 1.10 {
+ DELETE FROM t1;
+ INSERT INTO t1 VALUES(1,1,1),(2,2,2),(3,3,3);
+ BEGIN;
+ DELETE FROM t2;
+ INSERT INTO t2(x) VALUES(34567);
+ SAVEPOINT 'one';
+ INSERT INTO t2(x) VALUES('a string');
+ SELECT * FROM t1;
+} {1 1.0 1.0 2 2.0 2.0 3 3.0 3.0}
+do_test 1.11 {
+ set rc [catch {
+ set res {}
+ db eval { SELECT * FROM t1 } {
+ if {$id==2} {
+ # db eval { ROLLBACK TO 'one'; }
+ }
+ lappend res $id $x1 $x2
+ }
+ set res
+ } msg]
+ list $rc $msg
+} {0 {1 1.0 1.0 2 2.0 2.0 3 3.0 3.0}}
+
+do_execsql_test 1.12 {
+ COMMIT;
+ SELECT * FROM t1;
+} {1 1.0 1.0 2 2.0 2.0 3 3.0 3.0}
+
+#----------------------------------------------------------------------
+
+reset_db
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t1 USING rtree(id, x1, x2);
+ INSERT INTO t1 VALUES(1, 1, 1), (2, 2, 2);
+ CREATE TABLE t2(x);
+} {}
+
+do_test 2.1 {
+ db eval {
+ BEGIN;
+ INSERT INTO t1 VALUES(3, 3, 3);
+ PRAGMA writable_schema = RESET;
+ }
+
+ set rc [catch {
+ db eval { SELECT x1, x2 FROM t1 } {
+ if {$x1==1} {
+ db eval { ROLLBACK }
+ }
+ lappend res $x1 $x2
+ }
+ } msg]
+ list $rc $msg
+} {1 {query aborted}}
+
+do_execsql_test 2.1 {
+ CREATE TABLE bak_node(nodeno, data);
+ CREATE TABLE bak_parent(nodeno, parentnode);
+ CREATE TABLE bak_rowid(rowid, nodeno);
+}
+proc save_t1 {} {
+ db eval {
+ DELETE FROM bak_node;
+ DELETE FROM bak_parent;
+ DELETE FROM bak_rowid;
+ INSERT INTO bak_node SELECT * FROM t1_node;
+ INSERT INTO bak_parent SELECT * FROM t1_parent;
+ INSERT INTO bak_rowid SELECT * FROM t1_rowid;
+ }
+}
+proc restore_t1 {} {
+ db eval {
+ DELETE FROM t1_node;
+ DELETE FROM t1_parent;
+ DELETE FROM t1_rowid;
+ INSERT INTO t1_node SELECT * FROM bak_node;
+ INSERT INTO t1_parent SELECT * FROM bak_parent;
+ INSERT INTO t1_rowid SELECT * FROM bak_rowid;
+ }
+}
+
+do_test 2.3 {
+ save_t1
+ db eval {
+ INSERT INTO t1 VALUES(3, 3, 3);
+ }
+ set rc [catch {
+ db eval { SELECT rowid, x1, x2 FROM t1 } {
+ if {$x1==1} {
+ restore_t1
+ }
+ lappend res $x1 $x2
+ }
+ } msg]
+ list $rc $msg
+} {1 {query aborted}}
+do_execsql_test 2.4 {
+ SELECT * FROM t1
+} {1 1.0 1.0 2 2.0 2.0}
+
+do_test 2.5 {
+ save_t1
+ db eval {
+ INSERT INTO t1 VALUES(3, 3, 3);
+ }
+ set rc [catch {
+ db eval { SELECT x1 FROM t1 } {
+ if {$x1==1} {
+ restore_t1
+ }
+ lappend res $x1 $x2
+ }
+ } msg]
+ list $rc $msg
+} {1 {query aborted}}
+do_execsql_test 2.6 {
+ SELECT * FROM t1
+} {1 1.0 1.0 2 2.0 2.0}
+
+do_test 2.7 {
+ save_t1
+ db eval {
+ INSERT INTO t1 VALUES(3, 3, 3);
+ }
+ set ::res [list]
+ set rc [catch {
+ db eval { SELECT 'abc' FROM t1 } {
+ if {$::res==[list]} {
+ restore_t1
+ set ::bDone 1
+ }
+ lappend res abc
+ }
+ } msg]
+ set res
+} {abc abc abc}
+do_execsql_test 2.6 {
+ SELECT * FROM t1
+} {1 1.0 1.0 2 2.0 2.0}
+
+
+finish_test
diff --git a/ext/session/sessionstat1.test b/ext/session/sessionstat1.test
index 2757d60440..16dc4e2727 100644
--- a/ext/session/sessionstat1.test
+++ b/ext/session/sessionstat1.test
@@ -92,7 +92,7 @@ do_test 2.1 {
} {}
do_execsql_test -db db2 2.2 {
- SELECT * FROM sqlite_stat1
+ SELECT * FROM sqlite_stat1 ORDER BY tbl, idx
} {
t1 sqlite_autoindex_t1_1 {32 1}
t1 t1b {32 4}
@@ -104,7 +104,7 @@ do_test 2.3 {
} {}
do_execsql_test -db db2 2.4 {
- SELECT * FROM sqlite_stat1
+ SELECT * FROM sqlite_stat1 ORDER BY tbl, idx;
} {
t1 sqlite_autoindex_t1_1 {32 1}
t1 t1b {32 4}
diff --git a/ext/userauth/user-auth.txt b/ext/userauth/user-auth.txt
index ba4eabc137..9d6ba23336 100644
--- a/ext/userauth/user-auth.txt
+++ b/ext/userauth/user-auth.txt
@@ -1,3 +1,15 @@
+*********************************** NOTICE ************************************
+* This extension is deprecated. The SQLite developers do not maintain this *
+* extension. At some point in the future, it might disappear from the source *
+* tree. *
+* *
+* If you are using this extension and think it should be supported moving *
+* forward, visit the SQLite Forum (https://sqlite.org/forum) and argue your *
+* case there. *
+* *
+* This deprecation notice was added on 2024-01-22. *
+*******************************************************************************
+
Activate the user authentication logic by including the
ext/userauth/userauth.c source code file in the build and
adding the -DSQLITE_USER_AUTHENTICATION compile-time option.
diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile
index f0cff463a8..6e7b49875f 100644
--- a/ext/wasm/GNUmakefile
+++ b/ext/wasm/GNUmakefile
@@ -43,8 +43,9 @@
# which generates the makefile code, rather than using $(call) and
# $(eval), or at least centralize the setup of the numerous vars
# related to each build variant $(JS_BUILD_MODES). (Update: an
-# external script was attempted but it's even less legible than the
-# $(eval) indirection going on in this file.
+# external script was attempted but generating properly-escaped
+# makefile code from within a shell script is even less legible
+# than the $(eval) indirection going on in this file.)
#
default: all
#default: quick
@@ -306,8 +307,9 @@ DISTCLEAN_FILES += $(bin.stripccomments)
########################################################################
-# C-PP.FILTER: a $(call)able to transform $(1) to $(2) via ./c-pp -f
-# $(1) ...
+# C-PP.FILTER: a $(call)able to transform $(1) to $(2) via:
+#
+# ./c-pp -f $(1) -o $(2) $(3)
#
# Historical notes:
#
@@ -432,8 +434,10 @@ sqlite3-api.jses += $(sqlite3-api-build-version.js)
sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js
# sqlite3-api-worker.js = the Worker1 API:
sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js
-# sqlite3-v-helper = helper APIs for VFSes and VTABLEs:
-sqlite3-api.jses += $(dir.api)/sqlite3-v-helper.js
+# sqlite3-vfs-helper = helper APIs for VFSes:
+sqlite3-api.jses += $(dir.api)/sqlite3-vfs-helper.c-pp.js
+# sqlite3-vtab-helper = helper APIs for VTABLEs:
+sqlite3-api.jses += $(dir.api)/sqlite3-vtab-helper.c-pp.js
# sqlite3-vfs-opfs.c-pp.js = the first OPFS VFS:
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js
# sqlite3-vfs-opfs-sahpool.c-pp.js = the second OPFS VFS:
@@ -449,13 +453,14 @@ sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js
# the first OPFS VFS and necessarily an external file.
SOAP.js := $(dir.api)/sqlite3-opfs-async-proxy.js
SOAP.js.bld := $(dir.dout)/$(notdir $(SOAP.js))
-sqlite3-api.ext.jses += $(SOAP.js.bld)
+#
+# $(sqlite3-api.ext.jses) = API-related files which are standalone files,
+# not part of the amalgamation.
+#
+sqlite3-api.ext.jses := $(SOAP.js.bld)
$(SOAP.js.bld): $(SOAP.js)
cp $< $@
-all quick: $(sqlite3-api.ext.jses)
-q: quick
-
########################################################################
# $(sqlite3-api*.*js) contain the core library code but not the
# Emscripten-related glue which deals with loading sqlite3.wasm. In
@@ -528,6 +533,10 @@ emcc.jsflags += -sSTRICT_JS=0
# 3.1.31. The fix for that in newer emcc's is to throw a built-time
# error if STRICT_JS is used together with those options.
+# emcc.jsflags += -sSTRICT=1
+# -sSTRICT=1 Causes failures about unknown symbols which the build
+# tools should be installing, e.g. __syscall_geteuid32
+
# -sENVIRONMENT values for the various build modes:
emcc.environment.vanilla := web,worker
emcc.environment.bundler-friendly := $(emcc.environment.vanilla)
@@ -548,6 +557,11 @@ emcc.environment.node := node
# time with 16mb+ memory and 3X time when starting with 8MB. However,
# such test results are inconsistent due to browser internals which
# are opaque to us.
+#
+# 2024-03-04: emsdk 3.1.55 replaces INITIAL_MEMORY with INITIAL_HEAP,
+# but also says (in its changelog): "Note that it is currently not
+# supported in all configurations (#21071)."
+# https://github.com/emscripten-core/emscripten/blob/main/ChangeLog.md
emcc.jsflags += -sALLOW_MEMORY_GROWTH
emcc.INITIAL_MEMORY.128 := 134217728
emcc.INITIAL_MEMORY.96 := 100663296
@@ -652,6 +666,9 @@ $(sqlite3-api-build-version.js): $(bin.version-info) $(MAKEFILE)
########################################################################
# $(sqlite3-license-version.js) contains the license header and
# in-comment build version info.
+#
+# Maintenance reminder: there are awk binaries out there which do not
+# support -e SCRIPT.
$(sqlite3-license-version.js): $(sqlite3.h) $(sqlite3-license-version-header.js) \
$(MAKEFILE)
@echo "Making $@..."; { \
@@ -659,8 +676,8 @@ $(sqlite3-license-version.js): $(sqlite3.h) $(sqlite3-license-version-header.js)
echo '/*'; \
echo '** This code was built from sqlite3 version...'; \
echo "**"; \
- awk -e '/define SQLITE_VERSION/{$$1=""; print "**" $$0}' \
- -e '/define SQLITE_SOURCE_ID/{$$1=""; print "**" $$0}' $(sqlite3.h); \
+ awk '/define SQLITE_VERSION/{$$1=""; print "**" $$0}' $(sqlite3.h); \
+ awk '/define SQLITE_SOURCE_ID/{$$1=""; print "**" $$0}' $(sqlite3.h); \
echo "**"; \
echo "** Using the Emscripten SDK version $(emcc.version)."; \
echo '*/'; \
@@ -810,13 +827,13 @@ pre-post-jses.deps.common := $(extern-pre-js.js) $(sqlite3-license-version.js)
# $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)
+# $7 = optional extra flags for emcc
#
# 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 emcc's
-# -sENVIRONMENT flag.
+# Before calling this, emcc.environment.$(2) must be set to a value
+# for emcc's -sENVIRONMENT flag.
#
# $(cflags.$(1)) and $(cflags.$(1).$(2)) may be defined to append
# CFLAGS to a given build mode.
@@ -923,18 +940,39 @@ sqlite3-worker1.js.in := $(dir.api)/sqlite3-worker1.c-pp.js
sqlite3-worker1-promiser.js.in := $(dir.api)/sqlite3-worker1-promiser.c-pp.js
sqlite3-worker1.js := $(dir.dout)/sqlite3-worker1.js
sqlite3-worker1-promiser.js := $(dir.dout)/sqlite3-worker1-promiser.js
-sqlite3-worker1-bundler-friendly.js := $(dir.dout)/sqlite3-worker1-bundler-friendly.mjs
+sqlite3-worker1-promiser.mjs := $(dir.dout)/sqlite3-worker1-promiser.mjs
+sqlite3-worker1-bundler-friendly.mjs := $(dir.dout)/sqlite3-worker1-bundler-friendly.mjs
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),\
+$(eval $(call C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.mjs),\
$(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.sqlite3-bundler-friendly)))
-$(sqlite3-bundler-friendly.mjs): $(sqlite3-worker1-bundler-friendly.js) \
+$(eval $(call C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.mjs),\
+ -Dtarget=es6-module -Dtarget=es6-bundler-friendly))
+$(sqlite3-bundler-friendly.mjs): $(sqlite3-worker1-bundler-friendly.mjs) \
$(sqlite3-worker1-promiser-bundler-friendly.js)
-$(sqlite3.js) $(sqlite3.mjs): $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js)
+$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.js,demo-worker1-promiser.js))
+$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.js,demo-worker1-promiser.mjs,\
+ -Dtarget=es6-module))
+$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.html,demo-worker1-promiser.html))
+$(eval $(call C-PP.FILTER,demo-worker1-promiser.c-pp.html,demo-worker1-promiser-esm.html,\
+ -Dtarget=es6-module))
+all: $(sqlite3-worker1.js) \
+ $(sqlite3-worker1-promiser.js) $(sqlite3-worker1-promiser.mjs)
+
+demo-worker1-promiser.html: $(sqlite3-worker1-promiser.js) demo-worker1-promiser.js
+demo-worker1-promiser-esm.html: $(sqlite3-worker1-promiser.mjs) demo-worker1-promiser.mjs
+all: demo-worker1-promiser.html demo-worker1-promiser-esm.html
+
+sqlite3-api.ext.jses += \
+ $(sqlite3-worker1-promiser.mjs) \
+ $(sqlite3-worker1-bundler-friendly.mjs) \
+ $(sqlite3-worker1.js)
+all quick: $(sqlite3-api.ext.jses)
+q: quick
########################################################################
# batch-runner.js is part of one of the test apps which reads in SQL
@@ -975,6 +1013,9 @@ emcc.speedtest1.common += -sABORTING_MALLOC
emcc.speedtest1.common += -sSTRICT_JS=0
emcc.speedtest1.common += -sMODULARIZE
emcc.speedtest1.common += -Wno-limited-postlink-optimizations
+emcc.speedtest1.common += -Wno-unused-main
+# ^^^^ -Wno-unused-main is for emcc 3.1.52+. speedtest1 has a wasm_main() which is
+# exported and called by the JS code.
EXPORTED_FUNCTIONS.speedtest1 := $(abspath $(dir.tmp)/EXPORTED_FUNCTIONS.speedtest1)
emcc.speedtest1.common += -sSTACK_SIZE=512KB
emcc.speedtest1.common += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.speedtest1)
diff --git a/ext/wasm/SQLTester/SQLTester.mjs b/ext/wasm/SQLTester/SQLTester.mjs
index a72399aefc..71c5d4c538 100644
--- a/ext/wasm/SQLTester/SQLTester.mjs
+++ b/ext/wasm/SQLTester/SQLTester.mjs
@@ -174,11 +174,17 @@ const Rx = newObj({
squiggly: /[{}]/
});
+
+
const Util = newObj({
toss,
- unlink: function(fn){
- return 0==sqlite3.wasm.sqlite3_wasm_vfs_unlink(0,fn);
+ unlink: function f(fn){
+ if(!f.unlink){
+ f.unlink = sqlite3.wasm.xWrap('sqlite3__wasm_vfs_unlink','int',
+ ['*','string']);
+ }
+ return 0==f.unlink(0,fn);
},
argvToString: (list)=>{
@@ -197,7 +203,7 @@ const Util = newObj({
utf8Encode: (str)=>__utf8Encoder.encode(str),
- strglob: sqlite3.wasm.xWrap('sqlite3_wasm_SQLTester_strglob','int',
+ strglob: sqlite3.wasm.xWrap('sqlite3__wasm_SQLTester_strglob','int',
['string','string'])
})/*Util*/;
diff --git a/ext/wasm/api/README.md b/ext/wasm/api/README.md
index eb0f073cf7..ebd4aaacb1 100644
--- a/ext/wasm/api/README.md
+++ b/ext/wasm/api/README.md
@@ -78,10 +78,12 @@ browser client:
a Promise-based interface into the Worker #1 API. This is
a far user-friendlier way to interface with databases running
in a Worker thread.
-- **`sqlite3-v-helper.js`**\
- Installs `sqlite3.vfs` and `sqlite3.vtab`, namespaces which contain
- helpers for use by downstream code which creates `sqlite3_vfs`
- and `sqlite3_module` implementations.
+- **`sqlite3-vfs-helper.js`**\
+ Installs the `sqlite3.vfs` namespace, which contain helpers for use
+ by downstream code which creates `sqlite3_vfs` implementations.
+- **`sqlite3-vtab-helper.js`**\
+ Installs the `sqlite3.vtab` namespace, which contain helpers for use
+ by downstream code which creates `sqlite3_module` implementations.
- **`sqlite3-vfs-opfs.c-pp.js`**\
is an sqlite3 VFS implementation which supports the Origin-Private
FileSystem (OPFS) as a storage layer to provide persistent storage
diff --git a/ext/wasm/api/post-js-header.js b/ext/wasm/api/post-js-header.js
index 0e27e1fd94..7fd82a7d6c 100644
--- a/ext/wasm/api/post-js-header.js
+++ b/ext/wasm/api/post-js-header.js
@@ -19,8 +19,10 @@ Module.postRun.push(function(Module/*the Emscripten-style module object*/){
- sqlite3-api-glue.js => glues previous parts together
- sqlite3-api-oo.js => SQLite3 OO API #1
- sqlite3-api-worker1.js => Worker-based API
- - sqlite3-vfs-helper.js => Internal-use utilities for...
- - sqlite3-vfs-opfs.js => OPFS VFS
+ - sqlite3-vfs-helper.c-pp.js => Utilities for VFS impls
+ - sqlite3-vtab-helper.c-pp.js => Utilities for virtual table impls
+ - sqlite3-vfs-opfs.c-pp.js => OPFS VFS
+ - sqlite3-vfs-opfs-sahpool.c-pp.js => OPFS SAHPool VFS
- sqlite3-api-cleanup.js => final API cleanup
- post-js-footer.js => closes this postRun() function
*/
diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js
index 29efb3e07b..2cb4c800d2 100644
--- a/ext/wasm/api/sqlite3-api-glue.js
+++ b/ext/wasm/api/sqlite3-api-glue.js
@@ -14,7 +14,8 @@
previous steps of the sqlite3-api.js bootstrapping process:
sqlite3-api-prologue.js, whwasmutil.js, and jaccwabyt.js. It
initializes the main API pieces so that the downstream components
- (e.g. sqlite3-api-oo1.js) have all that they need.
+ (e.g. sqlite3-api-oo1.js) have all of the infrastructure that they
+ need.
*/
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
'use strict';
@@ -329,6 +330,14 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
if(wasm.exports.sqlite3_activate_see instanceof Function){
+ /**
+ This code is capable of using an SEE build but note that an SEE
+ WASM build is generally incompatible with SEE's license
+ conditions. It is permitted for use internally in organizations
+ which have licensed SEE, but not for public sites because
+ exposing an SEE build of sqlite3.wasm effectively provides all
+ clients with a working copy of the commercial SEE code.
+ */
wasm.bindingSignatures.push(
["sqlite3_key", "int", "sqlite3*", "string", "int"],
["sqlite3_key_v2","int","sqlite3*","string","*","int"],
@@ -341,6 +350,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
Functions which require BigInt (int64) support are separated from
the others because we need to conditionally bind them or apply
dummy impls, depending on the capabilities of the environment.
+ (That said: we never actually build without BigInt support,
+ and such builds are untested.)
Note that not all of these functions directly require int64
but are only for use with APIs which require int64. For example,
@@ -359,7 +370,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/* Careful! Short version: de/serialize() are problematic because they
might use a different allocator than the user for managing the
deserialized block. de/serialize() are ONLY safe to use with
- sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. */,
+ sqlite3_malloc(), sqlite3_free(), and its 64-bit variants. Because
+ of this, the canonical builds of sqlite3.wasm/js guarantee that
+ sqlite3.wasm.alloc() and friends use those allocators. Custom builds
+ may not guarantee that, however. */,
["sqlite3_drop_modules", "int", ["sqlite3*", "**"]],
["sqlite3_last_insert_rowid", "i64", ["sqlite3*"]],
["sqlite3_malloc64", "*","i64"],
@@ -422,8 +436,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
// Add session/changeset APIs...
if(wasm.bigIntEnabled && !!wasm.exports.sqlite3changegroup_add){
- /* ACHTUNG: 2022-12-23: the session/changeset API bindings are
- COMPLETELY UNTESTED. */
/**
FuncPtrAdapter options for session-related callbacks with the
native signature "i(ps)". This proxy converts the 2nd argument
@@ -601,16 +613,21 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/**
Functions which are intended solely for API-internal use by the
WASM components, not client code. These get installed into
- sqlite3.wasm. Some of them get exposed to clients via variants
- named sqlite3_js_...().
+ sqlite3.util. Some of them get exposed to clients via variants
+ in sqlite3_js_...().
+
+ 2024-01-11: these were renamed, with two underscores in the
+ prefix, to ensure that clients do not accidentally depend on
+ them. They have always been documented as internal-use-only, so
+ no clients "should" be depending on the old names.
*/
- wasm.bindingSignatures.wasm = [
- ["sqlite3_wasm_db_reset", "int", "sqlite3*"],
- ["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"],
- ["sqlite3_wasm_vfs_create_file", "int",
+ wasm.bindingSignatures.wasmInternal = [
+ ["sqlite3__wasm_db_reset", "int", "sqlite3*"],
+ ["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"]
+ ["sqlite3__wasm_posix_create_file", "int", "string","*", "int"],
+ ["sqlite3__wasm_vfs_unlink", "int", "sqlite3_vfs*","string"]
];
/**
@@ -652,7 +669,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
Use case: sqlite3_bind_pointer() and sqlite3_result_pointer()
call for "a static string and preferably a string
- literal". This converter is used to ensure that the string
+ literal." This converter is used to ensure that the string
value seen by those functions is long-lived and behaves as they
need it to.
*/
@@ -674,14 +691,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
`sqlite3_vfs*` via capi.sqlite3_vfs.pointer.
*/
const __xArgPtr = wasm.xWrap.argAdapter('*');
- const nilType = function(){}/*a class no value can ever be an instance of*/;
+ const nilType = function(){
+ /*a class which no value can ever be an instance of*/
+ };
wasm.xWrap.argAdapter('sqlite3_filename', __xArgPtr)
('sqlite3_context*', __xArgPtr)
('sqlite3_value*', __xArgPtr)
('void*', __xArgPtr)
('sqlite3_changegroup*', __xArgPtr)
('sqlite3_changeset_iter*', __xArgPtr)
- //('sqlite3_rebaser*', __xArgPtr)
('sqlite3_session*', __xArgPtr)
('sqlite3_stmt*', (v)=>
__xArgPtr((v instanceof (sqlite3?.oo1?.Stmt || nilType))
@@ -742,8 +760,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
for(const e of wasm.bindingSignatures){
capi[e[0]] = wasm.xWrap.apply(null, e);
}
- for(const e of wasm.bindingSignatures.wasm){
- wasm[e[0]] = wasm.xWrap.apply(null, e);
+ for(const e of wasm.bindingSignatures.wasmInternal){
+ util[e[0]] = wasm.xWrap.apply(null, e);
}
/* For C API functions which cannot work properly unless
@@ -765,9 +783,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
implicitly making it part of the public interface. */
delete wasm.bindingSignatures;
- if(wasm.exports.sqlite3_wasm_db_error){
+ if(wasm.exports.sqlite3__wasm_db_error){
const __db_err = wasm.xWrap(
- 'sqlite3_wasm_db_error', 'int', 'sqlite3*', 'int', 'string'
+ 'sqlite3__wasm_db_error', 'int', 'sqlite3*', 'int', 'string'
);
/**
Sets the given db's error state. Accepts:
@@ -785,7 +803,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
Returns the resulting code. Pass (pDb,0,0) to clear the error
state.
*/
- util.sqlite3_wasm_db_error = function(pDb, resultCode, message){
+ util.sqlite3__wasm_db_error = function(pDb, resultCode, message){
if(resultCode instanceof sqlite3.WasmAllocError){
resultCode = capi.SQLITE_NOMEM;
message = 0 /*avoid allocating message string*/;
@@ -796,17 +814,17 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return pDb ? __db_err(pDb, resultCode, message) : resultCode;
};
}else{
- util.sqlite3_wasm_db_error = function(pDb,errCode,msg){
- console.warn("sqlite3_wasm_db_error() is not exported.",arguments);
+ util.sqlite3__wasm_db_error = function(pDb,errCode,msg){
+ console.warn("sqlite3__wasm_db_error() is not exported.",arguments);
return errCode;
};
}
}/*xWrap() bindings*/
{/* Import C-level constants and structs... */
- const cJson = wasm.xCall('sqlite3_wasm_enum_json');
+ const cJson = wasm.xCall('sqlite3__wasm_enum_json');
if(!cJson){
- toss("Maintenance required: increase sqlite3_wasm_enum_json()'s",
+ toss("Maintenance required: increase sqlite3__wasm_enum_json()'s",
"static buffer size!");
}
//console.debug('wasm.ctype length =',wasm.cstrlen(cJson));
@@ -877,7 +895,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
delete capi[k];
}
capi.sqlite3_vtab_config = wasm.xWrap(
- 'sqlite3_wasm_vtab_config','int',[
+ 'sqlite3__wasm_vtab_config','int',[
'sqlite3*', 'int', 'int']
);
}/* end vtab-related setup */
@@ -889,7 +907,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
consistency with non-special-case wrappings.
*/
const __dbArgcMismatch = (pDb,f,n)=>{
- return util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE,
+ return util.sqlite3__wasm_db_error(pDb, capi.SQLITE_MISUSE,
f+"() requires "+n+" argument"+
(1===n?"":'s')+".");
};
@@ -898,7 +916,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
argument and require SQLITE_UTF8. Sets the db error code to
SQLITE_FORMAT and returns that code. */
const __errEncoding = (pDb)=>{
- return util.sqlite3_wasm_db_error(
+ return util.sqlite3__wasm_db_error(
pDb, capi.SQLITE_FORMAT, "SQLITE_UTF8 is the only supported encoding."
);
};
@@ -1128,7 +1146,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
return rc;
}catch(e){
- return util.sqlite3_wasm_db_error(pDb, e);
+ return util.sqlite3__wasm_db_error(pDb, e);
}
};
@@ -1254,7 +1272,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return rc;
}catch(e){
console.error("sqlite3_create_function_v2() setup threw:",e);
- return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e);
+ return util.sqlite3__wasm_db_error(pDb, e, "Creation of UDF threw: "+e);
}
};
@@ -1299,7 +1317,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return rc;
}catch(e){
console.error("sqlite3_create_window_function() setup threw:",e);
- return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e);
+ return util.sqlite3__wasm_db_error(pDb, e, "Creation of UDF threw: "+e);
}
};
/**
@@ -1394,7 +1412,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
case 'string': return __prepare.basic(pDb, xSql, xSqlLen, prepFlags, ppStmt, null);
case 'number': return __prepare.full(pDb, xSql, xSqlLen, prepFlags, ppStmt, pzTail);
default:
- return util.sqlite3_wasm_db_error(
+ return util.sqlite3__wasm_db_error(
pDb, capi.SQLITE_MISUSE,
"Invalid SQL argument type for sqlite3_prepare_v2/v3()."
);
@@ -1438,7 +1456,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}else if('string'===typeof text){
[p, n] = wasm.allocCString(text);
}else{
- return util.sqlite3_wasm_db_error(
+ return util.sqlite3__wasm_db_error(
capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE,
"Invalid 3rd argument type for sqlite3_bind_text()."
);
@@ -1446,7 +1464,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return __bindText(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC);
}catch(e){
wasm.dealloc(p);
- return util.sqlite3_wasm_db_error(
+ return util.sqlite3__wasm_db_error(
capi.sqlite3_db_handle(pStmt), e
);
}
@@ -1472,7 +1490,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}else if('string'===typeof pMem){
[p, n] = wasm.allocCString(pMem);
}else{
- return util.sqlite3_wasm_db_error(
+ return util.sqlite3__wasm_db_error(
capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE,
"Invalid 3rd argument type for sqlite3_bind_blob()."
);
@@ -1480,7 +1498,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return __bindBlob(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC);
}catch(e){
wasm.dealloc(p);
- return util.sqlite3_wasm_db_error(
+ return util.sqlite3__wasm_db_error(
capi.sqlite3_db_handle(pStmt), e
);
}
@@ -1504,11 +1522,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
case capi.SQLITE_CONFIG_SORTERREF_SIZE: // 28 /* int nByte */
case capi.SQLITE_CONFIG_STMTJRNL_SPILL: // 26 /* int nByte */
case capi.SQLITE_CONFIG_URI:// 17 /* int */
- return wasm.exports.sqlite3_wasm_config_i(op, args[0]);
+ return wasm.exports.sqlite3__wasm_config_i(op, args[0]);
case capi.SQLITE_CONFIG_LOOKASIDE: // 13 /* int int */
- return wasm.exports.sqlite3_wasm_config_ii(op, args[0], args[1]);
+ return wasm.exports.sqlite3__wasm_config_ii(op, args[0], args[1]);
case capi.SQLITE_CONFIG_MEMDB_MAXSIZE: // 29 /* sqlite3_int64 */
- return wasm.exports.sqlite3_wasm_config_j(op, args[0]);
+ return wasm.exports.sqlite3__wasm_config_j(op, args[0]);
case capi.SQLITE_CONFIG_GETMALLOC: // 5 /* sqlite3_mem_methods* */
case capi.SQLITE_CONFIG_GETMUTEX: // 11 /* sqlite3_mutex_methods* */
case capi.SQLITE_CONFIG_GETPCACHE2: // 19 /* sqlite3_pcache_methods2* */
@@ -1574,11 +1592,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if( pKvvfs ){/* kvvfs-specific glue */
if(util.isUIThread()){
const kvvfsMethods = new capi.sqlite3_kvvfs_methods(
- wasm.exports.sqlite3_wasm_kvvfs_methods()
+ wasm.exports.sqlite3__wasm_kvvfs_methods()
);
delete capi.sqlite3_kvvfs_methods;
- const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack,
+ const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKeyOnPstack,
pstack = wasm.pstack;
const kvvfsStorage = (zClass)=>
@@ -1587,7 +1605,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/**
Implementations for members of the object referred to by
- sqlite3_wasm_kvvfs_methods(). We swap out the native
+ sqlite3__wasm_kvvfs_methods(). We swap out the native
implementations with these, which use localStorage or
sessionStorage for their backing store.
*/
@@ -1667,5 +1685,181 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
}/*pKvvfs*/
+ /* Warn if client-level code makes use of FuncPtrAdapter. */
wasm.xWrap.FuncPtrAdapter.warnOnUse = true;
+
+ const StructBinder = sqlite3.StructBinder
+ /* we require a local alias b/c StructBinder is removed from the sqlite3
+ object during the final steps of the API cleanup. */;
+ /**
+ Installs a StructBinder-bound function pointer member of the
+ given name and function in the given StructBinder.StructType
+ target object.
+
+ It creates a WASM proxy for the given function and arranges for
+ that proxy to be cleaned up when tgt.dispose() is called. Throws
+ on the slightest hint of error, e.g. tgt is-not-a StructType,
+ name does not map to a struct-bound member, etc.
+
+ As a special case, if the given function is a pointer, then
+ `wasm.functionEntry()` is used to validate that it is a known
+ function. If so, it is used as-is with no extra level of proxying
+ or cleanup, else an exception is thrown. It is legal to pass a
+ value of 0, indicating a NULL pointer, with the caveat that 0
+ _is_ a legal function pointer in WASM but it will not be accepted
+ as such _here_. (Justification: the function at address zero must
+ be one which initially came from the WASM module, not a method we
+ want to bind to a virtual table or VFS.)
+
+ This function returns a proxy for itself which is bound to tgt
+ and takes 2 args (name,func). That function returns the same
+ thing as this one, permitting calls to be chained.
+
+ If called with only 1 arg, it has no side effects but returns a
+ func with the same signature as described above.
+
+ 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
+ undefined behavior in the C layer, that methods mapped via
+ this function do not throw. The exception, as it were, to that
+ rule is...
+
+ If applyArgcCheck is true then each JS function (as opposed to
+ function pointers) gets wrapped in a proxy which asserts that it
+ is passed the expected number of arguments, throwing if the
+ argument count does not match expectations. That is only intended
+ for dev-time usage for sanity checking, and may leave the C
+ environment in an undefined state.
+ */
+ const installMethod = function callee(
+ tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck
+ ){
+ if(!(tgt instanceof StructBinder.StructType)){
+ toss("Usage error: target object is-not-a StructType.");
+ }else if(!(func instanceof Function) && !wasm.isPtr(func)){
+ toss("Usage errror: expecting a Function or WASM pointer to one.");
+ }
+ if(1===arguments.length){
+ return (n,f)=>callee(tgt, n, f, applyArgcCheck);
+ }
+ if(!callee.argcProxy){
+ callee.argcProxy = function(tgt, funcName, func,sig){
+ return function(...args){
+ if(func.length!==arguments.length){
+ toss("Argument mismatch for",
+ tgt.structInfo.name+"::"+funcName
+ +": Native signature is:",sig);
+ }
+ return func.apply(this, args);
+ }
+ };
+ /* An ondispose() callback for use with
+ StructBinder-created types. */
+ callee.removeFuncList = function(){
+ if(this.ondispose.__removeFuncList){
+ this.ondispose.__removeFuncList.forEach(
+ (v,ndx)=>{
+ if('number'===typeof v){
+ try{wasm.uninstallFunction(v)}
+ catch(e){/*ignore*/}
+ }
+ /* else it's a descriptive label for the next number in
+ the list. */
+ }
+ );
+ delete this.ondispose.__removeFuncList;
+ }
+ };
+ }/*static init*/
+ const sigN = tgt.memberSignature(name);
+ if(sigN.length<2){
+ toss("Member",name,"does not have a function pointer signature:",sigN);
+ }
+ const memKey = tgt.memberKey(name);
+ const fProxy = (applyArgcCheck && !wasm.isPtr(func))
+ /** This middle-man proxy is only for use during development, to
+ confirm that we always pass the proper number of
+ arguments. We know that the C-level code will always use the
+ correct argument count. */
+ ? callee.argcProxy(tgt, memKey, func, sigN)
+ : func;
+ if(wasm.isPtr(fProxy)){
+ if(fProxy && !wasm.functionEntry(fProxy)){
+ toss("Pointer",fProxy,"is not a WASM function table entry.");
+ }
+ tgt[memKey] = fProxy;
+ }else{
+ const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
+ tgt[memKey] = pFunc;
+ if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){
+ tgt.addOnDispose('ondispose.__removeFuncList handler',
+ callee.removeFuncList);
+ tgt.ondispose.__removeFuncList = [];
+ }
+ tgt.ondispose.__removeFuncList.push(memKey, pFunc);
+ }
+ return (n,f)=>callee(tgt, n, f, applyArgcCheck);
+ }/*installMethod*/;
+ installMethod.installMethodArgcCheck = false;
+
+ /**
+ Installs methods into the given StructBinder.StructType-type
+ instance. Each entry in the given methods object must map to a
+ known member of the given StructType, else an exception will be
+ triggered. See installMethod() for more details, including the
+ semantics of the 3rd argument.
+
+ As an exception to the above, if any two or more methods in the
+ 2nd argument are the exact same function, installMethod() is
+ _not_ called for the 2nd and subsequent instances, and instead
+ those instances get assigned the same method pointer which is
+ created for the first instance. This optimization is primarily to
+ accommodate special handling of sqlite3_module::xConnect and
+ xCreate methods.
+
+ On success, returns its first argument. Throws on error.
+ */
+ const installMethods = function(
+ structInstance, methods, applyArgcCheck = installMethod.installMethodArgcCheck
+ ){
+ const seen = new Map /* map of */;
+ for(const k of Object.keys(methods)){
+ const m = methods[k];
+ const prior = seen.get(m);
+ if(prior){
+ const mkey = structInstance.memberKey(k);
+ structInstance[mkey] = structInstance[structInstance.memberKey(prior)];
+ }else{
+ installMethod(structInstance, k, m, applyArgcCheck);
+ seen.set(m, k);
+ }
+ }
+ return structInstance;
+ };
+
+ /**
+ Equivalent to calling installMethod(this,...arguments) with a
+ first argument of this object. If called with 1 or 2 arguments
+ and the first is an object, it's instead equivalent to calling
+ installMethods(this,...arguments).
+ */
+ StructBinder.StructType.prototype.installMethod = function callee(
+ name, func, applyArgcCheck = installMethod.installMethodArgcCheck
+ ){
+ return (arguments.length < 3 && name && 'object'===typeof name)
+ ? installMethods(this, ...arguments)
+ : installMethod(this, ...arguments);
+ };
+
+ /**
+ Equivalent to calling installMethods() with a first argument
+ of this object.
+ */
+ StructBinder.StructType.prototype.installMethods = function(
+ methods, applyArgcCheck = installMethod.installMethodArgcCheck
+ ){
+ return installMethods(this, methods, applyArgcCheck);
+ };
+
});
diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js
index ef1154f6b6..c3cc25f43a 100644
--- a/ext/wasm/api/sqlite3-api-prologue.js
+++ b/ext/wasm/api/sqlite3-api-prologue.js
@@ -37,7 +37,7 @@
This function expects a configuration object, intended to abstract
away details specific to any given WASM environment, primarily so
- that it can be used without any _direct_ dependency on
+ that it can be used without any direct dependency on
Emscripten. (Note the default values for the config object!) The
config object is only honored the first time this is
called. Subsequent calls ignore the argument and return the same
@@ -98,14 +98,37 @@
The returned object is the top-level sqlite3 namespace object.
+
+ Client code may optionally assign sqlite3ApiBootstrap.defaultConfig
+ an object-type value before calling sqlite3ApiBootstrap() (without
+ arguments) in order to tell that call to use this object as its
+ default config value. The intention of this is to provide
+ downstream clients with a reasonably flexible approach for plugging
+ in an environment-suitable configuration without having to define a
+ new global-scope symbol.
+
+ However, because clients who access this library via an
+ Emscripten-hosted module will not have an opportunity to call
+ sqlite3ApiBootstrap() themselves, nor to access it before it is
+ called, an alternative option for setting the configuration is to
+ define globalThis.sqlite3ApiConfig to an object. If it is set, it
+ is used instead of sqlite3ApiBootstrap.defaultConfig if
+ sqlite3ApiBootstrap() is called without arguments.
+
+ Both sqlite3ApiBootstrap.defaultConfig and
+ globalThis.sqlite3ApiConfig get deleted by sqlite3ApiBootstrap()
+ because any changes to them made after that point would have no
+ useful effect.
*/
'use strict';
globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
apiConfig = (globalThis.sqlite3ApiConfig || sqlite3ApiBootstrap.defaultConfig)
){
if(sqlite3ApiBootstrap.sqlite3){ /* already initalized */
- console.warn("sqlite3ApiBootstrap() called multiple times.",
- "Config and external initializers are ignored on calls after the first.");
+ (sqlite3ApiBootstrap.sqlite3.config || console).warn(
+ "sqlite3ApiBootstrap() called multiple times.",
+ "Config and external initializers are ignored on calls after the first."
+ );
return sqlite3ApiBootstrap.sqlite3;
}
const config = Object.assign(Object.create(null),{
@@ -114,8 +137,16 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
bigIntEnabled: (()=>{
if('undefined'!==typeof Module){
/* Emscripten module will contain HEAPU64 when built with
- -sWASM_BIGINT=1, else it will not. */
- return !!Module.HEAPU64;
+ -sWASM_BIGINT=1, else it will not.
+
+ As of emsdk 3.1.55, when building in strict mode, HEAPxyz
+ are only available if _explicitly_ included in the exports,
+ else they are not. We do not (as of 2024-03-04) use -sSTRICT
+ for the canonical builds.
+ */
+ if( !!Module.HEAPU64 ) return true;
+ /* Else fall through and hope for the best. Nobody _really_
+ builds this without BigInt support, do they? */
}
return !!globalThis.BigInt64Array;
})(),
@@ -149,6 +180,15 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
config[k] = config[k]();
}
});
+
+ /**
+ Eliminate any confusion about whether these config objects may
+ be used after library initialization by eliminating the outward-facing
+ objects...
+ */
+ delete globalThis.sqlite3ApiConfig;
+ delete sqlite3ApiBootstrap.defaultConfig;
+
/**
The main sqlite3 binding API gets installed into this object,
mimicking the C API as closely as we can. The numerous members
@@ -1061,7 +1101,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
are undefined if the passed-in value did not come from
this.pointer.
*/
- restore: wasm.exports.sqlite3_wasm_pstack_restore,
+ restore: wasm.exports.sqlite3__wasm_pstack_restore,
/**
Attempts to allocate the given number of bytes from the
pstack. On success, it zeroes out a block of memory of the
@@ -1083,7 +1123,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
if('string'===typeof n && !(n = wasm.sizeofIR(n))){
WasmAllocError.toss("Invalid value for pstack.alloc(",arguments[0],")");
}
- return wasm.exports.sqlite3_wasm_pstack_alloc(n)
+ return wasm.exports.sqlite3__wasm_pstack_alloc(n)
|| WasmAllocError.toss("Could not allocate",n,
"bytes from the pstack.");
},
@@ -1163,10 +1203,10 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
*/
pointer: {
configurable: false, iterable: true, writeable: false,
- get: wasm.exports.sqlite3_wasm_pstack_ptr
+ get: wasm.exports.sqlite3__wasm_pstack_ptr
//Whether or not a setter as an alternative to restore() is
//clearer or would just lead to confusion is unclear.
- //set: wasm.exports.sqlite3_wasm_pstack_restore
+ //set: wasm.exports.sqlite3__wasm_pstack_restore
},
/**
sqlite3.wasm.pstack.quota to the total number of bytes
@@ -1175,7 +1215,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
*/
quota: {
configurable: false, iterable: true, writeable: false,
- get: wasm.exports.sqlite3_wasm_pstack_quota
+ get: wasm.exports.sqlite3__wasm_pstack_quota
},
/**
sqlite3.wasm.pstack.remaining resolves to the amount of space
@@ -1183,7 +1223,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
*/
remaining: {
configurable: false, iterable: true, writeable: false,
- get: wasm.exports.sqlite3_wasm_pstack_remaining
+ get: wasm.exports.sqlite3__wasm_pstack_remaining
}
})/*wasm.pstack properties*/;
@@ -1256,14 +1296,14 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
}
try{
if(pdir && 0===wasm.xCallWrapped(
- 'sqlite3_wasm_init_wasmfs', 'i32', ['string'], pdir
+ 'sqlite3__wasm_init_wasmfs', 'i32', ['string'], pdir
)){
return __wasmfsOpfsDir = pdir;
}else{
return __wasmfsOpfsDir = "";
}
}catch(e){
- // sqlite3_wasm_init_wasmfs() is not available
+ // sqlite3__wasm_init_wasmfs() is not available
return __wasmfsOpfsDir = "";
}
};
@@ -1365,7 +1405,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
const zSchema = schema
? (wasm.isPtr(schema) ? schema : wasm.scopedAllocCString(''+schema))
: 0;
- let rc = wasm.exports.sqlite3_wasm_db_serialize(
+ let rc = wasm.exports.sqlite3__wasm_db_serialize(
pDb, zSchema, ppOut, pSize, 0
);
if(rc){
@@ -1391,7 +1431,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
or not provided, then "main" is assumed.
*/
capi.sqlite3_js_db_vfs =
- (dbPointer, dbName=0)=>wasm.sqlite3_wasm_db_vfs(dbPointer, dbName);
+ (dbPointer, dbName=0)=>util.sqlite3__wasm_db_vfs(dbPointer, dbName);
/**
A thin wrapper around capi.sqlite3_aggregate_context() which
@@ -1449,7 +1489,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
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);
+ const rc = util.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{
@@ -1551,7 +1591,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
SQLite3Error.toss("Invalid 4th argument for sqlite3_js_vfs_create_file().");
}
try{
- const rc = wasm.sqlite3_wasm_vfs_create_file(vfs, filename, pData, dataLen);
+ const rc = util.sqlite3__wasm_vfs_create_file(vfs, filename, pData, dataLen);
if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code",
capi.sqlite3_js_rc_str(rc));
}finally{
@@ -1672,12 +1712,12 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
*/
capi.sqlite3_db_config = function(pDb, op, ...args){
if(!this.s){
- this.s = wasm.xWrap('sqlite3_wasm_db_config_s','int',
+ this.s = wasm.xWrap('sqlite3__wasm_db_config_s','int',
['sqlite3*', 'int', 'string:static']
/* MAINDBNAME requires a static string */);
- this.pii = wasm.xWrap('sqlite3_wasm_db_config_pii', 'int',
+ this.pii = wasm.xWrap('sqlite3__wasm_db_config_pii', 'int',
['sqlite3*', 'int', '*','int', 'int']);
- this.ip = wasm.xWrap('sqlite3_wasm_db_config_ip','int',
+ this.ip = wasm.xWrap('sqlite3__wasm_db_config_ip','int',
['sqlite3*', 'int', 'int','*']);
}
switch(op){
diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js
index 3099c19ec4..bd99f3ec8e 100644
--- a/ext/wasm/api/sqlite3-api-worker1.js
+++ b/ext/wasm/api/sqlite3-api-worker1.js
@@ -359,6 +359,7 @@
*/
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
+const util = sqlite3.util;
sqlite3.initWorker1API = function(){
'use strict';
const toss = (...args)=>{throw new Error(args.join(' '))};
@@ -409,12 +410,12 @@ sqlite3.initWorker1API = function(){
if(db){
delete this.dbs[getDbId(db)];
const filename = db.filename;
- const pVfs = sqlite3.wasm.sqlite3_wasm_db_vfs(db.pointer, 0);
+ const pVfs = util.sqlite3__wasm_db_vfs(db.pointer, 0);
db.close();
const ddNdx = this.dbList.indexOf(db);
if(ddNdx>=0) this.dbList.splice(ddNdx, 1);
if(alsoUnlink && filename && pVfs){
- sqlite3.wasm.sqlite3_wasm_vfs_unlink(pVfs, filename);
+ util.sqlite3__wasm_vfs_unlink(pVfs, filename);
}
}
},
@@ -495,12 +496,12 @@ sqlite3.initWorker1API = function(){
}
if(pVfs){
/* 2022-11-02: this feature is as-yet untested except that
- sqlite3_wasm_vfs_create_file() has been tested from the
+ sqlite3__wasm_vfs_create_file() has been tested from the
browser dev console. */
let pMem;
try{
pMem = sqlite3.wasm.allocFromTypedArray(byteArray);
- const rc = sqlite3.wasm.sqlite3_wasm_vfs_create_file(
+ const rc = util.sqlite3__wasm_vfs_create_file(
pVfs, oargs.filename, pMem, byteArray.byteLength
);
if(rc) sqlite3.SQLite3Error.toss(rc);
diff --git a/ext/wasm/api/sqlite3-opfs-async-proxy.js b/ext/wasm/api/sqlite3-opfs-async-proxy.js
index cafd296c61..58b8bcb233 100644
--- a/ext/wasm/api/sqlite3-opfs-async-proxy.js
+++ b/ext/wasm/api/sqlite3-opfs-async-proxy.js
@@ -562,6 +562,17 @@ const installAsyncProxy = function(self){
wTimeEnd();
return;
}
+ if( state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN & opfsFlags ){
+ //log("async proxy opfsFlags =",opfsFlags);
+ try{
+ await hDir.removeEntry(filenamePart);
+ //log("Unlinked",filename,hDir,filenamePart);
+ }
+ catch(e){
+ /* ignoring */
+ //warn("Ignoring failed Unlink of",filename,":",e);
+ }
+ }
const hFile = await hDir.getFileHandle(filenamePart, {create});
wTimeEnd();
const fh = Object.assign(Object.create(null),{
diff --git a/ext/wasm/api/sqlite3-vfs-helper.c-pp.js b/ext/wasm/api/sqlite3-vfs-helper.c-pp.js
new file mode 100644
index 0000000000..4d29c7b91a
--- /dev/null
+++ b/ext/wasm/api/sqlite3-vfs-helper.c-pp.js
@@ -0,0 +1,103 @@
+/*
+** 2022-11-30
+**
+** 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 installs sqlite3.vfs, a namespace of helpers for use in
+ the creation of JavaScript implementations of sqlite3_vfs.
+*/
+'use strict';
+globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
+ const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3;
+ const vfs = Object.create(null);
+ sqlite3.vfs = vfs;
+
+ /**
+ Uses sqlite3_vfs_register() to register this
+ sqlite3.capi.sqlite3_vfs instance. This object must have already
+ been filled out properly. If the first argument is truthy, the
+ VFS is registered as the default VFS, else it is not.
+
+ On success, returns this object. Throws on error.
+ */
+ capi.sqlite3_vfs.prototype.registerVfs = function(asDefault=false){
+ if(!(this instanceof sqlite3.capi.sqlite3_vfs)){
+ toss("Expecting a sqlite3_vfs-type argument.");
+ }
+ const rc = capi.sqlite3_vfs_register(this, asDefault ? 1 : 0);
+ if(rc){
+ toss("sqlite3_vfs_register(",this,") failed with rc",rc);
+ }
+ if(this.pointer !== capi.sqlite3_vfs_find(this.$zName)){
+ toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS",
+ this);
+ }
+ return this;
+ };
+
+ /**
+ A wrapper for
+ sqlite3.StructBinder.StructType.prototype.installMethods() or
+ registerVfs() to reduce installation of a VFS and/or its I/O
+ methods to a single call.
+
+ Accepts an object which contains the properties "io" and/or
+ "vfs", each of which is itself an object with following properties:
+
+ - `struct`: an sqlite3.StructBinder.StructType-type struct. This
+ must be a populated (except for the methods) object of type
+ sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the
+ "vfs" entry).
+
+ - `methods`: an object mapping sqlite3_io_methods method names
+ (e.g. 'xClose') to JS implementations of those methods. The JS
+ implementations must be call-compatible with their native
+ counterparts.
+
+ For each of those object, this function passes its (`struct`,
+ `methods`, (optional) `applyArgcCheck`) properties to
+ installMethods().
+
+ If the `vfs` entry is set then:
+
+ - Its `struct` property's registerVfs() is called. The
+ `vfs` entry may optionally have an `asDefault` property, which
+ gets passed as the argument to registerVfs().
+
+ - 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 string
+ gets added to the on-dispose state of the struct.
+
+ On success returns this object. Throws on error.
+ */
+ vfs.installVfs = function(opt){
+ let count = 0;
+ const propList = ['io','vfs'];
+ for(const key of propList){
+ const o = opt[key];
+ if(o){
+ ++count;
+ o.struct.installMethods(o.methods, !!o.applyArgcCheck);
+ if('vfs'===key){
+ if(!o.struct.$zName && 'string'===typeof o.name){
+ o.struct.addOnDispose(
+ o.struct.$zName = wasm.allocCString(o.name)
+ );
+ }
+ o.struct.registerVfs(!!o.asDefault);
+ }
+ }
+ }
+ if(!count) toss("Misuse: installVfs() options object requires at least",
+ "one of:", propList);
+ return this;
+ };
+}/*sqlite3ApiBootstrap.initializers.push()*/);
diff --git a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js
index 870073cc09..f3664fd4b8 100644
--- a/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js
+++ b/ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js
@@ -1137,8 +1137,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
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
+ Results are undefined if the given db name refers to an opened
+ db. 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.
diff --git a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
index af89f216f7..4c654c3351 100644
--- a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
+++ b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js
@@ -245,7 +245,8 @@ const installOpfsVfs = function callee(options){
opfsIoMethods.$iVersion = 1;
opfsVfs.$iVersion = 2/*yes, two*/;
opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
- opfsVfs.$mxPathname = 1024/*sure, why not?*/;
+ opfsVfs.$mxPathname = 1024/* sure, why not? The OPFS name length limit
+ is undocumented/unspecified. */;
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;
@@ -422,10 +423,25 @@ const installOpfsVfs = function callee(options){
});
state.opfsFlags = Object.assign(Object.create(null),{
/**
- Flag for use with xOpen(). "opfs-unlock-asap=1" enables
- this. See defaultUnlockAsap, below.
+ Flag for use with xOpen(). URI flag "opfs-unlock-asap=1"
+ enables this. See defaultUnlockAsap, below.
*/
OPFS_UNLOCK_ASAP: 0x01,
+ /**
+ Flag for use with xOpen(). URI flag "delete-before-open=1"
+ tells the VFS to delete the db file before attempting to open
+ it. This can be used, e.g., to replace a db which has been
+ corrupted (without forcing us to expose a delete/unlink()
+ function in the public API).
+
+ Failure to unlink the file is ignored but may lead to
+ downstream errors. An unlink can fail if, e.g., another tab
+ has the handle open.
+
+ It goes without saying that deleting a file out from under another
+ instance results in Undefined Behavior.
+ */
+ OPFS_UNLINK_BEFORE_OPEN: 0x02,
/**
If true, any async routine which implicitly acquires a sync
access handle (i.e. an OPFS lock) will release that locks at
@@ -874,13 +890,17 @@ const installOpfsVfs = function callee(options){
let opfsFlags = 0;
if(0===zName){
zName = randomFilename();
- }else if('number'===typeof zName){
+ }else if(wasm.isPtr(zName)){
if(capi.sqlite3_uri_boolean(zName, "opfs-unlock-asap", 0)){
/* -----------------------^^^^^ MUST pass the untranslated
C-string here. */
opfsFlags |= state.opfsFlags.OPFS_UNLOCK_ASAP;
}
+ if(capi.sqlite3_uri_boolean(zName, "delete-before-open", 0)){
+ opfsFlags |= state.opfsFlags.OPFS_UNLINK_BEFORE_OPEN;
+ }
zName = wasm.cstrToJs(zName);
+ //warn("xOpen zName =",zName, "opfsFlags =",opfsFlags);
}
const fh = Object.create(null);
fh.fid = pFile;
@@ -993,27 +1013,6 @@ const installOpfsVfs = function callee(options){
*/
opfsUtil.randomFilename = randomFilename;
- /**
- Re-registers the OPFS VFS. This is intended only for odd use
- cases which have to call sqlite3_shutdown() as part of their
- initialization process, which will unregister the VFS
- registered by installOpfsVfs(). If passed a truthy value, the
- OPFS VFS is registered as the default VFS, else it is not made
- the default. Returns the result of the the
- sqlite3_vfs_register() call.
-
- Design note: the problem of having to re-register things after
- a shutdown/initialize pair is more general. How to best plug
- that in to the library is unclear. In particular, we cannot
- hook in to any C-side calls to sqlite3_initialize(), so we
- cannot add an after-initialize callback mechanism.
- */
- opfsUtil.registerVfs = (asDefault=false)=>{
- return wasm.exports.sqlite3_vfs_register(
- opfsVfs.pointer, asDefault ? 1 : 0
- );
- };
-
/**
Returns a promise which resolves to an object which represents
all files and directories in the OPFS tree. The top-most object
@@ -1213,16 +1212,18 @@ const installOpfsVfs = function callee(options){
Asynchronously imports the given bytes (a byte array or
ArrayBuffer) into the given database file.
+ Results are undefined if the given db name refers to an opened
+ db.
+
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.
+ changes: 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
diff --git a/ext/wasm/api/sqlite3-v-helper.js b/ext/wasm/api/sqlite3-vtab-helper.c-pp.js
similarity index 57%
rename from ext/wasm/api/sqlite3-v-helper.js
rename to ext/wasm/api/sqlite3-vtab-helper.c-pp.js
index e63da8afc3..7359ea39aa 100644
--- a/ext/wasm/api/sqlite3-v-helper.js
+++ b/ext/wasm/api/sqlite3-vtab-helper.c-pp.js
@@ -10,19 +10,13 @@
*/
/**
- This file installs sqlite3.vfs, and object which exists to assist
- in the creation of JavaScript implementations of sqlite3_vfs, along
- with its virtual table counterpart, sqlite3.vtab.
+ This file installs sqlite3.vtab, a namespace of helpers for use in
+ the creation of JavaScript implementations virtual tables.
*/
'use strict';
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3;
- const vfs = Object.create(null), vtab = Object.create(null);
-
- const StructBinder = sqlite3.StructBinder
- /* we require a local alias b/c StructBinder is removed from the sqlite3
- object during the final steps of the API cleanup. */;
- sqlite3.vfs = vfs;
+ const vtab = Object.create(null);
sqlite3.vtab = vtab;
const sii = capi.sqlite3_index_info;
@@ -72,257 +66,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return asPtr ? ptr : new sii.sqlite3_index_orderby(ptr);
};
- /**
- Installs a StructBinder-bound function pointer member of the
- given name and function in the given StructType target object.
-
- It creates a WASM proxy for the given function and arranges for
- that proxy to be cleaned up when tgt.dispose() is called. Throws
- on the slightest hint of error, e.g. tgt is-not-a StructType,
- name does not map to a struct-bound member, etc.
-
- As a special case, if the given function is a pointer, then
- `wasm.functionEntry()` is used to validate that it is a known
- function. If so, it is used as-is with no extra level of proxying
- or cleanup, else an exception is thrown. It is legal to pass a
- value of 0, indicating a NULL pointer, with the caveat that 0
- _is_ a legal function pointer in WASM but it will not be accepted
- as such _here_. (Justification: the function at address zero must
- be one which initially came from the WASM module, not a method we
- want to bind to a virtual table or VFS.)
-
- This function returns a proxy for itself which is bound to tgt
- and takes 2 args (name,func). That function returns the same
- thing as this one, permitting calls to be chained.
-
- If called with only 1 arg, it has no side effects but returns a
- func with the same signature as described above.
-
- 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
- undefined behavior in the C layer, that methods mapped via
- this function do not throw. The exception, as it were, to that
- rule is...
-
- If applyArgcCheck is true then each JS function (as opposed to
- function pointers) gets wrapped in a proxy which asserts that it
- is passed the expected number of arguments, throwing if the
- argument count does not match expectations. That is only intended
- for dev-time usage for sanity checking, and will leave the C
- environment in an undefined state.
- */
- const installMethod = function callee(
- tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck
- ){
- if(!(tgt instanceof StructBinder.StructType)){
- toss("Usage error: target object is-not-a StructType.");
- }else if(!(func instanceof Function) && !wasm.isPtr(func)){
- toss("Usage errror: expecting a Function or WASM pointer to one.");
- }
- if(1===arguments.length){
- return (n,f)=>callee(tgt, n, f, applyArgcCheck);
- }
- if(!callee.argcProxy){
- callee.argcProxy = function(tgt, funcName, func,sig){
- return function(...args){
- if(func.length!==arguments.length){
- toss("Argument mismatch for",
- tgt.structInfo.name+"::"+funcName
- +": Native signature is:",sig);
- }
- return func.apply(this, args);
- }
- };
- /* An ondispose() callback for use with
- StructBinder-created types. */
- callee.removeFuncList = function(){
- if(this.ondispose.__removeFuncList){
- this.ondispose.__removeFuncList.forEach(
- (v,ndx)=>{
- if('number'===typeof v){
- try{wasm.uninstallFunction(v)}
- catch(e){/*ignore*/}
- }
- /* else it's a descriptive label for the next number in
- the list. */
- }
- );
- delete this.ondispose.__removeFuncList;
- }
- };
- }/*static init*/
- const sigN = tgt.memberSignature(name);
- if(sigN.length<2){
- toss("Member",name,"does not have a function pointer signature:",sigN);
- }
- const memKey = tgt.memberKey(name);
- const fProxy = (applyArgcCheck && !wasm.isPtr(func))
- /** This middle-man proxy is only for use during development, to
- confirm that we always pass the proper number of
- arguments. We know that the C-level code will always use the
- correct argument count. */
- ? callee.argcProxy(tgt, memKey, func, sigN)
- : func;
- if(wasm.isPtr(fProxy)){
- if(fProxy && !wasm.functionEntry(fProxy)){
- toss("Pointer",fProxy,"is not a WASM function table entry.");
- }
- tgt[memKey] = fProxy;
- }else{
- const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
- tgt[memKey] = pFunc;
- if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){
- tgt.addOnDispose('ondispose.__removeFuncList handler',
- callee.removeFuncList);
- tgt.ondispose.__removeFuncList = [];
- }
- tgt.ondispose.__removeFuncList.push(memKey, pFunc);
- }
- return (n,f)=>callee(tgt, n, f, applyArgcCheck);
- }/*installMethod*/;
- installMethod.installMethodArgcCheck = false;
-
- /**
- Installs methods into the given StructType-type instance. Each
- entry in the given methods object must map to a known member of
- the given StructType, else an exception will be triggered. See
- installMethod() for more details, including the semantics of the
- 3rd argument.
-
- As an exception to the above, if any two or more methods in the
- 2nd argument are the exact same function, installMethod() is
- _not_ called for the 2nd and subsequent instances, and instead
- those instances get assigned the same method pointer which is
- created for the first instance. This optimization is primarily to
- accommodate special handling of sqlite3_module::xConnect and
- xCreate methods.
-
- On success, returns its first argument. Throws on error.
- */
- const installMethods = function(
- structInstance, methods, applyArgcCheck = installMethod.installMethodArgcCheck
- ){
- const seen = new Map /* map of */;
- for(const k of Object.keys(methods)){
- const m = methods[k];
- const prior = seen.get(m);
- if(prior){
- const mkey = structInstance.memberKey(k);
- structInstance[mkey] = structInstance[structInstance.memberKey(prior)];
- }else{
- installMethod(structInstance, k, m, applyArgcCheck);
- seen.set(m, k);
- }
- }
- return structInstance;
- };
-
- /**
- Equivalent to calling installMethod(this,...arguments) with a
- first argument of this object. If called with 1 or 2 arguments
- and the first is an object, it's instead equivalent to calling
- installMethods(this,...arguments).
- */
- StructBinder.StructType.prototype.installMethod = function callee(
- name, func, applyArgcCheck = installMethod.installMethodArgcCheck
- ){
- return (arguments.length < 3 && name && 'object'===typeof name)
- ? installMethods(this, ...arguments)
- : installMethod(this, ...arguments);
- };
-
- /**
- Equivalent to calling installMethods() with a first argument
- of this object.
- */
- StructBinder.StructType.prototype.installMethods = function(
- methods, applyArgcCheck = installMethod.installMethodArgcCheck
- ){
- return installMethods(this, methods, applyArgcCheck);
- };
-
- /**
- Uses sqlite3_vfs_register() to register this
- sqlite3.capi.sqlite3_vfs. This object must have already been
- filled out properly. If the first argument is truthy, the VFS is
- registered as the default VFS, else it is not.
-
- On success, returns this object. Throws on error.
- */
- capi.sqlite3_vfs.prototype.registerVfs = function(asDefault=false){
- if(!(this instanceof sqlite3.capi.sqlite3_vfs)){
- toss("Expecting a sqlite3_vfs-type argument.");
- }
- const rc = capi.sqlite3_vfs_register(this, asDefault ? 1 : 0);
- if(rc){
- toss("sqlite3_vfs_register(",this,") failed with rc",rc);
- }
- if(this.pointer !== capi.sqlite3_vfs_find(this.$zName)){
- toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS",
- this);
- }
- return this;
- };
-
- /**
- A wrapper for installMethods() or registerVfs() to reduce
- installation of a VFS and/or its I/O methods to a single
- call.
-
- Accepts an object which contains the properties "io" and/or
- "vfs", each of which is itself an object with following properties:
-
- - `struct`: an sqlite3.StructType-type struct. This must be a
- populated (except for the methods) object of type
- sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the
- "vfs" entry).
-
- - `methods`: an object mapping sqlite3_io_methods method names
- (e.g. 'xClose') to JS implementations of those methods. The JS
- implementations must be call-compatible with their native
- counterparts.
-
- For each of those object, this function passes its (`struct`,
- `methods`, (optional) `applyArgcCheck`) properties to
- installMethods().
-
- If the `vfs` entry is set then:
-
- - Its `struct` property's registerVfs() is called. The
- `vfs` entry may optionally have an `asDefault` property, which
- gets passed as the argument to registerVfs().
-
- - 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 string
- gets added to the on-dispose state of the struct.
-
- On success returns this object. Throws on error.
- */
- vfs.installVfs = function(opt){
- let count = 0;
- const propList = ['io','vfs'];
- for(const key of propList){
- const o = opt[key];
- if(o){
- ++count;
- installMethods(o.struct, o.methods, !!o.applyArgcCheck);
- if('vfs'===key){
- if(!o.struct.$zName && 'string'===typeof o.name){
- o.struct.addOnDispose(
- o.struct.$zName = wasm.allocCString(o.name)
- );
- }
- o.struct.registerVfs(!!o.asDefault);
- }
- }
- }
- if(!count) toss("Misuse: installVfs() options object requires at least",
- "one of:", propList);
- return this;
- };
-
/**
Internal factory function for xVtab and xCursor impls.
*/
@@ -456,30 +199,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
*/
vtab.xIndexInfo = (pIdxInfo)=>new capi.sqlite3_index_info(pIdxInfo);
- /**
- Given an error object, this function returns
- sqlite3.capi.SQLITE_NOMEM if (e instanceof
- sqlite3.WasmAllocError), else it returns its
- second argument. Its intended usage is in the methods
- of a sqlite3_vfs or sqlite3_module:
-
- ```
- try{
- let rc = ...
- return rc;
- }catch(e){
- return sqlite3.vtab.exceptionToRc(e, sqlite3.capi.SQLITE_XYZ);
- // where SQLITE_XYZ is some call-appropriate result code.
- }
- ```
- */
- /**vfs.exceptionToRc = vtab.exceptionToRc =
- (e, defaultRc=capi.SQLITE_ERROR)=>(
- (e instanceof sqlite3.WasmAllocError)
- ? capi.SQLITE_NOMEM
- : defaultRc
- );*/
-
/**
Given an sqlite3_module method name and error object, this
function returns sqlite3.capi.SQLITE_NOMEM if (e instanceof
@@ -525,20 +244,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
};
vtab.xError.errorReporter = 1 ? console.error.bind(console) : false;
- /**
- "The problem" with this is that it introduces an outer function with
- a different arity than the passed-in method callback. That means we
- cannot do argc validation on these. Additionally, some methods (namely
- xConnect) may have call-specific error handling. It would be a shame to
- hard-coded that per-method support in this function.
- */
- /** vtab.methodCatcher = function(methodName, method, defaultErrRc=capi.SQLITE_ERROR){
- return function(...args){
- try { method(...args); }
- }catch(e){ return vtab.xError(methodName, e, defaultRc) }
- };
- */
-
/**
A helper for sqlite3_vtab::xRowid() and xUpdate()
implementations. It must be passed the final argument to one of
@@ -685,12 +390,12 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
remethods[k] = fwrap(k, m);
}
}
- installMethods(mod, remethods, false);
+ mod.installMethods(remethods, false);
}else{
// No automatic exception handling. Trust the client
// to not throw.
- installMethods(
- mod, methods, !!opt.applyArgcCheck/*undocumented option*/
+ mod.installMethods(
+ methods, !!opt.applyArgcCheck/*undocumented option*/
);
}
if(0===mod.$iVersion){
diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c
index 618d0f085a..bd0eb884c9 100644
--- a/ext/wasm/api/sqlite3-wasm.c
+++ b/ext/wasm/api/sqlite3-wasm.c
@@ -238,28 +238,28 @@
** Another option is to malloc() a chunk of our own and call that our
** "stack".
*/
-SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_end(void){
+SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_end(void){
extern void __heap_base
/* see https://stackoverflow.com/questions/10038964 */;
return &__heap_base;
}
-SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_begin(void){
+SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_begin(void){
extern void __data_end;
return &__data_end;
}
static void * pWasmStackPtr = 0;
-SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_ptr(void){
- if(!pWasmStackPtr) pWasmStackPtr = sqlite3_wasm_stack_end();
+SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_ptr(void){
+ if(!pWasmStackPtr) pWasmStackPtr = sqlite3__wasm_stack_end();
return pWasmStackPtr;
}
-SQLITE_WASM_EXPORT void sqlite3_wasm_stack_restore(void * p){
+SQLITE_WASM_EXPORT void sqlite3__wasm_stack_restore(void * p){
pWasmStackPtr = p;
}
-SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_alloc(int n){
+SQLITE_WASM_EXPORT void * sqlite3__wasm_stack_alloc(int n){
if(n<=0) return 0;
n = (n + 7) & ~7 /* align to 8-byte boundary */;
- unsigned char * const p = (unsigned char *)sqlite3_wasm_stack_ptr();
- unsigned const char * const b = (unsigned const char *)sqlite3_wasm_stack_begin();
+ unsigned char * const p = (unsigned char *)sqlite3__wasm_stack_ptr();
+ unsigned const char * const b = (unsigned const char *)sqlite3__wasm_stack_begin();
if(b + n >= p || b + n < b/*overflow*/) return 0;
return pWasmStackPtr = p - n;
}
@@ -267,7 +267,7 @@ SQLITE_WASM_EXPORT void * sqlite3_wasm_stack_alloc(int n){
/*
** State for the "pseudo-stack" allocator implemented in
-** sqlite3_wasm_pstack_xyz(). In order to avoid colliding with
+** sqlite3__wasm_pstack_xyz(). In order to avoid colliding with
** Emscripten-controled stack space, it carves out a bit of stack
** memory to use for that purpose. This memory ends up in the
** WASM-managed memory, such that routines which manipulate the wasm
@@ -291,14 +291,14 @@ static struct {
/*
** Returns the current pstack position.
*/
-SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_ptr(void){
+SQLITE_WASM_EXPORT void * sqlite3__wasm_pstack_ptr(void){
return PStack.pPos;
}
/*
** Sets the pstack position poitner to p. Results are undefined if the
-** given value did not come from sqlite3_wasm_pstack_ptr().
+** given value did not come from sqlite3__wasm_pstack_ptr().
*/
-SQLITE_WASM_EXPORT void sqlite3_wasm_pstack_restore(unsigned char * p){
+SQLITE_WASM_EXPORT void sqlite3__wasm_pstack_restore(unsigned char * p){
assert(p>=PStack.pBegin && p<=PStack.pEnd && p>=PStack.pPos);
assert(0==((unsigned long long)p & 0x7));
if(p>=PStack.pBegin && p<=PStack.pEnd /*&& p>=PStack.pPos*/){
@@ -313,7 +313,7 @@ SQLITE_WASM_EXPORT void sqlite3_wasm_pstack_restore(unsigned char * p){
** JS code from having to do so, and most uses of the pstack will
** call for doing so).
*/
-SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_alloc(int n){
+SQLITE_WASM_EXPORT void * sqlite3__wasm_pstack_alloc(int n){
if( n<=0 ) return 0;
//if( n & 0x7 ) n += 8 - (n & 0x7) /* align to 8-byte boundary */;
n = (n + 7) & ~7 /* align to 8-byte boundary */;
@@ -324,9 +324,9 @@ SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_alloc(int n){
}
/*
** Return the number of bytes left which can be
-** sqlite3_wasm_pstack_alloc()'d.
+** sqlite3__wasm_pstack_alloc()'d.
*/
-SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_remaining(void){
+SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_remaining(void){
assert(PStack.pPos >= PStack.pBegin);
assert(PStack.pPos <= PStack.pEnd);
return (int)(PStack.pPos - PStack.pBegin);
@@ -337,7 +337,7 @@ SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_remaining(void){
** any space which is currently allocated. This value is a
** compile-time constant.
*/
-SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_quota(void){
+SQLITE_WASM_EXPORT int sqlite3__wasm_pstack_quota(void){
return (int)(PStack.pEnd - PStack.pBegin);
}
@@ -356,7 +356,7 @@ SQLITE_WASM_EXPORT int sqlite3_wasm_pstack_quota(void){
** Returns err_code.
*/
SQLITE_WASM_EXPORT
-int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){
+int sqlite3__wasm_db_error(sqlite3*db, int err_code, const char *zMsg){
if( db!=0 ){
if( 0!=zMsg ){
const int nMsg = sqlite3Strlen30(zMsg);
@@ -380,7 +380,7 @@ struct WasmTestStruct {
};
typedef struct WasmTestStruct WasmTestStruct;
SQLITE_WASM_EXPORT
-void sqlite3_wasm_test_struct(WasmTestStruct * s){
+void sqlite3__wasm_test_struct(WasmTestStruct * s){
if(s){
s->v4 *= 2;
s->v8 = s->v4 * 2;
@@ -408,7 +408,7 @@ void sqlite3_wasm_test_struct(WasmTestStruct * s){
** increased. In debug builds that will trigger an assert().
*/
SQLITE_WASM_EXPORT
-const char * sqlite3_wasm_enum_json(void){
+const char * sqlite3__wasm_enum_json(void){
static char aBuffer[1024 * 20] = {0} /* where the JSON goes */;
int n = 0, nChildren = 0, nStruct = 0
/* output counters for figuring out where commas go */;
@@ -425,7 +425,7 @@ const char * sqlite3_wasm_enum_json(void){
/* Core output macros... */
#define lenCheck assert(zPos < zEnd - 128 \
- && "sqlite3_wasm_enum_json() buffer is too small."); \
+ && "sqlite3__wasm_enum_json() buffer is too small."); \
if( zPos >= zEnd - 128 ) return 0
#define outf(format,...) \
zPos += snprintf(zPos, ((size_t)(zEnd - zPos)), format, __VA_ARGS__); \
@@ -1220,7 +1220,7 @@ const char * sqlite3_wasm_enum_json(void){
** call is returned.
*/
SQLITE_WASM_EXPORT
-int sqlite3_wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){
+int sqlite3__wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){
int rc = SQLITE_MISUSE /* ??? */;
if( 0==pVfs && 0!=zName ) pVfs = sqlite3_vfs_find(0);
if( zName && pVfs && pVfs->xDelete ){
@@ -1238,7 +1238,7 @@ int sqlite3_wasm_vfs_unlink(sqlite3_vfs *pVfs, const char *zName){
** given name is open.
*/
SQLITE_WASM_EXPORT
-sqlite3_vfs * sqlite3_wasm_db_vfs(sqlite3 *pDb, const char *zDbName){
+sqlite3_vfs * sqlite3__wasm_db_vfs(sqlite3 *pDb, const char *zDbName){
sqlite3_vfs * pVfs = 0;
sqlite3_file_control(pDb, zDbName ? zDbName : "main",
SQLITE_FCNTL_VFS_POINTER, &pVfs);
@@ -1261,7 +1261,7 @@ sqlite3_vfs * sqlite3_wasm_db_vfs(sqlite3 *pDb, const char *zDbName){
** SQLITE_MISUSE if pDb is NULL.
*/
SQLITE_WASM_EXPORT
-int sqlite3_wasm_db_reset(sqlite3 *pDb){
+int sqlite3__wasm_db_reset(sqlite3 *pDb){
int rc = SQLITE_MISUSE;
if( pDb ){
sqlite3_table_column_metadata(pDb, "main", 0, 0, 0, 0, 0, 0, 0);
@@ -1288,11 +1288,11 @@ int sqlite3_wasm_db_reset(sqlite3 *pDb){
** takes no measures to ensure that is the case.
**
** This implementation appears to work fine, but
-** sqlite3_wasm_db_serialize() is arguably the better way to achieve
+** sqlite3__wasm_db_serialize() is arguably the better way to achieve
** this.
*/
SQLITE_WASM_EXPORT
-int sqlite3_wasm_db_export_chunked( sqlite3* pDb,
+int sqlite3__wasm_db_export_chunked( sqlite3* pDb,
int (*xCallback)(unsigned const char *zOut, int n) ){
sqlite3_int64 nSize = 0;
sqlite3_int64 nPos = 0;
@@ -1343,7 +1343,7 @@ int sqlite3_wasm_db_export_chunked( sqlite3* pDb,
** sqlite3_free() to free it.
*/
SQLITE_WASM_EXPORT
-int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema,
+int sqlite3__wasm_db_serialize( sqlite3 *pDb, const char *zSchema,
unsigned char **pOut,
sqlite3_int64 *nOut, unsigned int mFlags ){
unsigned char * z;
@@ -1366,7 +1366,7 @@ int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema,
** 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
+** 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
@@ -1407,7 +1407,7 @@ int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema,
** support is disabled or unavailable.
*/
SQLITE_WASM_EXPORT
-int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs,
+int sqlite3__wasm_vfs_create_file( sqlite3_vfs *pVfs,
const char *zFilename,
const unsigned char * pData,
int nData ){
@@ -1497,7 +1497,7 @@ int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs,
** SQLITE_IOERR on error.
*/
SQLITE_WASM_EXPORT
-int sqlite3_wasm_posix_create_file( const char *zFilename,
+int sqlite3__wasm_posix_create_file( const char *zFilename,
const unsigned char * pData,
int nData ){
int rc;
@@ -1520,17 +1520,17 @@ int sqlite3_wasm_posix_create_file( const char *zFilename,
** for use by the sqlite project's own JS/WASM bindings.
**
** Allocates sqlite3KvvfsMethods.nKeySize bytes from
-** sqlite3_wasm_pstack_alloc() and returns 0 if that allocation fails,
+** sqlite3__wasm_pstack_alloc() and returns 0 if that allocation fails,
** else it passes that string to kvstorageMakeKey() and returns a
** NUL-terminated pointer to that string. It is up to the caller to
-** use sqlite3_wasm_pstack_restore() to free the returned pointer.
+** use sqlite3__wasm_pstack_restore() to free the returned pointer.
*/
SQLITE_WASM_EXPORT
-char * sqlite3_wasm_kvvfsMakeKeyOnPstack(const char *zClass,
+char * sqlite3__wasm_kvvfsMakeKeyOnPstack(const char *zClass,
const char *zKeyIn){
assert(sqlite3KvvfsMethods.nKeySize>24);
char *zKeyOut =
- (char *)sqlite3_wasm_pstack_alloc(sqlite3KvvfsMethods.nKeySize);
+ (char *)sqlite3__wasm_pstack_alloc(sqlite3KvvfsMethods.nKeySize);
if(zKeyOut){
kvstorageMakeKey(zClass, zKeyIn, zKeyOut);
}
@@ -1545,7 +1545,7 @@ char * sqlite3_wasm_kvvfsMakeKeyOnPstack(const char *zClass,
** I/O methods and associated state.
*/
SQLITE_WASM_EXPORT
-sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){
+sqlite3_kvvfs_methods * sqlite3__wasm_kvvfs_methods(void){
return &sqlite3KvvfsMethods;
}
@@ -1560,7 +1560,7 @@ sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){
** valid value.
*/
SQLITE_WASM_EXPORT
-int sqlite3_wasm_vtab_config(sqlite3 *pDb, int op, int arg){
+int sqlite3__wasm_vtab_config(sqlite3 *pDb, int op, int arg){
switch(op){
case SQLITE_VTAB_DIRECTONLY:
case SQLITE_VTAB_INNOCUOUS:
@@ -1580,7 +1580,7 @@ int sqlite3_wasm_vtab_config(sqlite3 *pDb, int op, int arg){
** (int,int*) variadic args.
*/
SQLITE_WASM_EXPORT
-int sqlite3_wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){
+int sqlite3__wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){
switch(op){
case SQLITE_DBCONFIG_ENABLE_FKEY:
case SQLITE_DBCONFIG_ENABLE_TRIGGER:
@@ -1613,7 +1613,7 @@ int sqlite3_wasm_db_config_ip(sqlite3 *pDb, int op, int arg1, int* pArg2){
** (void*,int,int) variadic args.
*/
SQLITE_WASM_EXPORT
-int sqlite3_wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int arg3){
+int sqlite3__wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int arg3){
switch(op){
case SQLITE_DBCONFIG_LOOKASIDE:
return sqlite3_db_config(pDb, op, pArg1, arg2, arg3);
@@ -1629,7 +1629,7 @@ int sqlite3_wasm_db_config_pii(sqlite3 *pDb, int op, void * pArg1, int arg2, int
** (const char *) variadic args.
*/
SQLITE_WASM_EXPORT
-int sqlite3_wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){
+int sqlite3__wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){
switch(op){
case SQLITE_DBCONFIG_MAINDBNAME:
return sqlite3_db_config(pDb, op, zArg);
@@ -1646,7 +1646,7 @@ int sqlite3_wasm_db_config_s(sqlite3 *pDb, int op, const char *zArg){
** a single integer argument.
*/
SQLITE_WASM_EXPORT
-int sqlite3_wasm_config_i(int op, int arg){
+int sqlite3__wasm_config_i(int op, int arg){
return sqlite3_config(op, arg);
}
@@ -1658,7 +1658,7 @@ int sqlite3_wasm_config_i(int op, int arg){
** two int arguments.
*/
SQLITE_WASM_EXPORT
-int sqlite3_wasm_config_ii(int op, int arg1, int arg2){
+int sqlite3__wasm_config_ii(int op, int arg1, int arg2){
return sqlite3_config(op, arg1, arg2);
}
@@ -1670,7 +1670,7 @@ int sqlite3_wasm_config_ii(int op, int arg1, int arg2){
** a single i64 argument.
*/
SQLITE_WASM_EXPORT
-int sqlite3_wasm_config_j(int op, sqlite3_int64 arg){
+int sqlite3__wasm_config_j(int op, sqlite3_int64 arg){
return sqlite3_config(op, arg);
}
@@ -1689,17 +1689,17 @@ int sqlite3_wasm_config_j(int op, sqlite3_int64 arg){
**
** ```
** sqlite3.wasm.functionEntry(
-** sqlite3.wasm.exports.sqlite3_wasm_ptr_to_sqlite3_free()
+** sqlite3.wasm.exports.sqlite3__wasm_ptr_to_sqlite3_free()
** ) === sqlite3.wasm.exports.sqlite3_free
** ```
**
** Using a function to return this pointer, as opposed to exporting it
-** via sqlite3_wasm_enum_json(), is an attempt to work around a
+** via sqlite3__wasm_enum_json(), is an attempt to work around a
** Safari-specific quirk covered at
** https://sqlite.org/forum/info/e5b20e1feb37a19a.
**/
SQLITE_WASM_EXPORT
-void * sqlite3_wasm_ptr_to_sqlite3_free(void){
+void * sqlite3__wasm_ptr_to_sqlite3_free(void){
return (void*)sqlite3_free;
}
#endif
@@ -1729,7 +1729,7 @@ void * sqlite3_wasm_ptr_to_sqlite3_free(void){
** defined, SQLITE_NOTFOUND is returned without side effects.
*/
SQLITE_WASM_EXPORT
-int sqlite3_wasm_init_wasmfs(const char *zMountPoint){
+int sqlite3__wasm_init_wasmfs(const char *zMountPoint){
static backend_t pOpfs = 0;
if( !zMountPoint || !*zMountPoint ) zMountPoint = "/opfs";
if( !pOpfs ){
@@ -1749,7 +1749,7 @@ int sqlite3_wasm_init_wasmfs(const char *zMountPoint){
}
#else
SQLITE_WASM_EXPORT
-int sqlite3_wasm_init_wasmfs(const char *zUnused){
+int sqlite3__wasm_init_wasmfs(const char *zUnused){
//emscripten_console_warn("WASMFS OPFS is not compiled in.");
if(zUnused){/*unused*/}
return SQLITE_NOTFOUND;
@@ -1759,51 +1759,51 @@ int sqlite3_wasm_init_wasmfs(const char *zUnused){
#if SQLITE_WASM_TESTS
SQLITE_WASM_EXPORT
-int sqlite3_wasm_test_intptr(int * p){
+int sqlite3__wasm_test_intptr(int * p){
return *p = *p * 2;
}
SQLITE_WASM_EXPORT
-void * sqlite3_wasm_test_voidptr(void * p){
+void * sqlite3__wasm_test_voidptr(void * p){
return p;
}
SQLITE_WASM_EXPORT
-int64_t sqlite3_wasm_test_int64_max(void){
+int64_t sqlite3__wasm_test_int64_max(void){
return (int64_t)0x7fffffffffffffff;
}
SQLITE_WASM_EXPORT
-int64_t sqlite3_wasm_test_int64_min(void){
- return ~sqlite3_wasm_test_int64_max();
+int64_t sqlite3__wasm_test_int64_min(void){
+ return ~sqlite3__wasm_test_int64_max();
}
SQLITE_WASM_EXPORT
-int64_t sqlite3_wasm_test_int64_times2(int64_t x){
+int64_t sqlite3__wasm_test_int64_times2(int64_t x){
return x * 2;
}
SQLITE_WASM_EXPORT
-void sqlite3_wasm_test_int64_minmax(int64_t * min, int64_t *max){
- *max = sqlite3_wasm_test_int64_max();
- *min = sqlite3_wasm_test_int64_min();
+void sqlite3__wasm_test_int64_minmax(int64_t * min, int64_t *max){
+ *max = sqlite3__wasm_test_int64_max();
+ *min = sqlite3__wasm_test_int64_min();
/*printf("minmax: min=%lld, max=%lld\n", *min, *max);*/
}
SQLITE_WASM_EXPORT
-int64_t sqlite3_wasm_test_int64ptr(int64_t * p){
- /*printf("sqlite3_wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/
+int64_t sqlite3__wasm_test_int64ptr(int64_t * p){
+ /*printf("sqlite3__wasm_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/
return *p = *p * 2;
}
SQLITE_WASM_EXPORT
-void sqlite3_wasm_test_stack_overflow(int recurse){
- if(recurse) sqlite3_wasm_test_stack_overflow(recurse);
+void sqlite3__wasm_test_stack_overflow(int recurse){
+ if(recurse) sqlite3__wasm_test_stack_overflow(recurse);
}
/* For testing the 'string:dealloc' whwasmutil.xWrap() conversion. */
SQLITE_WASM_EXPORT
-char * sqlite3_wasm_test_str_hello(int fail){
+char * sqlite3__wasm_test_str_hello(int fail){
char * s = fail ? 0 : (char *)sqlite3_malloc(6);
if(s){
memcpy(s, "hello", 5);
@@ -1838,12 +1838,12 @@ char * sqlite3_wasm_test_str_hello(int fail){
** 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){
+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;
+ static const recurse_f recurse = sqlite3__wasm_SQLTester_strnotglob;
while( (c = (*(zGlob++)))!=0 ){
if( c=='*' ){
@@ -1918,11 +1918,10 @@ static int sqlite3_wasm_SQLTester_strnotglob(const char *zGlob, const char *z){
}
SQLITE_WASM_EXPORT
-int sqlite3_wasm_SQLTester_strglob(const char *zGlob, const char *z){
- return !sqlite3_wasm_SQLTester_strnotglob(zGlob, z);
+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 --git a/ext/wasm/demo-worker1-promiser.html b/ext/wasm/demo-worker1-promiser.c-pp.html
similarity index 86%
rename from ext/wasm/demo-worker1-promiser.html
rename to ext/wasm/demo-worker1-promiser.c-pp.html
index e99131e6c9..e0b487bdf3 100644
--- a/ext/wasm/demo-worker1-promiser.html
+++ b/ext/wasm/demo-worker1-promiser.c-pp.html
@@ -6,7 +6,11 @@
+//#if target=es6-module
+ worker-promise (via ESM) tests
+//#else
worker-promise tests
+//#endif
worker-promise tests
@@ -22,13 +26,17 @@
Downloading...
-
+
Most stuff on this page happens in the dev console.