1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-08-01 06:27:03 +03:00

Merge trunk into js-cpp branch.

FossilOrigin-Name: e047b33d1fb7d6a32e967f03f9952249cd2da4d21dc301fe92bd7baa0da5d6a9
This commit is contained in:
stephan
2022-11-17 15:21:49 +00:00
57 changed files with 648 additions and 326 deletions

View File

@ -6278,6 +6278,7 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){
/* If this is a new term, query for it. Update cksum3 with the results. */
fts5TestTerm(p, &term, z, n, cksum2, &cksum3);
if( p->rc ) break;
if( eDetail==FTS5_DETAIL_NONE ){
if( 0==fts5MultiIterIsEmpty(p, pIter) ){

54
ext/rbu/rburename.test Normal file
View File

@ -0,0 +1,54 @@
# 2022 November 07
#
# 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]] rbu_common.tcl]
set ::testprefix rburename
do_execsql_test 1.0 {
CREATE TABLE t1(a, b);
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t1 VALUES(3, 4);
INSERT INTO t1 VALUES(5, 6);
}
forcedelete test.db-vacuum
proc my_rename {old new} {
lappend ::my_rename_calls [list [file tail $old] [file tail $new]]
file rename $old $new
}
do_test 1.1 {
sqlite3rbu_vacuum rbu test.db
rbu rename_handler my_rename
while {[rbu step]=="SQLITE_OK"} {}
rbu close
} SQLITE_DONE
do_test 1.2 {
set ::my_rename_calls
} {{test.db-oal test.db-wal}}
proc my_rename {old new} {
error "something went wrong"
}
do_test 1.3 {
sqlite3rbu_vacuum rbu test.db
rbu rename_handler my_rename
while {[rbu step]=="SQLITE_OK"} {}
list [catch { rbu close } msg] $msg
} {1 SQLITE_IOERR}
finish_test

View File

@ -227,10 +227,11 @@ do_test 6.1 {
rbu close
} {SQLITE_OK}
do_execsql_test 6.2 {
SELECT 1 FROM sqlite_master LIMIT 1;
PRAGMA wal_checkpoint;
} {1 0 4 4}
do_test 6.2 {
execsql { SELECT 1 FROM sqlite_master LIMIT 1 }
execsql { PRAGMA wal_checkpoint }
execsql { SELECT 1 FROM sqlite_master LIMIT 1 }
} {1}
do_test 6.3 {
sqlite3rbu_vacuum rbu test.db test.db2

View File

@ -393,6 +393,8 @@ struct sqlite3rbu {
int nPagePerSector; /* Pages per sector for pTargetFd */
i64 iOalSz;
i64 nPhaseOneStep;
void *pRenameArg;
int (*xRename)(void*, const char*, const char*);
/* The following state variables are used as part of the incremental
** checkpoint stage (eStage==RBU_STAGE_CKPT). See comments surrounding
@ -2781,7 +2783,7 @@ static void rbuOpenDatabase(sqlite3rbu *p, sqlite3 *dbMain, int *pbRetry){
sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p);
if( p->zState==0 ){
const char *zFile = sqlite3_db_filename(p->dbRbu, "main");
p->zState = rbuMPrintf(p, "file://%s-vacuum?modeof=%s", zFile, zFile);
p->zState = rbuMPrintf(p, "file:///%s-vacuum?modeof=%s", zFile, zFile);
}
}
@ -3241,32 +3243,7 @@ static void rbuMoveOalFile(sqlite3rbu *p){
}
if( p->rc==SQLITE_OK ){
#if defined(_WIN32_WCE)
{
LPWSTR zWideOal;
LPWSTR zWideWal;
zWideOal = rbuWinUtf8ToUnicode(zOal);
if( zWideOal ){
zWideWal = rbuWinUtf8ToUnicode(zWal);
if( zWideWal ){
if( MoveFileW(zWideOal, zWideWal) ){
p->rc = SQLITE_OK;
}else{
p->rc = SQLITE_IOERR;
}
sqlite3_free(zWideWal);
}else{
p->rc = SQLITE_IOERR_NOMEM;
}
sqlite3_free(zWideOal);
}else{
p->rc = SQLITE_IOERR_NOMEM;
}
}
#else
p->rc = rename(zOal, zWal) ? SQLITE_IOERR : SQLITE_OK;
#endif
p->rc = p->xRename(p->pRenameArg, zOal, zWal);
}
if( p->rc!=SQLITE_OK
@ -4005,6 +3982,7 @@ static sqlite3rbu *openRbuHandle(
/* Create the custom VFS. */
memset(p, 0, sizeof(sqlite3rbu));
sqlite3rbu_rename_handler(p, 0, 0);
rbuCreateVfs(p);
/* Open the target, RBU and state databases */
@ -4396,6 +4374,54 @@ int sqlite3rbu_savestate(sqlite3rbu *p){
return rc;
}
/*
** Default xRename callback for RBU.
*/
static int xDefaultRename(void *pArg, const char *zOld, const char *zNew){
int rc = SQLITE_OK;
#if defined(_WIN32_WCE)
{
LPWSTR zWideOld;
LPWSTR zWideNew;
zWideOld = rbuWinUtf8ToUnicode(zOld);
if( zWideOld ){
zWideNew = rbuWinUtf8ToUnicode(zNew);
if( zWideNew ){
if( MoveFileW(zWideOld, zWideNew) ){
rc = SQLITE_OK;
}else{
rc = SQLITE_IOERR;
}
sqlite3_free(zWideNew);
}else{
rc = SQLITE_IOERR_NOMEM;
}
sqlite3_free(zWideOld);
}else{
rc = SQLITE_IOERR_NOMEM;
}
}
#else
rc = rename(zOld, zNew) ? SQLITE_IOERR : SQLITE_OK;
#endif
return rc;
}
void sqlite3rbu_rename_handler(
sqlite3rbu *pRbu,
void *pArg,
int (*xRename)(void *pArg, const char *zOld, const char *zNew)
){
if( xRename ){
pRbu->xRename = xRename;
pRbu->pRenameArg = pArg;
}else{
pRbu->xRename = xDefaultRename;
pRbu->pRenameArg = 0;
}
}
/**************************************************************************
** Beginning of RBU VFS shim methods. The VFS shim modifies the behaviour
** of a standard VFS in the following ways:

View File

@ -544,6 +544,34 @@ SQLITE_API void sqlite3rbu_bp_progress(sqlite3rbu *pRbu, int *pnOne, int*pnTwo);
SQLITE_API int sqlite3rbu_state(sqlite3rbu *pRbu);
/*
** As part of applying an RBU update or performing an RBU vacuum operation,
** the system must at one point move the *-oal file to the equivalent *-wal
** path. Normally, it does this by invoking POSIX function rename(2) directly.
** Except on WINCE platforms, where it uses win32 API MoveFileW(). This
** function may be used to register a callback that the RBU module will invoke
** instead of one of these APIs.
**
** If a callback is registered with an RBU handle, it invokes it instead
** of rename(2) when it needs to move a file within the file-system. The
** first argument passed to the xRename() callback is a copy of the second
** argument (pArg) passed to this function. The second is the full path
** to the file to move and the third the full path to which it should be
** moved. The callback function should return SQLITE_OK to indicate
** success. If an error occurs, it should return an SQLite error code.
** In this case the RBU operation will be abandoned and the error returned
** to the RBU user.
**
** Passing a NULL pointer in place of the xRename argument to this function
** restores the default behaviour.
*/
SQLITE_API void sqlite3rbu_rename_handler(
sqlite3rbu *pRbu,
void *pArg,
int (*xRename)(void *pArg, const char *zOld, const char *zNew)
);
/*
** Create an RBU VFS named zName that accesses the underlying file-system
** via existing VFS zParent. Or, if the zParent parameter is passed NULL,

View File

@ -26,6 +26,14 @@
# endif
#endif
#include <assert.h>
#include <string.h>
typedef struct TestRbu TestRbu;
struct TestRbu {
sqlite3rbu *pRbu;
Tcl_Interp *interp;
Tcl_Obj *xRename;
};
/* From main.c */
extern const char *sqlite3ErrName(int);
@ -55,6 +63,20 @@ void test_rbu_delta(sqlite3_context *pCtx, int nArg, sqlite3_value **apVal){
Tcl_DecrRefCount(pScript);
}
static int xRenameCallback(void *pArg, const char *zOld, const char *zNew){
int rc = SQLITE_OK;
TestRbu *pTest = (TestRbu*)pArg;
Tcl_Obj *pEval = Tcl_DuplicateObj(pTest->xRename);
Tcl_IncrRefCount(pEval);
Tcl_ListObjAppendElement(pTest->interp, pEval, Tcl_NewStringObj(zOld, -1));
Tcl_ListObjAppendElement(pTest->interp, pEval, Tcl_NewStringObj(zNew, -1));
rc = Tcl_EvalObjEx(pTest->interp, pEval, TCL_GLOBAL_ONLY);
Tcl_DecrRefCount(pEval);
return rc ? SQLITE_IOERR : SQLITE_OK;
}
static int SQLITE_TCLAPI test_sqlite3rbu_cmd(
ClientData clientData,
@ -63,7 +85,8 @@ static int SQLITE_TCLAPI test_sqlite3rbu_cmd(
Tcl_Obj *CONST objv[]
){
int ret = TCL_OK;
sqlite3rbu *pRbu = (sqlite3rbu*)clientData;
TestRbu *pTest = (TestRbu*)clientData;
sqlite3rbu *pRbu = pTest->pRbu;
struct RbuCmd {
const char *zName;
int nArg;
@ -82,6 +105,7 @@ static int SQLITE_TCLAPI test_sqlite3rbu_cmd(
{"temp_size_limit", 3, "LIMIT"}, /* 10 */
{"temp_size", 2, ""}, /* 11 */
{"dbRbu_eval", 3, "SQL"}, /* 12 */
{"rename_handler", 3, "SCRIPT"},/* 13 */
{0,0,0}
};
int iCmd;
@ -127,6 +151,8 @@ static int SQLITE_TCLAPI test_sqlite3rbu_cmd(
}
ret = TCL_ERROR;
}
if( pTest->xRename ) Tcl_DecrRefCount(pTest->xRename);
ckfree(pTest);
break;
}
@ -214,6 +240,19 @@ static int SQLITE_TCLAPI test_sqlite3rbu_cmd(
break;
}
case 13: /* rename_handler */ {
Tcl_Obj *pScript = objv[2];
assert( !sqlite3_stricmp(aCmd[13].zName, "rename_handler") );
if( Tcl_GetCharLength(pScript)==0 ){
sqlite3rbu_rename_handler(pRbu, 0, 0);
}else{
pTest->xRename = Tcl_DuplicateObj(pScript);
Tcl_IncrRefCount(pTest->xRename);
sqlite3rbu_rename_handler(pRbu, pTest, xRenameCallback);
}
break;
}
default: /* seems unlikely */
assert( !"cannot happen" );
break;
@ -222,6 +261,18 @@ static int SQLITE_TCLAPI test_sqlite3rbu_cmd(
return ret;
}
static void createRbuWrapper(
Tcl_Interp *interp,
const char *zCmd,
sqlite3rbu *pRbu
){
TestRbu *pTest = (TestRbu*)ckalloc(sizeof(TestRbu));
memset(pTest, 0, sizeof(TestRbu));
pTest->pRbu = pRbu;
pTest->interp = interp;
Tcl_CreateObjCommand(interp, zCmd, test_sqlite3rbu_cmd, (ClientData)pTest, 0);
}
/*
** Tclcmd: sqlite3rbu CMD <target-db> <rbu-db> ?<state-db>?
*/
@ -247,7 +298,7 @@ static int SQLITE_TCLAPI test_sqlite3rbu(
if( objc==5 ) zStateDb = Tcl_GetString(objv[4]);
pRbu = sqlite3rbu_open(zTarget, zRbu, zStateDb);
Tcl_CreateObjCommand(interp, zCmd, test_sqlite3rbu_cmd, (ClientData)pRbu, 0);
createRbuWrapper(interp, zCmd, pRbu);
Tcl_SetObjResult(interp, objv[1]);
return TCL_OK;
}
@ -276,7 +327,7 @@ static int SQLITE_TCLAPI test_sqlite3rbu_vacuum(
if( zStateDb && zStateDb[0]=='\0' ) zStateDb = 0;
pRbu = sqlite3rbu_vacuum(zTarget, zStateDb);
Tcl_CreateObjCommand(interp, zCmd, test_sqlite3rbu_cmd, (ClientData)pRbu, 0);
createRbuWrapper(interp, zCmd, pRbu);
Tcl_SetObjResult(interp, objv[1]);
return TCL_OK;
}

View File

@ -71,6 +71,7 @@
** It contains one entry for each b-tree pointer between a parent and
** child page in the database.
*/
#if !defined(SQLITEINT_H)
#include "sqlite3ext.h"
@ -82,6 +83,8 @@ SQLITE_EXTENSION_INIT1
#include <string.h>
#include <assert.h>
#ifndef SQLITE_OMIT_VIRTUALTABLE
#define DBDATA_PADDING_BYTES 100
typedef struct DbdataTable DbdataTable;
@ -425,7 +428,7 @@ static void dbdataValue(
u32 enc,
int eType,
u8 *pData,
int nData
sqlite3_int64 nData
){
if( eType>=0 && dbdataValueBytes(eType)<=nData ){
switch( eType ){
@ -861,7 +864,7 @@ static int dbdataColumn(
case DBDATA_COLUMN_VALUE: {
if( pCsr->iField<0 ){
sqlite3_result_int64(ctx, pCsr->iIntkey);
}else{
}else if( &pCsr->pRec[pCsr->nRec] >= pCsr->pPtr ){
sqlite3_int64 iType;
dbdataGetVarintU32(pCsr->pHdrPtr, &iType);
dbdataValue(
@ -935,3 +938,5 @@ int sqlite3_dbdata_init(
SQLITE_EXTENSION_INIT2(pApi);
return sqlite3DbdataRegister(db);
}
#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */

View File

@ -10,16 +10,9 @@
#***********************************************************************
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source [file join [file dirname [info script]] recover_common.tcl]
source $testdir/tester.tcl
set testprefix recover1
proc compare_result {db1 db2 sql} {
set r1 [$db1 eval $sql]
set r2 [$db2 eval $sql]
@ -271,11 +264,13 @@ do_recover_test 14
#-------------------------------------------------------------------------
reset_db
do_execsql_test 15.1 {
execsql {
PRAGMA journal_mode=OFF;
PRAGMA mmap_size=10;
}
do_execsql_test 15.1 {
CREATE TABLE t1(x);
} {off 10}
} {}
do_recover_test 15
finish_test

View File

@ -1,5 +1,14 @@
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source $testdir/tester.tcl
if {[info commands sqlite3_recover_init]==""} {
finish_test
return -code return
}

View File

@ -12,17 +12,9 @@
# Tests for the SQLITE_RECOVER_ROWIDS option.
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source [file join [file dirname [info script]] recover_common.tcl]
source $testdir/tester.tcl
set testprefix recoverclobber
ifcapable !vtab {
finish_test; return
}
proc recover {db output} {
set R [sqlite3_recover_init db main test.db2]
$R run

View File

@ -10,12 +10,7 @@
#***********************************************************************
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source [file join [file dirname [info script]] recover_common.tcl]
source $testdir/tester.tcl
set testprefix recovercorrupt
database_may_be_corrupt

View File

@ -10,12 +10,7 @@
#***********************************************************************
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source [file join [file dirname [info script]] recover_common.tcl]
source $testdir/tester.tcl
set testprefix recovercorrupt2
do_execsql_test 1.0 {

View File

@ -10,12 +10,7 @@
#***********************************************************************
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source [file join [file dirname [info script]] recover_common.tcl]
source $testdir/tester.tcl
set testprefix recoverfault

View File

@ -10,12 +10,7 @@
#***********************************************************************
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source [file join [file dirname [info script]] recover_common.tcl]
source $testdir/tester.tcl
set testprefix recoverfault2

View File

@ -11,17 +11,9 @@
#
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source [file join [file dirname [info script]] recover_common.tcl]
source $testdir/tester.tcl
set testprefix recoverold
ifcapable !vtab {
finish_test; return
}
proc compare_result {db1 db2 sql} {
set r1 [$db1 eval $sql]
set r2 [$db2 eval $sql]
@ -71,7 +63,10 @@ proc do_recover_test {tn {tsql {}} {res {}}} {
sqlite3 db2 test.db2
db2 eval [join $::sqlhook ";"]
db cache flush
if {$tsql==""} {
compare_dbs db db2
uplevel [list do_test $tn.sql [list compare_dbs db db2] {}]
} else {
uplevel [list do_execsql_test -db db2 $tn.sql $tsql $res]
@ -155,7 +150,6 @@ do_recover_test 2.4.1 {
2 2 3 {} 8 9 7
}
breakpoint
do_execsql_test 2.5 {
CREATE TABLE x1(a, b, c);
WITH s(i) AS (
@ -173,17 +167,19 @@ do_recover_test 2.5.1 {
2 2 3 {} 8 9 7
}
do_test 2.6 {
forcedelete test.db2
set R [sqlite3_recover_init db main test.db2]
$R config lostandfound lost_and_found
$R config freelistcorrupt 1
$R run
$R finish
sqlite3 db2 test.db2
execsql { SELECT count(*) FROM lost_and_found_1; } db2
} {103}
db2 close
ifcapable !secure_delete {
do_test 2.6 {
forcedelete test.db2
set R [sqlite3_recover_init db main test.db2]
$R config lostandfound lost_and_found
$R config freelistcorrupt 1
$R run
$R finish
sqlite3 db2 test.db2
execsql { SELECT count(*) FROM lost_and_found_1; } db2
} {103}
db2 close
}
#-------------------------------------------------------------------------
breakpoint

View File

@ -10,11 +10,7 @@
#***********************************************************************
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source [file join [file dirname [info script]] recover_common.tcl]
source $testdir/tester.tcl
db close
sqlite3_test_control_pending_byte 0x1000000
@ -27,6 +23,7 @@ foreach {pgsz bOverflow} {
} {
reset_db
execsql "PRAGMA page_size = $pgsz"
execsql "PRAGMA auto_vacuum = 0"
do_execsql_test 1.$pgsz.$bOverflow.1 {
CREATE TABLE t1(a, b, c);
CREATE INDEX i1 ON t1(b, a, c);

View File

@ -12,17 +12,9 @@
# Tests for the SQLITE_RECOVER_ROWIDS option.
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source [file join [file dirname [info script]] recover_common.tcl]
source $testdir/tester.tcl
set testprefix recoverrowid
ifcapable !vtab {
finish_test; return
}
proc recover {db bRowids output} {
forcedelete $output

View File

@ -12,18 +12,11 @@
# Tests for the SQLITE_RECOVER_SLOWINDEXES option.
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source [file join [file dirname [info script]] recover_common.tcl]
source $testdir/tester.tcl
set testprefix recoverslowidx
ifcapable !vtab {
finish_test; return
}
do_execsql_test 1.0 {
PRAGMA auto_vacuum = 0;
CREATE TABLE t1(a, b);
CREATE INDEX i1 ON t1(a);
INSERT INTO t1 VALUES(1, 1), (2, 2), (3, 3), (4, 4);

View File

@ -11,17 +11,9 @@
#
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source [file join [file dirname [info script]] recover_common.tcl]
source $testdir/tester.tcl
set testprefix recoversql
ifcapable !vtab {
finish_test; return
}
do_execsql_test 1.0 {
CREATE TABLE "x.1" (x, y);
INSERT INTO "x.1" VALUES(1, 1), (2, 2), (3, 3);

View File

@ -17,6 +17,8 @@
#include <assert.h>
#include <string.h>
#ifndef SQLITE_OMIT_VIRTUALTABLE
/*
** Declaration for public API function in file dbdata.c. This may be called
** with NULL as the final two arguments to register the sqlite_dbptr and
@ -280,10 +282,16 @@ static RecoverGlobal recover_g;
*/
#define RECOVER_ROWID_DEFAULT 1
/*
** Mutex handling:
**
** recoverEnterMutex() - Enter the recovery mutex
** recoverLeaveMutex() - Leave the recovery mutex
** recoverAssertMutexHeld() - Assert that the recovery mutex is held
*/
#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0
# define recoverEnterMutex()
# define recoverLeaveMutex()
# define recoverAssertMutexHeld()
#else
static void recoverEnterMutex(void){
sqlite3_mutex_enter(sqlite3_mutex_alloc(RECOVER_MUTEX_ID));
@ -291,9 +299,13 @@ static void recoverEnterMutex(void){
static void recoverLeaveMutex(void){
sqlite3_mutex_leave(sqlite3_mutex_alloc(RECOVER_MUTEX_ID));
}
#endif
#if SQLITE_THREADSAFE+0>=1 && defined(SQLITE_DEBUG)
static void recoverAssertMutexHeld(void){
assert( sqlite3_mutex_held(sqlite3_mutex_alloc(RECOVER_MUTEX_ID)) );
}
#else
# define recoverAssertMutexHeld()
#endif
@ -301,11 +313,8 @@ static void recoverAssertMutexHeld(void){
** Like strlen(). But handles NULL pointer arguments.
*/
static int recoverStrlen(const char *zStr){
int nRet = 0;
if( zStr ){
while( zStr[nRet] ) nRet++;
}
return nRet;
if( zStr==0 ) return 0;
return (int)(strlen(zStr)&0x7fffffff);
}
/*
@ -2844,3 +2853,5 @@ int sqlite3_recover_finish(sqlite3_recover *p){
}
return rc;
}
#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */

View File

@ -18,6 +18,8 @@
#include <tcl.h>
#include <assert.h>
#ifndef SQLITE_OMIT_VIRTUALTABLE
typedef struct TestRecover TestRecover;
struct TestRecover {
sqlite3_recover *p;
@ -284,9 +286,10 @@ static int test_sqlite3_dbdata_init(
return TCL_OK;
}
#endif /* SQLITE_OMIT_VIRTUALTABLE */
int TestRecover_Init(Tcl_Interp *interp){
#ifndef SQLITE_OMIT_VIRTUALTABLE
struct Cmd {
const char *zCmd;
Tcl_ObjCmdProc *xProc;
@ -302,7 +305,7 @@ int TestRecover_Init(Tcl_Interp *interp){
struct Cmd *p = &aCmd[i];
Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, p->pArg, 0);
}
#endif
return TCL_OK;
}

View File

@ -110,7 +110,7 @@ emcc.WASM_BIGINT ?= 1
sqlite3.c := $(dir.top)/sqlite3.c
sqlite3.h := $(dir.top)/sqlite3.h
SQLITE_OPT = \
-DSQLITE_ENABLE_FTS4 \
-DSQLITE_ENABLE_FTS5 \
-DSQLITE_ENABLE_RTREE \
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
-DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION \
@ -465,7 +465,20 @@ emcc.jsflags += -sWASM_BIGINT=$(emcc.WASM_BIGINT)
# debugging info, _huge_.
########################################################################
sqlite3.js := $(dir.dout)/sqlite3.js
########################################################################
# AN EXPERIMENT: undocumented Emscripten feature: if the target file
# extension is "mjs", it defaults to ES6 module builds:
# https://github.com/emscripten-core/emscripten/issues/14383
ifeq (,$(filter esm,$(MAKECMDGOALS)))
sqlite3.js.ext := js
else
esm.deps := $(filter-out esm,$(MAKECMDGOALS))
esm: $(if $(esm.deps),$(esm.deps),all)
sqlite3.js.ext := mjs
endif
# /esm
########################################################################
sqlite3.js := $(dir.dout)/sqlite3.$(sqlite3.js.ext)
sqlite3.wasm := $(dir.dout)/sqlite3.wasm
sqlite3-wasm.c := $(dir.api)/sqlite3-wasm.c
# sqlite3-wasm.o vs sqlite3-wasm.c: building against the latter

View File

@ -15,7 +15,10 @@
impls which Emscripten installs at some point in the file above
this.
*/
const originalInit = self.sqlite3InitModule;
const originalInit =
/*Maintenance reminde: DO NOT use `self.` here. It's correct
for non-ES6 Module cases but wrong for ES6 modules because those
resolve this symbol differently! */ sqlite3InitModule;
if(!originalInit){
throw new Error("Expecting self.sqlite3InitModule to be defined by the Emscripten build.");
}

View File

@ -472,9 +472,11 @@ const installOpfsVfs = function callee(options){
/**
Returns an array of the deserialized state stored by the most
recent serialize() operation (from from this thread or the
counterpart thread), or null if the serialization buffer is empty.
counterpart thread), or null if the serialization buffer is
empty. If passed a truthy argument, the serialization buffer
is cleared after deserialization.
*/
state.s11n.deserialize = function(){
state.s11n.deserialize = function(clear=false){
++metrics.s11n.deserialize.count;
const t = performance.now();
const argc = viewU8[0];
@ -499,6 +501,7 @@ const installOpfsVfs = function callee(options){
rc.push(v);
}
}
if(clear) viewU8[0] = 0;
//log("deserialize:",argc, rc);
metrics.s11n.deserialize.time += performance.now() - t;
return rc;

View File

@ -17,22 +17,29 @@
conventions, and build process are very much under construction and
will be (re)documented once they've stopped fluctuating so much.
Specific goals of this project:
Project home page: https://sqlite.org
Documentation home page: https://sqlite.org/wasm
Specific goals of this subproject:
- Except where noted in the non-goals, provide a more-or-less
feature-complete wrapper to the sqlite3 C API, insofar as WASM
feature parity with C allows for. In fact, provide at least 3
feature parity with C allows for. In fact, provide at least 4
APIs...
1) Bind a low-level sqlite3 API which is as close to the native
one as feasible in terms of usage.
1) 1-to-1 bindings as exported from WASM, with no automatic
type conversions between JS and C.
2) A binding of (1) which provides certain JS/C type conversions
to greatly simplify its use.
2) A higher-level API, more akin to sql.js and node.js-style
3) A higher-level API, more akin to sql.js and node.js-style
implementations. This one speaks directly to the low-level
API. This API must be used from the same thread as the
low-level API.
3) A second higher-level API which speaks to the previous APIs via
4) A second higher-level API which speaks to the previous APIs via
worker messages. This one is intended for use in the main
thread, with the lower-level APIs installed in a Worker thread,
and talking to them via Worker messages. Because Workers are
@ -90,11 +97,13 @@
config object is only honored the first time this is
called. Subsequent calls ignore the argument and return the same
(configured) object which gets initialized by the first call.
This function will throw if any of the required config options are
missing.
The config object properties include:
- `exports`[^1]: the "exports" object for the current WASM
environment. In an Emscripten build, this should be set to
environment. In an Emscripten-based build, this should be set to
`Module['asm']`.
- `memory`[^1]: optional WebAssembly.Memory object, defaulting to
@ -104,7 +113,7 @@
WASM-exported memory.
- `bigIntEnabled`: true if BigInt support is enabled. Defaults to
true if self.BigInt64Array is available, else false. Some APIs
true if `self.BigInt64Array` is available, else false. Some APIs
will throw exceptions if called without BigInt support, as BigInt
is required for marshalling C-side int64 into and out of JS.
@ -116,10 +125,12 @@
the `free(3)`-compatible routine for the WASM
environment. Defaults to `"free"`.
- `wasmfsOpfsDir`[^1]: if the environment supports persistent storage, this
directory names the "mount point" for that directory. It must be prefixed
by `/` and may currently contain only a single directory-name part. Using
the root directory name is not supported by any current persistent backend.
- `wasmfsOpfsDir`[^1]: if the environment supports persistent
storage, this directory names the "mount point" for that
directory. It must be prefixed by `/` and may contain only a
single directory-name part. Using the root directory name is not
supported by any current persistent backend. This setting is
only used in WASMFS-enabled builds.
[^1] = This property may optionally be a function, in which case this
@ -388,8 +399,22 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
exceptions.
*/
class WasmAllocError extends Error {
/**
If called with 2 arguments and the 2nd one is an object, it
behaves like the Error constructor, else it concatenates all
arguments together with a single space between each to
construct an error message string. As a special case, if
called with no arguments then it uses a default error
message.
*/
constructor(...args){
super(...args);
if(2===args.length && 'object'===typeof args){
super(...args);
}else if(args.length){
super(args.join(' '));
}else{
super("Allocation failed.");
}
this.name = 'WasmAllocError';
}
};
@ -699,21 +724,33 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
API NOT throw and must instead return SQLITE_NOMEM (or
equivalent, depending on the context).
That said, very few cases in the API can result in
Very few cases in the sqlite3 JS APIs can result in
client-defined functions propagating exceptions via the C-style
API. Most notably, this applies ot User-defined SQL Functions
(UDFs) registered via sqlite3_create_function_v2(). For that
specific case it is recommended that all UDF creation be
funneled through a utility function and that a wrapper function
be added around the UDF which catches any exception and sets
the error state to OOM. (The overall complexity of registering
UDFs essentially requires a helper for doing so!)
API. Most notably, this applies to WASM-bound JS functions
which are created directly by clients and passed on _as WASM
function pointers_ to functions such as
sqlite3_create_function_v2(). Such bindings created
transparently by this API will automatically use wrappers which
catch exceptions and convert them to appropriate error codes.
For cases where non-throwing allocation is required, use
sqlite3.wasm.alloc.impl(), which is direct binding of the
underlying C-level allocator.
Design note: this function is not named "malloc" primarily
because Emscripten uses that name and we wanted to avoid any
confusion early on in this code's development, when it still
had close ties to Emscripten's glue code.
*/
alloc: undefined/*installed later*/,
/**
The API's one single point of access to the WASM-side memory
deallocator. Works like free(3) (and is likely bound to
free()).
Design note: this function is not named "free" for the same
reason that this.alloc() is not called this.malloc().
*/
dealloc: undefined/*installed later*/
@ -741,7 +778,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
wasm.allocFromTypedArray = function(srcTypedArray){
affirmBindableTypedArray(srcTypedArray);
const pRet = wasm.alloc(srcTypedArray.byteLength || 1);
wasm.heapForSize(srcTypedArray.constructor).set(srcTypedArray.byteLength ? srcTypedArray : [0], pRet);
wasm.heapForSize(srcTypedArray.constructor).set(
srcTypedArray.byteLength ? srcTypedArray : [0], pRet
);
return pRet;
};
@ -752,13 +791,13 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
if(!(f instanceof Function)) toss3("Missing required exports[",key,"] function.");
}
wasm.alloc = function(n){
const m = wasm.exports[keyAlloc](n);
if(!m) throw new WasmAllocError("Failed to allocate "+n+" bytes.");
wasm.alloc = function f(n){
const m = f.impl(n);
if(!m) throw new WasmAllocError("Failed to allocate",n," bytes.");
return m;
};
wasm.dealloc = (m)=>wasm.exports[keyDealloc](m);
wasm.alloc.impl = wasm.exports[keyAlloc];
wasm.dealloc = wasm.exports[keyDealloc];
/**
Reports info about compile-time options using

View File

@ -44,6 +44,7 @@ if(self.window === self){
this API.
*/
const state = Object.create(null);
/**
verbose:
@ -96,13 +97,27 @@ metrics.dump = ()=>{
};
/**
Map of sqlite3_file pointers (integers) to metadata related to a
given OPFS file handles. The pointers are, in this side of the
interface, opaque file handle IDs provided by the synchronous
part of this constellation. Each value is an object with a structure
demonstrated in the xOpen() impl.
__openFiles is a map of sqlite3_file pointers (integers) to
metadata related to a given OPFS file handles. The pointers are, in
this side of the interface, opaque file handle IDs provided by the
synchronous part of this constellation. Each value is an object
with a structure demonstrated in the xOpen() impl.
*/
const __openFiles = Object.create(null);
/**
__autoLocks is a Set of sqlite3_file pointers (integers) which were
"auto-locked". i.e. those for which we obtained a sync access
handle without an explicit xLock() call. Such locks will be
released during db connection idle time, whereas a sync access
handle obtained via xLock(), or subsequently xLock()'d after
auto-acquisition, will not be released until xUnlock() is called.
Maintenance reminder: if we relinquish auto-locks at the end of the
operation which acquires them, we pay a massive performance
penalty: speedtest1 benchmarks take up to 4x as long. By delaying
the lock release until idle time, the hit is negligible.
*/
const __autoLocks = new Set();
/**
Expects an OPFS file path. It gets resolved, such that ".."
@ -191,6 +206,10 @@ const getSyncHandle = async (fh)=>{
}
}
log("Got sync handle for",fh.filenameAbs,'in',performance.now() - t,'ms');
if(!fh.xLock){
__autoLocks.add(fh.fid);
log("Auto-locked",fh.fid,fh.filenameAbs);
}
}
return fh.syncHandle;
};
@ -210,10 +229,30 @@ const closeSyncHandle = async (fh)=>{
log("Closing sync handle for",fh.filenameAbs);
const h = fh.syncHandle;
delete fh.syncHandle;
delete fh.xLock;
__autoLocks.delete(fh.fid);
return h.close();
}
};
/**
A proxy for closeSyncHandle() which is guaranteed to not throw.
This function is part of a lock/unlock step in functions which
require a sync access handle but may be called without xLock()
having been called first. Such calls need to release that
handle to avoid locking the file for all of time. This is an
_attempt_ at reducing cross-tab contention but it may prove
to be more of a problem than a solution and may need to be
removed.
*/
const closeSyncHandleNoThrow = async (fh)=>{
try{await closeSyncHandle(fh)}
catch(e){
warn("closeSyncHandleNoThrow() ignoring:",e,fh);
}
};
/**
Stores the given value at state.sabOPView[state.opIds.rc] and then
Atomics.notify()'s it.
@ -342,9 +381,10 @@ const vfsAsyncImpls = {
xClose: async function(fid/*sqlite3_file pointer*/){
const opName = 'xClose';
mTimeStart(opName);
__autoLocks.delete(fid);
const fh = __openFiles[fid];
let rc = 0;
wTimeStart('xClose');
wTimeStart(opName);
if(fh){
delete __openFiles[fid];
await closeSyncHandle(fh);
@ -422,12 +462,17 @@ const vfsAsyncImpls = {
mTimeStart('xLock');
const fh = __openFiles[fid];
let rc = 0;
const oldLockType = fh.xLock;
fh.xLock = lockType;
if( !fh.syncHandle ){
wTimeStart('xLock');
try { await getSyncHandle(fh) }
catch(e){
try {
await getSyncHandle(fh);
__autoLocks.delete(fid);
}catch(e){
state.s11n.storeException(1,e);
rc = state.sq3Codes.SQLITE_IOERR_LOCK;
fh.xLock = oldLockType;
}
wTimeEnd();
}
@ -461,6 +506,7 @@ const vfsAsyncImpls = {
*/
wTimeEnd();
__openFiles[fid] = Object.assign(Object.create(null),{
fid: fid,
filenameAbs: filename,
filenamePart: filenamePart,
dirHandle: hDir,
@ -610,7 +656,7 @@ const initS11n = ()=>{
default: toss("Invalid type ID:",tid);
}
};
state.s11n.deserialize = function(){
state.s11n.deserialize = function(clear=false){
++metrics.s11n.deserialize.count;
const t = performance.now();
const argc = viewU8[0];
@ -635,6 +681,7 @@ const initS11n = ()=>{
rc.push(v);
}
}
if(clear) viewU8[0] = 0;
//log("deserialize:",argc, rc);
metrics.s11n.deserialize.time += performance.now() - t;
return rc;
@ -701,21 +748,30 @@ const waitLoop = async function f(){
We need to wake up periodically to give the thread a chance
to do other things.
*/
const waitTime = 1000;
const waitTime = 500;
while(!flagAsyncShutdown){
try {
if('timed-out'===Atomics.wait(
state.sabOPView, state.opIds.whichOp, 0, waitTime
)){
if(__autoLocks.size){
/* Release all auto-locks. */
for(const fid of __autoLocks){
const fh = __openFiles[fid];
await closeSyncHandleNoThrow(fh);
log("Auto-unlocked",fid,fh.filenameAbs);
}
}
continue;
}
const opId = Atomics.load(state.sabOPView, state.opIds.whichOp);
Atomics.store(state.sabOPView, state.opIds.whichOp, 0);
const hnd = opHandlers[opId] ?? toss("No waitLoop handler for whichOp #",opId);
const args = state.s11n.deserialize() || [];
state.s11n.serialize(/* clear s11n to keep the caller from
confusing this with an exception string
written by the upcoming operation */);
const args = state.s11n.deserialize(
true /* clear s11n to keep the caller from confusing this with
an exception string written by the upcoming
operation */
) || [];
//warn("waitLoop() whichOp =",opId, hnd, args);
if(hnd.f) await hnd.f(...args);
else error("Missing callback for opId",opId);

View File

@ -67,7 +67,7 @@
** larger cache benefits the larger workloads. Speed differences
** between 2x and nearly 3x have been measured with ample page cache.
*/
# define SQLITE_DEFAULT_CACHE_SIZE -16777216
# define SQLITE_DEFAULT_CACHE_SIZE -16384
#endif
#if 0 && !defined(SQLITE_DEFAULT_PAGE_SIZE)
/* TODO: experiment with this. */
@ -1108,9 +1108,6 @@ int sqlite3_wasm_init_wasmfs(const char *zMountPoint){
/** It's not enough to instantiate the backend. We have to create a
mountpoint in the VFS and attach the backend to it. */
if( pOpfs && 0!=access(zMountPoint, F_OK) ){
/* mkdir() simply hangs when called from fiddle app. Cause is
not yet determined but the hypothesis is an init-order
issue. */
/* Note that this check and is not robust but it will
hypothetically suffice for the transient wasm-based virtual
filesystem we're currently running in. */

View File

@ -189,7 +189,7 @@
<div id='list-compile-options' class='pseudolist wide2'></div>
</div><!-- .initially-hidden -->
<script src="jswasm/sqlite3.js">/* This tag MUST be in side the
<script src="jswasm/sqlite3.js">/* This tag MUST be inside the
fossil-doc block so that this part can work without modification in
the wasm docs repo. */</script>
<script>(async function(){

View File

@ -169,7 +169,11 @@
return str+a.join('&nbsp;');
};
const W = new Worker("speedtest1-worker.js?sqlite3.dir=jswasm");
const urlParams = new URL(self.location.href).searchParams;
const W = new Worker(
"speedtest1-worker.js?sqlite3.dir=jswasm"+
(urlParams.has('opfs-verbose') ? '&opfs-verbose' : '')
);
const mPost = function(msgType,payload){
W.postMessage({type: msgType, data: payload});
};
@ -179,7 +183,6 @@
const eLinkMainThread = E('#link-main-thread');
const eLinkWasmfs = E('#link-wasmfs');
const eLinkKvvfs = E('#link-kvvfs');
const urlParams = new URL(self.location.href).searchParams;
const getSelectedFlags = ()=>{
const f = Array.prototype.map.call(eFlags.selectedOptions, (v)=>v.value);
[

View File

@ -342,8 +342,20 @@
throw new sqlite3.WasmAllocError;
}catch(e){
T.assert(e instanceof Error)
.assert(e instanceof sqlite3.WasmAllocError);
.assert(e instanceof sqlite3.WasmAllocError)
.assert("Allocation failed." === e.message);
}
try {
throw new sqlite3.WasmAllocError("test",{
cause: 3
});
}catch(e){
T.assert(3 === e.cause)
.assert("test" === e.message);
}
try {throw new sqlite3.WasmAllocError("test","ing",".")}
catch(e){T.assert("test ing ." === e.message)}
try{ throw new sqlite3.SQLite3Error(capi.SQLITE_SCHEMA) }
catch(e){ T.assert('SQLITE_SCHEMA' === e.message) }
try{ sqlite3.SQLite3Error.toss(capi.SQLITE_CORRUPT,{cause: true}) }