diff options
Diffstat (limited to 'ext')
29 files changed, 1812 insertions, 830 deletions
diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 1c3053386..6345352c5 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -1986,9 +1986,9 @@ static void fts5SegIterSetNext(Fts5Index *p, Fts5SegIter *pIter){ ** leave an error in the Fts5Index object. */ static void fts5SegIterAllocTombstone(Fts5Index *p, Fts5SegIter *pIter){ - const int nTomb = pIter->pSeg->nPgTombstone; + const i64 nTomb = (i64)pIter->pSeg->nPgTombstone; if( nTomb>0 ){ - int nByte = SZ_FTS5TOMBSTONEARRAY(nTomb+1); + i64 nByte = SZ_FTS5TOMBSTONEARRAY(nTomb+1); Fts5TombstoneArray *pNew; pNew = (Fts5TombstoneArray*)sqlite3Fts5MallocZero(&p->rc, nByte); if( pNew ){ @@ -5847,7 +5847,7 @@ static Fts5Structure *fts5IndexOptimizeStruct( } nByte += (((i64)pStruct->nLevel)+1) * sizeof(Fts5StructureLevel); - assert( nByte==SZ_FTS5STRUCTURE(pStruct->nLevel+2) ); + assert( nByte==(i64)SZ_FTS5STRUCTURE(pStruct->nLevel+2) ); pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte); if( pNew ){ @@ -6822,10 +6822,13 @@ int sqlite3Fts5IndexRollback(Fts5Index *p){ */ int sqlite3Fts5IndexReinit(Fts5Index *p){ Fts5Structure *pTmp; - u8 tmpSpace[SZ_FTS5STRUCTURE(1)]; + union { + Fts5Structure sFts; + u8 tmpSpace[SZ_FTS5STRUCTURE(1)]; + } uFts; fts5StructureInvalidate(p); fts5IndexDiscardData(p); - pTmp = (Fts5Structure*)tmpSpace; + pTmp = &uFts.sFts; memset(pTmp, 0, SZ_FTS5STRUCTURE(1)); if( p->pConfig->bContentlessDelete ){ pTmp->nOriginCntr = 1; @@ -8286,19 +8289,27 @@ static int fts5TestUtf8(const char *z, int n){ /* ** This function is also purely an internal test. It does not contribute to ** FTS functionality, or even the integrity-check, in any way. +** +** This function sets output variable (*pbFail) to true if the test fails. Or +** leaves it unchanged if the test succeeds. */ static void fts5TestTerm( Fts5Index *p, Fts5Buffer *pPrev, /* Previous term */ const char *z, int n, /* Possibly new term to test */ u64 expected, - u64 *pCksum + u64 *pCksum, + int *pbFail ){ int rc = p->rc; if( pPrev->n==0 ){ fts5BufferSet(&rc, pPrev, n, (const u8*)z); }else - if( rc==SQLITE_OK && (pPrev->n!=n || memcmp(pPrev->p, z, n)) ){ + if( *pbFail==0 + && rc==SQLITE_OK + && (pPrev->n!=n || memcmp(pPrev->p, z, n)) + && (p->pHash==0 || p->pHash->nEntry==0) + ){ u64 cksum3 = *pCksum; const char *zTerm = (const char*)&pPrev->p[1]; /* term sans prefix-byte */ int nTerm = pPrev->n-1; /* Size of zTerm in bytes */ @@ -8348,7 +8359,7 @@ static void fts5TestTerm( fts5BufferSet(&rc, pPrev, n, (const u8*)z); if( rc==SQLITE_OK && cksum3!=expected ){ - rc = FTS5_CORRUPT; + *pbFail = 1; } *pCksum = cksum3; } @@ -8357,7 +8368,7 @@ static void fts5TestTerm( #else # define fts5TestDlidxReverse(x,y,z) -# define fts5TestTerm(u,v,w,x,y,z) +# define fts5TestTerm(t,u,v,w,x,y,z) #endif /* @@ -8615,6 +8626,7 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){ /* Used by extra internal tests only run if NDEBUG is not defined */ u64 cksum3 = 0; /* Checksum based on contents of indexes */ Fts5Buffer term = {0,0,0}; /* Buffer used to hold most recent term */ + int bTestFail = 0; #endif const int flags = FTS5INDEX_QUERY_NOOUTPUT; @@ -8657,7 +8669,7 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){ char *z = (char*)fts5MultiIterTerm(pIter, &n); /* If this is a new term, query for it. Update cksum3 with the results. */ - fts5TestTerm(p, &term, z, n, cksum2, &cksum3); + fts5TestTerm(p, &term, z, n, cksum2, &cksum3, &bTestFail); if( p->rc ) break; if( eDetail==FTS5_DETAIL_NONE ){ @@ -8675,7 +8687,7 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){ } } } - fts5TestTerm(p, &term, 0, 0, cksum2, &cksum3); + fts5TestTerm(p, &term, 0, 0, cksum2, &cksum3, &bTestFail); fts5MultiIterFree(pIter); if( p->rc==SQLITE_OK && bUseCksum && cksum!=cksum2 ){ @@ -8684,11 +8696,17 @@ int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum){ "fts5: checksum mismatch for table \"%s\"", p->pConfig->zName ); } - - fts5StructureRelease(pStruct); #ifdef SQLITE_DEBUG + /* In SQLITE_DEBUG builds, expensive extra checks were run as part of + ** the integrity-check above. If no other errors were detected, but one + ** of these tests failed, set the result to SQLITE_CORRUPT_VTAB here. */ + if( p->rc==SQLITE_OK && bTestFail ){ + p->rc = FTS5_CORRUPT; + } fts5BufferFree(&term); #endif + + fts5StructureRelease(pStruct); fts5BufferFree(&poslist); return fts5IndexReturn(p); } diff --git a/ext/fts5/test/fts5corrupt3.test b/ext/fts5/test/fts5corrupt3.test index 66acf07ee..eab4c3c91 100644 --- a/ext/fts5/test/fts5corrupt3.test +++ b/ext/fts5/test/fts5corrupt3.test @@ -6644,7 +6644,7 @@ do_test 48.0 { do_catchsql_test 48.1 { INSERT INTO t1(t1) VALUES('integrity-check'); -} {1 {database disk image is malformed}} +} {1 {fts5: corruption on page 1, segment 1, table "t1"}} #-------------------------------------------------------------------------- reset_db @@ -10106,7 +10106,7 @@ do_test 68.0 { do_catchsql_test 68.1 { PRAGMA reverse_unordered_selects=ON; INSERT INTO t1(t1) SELECT x FROM t2; -} {1 {database disk image is malformed}} +} {1 {fts5: corruption on page 1, segment 1, table "t1"}} #------------------------------------------------------------------------- reset_db @@ -16126,4 +16126,3 @@ do_catchsql_test 83.1 { sqlite3_fts5_may_be_corrupt 0 finish_test - diff --git a/ext/fts5/test/fts5corrupt8.test b/ext/fts5/test/fts5corrupt8.test index a43bbaa03..471a1b0e3 100644 --- a/ext/fts5/test/fts5corrupt8.test +++ b/ext/fts5/test/fts5corrupt8.test @@ -90,5 +90,58 @@ do_execsql_test 3.7 { SELECT * FROM sqlite_schema } +#------------------------------------------------------------------------- +reset_db + +proc hex_to_blob {hex} { + binary encode hex $hex +} +db func hex_to_blob hex_to_blob + +do_execsql_test 4.0 { + CREATE VIRTUAL TABLE x1 USING fts5(x, content='', contentless_delete=1); + BEGIN; + INSERT INTO x1(rowid, x) VALUES(1, 'a b c d e f g h'); + INSERT INTO x1(rowid, x) VALUES(2, 'a b c d e f g h'); + COMMIT; + DELETE FROM x1 WHERE rowid=1; +} + +do_execsql_test 4.1 { + SELECT hex(block) FROM x1_data WHERE id=10 +} { + 00000000FF00000101010200010101010101010102 +} + +do_execsql_test 4.2.1 { + UPDATE x1_data SET block= + X'00000000FF00000101010200010101010101819C9B95A8000102' + WHERE id=10; +} + +do_catchsql_test 4.2.2 { + SELECT * FROM x1('c d e'); +} {1 {out of memory}} + +do_execsql_test 4.3.1 { + UPDATE x1_data SET block= + X'00000000FF000001010102000101010101019282AFF9A0000102' + WHERE id=10; +} + +do_catchsql_test 4.3.2 { + SELECT * FROM x1('c d e'); +} {1 {out of memory}} + +do_execsql_test 4.4.1 { + UPDATE x1_data SET block= + X'00000000FF000001010102000101010101018181808080130102' + WHERE id=10; +} + +do_catchsql_test 4.3.2 { + SELECT * FROM x1('c d e'); +} {1 {out of memory}} + finish_test diff --git a/ext/fts5/test/fts5integrity.test b/ext/fts5/test/fts5integrity.test index 5c4002180..4bf120c44 100644 --- a/ext/fts5/test/fts5integrity.test +++ b/ext/fts5/test/fts5integrity.test @@ -37,6 +37,12 @@ do_execsql_test 2.1 { INSERT INTO yy(yy) VALUES('integrity-check'); } +db close +sqlite3 db test.db +do_execsql_test 2.1 { + INSERT INTO yy(yy) VALUES('integrity-check'); +} + #-------------------------------------------------------------------- # do_execsql_test 3.0 { diff --git a/ext/fts5/test/fts5leftjoin.test b/ext/fts5/test/fts5leftjoin.test index 4ef6a8961..69a172bd4 100644 --- a/ext/fts5/test/fts5leftjoin.test +++ b/ext/fts5/test/fts5leftjoin.test @@ -40,4 +40,53 @@ do_execsql_test 1.2 { SELECT * FROM t1 LEFT JOIN vt ON (vt MATCH 'abc') } {1 abc 2 abc} + +do_execsql_test 1.3 { + DELETE FROM t1; + INSERT INTO t1 VALUES(14); +} + +do_execsql_test 1.4 { + SELECT * FROM vt LEFT JOIN t1 ON vt.rowid = 1; +} { + abc 14 + xyz {} +} + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE t0 USING fts5(a,b); + INSERT INTO t0(a,b)VALUES(1,0); + CREATE TABLE t1(x); +} + +do_execsql_test 2.1 { + SELECT * FROM t0 LEFT JOIN t1; +} {1 0 {}} + +breakpoint +do_catchsql_test 2.2 { + SELECT * FROM t0 LEFT JOIN t1 ON t0.b MATCH '1'; +} {1 {no query solution}} + +do_execsql_test 2.3 { + SELECT * FROM t0 LEFT JOIN t1 ON +b MATCH '1'; +} {1 0 {}} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE t0 USING fts5(c0, c1); + INSERT INTO t0(c0,c1) VALUES (1,0); +} + +do_catchsql_test 3.1 { + SELECT * FROM t0 + LEFT JOIN ( SELECT 0 AS col_0 ) + ON ((((t0.c1 MATCH '1')AND(CASE WHEN t0.c0 THEN CAST(t0.c1 AS INTEGER) ELSE 1 END)))); +} {1 {no query solution}} + + finish_test diff --git a/ext/misc/vtablog.c b/ext/misc/vtablog.c index e8f084e1b..44acc32e6 100644 --- a/ext/misc/vtablog.c +++ b/ext/misc/vtablog.c @@ -14,6 +14,13 @@ ** on stdout when its key interfaces are called. This is intended for ** interactive analysis and debugging of virtual table interfaces. ** +** To build this extension as a separately loaded shared library or +** DLL, use compiler command-lines similar to the following: +** +** (linux) gcc -fPIC -shared vtablog.c -o vtablog.so +** (mac) clang -fPIC -dynamiclib vtablog.c -o vtablog.dylib +** (windows) cl vtablog.c -link -dll -out:vtablog.dll +** ** Usage example: ** ** .load ./vtablog @@ -436,6 +443,39 @@ static int vtablogFilter( } /* +** Return an sqlite3_index_info operator name in static space. +** The name is possibly overwritten on subsequent calls. +*/ +static char *vtablogOpName(unsigned char op){ + static char zUnknown[30]; + char *zOut; + switch( op ){ + case SQLITE_INDEX_CONSTRAINT_EQ: zOut = "EQ"; break; + case SQLITE_INDEX_CONSTRAINT_GT: zOut = "GT"; break; + case SQLITE_INDEX_CONSTRAINT_LE: zOut = "LE"; break; + case SQLITE_INDEX_CONSTRAINT_LT: zOut = "LT"; break; + case SQLITE_INDEX_CONSTRAINT_GE: zOut = "GE"; break; + case SQLITE_INDEX_CONSTRAINT_MATCH: zOut = "MATCH"; break; + case SQLITE_INDEX_CONSTRAINT_LIKE: zOut = "LIKE"; break; + case SQLITE_INDEX_CONSTRAINT_GLOB: zOut = "GLOB"; break; + case SQLITE_INDEX_CONSTRAINT_REGEXP: zOut = "REGEXP"; break; + case SQLITE_INDEX_CONSTRAINT_NE: zOut = "NE"; break; + case SQLITE_INDEX_CONSTRAINT_ISNOT: zOut = "ISNOT"; break; + case SQLITE_INDEX_CONSTRAINT_ISNOTNULL: zOut = "ISNOTNULL"; break; + case SQLITE_INDEX_CONSTRAINT_ISNULL: zOut = "ISNULL"; break; + case SQLITE_INDEX_CONSTRAINT_IS: zOut = "IS"; break; + case SQLITE_INDEX_CONSTRAINT_LIMIT: zOut = "LIMIT"; break; + case SQLITE_INDEX_CONSTRAINT_OFFSET: zOut = "OFFSET"; break; + case SQLITE_INDEX_CONSTRAINT_FUNCTION: zOut = "FUNCTION"; break; + default: + sqlite3_snprintf(sizeof(zUnknown),zUnknown,"%d",op); + zOut = zUnknown; + break; + } + return zOut; +} + +/* ** SQLite will invoke this method one or more times while planning a query ** that uses the vtablog virtual table. This routine needs to create ** a query plan for each invocation and compute an estimated cost for that @@ -451,14 +491,23 @@ static int vtablogBestIndex( printf(" colUsed: 0x%016llx\n", p->colUsed); printf(" nConstraint: %d\n", p->nConstraint); for(i=0; i<p->nConstraint; i++){ + sqlite3_value *pVal = 0; + int rc = sqlite3_vtab_rhs_value(p, i, &pVal); printf( - " constraint[%d]: col=%d termid=%d op=%d usabled=%d collseq=%s\n", + " constraint[%d]: col=%d termid=%d op=%s usabled=%d coll=%s rhs=", i, p->aConstraint[i].iColumn, p->aConstraint[i].iTermOffset, - p->aConstraint[i].op, + vtablogOpName(p->aConstraint[i].op), p->aConstraint[i].usable, - sqlite3_vtab_collation(p,i)); + sqlite3_vtab_collation(p,i) + ); + if( rc==SQLITE_OK ){ + vtablogQuote(pVal); + printf("\n"); + }else{ + printf("N/A\n"); + } } printf(" nOrderBy: %d\n", p->nOrderBy); for(i=0; i<p->nOrderBy; i++){ diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index f7d3bda01..fb35bc10e 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -1135,6 +1135,12 @@ static void resetCursor(RtreeCursor *pCsr){ pCsr->base.pVtab = (sqlite3_vtab*)pRtree; pCsr->pReadAux = pStmt; + /* The following will only fail if the previous sqlite3_step() call failed, + ** in which case the error has already been caught. This statement never + ** encounters an error within an sqlite3_column_xxx() function, as it + ** calls sqlite3_column_value(), which does not use malloc(). So it is safe + ** to ignore the error code here. */ + sqlite3_reset(pStmt); } /* diff --git a/ext/rtree/rtreeH.test b/ext/rtree/rtreeH.test index e26107f07..79bf9808d 100644 --- a/ext/rtree/rtreeH.test +++ b/ext/rtree/rtreeH.test @@ -99,5 +99,24 @@ do_execsql_test rtreeH-300 { ORDER BY id; } {box-48,48 box-49,48 box-48,49 xbox-49,49} +#------------------------------------------------------------------------- +reset_db +do_execsql_test rtreeH-300 { + CREATE TABLE t0(c0); + INSERT INTO t0(c0) VALUES (NULL); + INSERT INTO t0(c0) VALUES (1); + CREATE VIRTUAL TABLE t1 USING rtree(c0, c1, c2, +c3 BLOB ); + INSERT INTO t1(c2, c3, c0) VALUES (1, 2, 1); +} + +do_execsql_test rtreeH-310 { + SELECT 1 FROM t1 WHERE t1.c3; +} {1} + +do_execsql_test rtreeH-320 { + SELECT * FROM t0 WHERE NOT EXISTS ( + SELECT 1 FROM t1 WHERE t1.c3 OR t0.c0 ISNULL + ); +} {} finish_test diff --git a/ext/session/sessionI.test b/ext/session/sessionI.test new file mode 100644 index 000000000..2012f24dc --- /dev/null +++ b/ext/session/sessionI.test @@ -0,0 +1,88 @@ +# 2015 July 14 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} + +set testprefix sessionI + +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_test 1.0 { + do_common_sql { + CREATE TABLE t1(x INTEGER PRIMARY KEY, y); + } +} {} + +set C [changeset_from_sql { + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, 'two'); + INSERT INTO t1 VALUES(3, 'three'); + INSERT INTO t1 VALUES(4, 'four'); + INSERT INTO t1 VALUES(5, 'five'); + INSERT INTO t1 VALUES(6, 'six'); +}] + +do_execsql_test 1.1 { + SELECT * FROM t1 +} { + 1 one 2 two 3 three 4 four 5 five 6 six +} + +proc xFilter {data} { + foreach {op tname flag pk old new} $data {} + if {$op=="INSERT"} { + set ipk [lindex $new 1] + return [expr $ipk % 2] + } + return 1 +} +proc xConflict {args} { +} + +sqlite3changeset_apply_v3 db2 $C xConflict xFilter + +do_execsql_test -db db2 1.2 { + SELECT * FROM t1 +} { + 1 one 3 three 5 five +} + +do_execsql_test -db db2 1.3 { + DELETE FROM t1 +} +sqlite3changeset_apply_v3 db2 $C xConflict + +do_execsql_test -db db2 1.4 { + SELECT * FROM t1 +} { + 1 one 2 two 3 three 4 four 5 five 6 six +} + +proc xFilter2 {data} { + return 0 +} +do_execsql_test -db db2 1.5 { + DELETE FROM t1 +} +sqlite3changeset_apply_v3 db2 $C xConflict xFilter2 +do_execsql_test -db db2 1.6 { + SELECT * FROM t1 +} { } + +finish_test diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 175cacbe8..df40fdc1c 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -5182,6 +5182,10 @@ static int sessionChangesetApply( void *pCtx, /* Copy of sixth arg to _apply() */ const char *zTab /* Table name */ ), + int(*xFilterIter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p + ), int(*xConflict)( void *pCtx, /* Copy of fifth arg to _apply() */ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ @@ -5322,6 +5326,9 @@ static int sessionChangesetApply( ** next change. A log message has already been issued. */ if( schemaMismatch ) continue; + /* If this is a call to apply_v3(), invoke xFilterIter here. */ + if( xFilterIter && 0==xFilterIter(pCtx, pIter) ) continue; + rc = sessionApplyOneWithRetry(db, pIter, &sApply, xConflict, pCtx); } @@ -5390,17 +5397,64 @@ static int sessionChangesetApply( } /* -** Apply the changeset passed via pChangeset/nChangeset to the main -** database attached to handle "db". +** This function is called by all six sqlite3changeset_apply() variants: +** +** + sqlite3changeset_apply() +** + sqlite3changeset_apply_v2() +** + sqlite3changeset_apply_v3() +** + sqlite3changeset_apply_strm() +** + sqlite3changeset_apply_strm_v2() +** + sqlite3changeset_apply_strm_v3() +** +** Arguments passed to this function are as follows: +** +** db: +** Database handle to apply changeset to main database of. +** +** nChangeset/pChangeset: +** These are both passed zero for the streaming variants. For the normal +** apply() functions, these are passed the size of and the buffer containing +** the changeset, respectively. +** +** xInput/pIn: +** These are both passed zero for the normal variants. For the streaming +** apply() functions, these are passed the input callback and context +** pointer, respectively. +** +** xFilter: +** The filter function as passed to apply() or apply_v2() (to filter by +** table name), if any. This is always NULL for apply_v3() calls. +** +** xFilterIter: +** The filter function as passed to apply_v3(), if any. +** +** xConflict: +** The conflict handler callback (must not be NULL). +** +** pCtx: +** The context pointer passed to the xFilter and xConflict handler callbacks. +** +** ppRebase, pnRebase: +** Zero for apply(). The rebase changeset output pointers, if any, for +** apply_v2() and apply_v3(). +** +** flags: +** Zero for apply(). The flags parameter for apply_v2() and apply_v3(). */ -int sqlite3changeset_apply_v2( +static int sessionChangesetApplyV23( sqlite3 *db, /* Apply change to "main" db of this handle */ int nChangeset, /* Size of changeset in bytes */ void *pChangeset, /* Changeset blob */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ int(*xFilter)( void *pCtx, /* Copy of sixth arg to _apply() */ const char *zTab /* Table name */ ), + int(*xFilterIter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p /* Handle describing current change */ + ), int(*xConflict)( void *pCtx, /* Copy of sixth arg to _apply() */ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ @@ -5411,19 +5465,75 @@ int sqlite3changeset_apply_v2( int flags ){ sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ - int bInv = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); - int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset, bInv, 1); - + int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); + int rc = sessionChangesetStart( + &pIter, xInput, pIn, nChangeset, pChangeset, bInverse, 1 + ); if( rc==SQLITE_OK ){ - rc = sessionChangesetApply( - db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags + rc = sessionChangesetApply(db, pIter, + xFilter, xFilterIter, xConflict, pCtx, ppRebase, pnRebase, flags ); } - return rc; } /* +** Apply the changeset passed via pChangeset/nChangeset to the main +** database attached to handle "db". +*/ +int sqlite3changeset_apply_v2( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +){ + return sessionChangesetApplyV23(db, + nChangeset, pChangeset, 0, 0, + xFilter, 0, xConflict, pCtx, + ppRebase, pnRebase, flags + ); +} + +/* +** Apply the changeset passed via pChangeset/nChangeset to the main +** database attached to handle "db". +*/ +int sqlite3changeset_apply_v3( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p /* Handle describing current change */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +){ + return sessionChangesetApplyV23(db, + nChangeset, pChangeset, 0, 0, + 0, xFilter, xConflict, pCtx, + ppRebase, pnRebase, flags + ); +} + +/* ** Apply the changeset passed via pChangeset/nChangeset to the main database ** attached to handle "db". Invoke the supplied conflict handler callback ** to resolve any conflicts encountered while applying the change. @@ -5443,8 +5553,10 @@ int sqlite3changeset_apply( ), void *pCtx /* First argument passed to xConflict */ ){ - return sqlite3changeset_apply_v2( - db, nChangeset, pChangeset, xFilter, xConflict, pCtx, 0, 0, 0 + return sessionChangesetApplyV23(db, + nChangeset, pChangeset, 0, 0, + xFilter, 0, xConflict, pCtx, + 0, 0, 0 ); } @@ -5453,6 +5565,29 @@ int sqlite3changeset_apply( ** attached to handle "db". Invoke the supplied conflict handler callback ** to resolve any conflicts encountered while applying the change. */ +int sqlite3changeset_apply_v3_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +){ + return sessionChangesetApplyV23(db, + 0, 0, xInput, pIn, + 0, xFilter, xConflict, pCtx, + ppRebase, pnRebase, flags + ); +} int sqlite3changeset_apply_v2_strm( sqlite3 *db, /* Apply change to "main" db of this handle */ int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ @@ -5470,15 +5605,11 @@ int sqlite3changeset_apply_v2_strm( void **ppRebase, int *pnRebase, int flags ){ - sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ - int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); - int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse, 1); - if( rc==SQLITE_OK ){ - rc = sessionChangesetApply( - db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags - ); - } - return rc; + return sessionChangesetApplyV23(db, + 0, 0, xInput, pIn, + xFilter, 0, xConflict, pCtx, + ppRebase, pnRebase, flags + ); } int sqlite3changeset_apply_strm( sqlite3 *db, /* Apply change to "main" db of this handle */ @@ -5495,8 +5626,10 @@ int sqlite3changeset_apply_strm( ), void *pCtx /* First argument passed to xConflict */ ){ - return sqlite3changeset_apply_v2_strm( - db, xInput, pIn, xFilter, xConflict, pCtx, 0, 0, 0 + return sessionChangesetApplyV23(db, + 0, 0, xInput, pIn, + xFilter, 0, xConflict, pCtx, + 0, 0, 0 ); } diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index 4dff5ce87..a3b6987b9 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -1115,13 +1115,22 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); ** the changeset passed via the second and third arguments. ** ** The fourth argument (xFilter) passed to these functions is the "filter -** callback". If it is not NULL, then for each table affected by at least one -** change in the changeset, the filter callback is invoked with -** the table name as the second argument, and a copy of the context pointer -** passed as the sixth argument as the first. If the "filter callback" -** returns zero, then no attempt is made to apply any changes to the table. -** Otherwise, if the return value is non-zero or the xFilter argument to -** is NULL, all changes related to the table are attempted. +** callback". This may be passed NULL, in which case all changes in the +** changeset are applied to the database. For sqlite3changeset_apply() and +** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once +** for each table affected by at least one change in the changeset. In this +** case the table name is passed as the second argument, and a copy of +** the context pointer passed as the sixth argument to apply() or apply_v2() +** as the first. If the "filter callback" returns zero, then no attempt is +** made to apply any changes to the table. Otherwise, if the return value is +** non-zero, all changes related to the table are attempted. +** +** For sqlite3_changeset_apply_v3(), the xFilter callback is invoked once +** per change. The second argument in this case is an sqlite3_changeset_iter +** that may be queried using the usual APIs for the details of the current +** change. If the "filter callback" returns zero in this case, then no attempt +** is made to apply the current change. If it returns non-zero, the change +** is applied. ** ** For each table that is not excluded by the filter callback, this function ** tests that the target database contains a compatible table. A table is @@ -1142,11 +1151,11 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); ** one such warning is issued for each table in the changeset. ** ** For each change for which there is a compatible table, an attempt is made -** to modify the table contents according to the UPDATE, INSERT or DELETE -** change. If a change cannot be applied cleanly, the conflict handler -** function passed as the fifth argument to sqlite3changeset_apply() may be -** invoked. A description of exactly when the conflict handler is invoked for -** each type of change is below. +** to modify the table contents according to each UPDATE, INSERT or DELETE +** change that is not excluded by a filter callback. If a change cannot be +** applied cleanly, the conflict handler function passed as the fifth argument +** to sqlite3changeset_apply() may be invoked. A description of exactly when +** the conflict handler is invoked for each type of change is below. ** ** Unlike the xFilter argument, xConflict may not be passed NULL. The results ** of passing anything other than a valid function pointer as the xConflict @@ -1297,6 +1306,23 @@ int sqlite3changeset_apply_v2( void **ppRebase, int *pnRebase, /* OUT: Rebase data */ int flags /* SESSION_CHANGESETAPPLY_* flags */ ); +int sqlite3changeset_apply_v3( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p /* Handle describing change */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, /* OUT: Rebase data */ + int flags /* SESSION_CHANGESETAPPLY_* flags */ +); /* ** CAPI3REF: Flags for sqlite3changeset_apply_v2 @@ -1716,6 +1742,23 @@ int sqlite3changeset_apply_v2_strm( void **ppRebase, int *pnRebase, int flags ); +int sqlite3changeset_apply_v3_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +); int sqlite3changeset_concat_strm( int (*xInputA)(void *pIn, void *pData, int *pnData), void *pInA, diff --git a/ext/session/test_session.c b/ext/session/test_session.c index f28604abc..6ad5b3774 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -520,6 +520,65 @@ static int test_obj_eq_string(Tcl_Obj *p, const char *z){ return (nObj==n && (n==0 || 0==memcmp(zObj, z, n))); } +static Tcl_Obj *testIterData(sqlite3_changeset_iter *pIter){ + Tcl_Obj *pVar = 0; + int nCol; /* Number of columns in table */ + int nCol2; /* Number of columns in table */ + int op; /* SQLITE_INSERT, UPDATE or DELETE */ + const char *zTab; /* Name of table change applies to */ + Tcl_Obj *pOld; /* Vector of old.* values */ + Tcl_Obj *pNew; /* Vector of new.* values */ + int bIndirect; + + char *zPK; + unsigned char *abPK; + int i; + + sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect); + pVar = Tcl_NewObj(); + + Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj( + op==SQLITE_INSERT ? "INSERT" : + op==SQLITE_UPDATE ? "UPDATE" : + "DELETE", -1 + )); + + Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1)); + Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect)); + + zPK = ckalloc(nCol+1); + memset(zPK, 0, nCol+1); + sqlite3changeset_pk(pIter, &abPK, &nCol2); + assert( nCol==nCol2 ); + for(i=0; i<nCol; i++){ + zPK[i] = (abPK[i] ? 'X' : '.'); + } + Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1)); + ckfree(zPK); + + pOld = Tcl_NewObj(); + if( op!=SQLITE_INSERT ){ + for(i=0; i<nCol; i++){ + sqlite3_value *pVal; + sqlite3changeset_old(pIter, i, &pVal); + test_append_value(pOld, pVal); + } + } + pNew = Tcl_NewObj(); + if( op!=SQLITE_DELETE ){ + for(i=0; i<nCol; i++){ + sqlite3_value *pVal; + sqlite3changeset_new(pIter, i, &pVal); + test_append_value(pNew, pVal); + } + } + Tcl_ListObjAppendElement(0, pVar, pOld); + Tcl_ListObjAppendElement(0, pVar, pNew); + + return pVar; +} + + static int test_filter_handler( void *pCtx, /* Pointer to TestConflictHandler structure */ const char *zTab /* Table name */ @@ -543,6 +602,29 @@ static int test_filter_handler( return res; } +static int test_filter_v3_handler( + void *pCtx, /* Pointer to TestConflictHandler structure */ + sqlite3_changeset_iter *pIter +){ + TestConflictHandler *p = (TestConflictHandler *)pCtx; + int res = 1; + Tcl_Obj *pEval = 0; + Tcl_Interp *interp = p->interp; + + pEval = Tcl_DuplicateObj(p->pFilterScript); + Tcl_IncrRefCount(pEval); + Tcl_ListObjAppendElement(0, pEval, testIterData(pIter)); + + if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) + || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &res) + ){ + Tcl_BackgroundError(interp); + } + + Tcl_DecrRefCount(pEval); + return res; +} + static int test_conflict_handler( void *pCtx, /* Pointer to TestConflictHandler structure */ int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */ @@ -776,7 +858,7 @@ static int testStreamInput( static int SQLITE_TCLAPI testSqlite3changesetApply( - int bV2, + int iVersion, void * clientData, Tcl_Interp *interp, int objc, @@ -793,11 +875,13 @@ static int SQLITE_TCLAPI testSqlite3changesetApply( int nRebase = 0; int flags = 0; /* Flags for apply_v2() */ + assert( iVersion==1 || iVersion==2 || iVersion==3 ); + memset(&sStr, 0, sizeof(sStr)); sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); /* Check for the -nosavepoint, -invert or -ignorenoop switches */ - if( bV2 ){ + if( iVersion==2 || iVersion==3 ){ while( objc>1 ){ const char *z1 = Tcl_GetString(objv[1]); int n = (int)strlen(z1); @@ -822,7 +906,7 @@ static int SQLITE_TCLAPI testSqlite3changesetApply( if( objc!=4 && objc!=5 ){ const char *zMsg; - if( bV2 ){ + if( iVersion==2 || iVersion==3 ){ zMsg = "?-nosavepoint? ?-inverse? ?-ignorenoop? " "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"; }else{ @@ -842,30 +926,49 @@ static int SQLITE_TCLAPI testSqlite3changesetApply( ctx.interp = interp; if( sStr.nStream==0 ){ - if( bV2==0 ){ - rc = sqlite3changeset_apply(db, (int)nChangeset, pChangeset, - (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx - ); - }else{ - rc = sqlite3changeset_apply_v2(db, (int)nChangeset, pChangeset, - (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx, - &pRebase, &nRebase, flags - ); + switch( iVersion ){ + case 1: + rc = sqlite3changeset_apply(db, (int)nChangeset, pChangeset, + (objc==5)?test_filter_handler:0, test_conflict_handler, (void*)&ctx + ); + break; + case 2: + rc = sqlite3changeset_apply_v2(db, (int)nChangeset, pChangeset, + (objc==5)?test_filter_handler:0, test_conflict_handler, (void*)&ctx, + &pRebase, &nRebase, flags + ); + break; + case 3: + rc = sqlite3changeset_apply_v3(db, (int)nChangeset, pChangeset, + (objc==5)?test_filter_v3_handler:0, test_conflict_handler, + (void*)&ctx, &pRebase, &nRebase, flags + ); + break; } }else{ sStr.aData = (unsigned char*)pChangeset; sStr.nData = (int)nChangeset; - if( bV2==0 ){ - rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr, - (objc==5) ? test_filter_handler : 0, - test_conflict_handler, (void *)&ctx - ); - }else{ - rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr, - (objc==5) ? test_filter_handler : 0, - test_conflict_handler, (void *)&ctx, - &pRebase, &nRebase, flags - ); + switch( iVersion ){ + case 1: + rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr, + (objc==5) ? test_filter_handler : 0, + test_conflict_handler, (void *)&ctx + ); + break; + case 2: + rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr, + (objc==5) ? test_filter_handler : 0, + test_conflict_handler, (void *)&ctx, + &pRebase, &nRebase, flags + ); + break; + case 3: + rc = sqlite3changeset_apply_v3_strm(db, testStreamInput, (void*)&sStr, + (objc==5) ? test_filter_v3_handler : 0, + test_conflict_handler, (void *)&ctx, + &pRebase, &nRebase, flags + ); + break; } } @@ -873,7 +976,7 @@ static int SQLITE_TCLAPI testSqlite3changesetApply( return test_session_error(interp, rc, 0); }else{ Tcl_ResetResult(interp); - if( bV2 && pRebase ){ + if( (iVersion==2 || iVersion==3) && pRebase ){ Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pRebase, nRebase)); } } @@ -890,7 +993,7 @@ static int SQLITE_TCLAPI test_sqlite3changeset_apply( int objc, Tcl_Obj *CONST objv[] ){ - return testSqlite3changesetApply(0, clientData, interp, objc, objv); + return testSqlite3changesetApply(1, clientData, interp, objc, objv); } /* ** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? @@ -901,7 +1004,18 @@ static int SQLITE_TCLAPI test_sqlite3changeset_apply_v2( int objc, Tcl_Obj *CONST objv[] ){ - return testSqlite3changesetApply(1, clientData, interp, objc, objv); + return testSqlite3changesetApply(2, clientData, interp, objc, objv); +} +/* +** sqlite3changeset_apply_v3 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? +*/ +static int SQLITE_TCLAPI test_sqlite3changeset_apply_v3( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + return testSqlite3changesetApply(3, clientData, interp, objc, objv); } /* @@ -1034,64 +1148,6 @@ static int SQLITE_TCLAPI test_sqlite3changeset_concat( return rc; } -static Tcl_Obj *testIterData(sqlite3_changeset_iter *pIter){ - Tcl_Obj *pVar = 0; - int nCol; /* Number of columns in table */ - int nCol2; /* Number of columns in table */ - int op; /* SQLITE_INSERT, UPDATE or DELETE */ - const char *zTab; /* Name of table change applies to */ - Tcl_Obj *pOld; /* Vector of old.* values */ - Tcl_Obj *pNew; /* Vector of new.* values */ - int bIndirect; - - char *zPK; - unsigned char *abPK; - int i; - - sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect); - pVar = Tcl_NewObj(); - - Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj( - op==SQLITE_INSERT ? "INSERT" : - op==SQLITE_UPDATE ? "UPDATE" : - "DELETE", -1 - )); - - Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1)); - Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect)); - - zPK = ckalloc(nCol+1); - memset(zPK, 0, nCol+1); - sqlite3changeset_pk(pIter, &abPK, &nCol2); - assert( nCol==nCol2 ); - for(i=0; i<nCol; i++){ - zPK[i] = (abPK[i] ? 'X' : '.'); - } - Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1)); - ckfree(zPK); - - pOld = Tcl_NewObj(); - if( op!=SQLITE_INSERT ){ - for(i=0; i<nCol; i++){ - sqlite3_value *pVal; - sqlite3changeset_old(pIter, i, &pVal); - test_append_value(pOld, pVal); - } - } - pNew = Tcl_NewObj(); - if( op!=SQLITE_DELETE ){ - for(i=0; i<nCol; i++){ - sqlite3_value *pVal; - sqlite3changeset_new(pIter, i, &pVal); - test_append_value(pNew, pVal); - } - } - Tcl_ListObjAppendElement(0, pVar, pOld); - Tcl_ListObjAppendElement(0, pVar, pNew); - - return pVar; -} - /* ** sqlite3session_foreach VARNAME CHANGESET SCRIPT */ @@ -1751,6 +1807,7 @@ int TestSession_Init(Tcl_Interp *interp){ { "sqlite3changeset_concat", test_sqlite3changeset_concat }, { "sqlite3changeset_apply", test_sqlite3changeset_apply }, { "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2 }, + { "sqlite3changeset_apply_v3", test_sqlite3changeset_apply_v3 }, { "sqlite3changeset_apply_replace_all", test_sqlite3changeset_apply_replace_all }, { "sql_exec_changeset", test_sql_exec_changeset }, diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index bf1a49111..5cd0aa66a 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -39,10 +39,11 @@ ######################################################################## default: all #default: quick -MAKEFILE := $(lastword $(MAKEFILE_LIST)) -CLEAN_FILES := -DISTCLEAN_FILES := config.make -MAKING_CLEAN := $(if $(filter %clean,$(MAKECMDGOALS)),1,0) +MAKEFILE = $(lastword $(MAKEFILE_LIST)) +MAKEFILE.fiddle = fiddle.make +CLEAN_FILES = +DISTCLEAN_FILES = config.make +MAKING_CLEAN = $(if $(filter %clean,$(MAKECMDGOALS)),1,0) .PHONY: clean distclean clean: -rm -f $(CLEAN_FILES) @@ -53,17 +54,17 @@ distclean: clean ######################################################################## # Special-case builds for which we require certain pre-conditions # which, if not met, may cause warnings or fatal errors in the build. -# This also affects the default optimization level flags. Note that -# the fiddle targets are in this list because they are used for -# generating sqlite.org/fiddle. -OPTIMIZED_TARGETS := dist snapshot fiddle fiddle.debug +# This also affects the default optimization level flags. The fiddle +# targets are in this list because they are used for generating +# sqlite.org/fiddle. +OPTIMIZED_TARGETS = dist snapshot fiddle fiddle.debug ifeq (1,$(MAKING_CLEAN)) - bin.wasm-strip := echo "not stripping" - bin.wasm-opt := irrelevant - bin.emcc := irrelevant - bin.bash := irrelevant - emcc.version := unknown + bin.wasm-strip = echo "not stripping" + bin.wasm-opt = irrelevant + bin.emcc = irrelevant + bin.bash = irrelevant + emcc.version = unknown else # Include config.make and perform some bootstrapping... ifeq (,$(wildcard ./config.make)) @@ -76,7 +77,7 @@ else ifeq (,$(bin.emcc)) $(error Configure script did not find emcc) endif - emcc.version := $(shell $(bin.emcc) --version | sed -n 1p | sed -e 's/^.* \([3-9][^ ]*\) .*$$/\1/;') + emcc.version = $(shell $(bin.emcc) --version | sed -n 1p | sed -e 's/^.* \([3-9][^ ]*\) .*$$/\1/;') $(info using emcc version [$(emcc.version)]) ifeq (,$(bin.wasm-strip)) #################################################################### @@ -98,7 +99,7 @@ else ifneq (,$(filter $(OPTIMIZED_TARGETS),$(MAKECMDGOALS))) $(error Cannot make release-quality binary because wasm-strip is not available.) endif - bin.wasm-strip := echo "not wasm-stripping" + bin.wasm-strip = echo "not wasm-stripping" endif ifeq (,$(filter $(OPTIMIZED_TARGETS),$(MAKECMDGOALS))) $(info ==============================================================) @@ -108,7 +109,7 @@ else endif endif # ^^^ end of are-we-MAKING_CLEAN -maybe-wasm-strip := $(bin.wasm-strip) +maybe-wasm-strip = $(bin.wasm-strip) ######################################################################## # JS_BUILD_NAMES exists for documentation purposes only. It enumerates @@ -118,7 +119,7 @@ maybe-wasm-strip := $(bin.wasm-strip) # # - sqlite3-wasmfs = WASMFS-capable library build # -JS_BUILD_NAMES := sqlite3 sqlite3-wasmfs +JS_BUILD_NAMES = sqlite3 sqlite3-wasmfs ######################################################################## # JS_BUILD_MODES exists for documentation purposes only. It enumerates @@ -140,31 +141,31 @@ JS_BUILD_NAMES := sqlite3 sqlite3-wasmfs # that persistent storage (OPFS) is not available in these builds. # These builds are UNTESTED and UNSUPPORTED! # -JS_BUILD_MODES := vanilla esm bunder-friendly node +JS_BUILD_MODES = vanilla esm bunder-friendly node ######################################################################## # dir.top = the top dir of the canonical build tree, where # sqlite3.[ch] live. -dir.top := ../.. +dir.top = ../.. # Maintenance reminder: some Emscripten flags require absolute paths # but we want relative paths for most stuff simply to reduce # noise. The $(abspath...) GNU make function can transform relative # paths to absolute. -dir.wasm := $(patsubst %/,%,$(dir $(MAKEFILE))) -dir.api := api -dir.jacc := jaccwabyt -dir.common := common -dir.fiddle := fiddle -dir.fiddle-debug := fiddle-debug -dir.tool := $(dir.top)/tool +dir.wasm = $(patsubst %/,%,$(dir $(MAKEFILE))) +dir.api = api +dir.jacc = jaccwabyt +dir.common = common +dir.fiddle = fiddle +dir.fiddle-debug = fiddle-debug +dir.tool = $(dir.top)/tool # dir.dout = output dir for deliverables -dir.dout := $(dir.wasm)/jswasm +dir.dout = $(dir.wasm)/jswasm # dir.tmp = output dir for intermediary build files, as opposed to # end-user deliverables. -dir.tmp := $(dir.wasm)/bld -dir.wasmfs := $(dir.dout) +dir.tmp = $(dir.wasm)/bld +dir.wasmfs = $(dir.dout) -MKDIR.bld := $(dir.tmp) +MKDIR.bld = $(dir.tmp) $(MKDIR.bld): @mkdir -p $@ $(dir.dout) @@ -188,17 +189,17 @@ CLEAN_FILES += *~ $(dir.jacc)/*~ $(dir.api)/*~ $(dir.common)/*~ $(dir.fiddle)/*~ # $(sqlite3.canonical.c) must point to the sqlite3.c in # the sqlite3 canonical source tree, as that source file # is required for certain utility and test code. -sqlite3.canonical.c := $(dir.top)/sqlite3.c +sqlite3.canonical.c = $(dir.top)/sqlite3.c sqlite3.c ?= $(firstword $(wildcard $(dir.top)/sqlite3-see.c) $(sqlite3.canonical.c)) -sqlite3.h := $(dir.top)/sqlite3.h +sqlite3.h = $(dir.top)/sqlite3.h ifeq (1,$(MAKING_CLEAN)) - SQLITE_C_IS_SEE := 0 + SQLITE_C_IS_SEE = 0 else ifeq (,$(shell grep sqlite3_activate_see $(sqlite3.c))) - SQLITE_C_IS_SEE := 0 + SQLITE_C_IS_SEE = 0 else - SQLITE_C_IS_SEE := 1 + SQLITE_C_IS_SEE = 1 $(info This is an SEE build) endif endif @@ -216,19 +217,19 @@ $(sqlite3.c): $(sqlite3.h) # barebones=1 disables all "extraneous" stuff from sqlite3-wasm.c, the # goal being to create a WASM file with only the core APIs. ifeq (1,$(barebones)) - wasm-bare-bones := 1 + wasm-bare-bones = 1 $(info ==============================================================) $(info == This is a bare-bones build. It trades away features for) $(info == a smaller .wasm file.) $(info ==============================================================) else - wasm-bare-bones := 0 + wasm-bare-bones = 0 endif # undefine barebones # relatively new gmake feature, not ubiquitous # Common options for building sqlite3-wasm.c and speedtest1.c. # Explicit ENABLEs... -SQLITE_OPT.common := \ +SQLITE_OPT.common = \ -DSQLITE_THREADSAFE=0 \ -DSQLITE_TEMP_STORE=2 \ -DSQLITE_ENABLE_MATH_FUNCTIONS \ @@ -249,7 +250,7 @@ SQLITE_OPT.common := \ SQLITE_OPT.common += -DSQLITE_WASM_ENABLE_C_TESTS # Extra flags for full-featured builds... -SQLITE_OPT.full-featured := \ +SQLITE_OPT.full-featured = \ -DSQLITE_ENABLE_BYTECODE_VTAB \ -DSQLITE_ENABLE_DBPAGE_VTAB \ -DSQLITE_ENABLE_DBSTAT_VTAB \ @@ -265,12 +266,12 @@ SQLITE_OPT.full-featured := \ ifeq (0,$(wasm-bare-bones)) # The so-called canonical build is full-featured: - SQLITE_OPT := \ + SQLITE_OPT = \ $(SQLITE_OPT.common) \ $(SQLITE_OPT.full-featured) else # The so-called bare-bones build is exactly that: - SQLITE_OPT := \ + SQLITE_OPT = \ $(SQLITE_OPT.common) \ -DSQLITE_WASM_BARE_BONES # SQLITE_WASM_BARE_BONES tells sqlite3-wasm.c to explicitly omit @@ -345,10 +346,10 @@ endif # See example_extra_init.c for an example implementation. ######################################################################## sqlite3_wasm_extra_init.c ?= $(wildcard sqlite3_wasm_extra_init.c) -cflags.wasm_extra_init := +cflags.wasm_extra_init = ifneq (,$(sqlite3_wasm_extra_init.c)) $(info Enabling SQLITE_EXTRA_INIT via $(sqlite3_wasm_extra_init.c).) - cflags.wasm_extra_init := -DSQLITE_WASM_EXTRA_INIT + cflags.wasm_extra_init = -DSQLITE_WASM_EXTRA_INIT endif ######################################################################### @@ -362,7 +363,7 @@ endif # end result is that the generated JS files may have static version # info from $(bin.version-info) which differ from their runtime-emitted # version info (e.g. from sqlite3_libversion()). -bin.version-info := $(dir.top)/version-info +bin.version-info = $(dir.top)/version-info .NOTPARALLEL: $(bin.version-info) $(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile $(MAKE) -C $(dir.top) version-info @@ -373,7 +374,7 @@ $(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile # don't need for all builds. That app's -k flag is of particular # importance here, as it allows us to retain the opening comment # block(s), which contain the license header and version info. -bin.stripccomments := $(dir.tool)/stripccomments +bin.stripccomments = $(dir.tool)/stripccomments $(bin.stripccomments): $(bin.stripccomments).c $(MAKEFILE) $(CC) -o $@ $< DISTCLEAN_FILES += $(bin.stripccomments) @@ -410,7 +411,7 @@ DISTCLEAN_FILES += $(bin.stripccomments) # # -D... flags which should be included in all invocations should be # appended to $(SQLITE.CALL.C-PP.FILTER.global). -bin.c-pp := ./c-pp +bin.c-pp = ./c-pp $(bin.c-pp): c-pp.c $(sqlite3.c) # $(MAKEFILE) $(CC) -O0 -o $@ c-pp.c $(sqlite3.c) '-DCMPP_DEFAULT_DELIM="//#"' -I$(dir.top) \ -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_UTF16 \ @@ -429,13 +430,13 @@ define SQLITE.CALL.C-PP.FILTER $(2): $(1) $$(MAKEFILE_LIST) $$(bin.c-pp) @mkdir -p $$(dir $$@) $$(bin.c-pp) -f $(1) -o $$@ $(3) $(SQLITE.CALL.C-PP.FILTER.global) -#CLEAN_FILES += $(2) +CLEAN_FILES += $(2) endef # /end SQLITE.CALL.C-PP.FILTER ######################################################################## # cflags.common = C compiler flags for all builds -cflags.common := -I. -I$(dir $(sqlite3.c)) +cflags.common = -I. -I$(dir $(sqlite3.c)) # emcc.WASM_BIGINT = 1 for BigInt (C int64) support, else 0. The API # disables certain features if BigInt is not enabled and such builds # _are not tested_ on any regular basis. @@ -456,9 +457,24 @@ else emcc_opt ?= -Oz endif +# Our JS code installs bindings of each sqlite3_...() WASM export. The +# generated Emscripten JS file does the same using its own framework, +# but we don't use those results and can speed up lib init, and reduce +# memory cost a bit, by stripping them out. Emscripten-side changes +# can "break" this, causing this to be a no-op, but the worst that can +# happen in that case is that it doesn't actually strip anything, +# leading to slightly larger JS files. +# +# This snippet is intended to be used in makefile targets which +# generate an Emscripten module and where $@ is the module's .js/.mjs +# file. +SQLITE.strip-createExportWrapper = \ + sed -i -e '/^.*= \(_sqlite3\|_fiddle\)[^=]* = createExportWrapper/d' $@ || exit; \ + echo '(Probably) stripped out extraneous createExportWrapper() parts.' + # When passing emcc_opt from the CLI, += and re-assignment have no # effect, so emcc_opt+=-g3 doesn't work. So... -emcc_opt_full := $(emcc_opt) -g3 +emcc_opt_full = $(emcc_opt) -g3 # ^^^ ALWAYS use -g3. See below for why. # # ^^^ -flto improves runtime speed at -O0 considerably but doubles @@ -489,31 +505,28 @@ emcc_opt_full := $(emcc_opt) -g3 ######################################################################## # EXPORTED_FUNCTIONS.* = files for use with Emscripten's # -sEXPORTED_FUNCTION flag. -EXPORTED_FUNCTIONS.api.core := $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-core -EXPORTED_FUNCTIONS.api.in := $(EXPORTED_FUNCTIONS.api.core) +EXPORTED_FUNCTIONS.api.core = $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-core +EXPORTED_FUNCTIONS.api.in = $(EXPORTED_FUNCTIONS.api.core) ifeq (1,$(SQLITE_C_IS_SEE)) EXPORTED_FUNCTIONS.api.in += $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-see endif ifeq (0,$(wasm-bare-bones)) EXPORTED_FUNCTIONS.api.in += $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-extras endif -EXPORTED_FUNCTIONS.api := $(dir.tmp)/EXPORTED_FUNCTIONS.api +EXPORTED_FUNCTIONS.api = $(dir.tmp)/EXPORTED_FUNCTIONS.api $(EXPORTED_FUNCTIONS.api): $(MKDIR.bld) $(EXPORTED_FUNCTIONS.api.in) $(sqlite3.c) $(MAKEFILE) cat $(EXPORTED_FUNCTIONS.api.in) > $@ ######################################################################## # sqlite3-license-version.js = generated JS file with the license # header and version info. -sqlite3-license-version.js := $(dir.tmp)/sqlite3-license-version.js -# sqlite3-license-version-header.js = JS file containing only the -# license header. -sqlite3-license-version-header.js := $(dir.api)/sqlite3-license-version-header.js +sqlite3-license-version.js = $(dir.tmp)/sqlite3-license-version.js # sqlite3-api-build-version.js = generated JS file which populates the # sqlite3.version object using $(bin.version-info). -sqlite3-api-build-version.js := $(dir.tmp)/sqlite3-api-build-version.js +sqlite3-api-build-version.js = $(dir.tmp)/sqlite3-api-build-version.js # sqlite3-api.jses = the list of JS files which make up # $(sqlite3-api.js.in), in the order they need to be assembled. -sqlite3-api.jses := $(sqlite3-license-version.js) +sqlite3-api.jses = $(sqlite3-license-version.js) # sqlite3-api-prologue.js: initial bootstrapping bits: sqlite3-api.jses += $(dir.api)/sqlite3-api-prologue.js # whwhasm.js and jaccwabyt.js: Low-level utils, mostly replacing @@ -547,13 +560,13 @@ sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js # SOAP.js is an external API file which is part of our distribution # but not part of the sqlite3-api.js amalgamation. It's a component of # 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)) +SOAP.js = $(dir.api)/sqlite3-opfs-async-proxy.js +SOAP.js.bld = $(dir.dout)/$(notdir $(SOAP.js)) # # $(sqlite3-api.ext.jses) = API-related files which are standalone files, # not part of the amalgamation. # -sqlite3-api.ext.jses := $(SOAP.js.bld) +sqlite3-api.ext.jses = $(SOAP.js.bld) $(SOAP.js.bld): $(SOAP.js) cp $< $@ @@ -573,17 +586,12 @@ $(SOAP.js.bld): $(SOAP.js) # Sidebar: some of the imports are used soley by the Emscripten glue, # which the sqlite3 JS code does not rely on. # -# We build $(sqlite3-api*.*) "because we can" and because it might be -# a useful point of experimentation for some clients, but the -# above-described caveat may well make them unusable for real-life -# clients. -# -# sqlite3-api.js.in = the generated sqlite3-api.js before it gets +# sqlite3-api.js.in = the amalgamated sqlite3-api.js before it gets # preprocessed. It contains all of $(sqlite3-api.jses) but none of the # Emscripten-specific headers and footers. -sqlite3-api.js.in := $(dir.tmp)/sqlite3-api.c-pp.js +sqlite3-api.js.in = $(dir.tmp)/sqlite3-api.c-pp.js $(sqlite3-api.js.in): $(MKDIR.bld) $(sqlite3-api.jses) $(MAKEFILE) - @echo "Making $@..." + @echo "Making $@ ..." @for i in $(sqlite3-api.jses); do \ echo "/* BEGIN FILE: $$i */"; \ cat $$i; \ @@ -592,7 +600,7 @@ $(sqlite3-api.js.in): $(MKDIR.bld) $(sqlite3-api.jses) $(MAKEFILE) ######################################################################## # emcc flags for .c/.o/.wasm/.js. -emcc.flags := +emcc.flags = ifeq (1,$(emcc.verbose)) emcc.flags += -v # -v is _very_ loud but also informative about what it's doing @@ -601,25 +609,25 @@ endif ######################################################################## # emcc flags for .c/.o. -emcc.cflags := +emcc.cflags = emcc.cflags += -std=c99 -fPIC # -------------^^^^^^^^ we need c99 for $(sqlite3-wasm.c), primarily # for variadic macros and snprintf() to implement -# sqlite3_wasm_enum_json(). +# sqlite3__wasm_enum_json(). emcc.cflags += -I. -I$(dir.top) ######################################################################## # emcc flags specific to building .js/.wasm files... -emcc.jsflags := -fPIC +emcc.jsflags = -fPIC emcc.jsflags += --no-entry emcc.jsflags += -sWASM_BIGINT=$(emcc.WASM_BIGINT) emcc.jsflags += -sMODULARIZE emcc.jsflags += -sDYNAMIC_EXECUTION=0 emcc.jsflags += -sNO_POLYFILL emcc.jsflags += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.api) -emcc.exportedRuntimeMethods := \ - -sEXPORTED_RUNTIME_METHODS=wasmMemory,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAP64,HEAPU64 +emcc.exportedRuntimeMethods = \ + -sEXPORTED_RUNTIME_METHODS=wasmMemory # wasmMemory ==> required by our code for use with -sIMPORTED_MEMORY -# Emscripten 4.0.7 (2025-04-15) stops exporting HEAP* by default +# Emscripten 4.0.7 (2025-04-15) stops exporting HEAP* by default. emcc.jsflags += $(emcc.exportedRuntimeMethods) emcc.jsflags += -sUSE_CLOSURE_COMPILER=0 emcc.jsflags += -sIMPORTED_MEMORY @@ -641,10 +649,10 @@ emcc.jsflags += -sSTRICT_JS=0 # 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) -emcc.environment.esm := $(emcc.environment.vanilla) -emcc.environment.node := node +emcc.environment.vanilla = web,worker +emcc.environment.bundler-friendly = $(emcc.environment.vanilla) +emcc.environment.esm = $(emcc.environment.vanilla) +emcc.environment.node = node # Note that adding ",node" to the list for the other builds causes # Emscripten to generate code which confuses node: it cannot reliably # determine whether the build is for a browser or for node. @@ -666,12 +674,12 @@ emcc.environment.node := node # 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 -emcc.INITIAL_MEMORY.64 := 67108864 -emcc.INITIAL_MEMORY.32 := 33554432 -emcc.INITIAL_MEMORY.16 := 16777216 -emcc.INITIAL_MEMORY.8 := 8388608 +emcc.INITIAL_MEMORY.128 = 134217728 +emcc.INITIAL_MEMORY.96 = 100663296 +emcc.INITIAL_MEMORY.64 = 67108864 +emcc.INITIAL_MEMORY.32 = 33554432 +emcc.INITIAL_MEMORY.16 = 16777216 +emcc.INITIAL_MEMORY.8 = 8388608 emcc.INITIAL_MEMORY ?= 16 ifeq (,$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY))) $(error emcc.INITIAL_MEMORY must be one of: 8, 16, 32, 64, 96, 128 (megabytes)) @@ -704,7 +712,7 @@ emcc.jsflags += -sSTACK_SIZE=512KB # symbols: we cannot "delete" the Emscripten-defined # $(sqlite3.js.init-func) from vanilla builds (as opposed to ESM # builds) because it's declared with "var". -sqlite3.js.init-func := sqlite3InitModule +sqlite3.js.init-func = sqlite3InitModule emcc.jsflags += -sEXPORT_NAME=$(sqlite3.js.init-func) emcc.jsflags += -sGLOBAL_BASE=4096 # HYPOTHETICALLY keep func table indexes from overlapping w/ heap addr. #emcc.jsflags += -sSTRICT # fails due to missing __syscall_...() @@ -772,17 +780,17 @@ $(sqlite3-api-build-version.js): $(MKDIR.bld) $(bin.version-info) $(MAKEFILE) # # Maintenance reminder: there are awk binaries out there which do not # support -e SCRIPT. -$(sqlite3-license-version.js): $(MKDIR.bld) $(sqlite3.h) $(sqlite3-license-version-header.js) \ - $(MAKEFILE) +$(sqlite3-license-version.js): $(MKDIR.bld) $(sqlite3.h) \ + $(dir.api)/sqlite3-license-version-header.js $(MAKEFILE) @echo "Making $@..."; { \ - cat $(sqlite3-license-version-header.js); \ + cat $(dir.api)/sqlite3-license-version-header.js; \ echo '/*'; \ echo '** This code was built from sqlite3 version...'; \ echo "**"; \ 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 "** with the help of Emscripten SDK version $(emcc.version)."; \ echo '*/'; \ } > $@ @@ -794,9 +802,9 @@ $(sqlite3-license-version.js): $(MKDIR.bld) $(sqlite3.h) $(sqlite3-license-versi # --post-js injects code which runs after the WASM module is loaded # and includes the entirety of the library plus some # Emscripten-specific post-bootstrapping code. -pre-js.js.in := $(dir.api)/pre-js.c-pp.js -post-js.js.in := $(dir.tmp)/post-js.c-pp.js -post-jses.js := \ +pre-js.js.in = $(dir.api)/pre-js.c-pp.js +post-js.js.in = $(dir.tmp)/post-js.c-pp.js +post-jses.js = \ $(dir.api)/post-js-header.js \ $(sqlite3-api.js.in) \ $(dir.api)/post-js-footer.js @@ -812,10 +820,10 @@ $(post-js.js.in): $(MKDIR.bld) $(post-jses.js) $(MAKEFILE) # Undocumented Emscripten feature: if the target file extension is # "mjs", it defaults to ES6 module builds: # https://github.com/emscripten-core/emscripten/issues/14383 -sqlite3.wasm := $(dir.dout)/sqlite3.wasm -sqlite3-wasm.c := $(dir.api)/sqlite3-wasm.c -sqlite3-wasm.cfiles := $(sqlite3-wasm.c) $(sqlite3_wasm_extra_init.c) -sqlite3-wasmfs.cfiles := $(sqlite3-wasm.cfiles) +sqlite3.wasm = $(dir.dout)/sqlite3.wasm +sqlite3-wasm.c = $(dir.api)/sqlite3-wasm.c +sqlite3-wasm.cfiles = $(sqlite3-wasm.c) $(sqlite3_wasm_extra_init.c) +sqlite3-wasmfs.cfiles = $(sqlite3-wasm.cfiles) # sqlite3-wasm.o vs sqlite3-wasm.c: building against the latter # (predictably) results in a slightly faster binary. We're close # enough to the target speed requirements that the 500ms makes a @@ -860,17 +868,9 @@ if [ x1 = x$(1) ]; then \ fi endef -sqlite3-api.js := $(dir.dout)/sqlite3-api.js -sqlite3.js := $(dir.dout)/sqlite3.js -sqlite3-api.mjs := $(dir.dout)/sqlite3-api.mjs -sqlite3.mjs := $(dir.dout)/sqlite3.mjs -sqlite3-api-bundler-friendly.mjs := $(dir.dout)/sqlite3-api-bundler-friendly.mjs -sqlite3-bundler-friendly.mjs := $(dir.dout)/sqlite3-bundler-friendly.mjs -sqlite3-api-node.mjs := $(dir.dout)/sqlite3-api-node.mjs -sqlite3-node.mjs := $(dir.dout)/sqlite3-node.mjs -sqlite3-api-wasmfs.mjs := $(dir.tmp)/sqlite3-api-wasmfs.mjs -sqlite3-wasmfs.mjs := $(dir.wasmfs)/sqlite3-wasmfs.mjs -EXPORTED_FUNCTIONS.fiddle := $(dir.tmp)/EXPORTED_FUNCTIONS.fiddle +sqlite3.js = $(dir.dout)/sqlite3.js +sqlite3.mjs = $(dir.dout)/sqlite3.mjs +EXPORTED_FUNCTIONS.fiddle = $(dir.tmp)/EXPORTED_FUNCTIONS.fiddle # The various -D... values used by *.c-pp.js include: # @@ -904,24 +904,10 @@ EXPORTED_FUNCTIONS.fiddle := $(dir.tmp)/EXPORTED_FUNCTIONS.fiddle # build time). $(sqlite3.wasm): $(sqlite3.js) $(sqlite3.mjs): $(sqlite3.js) -$(sqlite3-bundler-friendly.mjs): $(sqlite3.mjs) -$(sqlite3-node.mjs): $(sqlite3.mjs) +$(dir.dout)/sqlite3-bundler-friendly.mjs: $(sqlite3.mjs) +$(dir.dout)/sqlite3-node.mjs: $(sqlite3.mjs) #CLEAN_FILES += $(sqlite3.wasm) -######################################################################## -# We need separate copies of certain supplementary JS files for the -# bundler-friendly build. Concretely, any supplemental JS files which -# themselves use importScripts() or Workers or URL() constructors -# which refer to other in-tree (m)JS files require a bundler-friendly -# copy. -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-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 - ifneq (1,$(MAKING_CLEAN)) # This block MUST come between the above definitions of # sqlite3-...js/mjs and the $(eval) calls below this block which use @@ -934,7 +920,10 @@ ifneq (1,$(MAKING_CLEAN)) # the $ references in those languages made it just as illegible as the # native makefile code. Somewhat surprisingly, moving that code generation # to C makes it slightly less illegible than the previous 3 options. -bin.mkwb := ./mkwasmbuilds +# +# Maintenance note: the various $(c-pp.D.XYZ) vars are defined in this +# step. +bin.mkwb = ./mkwasmbuilds $(bin.mkwb): $(bin.mkwb).c $(MAKEFILE) $(CC) -o $@ $< DISTCLEAN_FILES += $(bin.mkwb) @@ -946,45 +935,60 @@ DISTCLEAN_FILES += $(bin.mkwb) endif DISTCLEAN_FILES += .wasmbuilds.make -$(eval $(call SQLITE.CALL.C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1.js))) -$(eval $(call SQLITE.CALL.C-PP.FILTER,$(sqlite3-worker1.js.in),$(sqlite3-worker1-bundler-friendly.mjs),\ +######################################################################## +# We need separate copies of certain supplementary JS files for the +# bundler-friendly build. Concretely, any supplemental JS files which +# themselves use importScripts() or Workers or URL() constructors +# which refer to other in-tree (m)JS files require a bundler-friendly +# copy. Bundler-friendly builds replace certain references to string +# vars/expressions with string literals, as bundler tools are static +# code analyzers and cannot cope with the former. +# +# Most of what follows is the generation of those copies. +$(eval $(call SQLITE.CALL.C-PP.FILTER,$(dir.api)/sqlite3-worker1.c-pp.js,\ + $(dir.dout)/sqlite3-worker1.js)) +$(eval $(call SQLITE.CALL.C-PP.FILTER,$(dir.api)/sqlite3-worker1.c-pp.js,\ + $(dir.dout)/sqlite3-worker1-bundler-friendly.mjs,\ $(c-pp.D.sqlite3-bundler-friendly))) -$(eval $(call SQLITE.CALL.C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.js))) -$(eval $(call SQLITE.CALL.C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),\ - $(sqlite3-worker1-promiser-bundler-friendly.js),\ +$(eval $(call SQLITE.CALL.C-PP.FILTER,$(dir.api)/sqlite3-worker1-promiser.c-pp.js,\ + $(dir.dout)/sqlite3-worker1-promiser.js)) +$(eval $(call SQLITE.CALL.C-PP.FILTER,$(dir.api)/sqlite3-worker1-promiser.c-pp.js,\ + $(dir.dout)/sqlite3-worker1-promiser-bundler-friendly.js,\ $(c-pp.D.sqlite3-bundler-friendly))) -$(eval $(call SQLITE.CALL.C-PP.FILTER,$(sqlite3-worker1-promiser.js.in),$(sqlite3-worker1-promiser.mjs),\ +$(eval $(call SQLITE.CALL.C-PP.FILTER,$(dir.api)/sqlite3-worker1-promiser.c-pp.js,\ + $(dir.dout)/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) $(eval $(call SQLITE.CALL.C-PP.FILTER,demo-worker1-promiser.c-pp.js,demo-worker1-promiser.js)) $(eval $(call SQLITE.CALL.C-PP.FILTER,demo-worker1-promiser.c-pp.js,demo-worker1-promiser.mjs,\ -Dtarget=es6-module)) $(eval $(call SQLITE.CALL.C-PP.FILTER,demo-worker1-promiser.c-pp.html,demo-worker1-promiser.html)) $(eval $(call SQLITE.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 +$(dir.dout)/sqlite3-bundler-friendly.mjs: \ + $(dir.dout)/sqlite3-worker1-bundler-friendly.mjs \ + $(dir.dout)/sqlite3-worker1-promiser-bundler-friendly.js + +demo-worker1-promiser.html: $(dir.dout)/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) + $(dir.dout)/sqlite3-worker1-promiser.mjs \ + $(dir.dout)/sqlite3-worker1-promiser.js \ + $(dir.dout)/sqlite3-worker1-bundler-friendly.mjs \ + $(dir.dout)/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 # dumps generated by $(speedtest1) and executes them. -dir.sql := sql -speedtest1 := ../../speedtest1 -speedtest1.c := ../../test/speedtest1.c -speedtest1.sql := $(dir.sql)/speedtest1.sql -speedtest1.cliflags := --size 10 --big-transactions +dir.sql = sql +speedtest1 = ../../speedtest1 +speedtest1.c = ../../test/speedtest1.c +speedtest1.sql = $(dir.sql)/speedtest1.sql +speedtest1.cliflags = --size 10 --big-transactions $(speedtest1): $(MAKE) -C ../.. speedtest1 $(speedtest1.sql): $(speedtest1) $(MAKEFILE) @@ -1005,8 +1009,8 @@ batch: batch-runner.list # # emcc.speedtest1.common = emcc flags used by multiple builds of speedtest1 # emcc.speedtest1 = emcc flags used by main build of speedtest1 -emcc.speedtest1.common := $(emcc_opt_full) -emcc.speedtest1 := -I. -I$(dir $(sqlite3.canonical.c)) +emcc.speedtest1.common = $(emcc_opt_full) +emcc.speedtest1 = -I. -I$(dir $(sqlite3.canonical.c)) emcc.speedtest1 += -sENVIRONMENT=web emcc.speedtest1 += -sALLOW_MEMORY_GROWTH emcc.speedtest1 += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.$(emcc.INITIAL_MEMORY)) @@ -1019,7 +1023,7 @@ 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) +EXPORTED_FUNCTIONS.speedtest1 = $(abspath $(dir.tmp)/EXPORTED_FUNCTIONS.speedtest1) emcc.speedtest1.common += -sSTACK_SIZE=512KB emcc.speedtest1.common += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.speedtest1) emcc.speedtest1.common += $(emcc.exportedRuntimeMethods) @@ -1028,8 +1032,8 @@ emcc.speedtest1.common += -sDYNAMIC_EXECUTION=0 emcc.speedtest1.common += --minify 0 emcc.speedtest1.common += -sEXPORT_NAME=$(sqlite3.js.init-func) emcc.speedtest1.common += -sWASM_BIGINT=$(emcc.WASM_BIGINT) -speedtest1.exit-runtime0 := -sEXIT_RUNTIME=0 -speedtest1.exit-runtime1 := -sEXIT_RUNTIME=1 +speedtest1.exit-runtime0 = -sEXIT_RUNTIME=0 +speedtest1.exit-runtime1 = -sEXIT_RUNTIME=1 # Re -sEXIT_RUNTIME=1 vs 0: if it's 1 and speedtest1 crashes, we get # this error from emscripten: # @@ -1050,10 +1054,9 @@ speedtest1.exit-runtime1 := -sEXIT_RUNTIME=1 $(EXPORTED_FUNCTIONS.speedtest1): $(MKDIR.bld) $(EXPORTED_FUNCTIONS.api.core) @echo "Making $@ ..." @{ echo _wasm_main; cat $(EXPORTED_FUNCTIONS.api.core); } > $@ -speedtest1.js := $(dir.dout)/speedtest1.js -speedtest1.wasm := $(dir.dout)/speedtest1.wasm -emcc.flags.speedtest1-vanilla := $(cflags.common) -DSQLITE_SPEEDTEST1_WASM -speedtest1.cfiles := $(speedtest1.c) $(sqlite3-wasm.c) +speedtest1.js = $(dir.dout)/speedtest1.js +emcc.flags.speedtest1-vanilla = $(cflags.common) -DSQLITE_SPEEDTEST1_WASM +speedtest1.cfiles = $(speedtest1.c) $(sqlite3-wasm.c) $(speedtest1.js): $(MAKEFILE) $(speedtest1.cfiles) \ $(pre-post-speedtest1-vanilla.deps) \ $(EXPORTED_FUNCTIONS.speedtest1) @@ -1067,14 +1070,13 @@ $(speedtest1.js): $(MAKEFILE) $(speedtest1.cfiles) \ -USQLITE_C -DSQLITE_C=$(sqlite3.canonical.c) \ $(speedtest1.exit-runtime0) \ -o $@ $(speedtest1.cfiles) -lm - $(maybe-wasm-strip) $(speedtest1.wasm) - sed -i -e '/^var _sqlite3.*createExportWrapper/d' $@ - chmod -x $(speedtest1.wasm) - ls -la $@ $(speedtest1.wasm) + @chmod -x $(basename $@).wasm + @$(maybe-wasm-strip) $(basename $@).wasm + @$(SQLITE.strip-createExportWrapper) + @ls -la $@ $(speedtest1.wasm) speedtest1: $(speedtest1.js) all: speedtest1 -#CLEAN_FILES += $(speedtest1.js) $(speedtest1.wasm) # end speedtest1.js ######################################################################## @@ -1099,7 +1101,7 @@ $(eval $(call SQLITE.CALL.C-PP.FILTER,tester1.c-pp.js,tester1.mjs,$(c-pp.D.sqlit $(eval $(call SQLITE.CALL.C-PP.FILTER,tester1.c-pp.html,tester1.html)) $(eval $(call SQLITE.CALL.C-PP.FILTER,tester1.c-pp.html,tester1-esm.html,$(c-pp.D.sqlite3-esm))) tester1: tester1.js tester1.mjs tester1.html tester1-esm.html -# Note that we do not include $(sqlite3-bundler-friendly.mjs) in this +# Note that we do not include $(dir.dout)/sqlite3-bundler-friendly.mjs in this # because bundlers are client-specific. all quick: tester1 quick: $(sqlite3.js) @@ -1111,7 +1113,7 @@ quick: $(sqlite3.js) # painful. .PHONY: o0 o1 o2 o3 os oz -emcc-opt-extra := +emcc-opt-extra = #ifeq (1,$(wasm-bare-bones)) #emcc-opt-extra += -flto # ^^^^ -flto can have a considerably performance boost at -O0 but @@ -1139,7 +1141,7 @@ oz: clean # Sub-makes... # sqlite.org/fiddle application... -include fiddle.make +include $(MAKEFILE.fiddle) # Only add wasmfs if wasmfs.enable=1 or we're running (dist)clean ifneq (,$(filter wasmfs,$(MAKECMDGOALS))) @@ -1157,16 +1159,17 @@ ifeq (1,$(wasmfs.enable)) # little benefit. # ######################################################################## -# Some platforms do not support the WASMFS build. Raspberry Pi OS is one -# of them. As such platforms are discovered, add their (uname -m) name -# to PLATFORMS_WITH_NO_WASMFS to exclude the wasmfs build parts. -PLATFORMS_WITH_NO_WASMFS := aarch64 # add any others here -THIS_ARCH := $(shell /usr/bin/uname -m) +# Some platforms do not support the WASMFS build. Raspberry Pi OS is +# one of them (or was when that comment was initially written). As +# such platforms are discovered, add their (uname -m) name to +# PLATFORMS_WITH_NO_WASMFS to exclude the wasmfs build parts. +PLATFORMS_WITH_NO_WASMFS = aarch64 # add any others here +THIS_ARCH = $(shell /usr/bin/uname -m) ifneq (,$(filter $(THIS_ARCH),$(PLATFORMS_WITH_NO_WASMFS))) $(info This platform does not support the WASMFS build.) -HAVE_WASMFS := 0 +HAVE_WASMFS = 0 else -HAVE_WASMFS := 1 +HAVE_WASMFS = 1 include wasmfs.make endif endif @@ -1204,7 +1207,7 @@ update-docs: echo "Pass wasm.docs.home=/path/to/wasm/docs/checkout or edit this makefile to suit."; \ exit 127 else -wasm.docs.jswasm := $(wasm.docs.home)/jswasm +wasm.docs.jswasm = $(wasm.docs.home)/jswasm update-docs: $(bin.stripccomments) $(sqlite3.js) $(sqlite3.wasm) @echo "Copying files to the /wasm docs. Be sure to use an -Oz build for this!" cp $(sqlite3.wasm) $(wasm.docs.jswasm)/. diff --git a/ext/wasm/api/sqlite3-api-oo1.c-pp.js b/ext/wasm/api/sqlite3-api-oo1.c-pp.js index 06f916002..8663dcdde 100644 --- a/ext/wasm/api/sqlite3-api-oo1.c-pp.js +++ b/ext/wasm/api/sqlite3-api-oo1.c-pp.js @@ -13,7 +13,7 @@ This file contains the so-called OO #1 API wrapper for the sqlite3 WASM build. It requires that sqlite3-api-glue.js has already run - and it installs its deliverable as globalThis.sqlite3.oo1. + and it installs its deliverable as sqlite3.oo1. */ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ const toss3 = (...args)=>{throw new sqlite3.SQLite3Error(...args)}; @@ -38,6 +38,21 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ */ const __ptrMap = new WeakMap(); /** + A Set of oo1.DB or oo1.Stmt objects which are proxies for + (sqlite3*) resp. (sqlite3_stmt*) pointers which themselves are + owned elsewhere. Objects in this Set do not own their underlying + handle and that handle must be guaranteed (by the client) to + outlive the proxy. DB.close()/Stmt.finalize() methods will remove + the object from this Set _instead_ of closing/finalizing the + pointer. These proxies are primarily intended as a way to briefly + wrap an (sqlite3[_stmt]*) object as an oo1.DB/Stmt without taking + over ownership, to take advantage of simplifies usage compared to + the C API while not imposing any change of ownership. + + See DB.wrapHandle() and Stmt.wrapHandle(). + */ + const __doesNotOwnHandle = new Set(); + /** Map of DB instances to objects, each object being a map of Stmt wasm pointers to Stmt objects. */ @@ -234,73 +249,89 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }; } const opt = ctor.normalizeArgs(...args); - let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags; - if(('string'!==typeof fn && 'number'!==typeof fn) - || 'string'!==typeof flagsStr - || (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){ - sqlite3.config.error("Invalid DB ctor args",opt,arguments); - toss3("Invalid arguments for DB constructor."); - } - let fnJs = ('number'===typeof fn) ? wasm.cstrToJs(fn) : fn; - const vfsCheck = ctor._name2vfs[fnJs]; - if(vfsCheck){ - vfsName = vfsCheck.vfs; - fn = fnJs = vfsCheck.filename(fnJs); - } - let pDb, oflags = 0; - if( flagsStr.indexOf('c')>=0 ){ - oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE; - } - if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE; - if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY; - oflags |= capi.SQLITE_OPEN_EXRESCODE; - const stack = wasm.pstack.pointer; - try { - const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */; - let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0); - pDb = wasm.peekPtr(pPtr); - checkSqlite3Rc(pDb, rc); - capi.sqlite3_extended_result_codes(pDb, 1); - if(flagsStr.indexOf('t')>=0){ - capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT, - __dbTraceToConsole, pDb); + //sqlite3.config.debug("DB ctor",opt); + let pDb; + if( (pDb = opt['sqlite3*']) ){ + /* This property ^^^^^ is very specifically NOT DOCUMENTED and + NOT part of the public API. This is a back door for functions + like DB.wrapDbHandle(). */ + //sqlite3.config.debug("creating proxy db from",opt); + if( !opt['sqlite3*:takeOwnership'] ){ + /* This is object does not own its handle. */ + __doesNotOwnHandle.add(this); } - }catch( e ){ - if( pDb ) capi.sqlite3_close_v2(pDb); - throw e; - }finally{ - wasm.pstack.restore(stack); + this.filename = capi.sqlite3_db_filename(pDb,'main'); + }else{ + let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags; + if(('string'!==typeof fn && 'number'!==typeof fn) + || 'string'!==typeof flagsStr + || (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){ + sqlite3.config.error("Invalid DB ctor args",opt,arguments); + toss3("Invalid arguments for DB constructor."); + } + let fnJs = ('number'===typeof fn) ? wasm.cstrToJs(fn) : fn; + const vfsCheck = ctor._name2vfs[fnJs]; + if(vfsCheck){ + vfsName = vfsCheck.vfs; + fn = fnJs = vfsCheck.filename(fnJs); + } + let oflags = 0; + if( flagsStr.indexOf('c')>=0 ){ + oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE; + } + if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE; + if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY; + oflags |= capi.SQLITE_OPEN_EXRESCODE; + const stack = wasm.pstack.pointer; + try { + const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */; + let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0); + pDb = wasm.peekPtr(pPtr); + checkSqlite3Rc(pDb, rc); + capi.sqlite3_extended_result_codes(pDb, 1); + if(flagsStr.indexOf('t')>=0){ + capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT, + __dbTraceToConsole, pDb); + } + }catch( e ){ + if( pDb ) capi.sqlite3_close_v2(pDb); + throw e; + }finally{ + wasm.pstack.restore(stack); + } + this.filename = fnJs; } - this.filename = fnJs; __ptrMap.set(this, pDb); __stmtMap.set(this, Object.create(null)); - try{ + if( !opt['sqlite3*'] ){ + try{ //#if enable-see - dbCtorApplySEEKey(this,opt); + dbCtorApplySEEKey(this,opt); //#endif - // Check for per-VFS post-open SQL/callback... - const pVfs = capi.sqlite3_js_db_vfs(pDb) - || toss3("Internal error: cannot get VFS for new db handle."); - const postInitSql = __vfsPostOpenCallback[pVfs]; - if(postInitSql){ - /** - Reminder: if this db is encrypted and the client did _not_ pass - in the key, any init code will fail, causing the ctor to throw. - We don't actually know whether the db is encrypted, so we cannot - sensibly apply any heuristics which skip the init code only for - encrypted databases for which no key has yet been supplied. - */ - if(postInitSql instanceof Function){ - postInitSql(this, sqlite3); - }else{ - checkSqlite3Rc( - pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0) - ); + // Check for per-VFS post-open SQL/callback... + const pVfs = capi.sqlite3_js_db_vfs(pDb) + || toss3("Internal error: cannot get VFS for new db handle."); + const postInitSql = __vfsPostOpenCallback[pVfs]; + if(postInitSql){ + /** + Reminder: if this db is encrypted and the client did _not_ pass + in the key, any init code will fail, causing the ctor to throw. + We don't actually know whether the db is encrypted, so we cannot + sensibly apply any heuristics which skip the init code only for + encrypted databases for which no key has yet been supplied. + */ + if(postInitSql instanceof Function){ + postInitSql(this, sqlite3); + }else{ + checkSqlite3Rc( + pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0) + ); + } } + }catch(e){ + this.close(); + throw e; } - }catch(e){ - this.close(); - throw e; } }; @@ -403,7 +434,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - `vfs`: the VFS fname //#if enable-see - SEE-capable builds optionally support ONE of the following additional options: @@ -429,7 +459,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ is supplied and the database is encrypted, execution of the post-initialization SQL will fail, causing the constructor to throw. - //#endif enable-see The `filename` and `vfs` arguments may be either JS strings or @@ -457,8 +486,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ /** Internal-use enum for mapping JS types to DB-bindable types. These do not (and need not) line up with the SQLITE_type - values. All values in this enum must be truthy and distinct - but they need not be numbers. + values. All values in this enum must be truthy and (mostly) + distinct but they need not be numbers. */ const BindTypes = { null: 1, @@ -467,7 +496,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ boolean: 4, blob: 5 }; - BindTypes['undefined'] == BindTypes.null; if(wasm.bigIntEnabled){ BindTypes.bigint = BindTypes.number; } @@ -486,26 +514,30 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - `db`: the DB object which created the statement. - `columnCount`: the number of result columns in the query, or 0 - for queries which cannot return results. This property is a proxy - for sqlite3_column_count() and its use in loops should be avoided - because of the call overhead associated with that. The - `columnCount` is not cached when the Stmt is created because a - schema change made via a separate db connection between this - statement's preparation and when it is stepped may invalidate it. + for queries which cannot return results. This property is a + read-only proxy for sqlite3_column_count() and its use in loops + should be avoided because of the call overhead associated with + that. The `columnCount` is not cached when the Stmt is created + because a schema change made between this statement's preparation + and when it is stepped may invalidate it. - - `parameterCount`: the number of bindable parameters in the query. + - `parameterCount`: the number of bindable parameters in the + query. Like `columnCount`, this property is ready-only and is a + proxy for a C API call. As a general rule, most methods of this class will throw if called on an instance which has been finalized. For brevity's sake, the method docs do not all repeat this warning. */ - const Stmt = function(){ + const Stmt = function(/*oo1db, stmtPtr, BindTypes [,takeOwnership=true] */){ if(BindTypes!==arguments[2]){ toss3(capi.SQLITE_MISUSE, "Do not call the Stmt constructor directly. Use DB.prepare()."); } this.db = arguments[0]; __ptrMap.set(this, arguments[1]); - this.parameterCount = capi.sqlite3_bind_parameter_count(this.pointer); + if( arguments.length>3 && !arguments[3] ){ + __doesNotOwnHandle.add(this); + } }; /** Throws if the given DB has been closed, else it is returned. */ @@ -698,10 +730,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }, /** Finalizes all open statements and closes this database - connection. This is a no-op if the db has already been - closed. After calling close(), `this.pointer` will resolve to - `undefined`, so that can be used to check whether the db - instance is still opened. + connection (with one exception noted below). This is a no-op if + the db has already been closed. After calling close(), + `this.pointer` will resolve to `undefined`, and that can be + used to check whether the db instance is still opened. If this.onclose.before is a function then it is called before any close-related cleanup. @@ -721,14 +753,19 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ all, will never trigger close(), so onclose handlers are not a reliable way to implement close-time cleanup or maintenance of a db. + + If this instance was created using DB.wrapHandle() and does not + own this.pointer then it does not close the db handle but it + does perform all other work, such as calling onclose callbacks + and disassociating this object from this.pointer. */ close: function(){ - if(this.pointer){ + const pDb = this.pointer; + if(pDb){ if(this.onclose && (this.onclose.before instanceof Function)){ try{this.onclose.before(this)} catch(e){/*ignore*/} } - const pDb = this.pointer; Object.keys(__stmtMap.get(this)).forEach((k,s)=>{ if(s && s.pointer){ try{s.finalize()} @@ -737,7 +774,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }); __ptrMap.delete(this); __stmtMap.delete(this); - capi.sqlite3_close_v2(pDb); + if( !__doesNotOwnHandle.delete(this) ){ + capi.sqlite3_close_v2(pDb); + } if(this.onclose && (this.onclose.after instanceof Function)){ try{this.onclose.after(this)} catch(e){/*ignore*/} @@ -1450,9 +1489,63 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ */ checkRc: function(resultCode){ return checkSqlite3Rc(this, resultCode); - } + }, }/*DB.prototype*/; + /** + Returns a new oo1.DB instance which wraps the given (sqlite3*) + WASM pointer, optionally with or without taking over ownership of + that pointer. + + The first argument must be either a non-NULL (sqlite3*) WASM + pointer. + + The second argument, defaulting to false, specifies ownership of + the first argument. If it is truthy, the returned object will + pass that pointer to sqlite3_close() when its close() method is + called, otherwise it will not. + + Throws if pDb is not a non-0 WASM pointer. + + The caller MUST GUARANTEE that the passed-in handle will outlive + the returned object, i.e. that it will not be closed. If it is closed, + this object will hold a stale pointer and results are undefined. + + Aside from its lifetime, the proxy is to be treated as any other + DB instance, including the requirement of calling close() on + it. close() will free up internal resources owned by the proxy + and disassociate the proxy from that handle but will not + actually close the proxied db handle unless this function is + passed a thruthy second argument. + + To stress: + + - DO NOT call sqlite3_close() (or similar) on the being-proxied + pointer while a proxy is active. + + - ALWAYS eventually call close() on the returned object. If the + proxy does not own the underlying handle then its MUST be + closed BEFORE the being-proxied handle is closed. + + Design notes: + + - wrapHandle() "could" accept a DB object instance as its first + argument and proxy thatDb.pointer but there is currently no use + case where doing so would be useful, so it does not allow + that. That restriction may be lifted in a future version. + */ + DB.wrapHandle = function(pDb, takeOwnership=false){ + if( !pDb || !wasm.isPtr(pDb) ){ + throw new sqlite3.SQLite3Error(capi.SQLITE_MISUSE, + "Argument must be a WASM sqlite3 pointer"); + } + return new DB({ + /* This ctor call style is very specifically internal-use-only. + It is not documented and may change at any time. */ + "sqlite3*": pDb, + "sqlite3*:takeOwnership": !!takeOwnership + }); + }; /** Throws if the given Stmt has been finalized, else stmt is returned. */ @@ -1474,8 +1567,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ case BindTypes.string: return t; case BindTypes.bigint: - if(wasm.bigIntEnabled) return t; - /* else fall through */ + return wasm.bigIntEnabled ? t : undefined; default: return util.isBindableTypedArray(v) ? BindTypes.blob : undefined; } @@ -1641,12 +1733,19 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ This method always throws if called when it is illegal to do so. Namely, when triggered via a per-row callback handler of a DB.exec() call. + + If Stmt does not own its underlying (sqlite3_stmt*) (see + Stmt.wrapHandle()) then this function will not pass it to + sqlite3_finalize(). */ finalize: function(){ - if(this.pointer){ + const ptr = this.pointer; + if(ptr){ affirmNotLockedByExec(this,'finalize()'); - const rc = capi.sqlite3_finalize(this.pointer); - delete __stmtMap.get(this.db)[this.pointer]; + const rc = (__doesNotOwnHandle.delete(this) + ? 0 + : capi.sqlite3_finalize(ptr)); + delete __stmtMap.get(this.db)[ptr]; __ptrMap.delete(this); __execLock.delete(this); __stmtMayGet.delete(this); @@ -2134,6 +2233,64 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ set: ()=>toss3("The columnCount property is read-only.") }); + Object.defineProperty(Stmt.prototype, 'parameterCount', { + enumerable: false, + get: function(){return capi.sqlite3_bind_parameter_count(this.pointer)}, + set: ()=>toss3("The parameterCount property is read-only.") + }); + + /** + The Stmt counterpart of oo1.DB.wrapHandle(), this creates a Stmt + instance which wraps a WASM (sqlite3_stmt*) in the oo1 API, + optionally with or without taking over ownership of that pointer. + + The first argument must be an oo1.DB instance[^1]. + + The second argument must be a valid WASM (sqlite3_stmt*), as + produced by sqlite3_prepare_v2() and sqlite3_prepare_v3(). + + The third argument, defaulting to false, specifies whether the + returned Stmt object takes over ownership of the underlying + (sqlite3_stmt*). If true, the returned object's finalize() method + will finalize that handle, else it will not. If it is false, + ownership of pStmt is unchanged and pStmt MUST outlive the + returned object or results are undefined. + + This function throws if the arguments are invalid. On success it + returns a new Stmt object which wraps the given statement + pointer. + + Like all Stmt objects, the finalize() method must eventually be + called on the returned object to free up internal resources, + regardless of whether this function's third argument is true or + not. + + [^1]: The first argument cannot be a (sqlite3*) because the + resulting Stmt object requires a parent DB object. It is not yet + determined whether it would be of general benefit to refactor the + DB/Stmt pair internals to communicate in terms of the underlying + (sqlite3*) rather than a DB object. If so, we could laxen the + first argument's requirement and allow an (sqlite3*). Because + DB.wrapHandle() enables multiple DB objects to proxy the same + (sqlite3*), we cannot unambiguously translate the first arugment + from (sqlite3*) to DB instances for us with this function's first + argument. + */ + Stmt.wrapHandle = function(oo1db, pStmt, takeOwnership=false){ + let ctor = Stmt; + if( !(oo1db instanceof DB) || !oo1db.pointer ){ + throw new sqlite3.SQLite3Error(sqlite3.SQLITE_MISUSE, + "First argument must be an opened "+ + "sqlite3.oo1.DB instance"); + } + if( !pStmt || !wasm.isPtr(pStmt) ){ + throw new sqlite3.SQLite3Error(sqlite3.SQLITE_MISUSE, + "Second argument must be a WASM "+ + "sqlite3_stmt pointer"); + } + return new Stmt(oo1db, pStmt, BindTypes, !!takeOwnership); + } + /** The OO API's public namespace. */ sqlite3.oo1 = { DB, diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index 7e128a3fa..e3807a314 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -134,22 +134,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( const config = Object.assign(Object.create(null),{ exports: undefined, memory: undefined, - bigIntEnabled: (()=>{ - if('undefined'!==typeof Module){ - /* Emscripten module will contain HEAPU64 when built with - -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; - })(), + bigIntEnabled: !!globalThis.BigInt64Array, debug: console.debug.bind(console), warn: console.warn.bind(console), error: console.error.bind(console), diff --git a/ext/wasm/api/sqlite3-api-worker1.c-pp.js b/ext/wasm/api/sqlite3-api-worker1.c-pp.js index 5e088f438..55ad16185 100644 --- a/ext/wasm/api/sqlite3-api-worker1.c-pp.js +++ b/ext/wasm/api/sqlite3-api-worker1.c-pp.js @@ -385,10 +385,19 @@ sqlite3.initWorker1API = function(){ const getDbId = function(db){ let id = wState.idMap.get(db); if(id) return id; - id = 'db#'+(++wState.idSeq)+'@'+db.pointer; + id = 'db#'+(++wState.idSeq)+':'+ + Math.floor(Math.random() * 100000000)+':'+ + Math.floor(Math.random() * 100000000); /** ^^^ can't simply use db.pointer b/c closing/opening may re-use the same address, which could map pending messages to a wrong - instance. */ + instance. + + 2025-07: https://github.com/sqlite/sqlite-wasm/issues/113 + demonstrates that two Worker1s can end up with the same IDs, + despite using different instances of the library, so we need + to add some randomness to the IDs instead of relying on the + pointer addresses. + */ wState.idMap.set(db, id); return id; }; diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index ee8a10209..574684ce9 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -939,6 +939,7 @@ const char * sqlite3__wasm_enum_json(void){ DefInt(SQLITE_INNOCUOUS); DefInt(SQLITE_SUBTYPE); DefInt(SQLITE_RESULT_SUBTYPE); + DefInt(SQLITE_SELFORDER1); } _DefGroup; DefGroup(version) { diff --git a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js index c043fd148..2edabe3e6 100644 --- a/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js +++ b/ext/wasm/api/sqlite3-worker1-promiser.c-pp.js @@ -130,12 +130,10 @@ Notable shortcomings: - - This API was not designed with ES6 modules in mind. Neither Firefox - nor Safari support, as of March 2023, the {type:"module"} flag to the - Worker constructor, so that particular usage is not something we're going - to target for the time being: - - https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker + - "v1" of this this API is not suitable for use as an ESM module + because ESM worker modules were not widely supported when it was + developed. For use as an ESM module, see the "v2" interface later + on in this file. */ globalThis.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){ // Inspired by: https://stackoverflow.com/a/52439530 @@ -296,7 +294,7 @@ globalThis.sqlite3Worker1Promiser.defaultConfig = { after calling the original function and will reject if that function throws. */ -sqlite3Worker1Promiser.v2 = function(config){ +globalThis.sqlite3Worker1Promiser.v2 = function callee(config = callee.defaultConfig){ let oldFunc; if( 'function' == typeof config ){ oldFunc = config; @@ -326,11 +324,14 @@ sqlite3Worker1Promiser.v2 = function(config){ } return p; }.bind({ - /* We do this because clients are - recommended to delete globalThis.sqlite3Worker1Promiser. */ + /* We do this because clients are recommended to delete + globalThis.sqlite3Worker1Promiser. */ original: sqlite3Worker1Promiser }); +globalThis.sqlite3Worker1Promiser.v2.defaultConfig = + globalThis.sqlite3Worker1Promiser.defaultConfig; + //#if target=es6-module /** When built as a module, we export sqlite3Worker1Promiser.v2() diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js index b4d8f691b..e85669579 100644 --- a/ext/wasm/common/whwasmutil.js +++ b/ext/wasm/common/whwasmutil.js @@ -1773,10 +1773,10 @@ globalThis.WhWasmUtilInstaller = function(target){ does not have a stable interface. */ xArg.FuncPtrAdapter.warnOnUse = false; - /** If true, convertArg() will FuncPtrAdapter.debugOut() when it - (un)installs a function binding to/from WASM. Note that - deinstallation of bindScope=transient bindings happens - via scopedAllocPop() so will not be output. */ + /** If true, convertArg() will call FuncPtrAdapter.debugOut() when + it (un)installs a function binding to/from WASM. Note that + deinstallation of bindScope=transient bindings happens via + scopedAllocPop() so will not be output. */ xArg.FuncPtrAdapter.debugFuncInstall = false; /** Function used for debug output. */ @@ -1827,9 +1827,8 @@ globalThis.WhWasmUtilInstaller = function(target){ The first argument must be one of: - A JavaScript function. - - The name of a WASM-exported function. In the latter case xGet() - is used to fetch the exported function, which throws if it's not - found. + - The name of a WASM-exported function. xGet() is used to fetch + the exported function, which throws if it's not found. - A pointer into the indirect function table. e.g. a pointer returned from target.installFunction(). @@ -1874,9 +1873,6 @@ globalThis.WhWasmUtilInstaller = function(target){ which convert their argument to an integer and truncate it to the given bit length. - - `N*` (args): a type name in the form `N*`, where N is a numeric - type name, is treated the same as WASM pointer. - - `*` and `pointer` (args): are assumed to be WASM pointer values and are returned coerced to an appropriately-sized pointer value (i32 or i64). Non-numeric values will coerce to 0 and @@ -1887,7 +1883,15 @@ globalThis.WhWasmUtilInstaller = function(target){ WASM pointer numeric type. - `**` (args): is simply a descriptive alias for the WASM pointer - type. It's primarily intended to mark output-pointer arguments. + type. It's primarily intended to mark output-pointer arguments, + noting that JS's view of WASM does not distinguish between + pointers and pointers-to-pointers, so all such interpretation + of `**`, as distinct from `*`, necessarily happens at the + client level. + + - `NumType*` (args): a type name in this form, where T is + the name of a numeric mapping, e.g. 'int16' or 'double', + is treated like `*`. - `i64` (args and results): passes the value to BigInt() to convert it to an int64. Only available if bigIntEnabled is @@ -1916,7 +1920,7 @@ globalThis.WhWasmUtilInstaller = function(target){ UTF-8-encoded C-string to pass to the exported function, cleaning it up before the wrapper returns. If a long-lived C-string pointer is required, that requires client-side code - to create the string, then pass its pointer to the function. + to create the string then pass its pointer to the function. - Else the arg is assumed to be a pointer to a string the client has already allocated and it's passed on as @@ -2091,8 +2095,8 @@ globalThis.WhWasmUtilInstaller = function(target){ easily convert, e.g., to C-strings, and have them cleaned up automatically before the wrapper returns to the caller. Likewise, if a _result_ adapter uses scoped allocation, the result will be - freed before because they would be freed before the wrapper - returns, leading to chaos and undefined behavior. + freed before the wrapper returns, leading to chaos and undefined + behavior. Except when called as a getter, this function returns itself. */ diff --git a/ext/wasm/config.make.in b/ext/wasm/config.make.in index f30baac3f..4c8d7893b 100644 --- a/ext/wasm/config.make.in +++ b/ext/wasm/config.make.in @@ -1,15 +1,16 @@ -# Gets filtered by the configure script +# config.make.in gets filtered by the top-most configure script to +# create config.make. bin.bash = @BIN_BASH@ bin.emcc = @EMCC_WRAPPER@ bin.wasm-strip = @BIN_WASM_STRIP@ bin.wasm-opt = @BIN_WASM_OPT@ -SHELL := $(bin.bash) +SHELL = $(bin.bash) # The following overrides can be uncommented to test various # validation and if/else branches the makefile code: # -#bin.bash := -#bin.emcc := -#bin.wasm-strip := -#bin.wasm-opt := +#bin.bash = +#bin.emcc = +#bin.wasm-strip = +#bin.wasm-opt = diff --git a/ext/wasm/dist.make b/ext/wasm/dist.make index 60699ff5c..176972fd7 100644 --- a/ext/wasm/dist.make +++ b/ext/wasm/dist.make @@ -11,7 +11,7 @@ # distinctly different zip file and top directory name to distinguish # them from release builds. ####################################################################### -MAKEFILE.dist := $(lastword $(MAKEFILE_LIST)) +MAKEFILE.dist = $(lastword $(MAKEFILE_LIST)) ######################################################################## # Chicken/egg situation: we need $(bin.version-info) to get the @@ -20,16 +20,16 @@ MAKEFILE.dist := $(lastword $(MAKEFILE_LIST)) # have to use a temporary name for the archive until we can get # that binary built. ifeq (1,$(SQLITE_C_IS_SEE)) -dist-name-extra := -see +dist-name-extra = -see else -dist-name-extra := +dist-name-extra = endif ifeq (,$(filter snapshot,$(MAKECMDGOALS))) -dist-name-prefix := sqlite-wasm$(dist-name-extra) +dist-name-prefix = sqlite-wasm$(dist-name-extra) else -dist-name-prefix := sqlite-wasm$(dist-name-extra)-snapshot-$(shell /usr/bin/date +%Y%m%d) +dist-name-prefix = sqlite-wasm$(dist-name-extra)-snapshot-$(shell /usr/bin/date +%Y%m%d) endif -dist-name := $(dist-name-prefix)-TEMP +dist-name = $(dist-name-prefix)-TEMP ######################################################################## # dist.build must be the name of a target which triggers the build of @@ -45,10 +45,10 @@ dist-name := $(dist-name-prefix)-TEMP # reason not to. dist.build ?= oz -dist-dir.top := $(dist-name) -dist-dir.jswasm := $(dist-dir.top)/$(notdir $(dir.dout)) -dist-dir.common := $(dist-dir.top)/common -dist.top.extras := \ +dist-dir.top = $(dist-name) +dist-dir.jswasm = $(dist-dir.top)/$(notdir $(dir.dout)) +dist-dir.common = $(dist-dir.top)/common +dist.top.extras = \ demo-123.html demo-123-worker.html demo-123.js \ tester1.html tester1-worker.html tester1-esm.html \ tester1.js tester1.mjs \ @@ -56,9 +56,9 @@ dist.top.extras := \ demo-worker1.html demo-worker1.js \ demo-worker1-promiser.html demo-worker1-promiser.js \ demo-worker1-promiser-esm.html demo-worker1-promiser.mjs -dist.jswasm.extras := $(sqlite3.wasm) \ +dist.jswasm.extras = $(sqlite3.wasm) \ $(sqlite3-api.ext.jses) -dist.common.extras := \ +dist.common.extras = \ $(wildcard $(dir.common)/*.css) \ $(dir.common)/SqliteTestUtil.js @@ -77,12 +77,12 @@ $(bin.stripccomments) $(2) < $(1) > $(dist-dir.jswasm)/$(notdir $(1)) || exit; endef # STRIP_K1.js = list of JS files which need to be passed through # $(bin.stripcomments) with a single -k flag. -STRIP_K1.js := $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js) \ +STRIP_K1.js = $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js) \ $(sqlite3-worker1-bundler-friendly.js) \ $(sqlite3-api.ext.jses) # STRIP_K2.js = list of JS files which need to be passed through # $(bin.stripcomments) with two -k flags. -STRIP_K2.js := $(sqlite3.js) $(sqlite3.mjs) \ +STRIP_K2.js = $(sqlite3.js) $(sqlite3.mjs) \ $(sqlite3-bundler-friendly.mjs) $(sqlite3-node.mjs) ######################################################################## # dist: create the end-user deliverable archive. @@ -104,8 +104,8 @@ STRIP_K2.js := $(sqlite3.js) $(sqlite3.mjs) \ # to know that it's in a regex or string literal. Because of that, # comment-stripping is currently disabled, which means the builds will # be significantly larger than before. -#apply_comment_stripper := false -apply_comment_stripper := true +#apply_comment_stripper = false +apply_comment_stripper = true # ^^^ shell command true or false dist: \ $(bin.stripccomments) $(bin.version-info) \ diff --git a/ext/wasm/fiddle.make b/ext/wasm/fiddle.make index 8110384a6..6bdf44195 100644 --- a/ext/wasm/fiddle.make +++ b/ext/wasm/fiddle.make @@ -3,13 +3,12 @@ # # Intended to include'd by ./GNUmakefile. ####################################################################### -MAKEFILE.fiddle := $(lastword $(MAKEFILE_LIST)) ######################################################################## # shell.c and its build flags... ifneq (1,$(MAKING_CLEAN)) - make-np-0 := make -C $(dir.top) -n -p - make-np-1 := sed -e 's/(TOP)/(dir.top)/g' + make-np-0 = make -C $(dir.top) -n -p + make-np-1 = sed -e 's/(TOP)/(dir.top)/g' # Extract SHELL_OPT and SHELL_DEP from the top-most makefile and import # them as vars here... $(eval $(shell $(make-np-0) | grep -e '^SHELL_OPT ' | $(make-np-1))) @@ -27,7 +26,7 @@ endif # /shell.c ######################################################################## -EXPORTED_FUNCTIONS.fiddle := $(dir.tmp)/EXPORTED_FUNCTIONS.fiddle +EXPORTED_FUNCTIONS.fiddle = $(dir.tmp)/EXPORTED_FUNCTIONS.fiddle fiddle.emcc-flags = \ $(emcc.cflags) $(emcc_opt_full) \ --minify 0 \ @@ -40,9 +39,8 @@ fiddle.emcc-flags = \ -sWASM_BIGINT=$(emcc.WASM_BIGINT) \ -sEXPORT_NAME=$(sqlite3.js.init-func) \ -Wno-limited-postlink-optimizations \ - $(emcc.exportedRuntimeMethods) \ + $(emcc.exportedRuntimeMethods),FS \ -sEXPORTED_FUNCTIONS=@$(abspath $(EXPORTED_FUNCTIONS.fiddle)) \ - -sEXPORTED_RUNTIME_METHODS=FS,wasmMemory \ $(SQLITE_OPT.full-featured) \ $(SQLITE_OPT.common) \ $(SHELL_OPT) \ @@ -53,12 +51,12 @@ fiddle.emcc-flags = \ # Flags specifically for debug builds of fiddle. Performance suffers # greatly in debug builds. -fiddle.emcc-flags.debug := $(fiddle.emcc-flags) \ +fiddle.emcc-flags.debug = $(fiddle.emcc-flags) \ -DSQLITE_DEBUG \ -DSQLITE_ENABLE_SELECTTRACE \ -DSQLITE_ENABLE_WHERETRACE -fiddle.EXPORTED_FUNCTIONS.in := \ +fiddle.EXPORTED_FUNCTIONS.in = \ EXPORTED_FUNCTIONS.fiddle.in \ $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-core \ $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-extras @@ -67,10 +65,7 @@ $(EXPORTED_FUNCTIONS.fiddle): $(MKDIR.bld) $(fiddle.EXPORTED_FUNCTIONS.in) \ $(MAKEFILE.fiddle) sort -u $(fiddle.EXPORTED_FUNCTIONS.in) > $@ -fiddle.cses := $(dir.top)/shell.c $(sqlite3-wasm.c) - -fiddle: $(fiddle-module.js) $(fiddle-module.js.debug) -fiddle.debug: $(fiddle-module.js.debug) +fiddle.cses = $(dir.top)/shell.c $(sqlite3-wasm.c) clean: clean-fiddle clean-fiddle: @@ -84,10 +79,9 @@ clean-fiddle: all: fiddle ######################################################################## -# fiddle_remote is the remote destination for the fiddle app. It -# must be a [user@]HOST:/path for rsync. -# Note that the target "should probably" contain a symlink of -# index.html -> fiddle.html. +# fiddle_remote is the remote destination for the fiddle app. It must +# be a [user@]HOST:/path for rsync. The target "should probably" +# contain a symlink of index.html -> fiddle.html. fiddle_remote ?= ifeq (,$(fiddle_remote)) ifneq (,$(wildcard /home/stephan)) @@ -154,11 +148,6 @@ push-fiddle: fiddle # because certain execution environments disallow those constructs. # This flag is not strictly necessary, however. # -# -sWASM_BIGINT is UNTESTED but "should" allow the int64-using C APIs -# to work with JS/wasm, insofar as the JS environment supports the -# BigInt type. That support requires an extremely recent browser: -# Safari didn't get that support until late 2020. -# # --no-entry: for compiling library code with no main(). If this is # not supplied and the code has a main(), it is called as part of the # module init process. Note that main() is #if'd out of shell.c @@ -180,14 +169,15 @@ push-fiddle: fiddle # minification makes little difference in terms of overall # distributable size. # -# --minify 0: disables minification of the generated JS code, -# regardless of optimization level. Minification of the JS has -# minimal overall effect in the larger scheme of things and results -# in JS files which can neither be edited nor viewed as text files in -# Fossil (which flags them as binary because of their extreme line -# lengths). Interestingly, whether or not the comments in the -# generated JS file get stripped is unaffected by this setting and -# depends entirely on the optimization level. Higher optimization +# --minify 0: supposedly disables minification of the generated JS +# code, regardless of optimization level, but that's not quite true: +# search the main makefile for wasm-strip for details. Minification +# of the JS has minimal overall effect in the larger scheme of things +# and results in JS files which can neither be edited nor viewed as +# text files in Fossil (which flags them as binary because of their +# extreme line lengths). Interestingly, whether or not the comments +# in the generated JS file get stripped is unaffected by this setting +# and depends entirely on the optimization level. Higher optimization # levels reduce the size of the JS considerably even without # minification. # diff --git a/ext/wasm/fiddle/fiddle-worker.js b/ext/wasm/fiddle/fiddle-worker.js index 27d915eb2..9c6cddb0f 100644 --- a/ext/wasm/fiddle/fiddle-worker.js +++ b/ext/wasm/fiddle/fiddle-worker.js @@ -163,9 +163,11 @@ fiddleModule.isDead = true; return false; } - stdout("SQLite version", capi.sqlite3_libversion(), - capi.sqlite3_sourceid().substr(0,19)); - stdout('Welcome to the "fiddle" shell.'); + wMsg('sqlite-version', { + lib: capi.sqlite3_libversion(), + srcId: capi.sqlite3_sourceid() + }); + stdout('Welcome to the "fiddle" shell. Tap the About button for more info.'); if(capi.sqlite3_vfs_find("opfs")){ stdout("\nOPFS is available. To open a persistent db, use:\n\n", " .open file:name?vfs=opfs\n\nbut note that some", @@ -281,6 +283,7 @@ stderr("'open' expects {buffer:Uint8Array} containing an uploaded db."); return; } + buffer.set([1,1], 18)/*force db out of WAL mode*/; const fn = ( opt.filename ? opt.filename.split(/[/\\]/).pop().replace('"','_') diff --git a/ext/wasm/fiddle/fiddle.js b/ext/wasm/fiddle/fiddle.js index f0a89f25d..877a87772 100644 --- a/ext/wasm/fiddle/fiddle.js +++ b/ext/wasm/fiddle/fiddle.js @@ -329,6 +329,21 @@ SF.worker = new Worker('fiddle-worker.js'+self.location.search); SF.worker.onmessage = (ev)=>SF.runMsgHandlers(ev.data); SF.addMsgHandler(['stdout', 'stderr'], (ev)=>SF.echo(ev.data)); + SF.addMsgHandler('sqlite-version', (ev)=>{ + const v = ev.data; + const a = E('#sqlite-version-link'); + const li = v.srcId.split(' ')/*DATE TIME HASH*/; + a.setAttribute('href', + //'https://sqlite.org/src/timeline/?c='+li[2].substr(0,20) + 'https://sqlite.org/src/info/'+li[2].substr(0,20) + ); + a.setAttribute('target', '_blank'); + a.innerText = [ + v.lib, + v.srcId.substr(0,34) + ].join(' '); + SF.echo("SQLite version",a.innerText); + }); /* querySelectorAll() proxy */ const EAll = function(/*[element=document,] cssSelector*/){ @@ -391,6 +406,143 @@ self.onSFLoaded(); }); + SF.e ={ + about: E('#view-about'), + split: E('#view-split'), + terminal: E('#view-terminal'), + hideInTerminal: EAll('.hide-in-terminal' + /* Elements to hide when in terminal mode */) + }; + SF.eViews = EAll('.app-view'); + SF.setMainView = function(eMain){ + if( SF.e.main === eMain ) return; + SF.eViews.forEach((e)=>{ + if( e===eMain ) e.classList.remove('hidden'); + else e.classList.add('hidden'); + }); + SF.e.hideInTerminal.forEach(e=>{ + if( eMain === SF.e.terminal ) e.classList.add('hidden'); + else e.classList.remove('hidden'); + }); + SF.e.main = eMain; + SF.ForceResizeKludge(); + }; + + /** Toggle the "About" view on and off. */ + SF.toggleAbout = function(){ + if( SF.e.about.classList.contains('hidden') ){ + SF.e.about.$returnTo = SF.e.main; + SF.setMainView( SF.e.about ); + }else{ + const e = SF.e.about.$returnTo; + delete SF.e.about.$returnTo; + SF.setMainView( e ); + } + }; + + /** + Given a DOM element, this routine measures its "effective + height", which is the bounding top/bottom range of this element + and all of its children, recursively. For some DOM structure + cases, a parent may have a reported height of 0 even though + children have non-0 sizes. + + Returns 0 if !e or if the element really has no height. + */ + const effectiveHeight = function f(e){ + if(!e) return 0; + if(!f.measure){ + f.measure = function callee(e, depth){ + if(!e) return; + const m = e.getBoundingClientRect(); + if(0===depth){ + callee.top = m.top; + callee.bottom = m.bottom; + }else{ + callee.top = m.top ? Math.min(callee.top, m.top) : callee.top; + callee.bottom = Math.max(callee.bottom, m.bottom); + } + Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1)); + if(0===depth){ + //console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top)); + f.extra += callee.bottom - callee.top; + } + return f.extra; + }; + } + f.extra = 0; + f.measure(e,0); + return f.extra; + }; + + /** + Returns a function, that, as long as it continues to be invoked, + will not be triggered. The function will be called after it stops + being called for N milliseconds. If `immediate` is passed, call + the callback immediately and hinder future invocations until at + least the given time has passed. + + If passed only 1 argument, or passed a falsy 2nd argument, + the default wait time set in this function's $defaultDelay + property is used. + + Source: underscore.js, by way of https://davidwalsh.name/javascript-debounce-function + */ + const debounce = function f(func, wait, immediate) { + var timeout; + if(!wait) wait = f.$defaultDelay; + return function() { + const context = this, args = Array.prototype.slice.call(arguments); + const later = function() { + timeout = undefined; + if(!immediate) func.apply(context, args); + }; + const callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if(callNow) func.apply(context, args); + }; + }; + debounce.$defaultDelay = 500 /*arbitrary*/; + + SF.ForceResizeKludge = (function(){ + /* Workaround for Safari mayhem regarding use of vh CSS + units.... We cannot use vh units to set the main view + size because Safari chokes on that, so we calculate + that height here. Larger than ~95% is too big for + Firefox on Android, causing the input area to move + off-screen. */ + const eVisibles = EAll('.app-view'); + const elemsToCount = [ + /* Elements which we need to always count in the + visible body size. */ + E('body > header'), + E('body > footer'), + E('body > fieldset.options') + ]; + const resized = function f(){ + if(f.$disabled) return; + const wh = window.innerHeight; + var ht; + var extra = 0; + elemsToCount.forEach((e)=>e ? extra += effectiveHeight(e) : false); + ht = wh - extra; + eVisibles.forEach(function(e){ + e.style.height = + e.style.maxHeight = [ + "calc(", (ht>=100 ? ht : 100), "px", + " - 2em"/*fudge value*/,")" + /* ^^^^ hypothetically not needed, but both + Chrome/FF on Linux will force scrollbars on the + body if this value is too small. */ + ].join(''); + }); + }; + resized.$disabled = true/*gets deleted when setup is finished*/; + window.addEventListener('resize', debounce(resized, 250), false); + return resized; + })(); + /** Performs all app initialization which must wait until after the worker module is loaded. This function removes itself when it's @@ -398,9 +550,19 @@ */ self.onSFLoaded = function(){ delete this.onSFLoaded; + // Unhide all elements which start out hidden EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden')); + SF.e.main = EAll('.app-view:not(.hidden)')[0] + /** The main view widget. Initially the first non-hidden + .app-view element. */; + if( (new URL(self.location.href).searchParams).has('about') ){ + SF.toggleAbout() /* for use while editing the About page */; + } E('#btn-reset').addEventListener('click',()=>SF.resetDb()); + EAll('#btn-about, #btn-about-close').forEach((e)=>{ + e.addEventListener('click',()=>SF.toggleAbout()) + }); const taInput = E('#input'); const btnClearIn = E('#btn-clear'); const selectExamples = E('#select-examples'); @@ -618,108 +780,6 @@ }, false); }); - /** - Given a DOM element, this routine measures its "effective - height", which is the bounding top/bottom range of this element - and all of its children, recursively. For some DOM structure - cases, a parent may have a reported height of 0 even though - children have non-0 sizes. - - Returns 0 if !e or if the element really has no height. - */ - const effectiveHeight = function f(e){ - if(!e) return 0; - if(!f.measure){ - f.measure = function callee(e, depth){ - if(!e) return; - const m = e.getBoundingClientRect(); - if(0===depth){ - callee.top = m.top; - callee.bottom = m.bottom; - }else{ - callee.top = m.top ? Math.min(callee.top, m.top) : callee.top; - callee.bottom = Math.max(callee.bottom, m.bottom); - } - Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1)); - if(0===depth){ - //console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top)); - f.extra += callee.bottom - callee.top; - } - return f.extra; - }; - } - f.extra = 0; - f.measure(e,0); - return f.extra; - }; - - /** - Returns a function, that, as long as it continues to be invoked, - will not be triggered. The function will be called after it stops - being called for N milliseconds. If `immediate` is passed, call - the callback immediately and hinder future invocations until at - least the given time has passed. - - If passed only 1 argument, or passed a falsy 2nd argument, - the default wait time set in this function's $defaultDelay - property is used. - - Source: underscore.js, by way of https://davidwalsh.name/javascript-debounce-function - */ - const debounce = function f(func, wait, immediate) { - var timeout; - if(!wait) wait = f.$defaultDelay; - return function() { - const context = this, args = Array.prototype.slice.call(arguments); - const later = function() { - timeout = undefined; - if(!immediate) func.apply(context, args); - }; - const callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if(callNow) func.apply(context, args); - }; - }; - debounce.$defaultDelay = 500 /*arbitrary*/; - - const ForceResizeKludge = (function(){ - /* Workaround for Safari mayhem regarding use of vh CSS - units.... We cannot use vh units to set the main view - size because Safari chokes on that, so we calculate - that height here. Larger than ~95% is too big for - Firefox on Android, causing the input area to move - off-screen. */ - const appViews = EAll('.app-view'); - const elemsToCount = [ - /* Elements which we need to always count in the - visible body size. */ - E('body > header'), - E('body > footer') - ]; - const resized = function f(){ - if(f.$disabled) return; - const wh = window.innerHeight; - var ht; - var extra = 0; - elemsToCount.forEach((e)=>e ? extra += effectiveHeight(e) : false); - ht = wh - extra; - appViews.forEach(function(e){ - e.style.height = - e.style.maxHeight = [ - "calc(", (ht>=100 ? ht : 100), "px", - " - 2em"/*fudge value*/,")" - /* ^^^^ hypothetically not needed, but both - Chrome/FF on Linux will force scrollbars on the - body if this value is too small. */ - ].join(''); - }); - }; - resized.$disabled = true/*gets deleted when setup is finished*/; - window.addEventListener('resize', debounce(resized, 250), false); - return resized; - })(); - /** Set up a selection list of examples */ (function(){ const xElem = E('#select-examples'); @@ -790,33 +850,35 @@ })()/* example queries */; //SF.echo(null/*clear any output generated by the init process*/); - if(window.jQuery && window.jQuery.terminal){ + if(window.jQuery && window.jQuery.terminal && SF.e.terminal){ /* Set up the terminal-style view... */ - const eTerm = window.jQuery('#view-terminal').empty(); - SF.jqTerm = eTerm.terminal(SF.dbExec.bind(SF),{ + const jqeTerm = window.jQuery(SF.e.terminal).empty(); + SF.jqTerm = jqeTerm.terminal(SF.dbExec.bind(SF),{ prompt: 'sqlite> ', greetings: false /* note that the docs incorrectly call this 'greeting' */ }); + EAll('.unhide-if-terminal-available').forEach(e=>{ + e.classList.remove('hidden'); + }); + EAll('.remove-if-terminal-available').forEach(e=>{ + e.parentElement.removeChild(e); + }); /* Set up a button to toggle the views... */ - const head = E('header#titlebar'); + const ePlaceholder = E('#terminal-button-placeholder'); + ePlaceholder.classList.add('labeled-input'); + ePlaceholder.classList.remove('hidden'); const btnToggleView = document.createElement('button'); - btnToggleView.appendChild(document.createTextNode("Toggle View")); - head.appendChild(btnToggleView); + btnToggleView.innerText = "Toggle view"; + ePlaceholder.appendChild( btnToggleView ); btnToggleView.addEventListener('click',function f(){ - EAll('.app-view').forEach(e=>e.classList.toggle('hidden')); - if(document.body.classList.toggle('terminal-mode')){ - ForceResizeKludge(); - } + const terminalOn = document.body.classList.toggle('terminal-mode'); + SF.setMainView( terminalOn ? SF.e.terminal : SF.e.split ); }, false); btnToggleView.click()/*default to terminal view*/; } - SF.echo('This experimental app is provided in the hope that it', - 'may prove interesting or useful but is not an officially', - 'supported deliverable of the sqlite project. It is subject to', - 'any number of changes or outright removal at any time.\n'); const urlParams = new URL(self.location.href).searchParams; SF.dbExec(urlParams.get('sql') || null); - delete ForceResizeKludge.$disabled; - ForceResizeKludge(); + delete SF.ForceResizeKludge.$disabled; + SF.ForceResizeKludge(); }/*onSFLoaded()*/; })(); diff --git a/ext/wasm/fiddle/index.html b/ext/wasm/fiddle/index.html index ca6788ef0..7f79b754b 100644 --- a/ext/wasm/fiddle/index.html +++ b/ext/wasm/fiddle/index.html @@ -5,16 +5,27 @@ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>SQLite3 Fiddle</title> <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon"> - <!-- to add a toggleable terminal-style view, uncomment the following - two lines and ensure that these files are on the web server. --> + <!-- + To add a terminal-style view using jquery.terminal[^1], + uncomment the following two HTML lines and ensure that these + files are on the web server. + + jquery-bundle.min.js is a concatenation of jquery.min.js from + [^2] and jquery.terminal.min.js from [^1]. + jquery.terminal.min.css is from [^1]. + + [^1]: https://github.com/jcubic/jquery.terminal + [^2]: https://jquery.com + --> <!--script src="jqterm/jqterm-bundle.min.js"></script> - <link rel="stylesheet" href="jqterm/jquery.terminal.min.css"/--> + <link rel="stylesheet" href="jqterm/jquery.terminal.min.css"--> <style> /* The following styles are for app-level use. */ :root { --sqlite-blue: #044a64; - --textarea-color1: #044a64; + --textarea-color1: #000 /*044a64 is nice too*/; --textarea-color2: white; + --size: 1.25 /* used by jqterm to calculate font size and the default is too tiny.*/; } textarea { font-family: monospace; @@ -170,6 +181,17 @@ display: flex; flex-direction: column-reverse; } + #view-about { + flex: auto; + overflow: auto; + } + #view-about h1:first-child { + display: flex; + } + #view-about h1:first-child > button { + align-self: center; + margin-left: 1em; + } /* emcscript-related styling, used during the module load/intialization processes... */ .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } @@ -200,8 +222,10 @@ <body> <header id='titlebar'> <span>SQLite3 Fiddle</span> - <span class='powered-by'>Powered by - <a href='https://sqlite.org'>SQLite3</a></span> + <span id='titlebar-buttons'> + <span class='powered-by'>Powered by + <a href='https://sqlite.org'>SQLite3</a></span> + </span> </header> <!-- emscripten bits --> <figure id="module-spinner"> @@ -215,7 +239,7 @@ </figure> <div class="emscripten" id="module-status">Downloading...</div> <div class="emscripten"> - <progress value="0" max="100" id="module-progress" hidden='1'></progress> + <progress value="0" max="100" id="module-progress" hidden='1'></progress> </div><!-- /emscripten bits --> <div id='view-terminal' class='app-view hidden initially-hidden'> @@ -224,45 +248,6 @@ </div> <div id='view-split' class='app-view initially-hidden'> - <fieldset class='options collapsible'> - <legend><button class='fieldset-toggle'>Options</button></legend> - <div class=''> - <span class='labeled-input'> - <input type='checkbox' id='opt-cb-sbs' - data-csstgt='#main-wrapper' - data-cssclass='side-by-side' - data-config='sideBySide'> - <label for='opt-cb-sbs'>Side-by-side</label> - </span> - <span class='labeled-input'> - <input type='checkbox' id='opt-cb-swapio' - data-csstgt='#main-wrapper' - data-cssclass='swapio' - data-config='swapInOut'> - <label for='opt-cb-swapio'>Swap in/out</label> - </span> - <span class='labeled-input'> - <input type='checkbox' id='opt-cb-autoscroll' - data-config='autoScrollOutput'> - <label for='opt-cb-autoscroll'>Auto-scroll output</label> - </span> - <span class='labeled-input'> - <input type='checkbox' id='opt-cb-autoclear' - data-config='autoClearOutput'> - <label for='opt-cb-autoclear'>Auto-clear output</label> - </span> - <span class='labeled-input'> - <input type='file' id='load-db' class='hidden'/> - <button id='btn-load-db'>Load DB...</button> - </span> - <span class='labeled-input'> - <button id='btn-export'>Download DB</button> - </span> - <span class='labeled-input'> - <button id='btn-reset'>Reset DB</button> - </span> - </div> - </fieldset><!-- .options --> <div id='main-wrapper' class=''> <fieldset class='zone-wrapper input'> <legend><div class='button-bar'> @@ -295,8 +280,113 @@ <div><textarea id="output" readonly placeholder="Shell output."></textarea></div> </fieldset> - </div> + </div><!-- #main-wrapper --> </div> <!-- #view-split --> - <script src="fiddle.js"></script> + +<div class='hidden app-view' id='view-about'> + <h1>About SQLite Fiddle <button id='btn-about-close'>close</button></h1> + + <p>Fiddle is a JavaScript application wrapping a <a href='https://webassembly.org'>WebAssembly</a> + build of <a href="https://sqlite.org/cli.html">the SQLite CLI shell</a>, slightly + modified to account for browser-based user input. Aside from the different layout, + it works just like the CLI shell. This copy was built with SQLite version + <a id='sqlite-version-link'></a>. + </p> + + <p>This app is provided in the hope that it may prove interesting or useful + but it is not an officially-supported deliverable of the SQLite project. + It is subject to any number of changes or outright removal at any time. + That said, for as long as it's online we do respond to support requests + in <a href="https://sqlite.org/forum">the SQLite forum</a>. + </p> + + <p>This app runs on your device. After loading, it does not interact + with the remote server at all. Similarly, this app does not use any + HTTP cookies.</p> + + <p>Fiddle databases are transient in-memory databases unless they + specifically use a persistent storage option (if available, help + text in the SQL result output area will indicate how to use + persistent storage when this app starts up). + </p> + + <h1>Usage Summary</h1> + + <ul> + <li class='hidden unhide-if-terminal-available'>In "terminal + mode" it accepts input just like the CLI shell does.</li> + <li>In split-view mode: + <ul> + <li>Input can be executed with either the Run + button or tapping one of Ctrl-enter or Shift-enter from within + the text input field. If a portion of the input field is + selected, only that portion will be run. + </li> + <li>The various toggle checkboxes can be used to tweak the layout + and behaviors. Those toggles are persistent if the JS environment + allows it. + </li> + </ul> + </li> + <li class='remove-if-terminal-available'>"Terminal mode" is + not available in this deployment. + </li> + <li>Databases can be imported and exported using the buttons in + the Options toolbar. No specific limit for imported database + sizes is imposed, but large databases may cause it to fail with + an out-of-memory error.</li> + <!--li></li--> + </ul> + +</div><!-- #view-about --> + +<fieldset class='options'> + <legend>Options</legend> + <div class=''> + <span class='labeled-input'> + <input type='file' id='load-db' class='hidden'/> + <button id='btn-load-db'>Load DB...</button> + </span> + <span class='labeled-input'> + <button id='btn-export'>Download DB</button> + </span> + <span class='labeled-input'> + <button id='btn-reset'>Reset DB</button> + </span> + <span id='terminal-button-placeholder' class='hidden'></span> + <span class='labeled-input'> + <button id='btn-about'>About...</button> + </span> + <span class='labeled-input hide-in-terminal'> + <input type='checkbox' id='opt-cb-sbs' + data-csstgt='#main-wrapper' + data-cssclass='side-by-side' + data-config='sideBySide' + > + <label for='opt-cb-sbs'>Side-by-side</label> + </span> + <span class='labeled-input hide-in-terminal'> + <input type='checkbox' id='opt-cb-swapio' + data-csstgt='#main-wrapper' + data-cssclass='swapio' + data-config='swapInOut' + > + <label for='opt-cb-swapio'>Swap in/out</label> + </span> + <span class='labeled-input hide-in-terminal'> + <input type='checkbox' id='opt-cb-autoscroll' + data-config='autoScrollOutput' + > + <label for='opt-cb-autoscroll'>Auto-scroll output</label> + </span> + <span class='labeled-input hide-in-terminal'> + <input type='checkbox' id='opt-cb-autoclear' + data-config='autoClearOutput'> + <label for='opt-cb-autoclear'>Auto-clear output</label> + </span> + </div> +</fieldset><!-- .options --> + +<script src="fiddle.js"></script> </body> </html> diff --git a/ext/wasm/mkwasmbuilds.c b/ext/wasm/mkwasmbuilds.c index 8aa29c0fe..d33a10c01 100644 --- a/ext/wasm/mkwasmbuilds.c +++ b/ext/wasm/mkwasmbuilds.c @@ -11,18 +11,17 @@ ************************************************************************* ** ** This app's single purpose is to emit parts of the Makefile code for -** building sqlite3's WASM build. The main motivation is to generate -** code which "can" be created via GNU Make's eval command but is +** sqlite3's canonical WASM build. The main motivation is to generate +** code which "could" be created via GNU Make's eval command but is ** highly illegible when constructed that way. Attempts to write this -** app in Bash and TCL have suffered from the problem that both -** require escaping $ symbols, making the resulting script code as -** illegible as the eval spaghetti we want to get away from. Writing -** it in C is, somewhat surprisingly, _slightly_ less illegible than -** writing it in bash, tcl, or native Make code. +** app in Bash and TCL have suffered from the problem that those +** languages require escaping $ symbols, making the resulting script +** code as illegible as the eval spaghetti we want to get away +** from. Maintaining it in C is, somewhat surprisingly, _slightly_ +** less illegible than writing it in bash, tcl, or native Make code. ** ** The emitted makefile code is not standalone - it depends on ** variables and $(call)able functions from the main makefile. -** */ #undef NDEBUG @@ -33,38 +32,145 @@ #define pf printf #define ps puts -/* Very common printf() args combo. */ -#define zNM zName, zMode /* -** Valid names for the zName arguments. +** Valid build names. Each build is a combination of one of these and +** one of JS_BUILD_MODES, but only certain combinations are legal. +** This macro and JS_BUILD_MODES exist solely for documentation +** purposes: they are not expanded into code anywhere. */ #define JS_BUILD_NAMES sqlite3 sqlite3-wasmfs /* -** Valid names for the zMode arguments of the "sqlite3" build. For the -** "sqlite3-wasmfs" build, only "esm" (ES6 Module) is legal. +** Valid build modes. For the "sqlite3-wasmfs" build, only "esm" (ES6 +** Module) is legal. */ #define JS_BUILD_MODES vanilla esm bundler-friendly node -/* Separator to help eyeballs find the different sections */ + +/* Separator to help eyeballs find the different output sections */ static const char * zBanner = "\n########################################################################\n"; /* +** Flags for use with BuildDef::flags and the 3rd argument to +** mk_pre_post(). +** +** Maintenance reminder: do not combine flags within this enum, +** e.g. LIBMODE_BUNDLER_FRIENDLY=0x02|LIBMODE_ESM, as that will lead +** to breakage in some of the flag checks. +*/ +enum LibModeFlags { + /* Indicates an ESM module build. */ + LIBMODE_ESM = 0x01, + /* Indicates a "bundler-friendly" build mode. */ + LIBMODE_BUNDLER_FRIENDLY = 0x02, + /* Indicates that this build is unsupported. Such builds are not + ** added to the 'all' target. The unsupported builds exist primarily + ** for experimentation's sake. */ + LIBMODE_UNSUPPORTED = 0x04, + /* Indicates a node.js-for-node.js build (untested and + ** unsupported). */ + LIBMODE_NODEJS = 0x08, + /* Indicates a wasmfs build (untested and unsupported). */ + LIBMODE_WASMFS = 0x10 +}; + +/* +** Info needed for building one combination of JS_BUILD_NAMES and +** JS_BUILD_MODE, noting that only a subset of those combinations are +** legal/sensical. +*/ +struct BuildDef { + const char *zName; /* Name from JS_BUILD_NAMES */ + const char *zMode; /* Name from JS_BUILD_MODES */ + int flags; /* Flags from LibModeFlags */ + const char *zJsOut; /* Name of generated sqlite3.js/.mjs */ + /* TODO: dynamically determine zJsOut based on zName, zMode, and + flags. */ + const char *zCmppD; /* Extra -D... flags for c-pp */ + const char *zEmcc; /* Extra flags for emcc */ +}; +typedef struct BuildDef BuildDef; + +/* +** The set of WASM builds for the library (as opposed to the apps +** (fiddle, speedtest1)). This array must end with an empty sentinel +** entry. Their order is mostly insignificant, but some makefile vars +** used by some builds are set up by prior builds. Because of that, +** the (sqlite3, vanilla), (sqlite3, esm), and (sqlite3, +** bundler-friendly) builds should be defined first (in that order). +*/ +const BuildDef aBuildDefs[] = { + {/* Core build */ + "sqlite3", "vanilla", 0, "$(sqlite3.js)", 0, 0}, + + {/* Core ESM */ + "sqlite3", "esm", LIBMODE_ESM, "$(sqlite3.mjs)", + "-Dtarget=es6-module", 0}, + + {/* Core bundler-friendly build. Untested and "not really" + ** supported, but required by the downstream npm subproject. + ** Testing these would require special-purpose node-based tools and + ** custom test apps. Or we can pass them off as-is to the npm + ** subproject and they spot failures pretty quickly ;). */ + "sqlite3", "bundler-friendly", + LIBMODE_BUNDLER_FRIENDLY | LIBMODE_ESM, + "$(dir.dout)/sqlite3-bundler-friendly.mjs", + "$(c-pp.D.sqlite3-esm) -Dtarget=es6-bundler-friendly", 0}, + + {/* node.js mode. Untested and unsupported. */ + "sqlite3", "node", LIBMODE_UNSUPPORTED | LIBMODE_NODEJS, + "$(dir.dout)/sqlite3-node.mjs", + "$(c-pp.D.sqlite3-bundler-friendly) -Dtarget=node", 0}, + + {/* Wasmfs build. Fully unsupported and largely untested. */ + "sqlite3-wasmfs", "esm" , + LIBMODE_UNSUPPORTED | LIBMODE_WASMFS | LIBMODE_ESM, + "$(dir.wasmfs)/sqlite3-wasmfs.mjs", + "$(c-pp.D.sqlite3-bundler-friendly) -Dwasmfs", + "-sEXPORT_ES6 -sUSE_ES6_IMPORT_META"}, + + {/*End-of-list sentinel*/0,0,0,0,0,0} +}; + +/* ** Emits common vars needed by the rest of the emitted code (but not ** needed by makefile code outside of these generated pieces). */ static void mk_prologue(void){ + /* A 0-terminated list of makefile vars which we expect to have been + ** set up by this point in the build process. */ + char const * aRequiredVars[] = { + "dir.top", + "dir.api", "dir.dout", "dir.tmp", + "sqlite3-license-version.js", + "MAKEFILE", "MAKEFILE_LIST", + /* Fiddle... */ + "dir.fiddle", "dir.fiddle-debug", + "MAKEFILE.fiddle", + "EXPORTED_FUNCTIONS.fiddle", + /*"just-testing",*/ + 0 + }; + char const * zVar; + int i; + pf("%s# Build setup sanity checks...\n", zBanner); + for( i = 0; (zVar = aRequiredVars[i]); ++i ){ + pf("ifeq (,$(%s))\n", zVar); + pf(" $(error build process error: expecting make var $$(%s) to " + "have been set up by now)\n", zVar); + ps("endif"); + } pf("%s", zBanner); ps("# extern-post-js* and extern-pre-js* are files for use with"); ps("# Emscripten's --extern-pre-js and --extern-post-js flags."); - ps("extern-pre-js.js := $(dir.api)/extern-pre-js.js"); - ps("extern-post-js.js.in := $(dir.api)/extern-post-js.c-pp.js"); + ps("extern-pre-js.js = $(dir.api)/extern-pre-js.js"); + ps("extern-post-js.js.in = $(dir.api)/extern-post-js.c-pp.js"); ps("# Emscripten flags for --[extern-][pre|post]-js=... for the"); ps("# various builds."); - ps("pre-post-common.flags := --extern-pre-js=$(sqlite3-license-version.js)"); - ps("# pre-post-jses.deps.* = a list of dependencies for the"); - ps("# --[extern-][pre/post]-js files."); - ps("pre-post-jses.deps.common := $(extern-pre-js.js) $(sqlite3-license-version.js)"); + ps("pre-post-common.flags = --extern-pre-js=$(sqlite3-license-version.js)"); + ps("# pre-post-jses.deps.* = a list of dependencies for the\n" + "# --[extern-][pre/post]-js files."); + ps("pre-post-jses.deps.common = $(extern-pre-js.js) $(sqlite3-license-version.js)"); { /* SQLITE.CALL.WASM-OPT = shell code to run $(1) (source wasm file @@ -127,7 +233,7 @@ static void mk_prologue(void){ "\t\techo -n 'After wasm-opt: '; \\\n" "\t\tls -l $(1); \\\n" "\telse \\\n" - "\t\techo 'WARNING: ignoring wasm-opt failure'; \\\n" + "\t\techo 'WARNING: ignoring wasm-opt failure for $(1)'; \\\n" "\tfi\n", zOptFlags ); @@ -137,52 +243,32 @@ static void mk_prologue(void){ } /* -** Flags for use with the 3rd argument to mk_pre_post() and -** mk_lib_mode(). -** -** Maintenance reminder: do not combine flags within this enum, -** e.g. LIBMODE_BUNDLER_FRIENDLY=0x02|LIBMODE_ESM, as that will lead -** to breakage in some of the flag checks. -*/ -enum LibModeFlags { - /* Indicates an ESM module build. */ - LIBMODE_ESM = 0x01, - /* Indicates a "bundler-friendly" build mode. */ - LIBMODE_BUNDLER_FRIENDLY = 0x02, - /* Indicates to _not_ add this build to the 'all' target. */ - LIBMODE_DONT_ADD_TO_ALL = 0x04, - /* Indicates a node.js-for-node.js build (untested and - ** unsupported). */ - LIBMODE_NODEJS = 0x08, - /* Indicates a wasmfs build (untested and unsupported). */ - LIBMODE_WASMFS = 0x10 -}; - -/* ** Emits makefile code for setting up values for the --pre-js=FILE, ** --post-js=FILE, and --extern-post-js=FILE emcc flags, as well as ** populating those files. */ static void mk_pre_post(const char *zName /* build name */, const char *zMode /* build mode */, - int flags /* LIBMODE_... mask */, const char *zCmppD /* optional -D flags for c-pp for the ** --pre/--post-js files. */){ +/* Very common printf() args combo. */ +#define zNM zName, zMode + pf("%s# Begin --pre/--post flags for %s-%s\n", zBanner, zNM); - pf("c-pp.D.%s-%s := %s\n", zNM, zCmppD ? zCmppD : ""); + pf("c-pp.D.%s-%s = %s\n", zNM, zCmppD ? zCmppD : ""); pf("pre-post-%s-%s.flags ?=\n", zNM); /* --pre-js=... */ - pf("pre-js.js.%s-%s := $(dir.tmp)/pre-js.%s-%s.js\n", + pf("pre-js.js.%s-%s = $(dir.tmp)/pre-js.%s-%s.js\n", zNM, zNM); - pf("$(pre-js.js.%s-%s): $(MAKEFILE_LIST)\n", zNM); + pf("$(pre-js.js.%s-%s): $(MAKEFILE_LIST) $(sqlite3-license-version.js)\n", zNM); #if 1 pf("$(eval $(call SQLITE.CALL.C-PP.FILTER,$(pre-js.js.in),$(pre-js.js.%s-%s)," "$(c-pp.D.%s-%s)))\n", zNM, zNM); #else /* This part is needed if/when we re-enable the custom ** Module.instantiateModule() impl in api/pre-js.c-pp.js. */ - pf("pre-js.js.%s-%s.intermediary := $(dir.tmp)/pre-js.%s-%s.intermediary.js\n", + pf("pre-js.js.%s-%s.intermediary = $(dir.tmp)/pre-js.%s-%s.intermediary.js\n", zNM, zNM); pf("$(eval $(call SQLITE.CALL.C-PP.FILTER,$(pre-js.js.in),$(pre-js.js.%s-%s.intermediary)," "$(c-pp.D.%s-%s) -Dcustom-Module.instantiateModule))\n", zNM, zNM); @@ -200,17 +286,17 @@ static void mk_pre_post(const char *zName /* build name */, #endif /* --post-js=... */ - pf("post-js.js.%s-%s := $(dir.tmp)/post-js.%s-%s.js\n", zNM, zNM); + pf("post-js.js.%s-%s = $(dir.tmp)/post-js.%s-%s.js\n", zNM, zNM); pf("$(eval $(call SQLITE.CALL.C-PP.FILTER,$(post-js.js.in)," "$(post-js.js.%s-%s),$(c-pp.D.%s-%s)))\n", zNM, zNM); /* --extern-post-js=... */ - pf("extern-post-js.js.%s-%s := $(dir.tmp)/extern-post-js.%s-%s.js\n", zNM, zNM); + pf("extern-post-js.js.%s-%s = $(dir.tmp)/extern-post-js.%s-%s.js\n", zNM, zNM); pf("$(eval $(call SQLITE.CALL.C-PP.FILTER,$(extern-post-js.js.in),$(extern-post-js.js.%s-%s)," "$(c-pp.D.%s-%s)))\n", zNM, zNM); /* Combined flags for use with emcc... */ - pf("pre-post-common.flags.%s-%s := " + pf("pre-post-common.flags.%s-%s = " "$(pre-post-common.flags) " "--post-js=$(post-js.js.%s-%s) " "--extern-post-js=$(extern-post-js.js.%s-%s)\n", zNM, zNM, zNM); @@ -219,30 +305,29 @@ static void mk_pre_post(const char *zName /* build name */, "--pre-js=$(pre-js.js.%s-%s)\n", zNM, zNM, zNM); /* Set up deps... */ - pf("pre-post-jses.%s-%s.deps := $(pre-post-jses.deps.common) " + pf("pre-post-jses.%s-%s.deps = $(pre-post-jses.deps.common) " "$(post-js.js.%s-%s) $(extern-post-js.js.%s-%s)\n", zNM, zNM, zNM); - pf("pre-post-%s-%s.deps := $(pre-post-jses.%s-%s.deps) $(dir.tmp)/pre-js.%s-%s.js\n", + pf("pre-post-%s-%s.deps = $(pre-post-jses.%s-%s.deps) $(dir.tmp)/pre-js.%s-%s.js\n", zNM, zNM, zNM); pf("# End --pre/--post flags for %s-%s%s", zNM, zBanner); +#undef zNM } /* ** Emits rules for the fiddle builds. -** */ -static void mk_fiddle(){ +static void mk_fiddle(void){ int i = 0; - mk_pre_post("fiddle-module","vanilla", 0, 0); + mk_pre_post("fiddle-module","vanilla", 0); for( ; i < 2; ++i ){ + /* 0==normal, 1==debug */ const char *zTail = i ? ".debug" : ""; const char *zDir = i ? "$(dir.fiddle-debug)" : "$(dir.fiddle)"; pf("%s# Begin fiddle%s\n", zBanner, zTail); - pf("fiddle-module.js%s := %s/fiddle-module.js\n", zTail, zDir); - pf("fiddle-module.wasm%s := " - "$(subst .js,.wasm,$(fiddle-module.js%s))\n", zTail, zTail); + pf("fiddle-module.js%s = %s/fiddle-module.js\n", zTail, zDir); pf("$(fiddle-module.js%s):%s $(MAKEFILE_LIST) $(MAKEFILE.fiddle) " "$(EXPORTED_FUNCTIONS.fiddle) " "$(fiddle.cses) $(pre-post-fiddle-module-vanilla.deps) " @@ -254,7 +339,9 @@ static void mk_fiddle(){ pf("\t$(bin.emcc) -o $@ $(fiddle.emcc-flags%s) " "$(pre-post-fiddle-module-vanilla.flags) $(fiddle.cses)\n", zTail); - pf("\t$(maybe-wasm-strip) $(fiddle-module.wasm%s)\n", zTail); + ps("\t@chmod -x $(basename $@).wasm"); + ps("\t@$(maybe-wasm-strip) $(basename $@).wasm"); + ps("\t@$(SQLITE.strip-createExportWrapper)"); pf("\t@cp -p $(SOAP.js) $(dir $@)\n"); if( 1==i ){/*fiddle.debug*/ pf("\tcp -p $(dir.fiddle)/index.html " @@ -263,13 +350,13 @@ static void mk_fiddle(){ "$(dir $@)\n"); } pf("\t@for i in %s/*.*js %s/*.html %s/*.wasm; do \\\n" - "\t\ttest -f $${i} || continue; \\\n" + "\t\ttest -f $${i} || continue; \\\n" "\t\tgzip < $${i} > $${i}.gz; \\\n" "\tdone\n", zDir, zDir, zDir); if( 0==i ){ ps("fiddle: $(fiddle-module.js)"); }else{ - ps("fiddle-debug: $(fiddle-module-debug.js)"); + ps("fiddle-debug: $(fiddle-module.js.debug)"); } pf("# End fiddle%s%s", zTail, zBanner); } @@ -280,138 +367,110 @@ static void mk_fiddle(){ ** by the combination of zName and zMode, each of which must be values ** from JS_BUILD_NAMES resp. JS_BUILD_MODES. */ -static void mk_lib_mode(const char *zName /* build name */, - const char *zMode /* build mode */, - int flags /* LIBMODE_... mask */, - const char *zApiJsOut /* name of generated sqlite3-api.js/.mjs */, - const char *zJsOut /* name of generated sqlite3.js/.mjs */, - const char *zCmppD /* extra -D flags for c-pp */, - const char *zEmcc /* extra flags for emcc */){ +static void mk_lib_mode(const BuildDef * pB){ const char * zWasmOut = "$(basename $@).wasm" /* The various targets named X.js or X.mjs (zJsOut) also generate ** X.wasm, and we need that part of the name to perform some ** post-processing after Emscripten generates X.wasm. */; - assert( zName ); - assert( zMode ); - assert( zApiJsOut ); - assert( zJsOut ); - if( !zCmppD ) zCmppD = ""; - if( !zEmcc ) zEmcc = ""; + assert( pB->zName ); + assert( pB->zMode ); + assert( pB->zJsOut ); +/* Very common printf() args combo. */ +#define zNM pB->zName, pB->zMode - pf("%s# Begin build [%s-%s]\n", zBanner, zNM); - pf("# zApiJsOut=%s\n# zJsOut=%s\n# zCmppD=%s\n", zApiJsOut, zJsOut, zCmppD); - pf("$(info Setting up build [%s-%s]: %s)\n", zNM, zJsOut); - mk_pre_post(zNM, flags, zCmppD); + pf("%s# Begin build [%s-%s]. flags=0x%02x\n", zBanner, zNM, pB->flags); + pf("# zJsOut=%s\n# zCmppD=%s\n", pB->zJsOut, + pB->zCmppD ? pB->zCmppD : "<none>"); + pf("$(info Setting up build [%s-%s]: %s)\n", zNM, pB->zJsOut); + mk_pre_post(zNM, pB->zCmppD); pf("\nemcc.flags.%s.%s ?=\n", zNM); - if( zEmcc[0] ){ - pf("emcc.flags.%s.%s += %s\n", zNM, zEmcc); + if( pB->zEmcc && pB->zEmcc[0] ){ + pf("emcc.flags.%s.%s += %s\n", zNM, pB->zEmcc); } - pf("$(eval $(call SQLITE.CALL.C-PP.FILTER, $(sqlite3-api.js.in), %s, %s))\n", - zApiJsOut, zCmppD); - /* target zJsOut */ - pf("%s: %s $(MAKEFILE_LIST) $(sqlite3-wasm.cfiles) $(EXPORTED_FUNCTIONS.api) " + /* target pB->zJsOut */ + pf("%s: $(MAKEFILE_LIST) $(sqlite3-wasm.cfiles) $(EXPORTED_FUNCTIONS.api) " "$(pre-post-%s-%s.deps) " "$(sqlite3-api.ext.jses)" /* ^^^ maintenance reminder: we set these as deps so that they - get copied into place early. That allows the developer to - reload the base-most test pages while the later-stage builds - are still compiling, which is especially helpful when running - builds with long build times (like -Oz). */ + ** get copied into place early. That allows the developer to + ** reload the base-most test pages while the later-stage builds + ** are still compiling, which is especially helpful when running + ** builds with long build times (like -Oz). */ "\n", - zJsOut, zApiJsOut, zNM); + pB->zJsOut, zNM); pf("\t@echo \"Building $@ ...\"\n"); + if( LIBMODE_UNSUPPORTED & pB->flags ){ + ps("\t@echo 'ACHTUNG: $@ is an unsupported build. " + "Use at your own risk.'"); + } pf("\t$(bin.emcc) -o $@ $(emcc_opt_full) $(emcc.flags) \\\n"); - pf("\t\t$(emcc.jsflags) -sENVIRONMENT=$(emcc.environment.%s) \\\n", zMode); + pf("\t\t$(emcc.jsflags) -sENVIRONMENT=$(emcc.environment.%s) \\\n", + pB->zMode); pf("\t\t$(pre-post-%s-%s.flags) \\\n", zNM); - pf("\t\t$(emcc.flags.%s) $(emcc.flags.%s.%s) \\\n", zName, zNM); + pf("\t\t$(emcc.flags.%s) $(emcc.flags.%s.%s) \\\n", pB->zName, zNM); pf("\t\t$(cflags.common) $(SQLITE_OPT) \\\n" "\t\t$(cflags.%s) $(cflags.%s.%s) \\\n" - "\t\t$(cflags.wasm_extra_init) $(sqlite3-wasm.cfiles)\n", zName, zNM); - if( (LIBMODE_ESM & flags) || (LIBMODE_NODEJS & flags) ){ + "\t\t$(cflags.wasm_extra_init) $(sqlite3-wasm.cfiles)\n", pB->zName, zNM); + if( (LIBMODE_ESM & pB->flags) || (LIBMODE_NODEJS & pB->flags) ){ /* TODO? Replace this $(call) with the corresponding makefile ** code. OTOH, we also use this $(call) in the speedtest1-wasmfs ** build, which is not part of the rules emitted by this ** program. */ pf("\t@$(call SQLITE.CALL.xJS.ESM-EXPORT-DEFAULT,1,%d)\n", - (LIBMODE_WASMFS & flags) ? 1 : 0); + (LIBMODE_WASMFS & pB->flags) ? 1 : 0); } - pf("\t@chmod -x %s; \\\n" - "\t\t$(maybe-wasm-strip) %s;\n", - zWasmOut, zWasmOut); + pf("\t@chmod -x %s\n", zWasmOut); + pf("\t@$(maybe-wasm-strip) %s\n", zWasmOut); pf("\t@$(call SQLITE.CALL.WASM-OPT,%s)\n", zWasmOut); - pf("\t@sed -i -e '/^var _sqlite3.*createExportWrapper/d' %s || exit; \\\n" - /* ^^^^^^ reminder: Mac/BSD sed has no -i flag */ - "\t\techo 'Stripped out createExportWrapper() parts.'\n", - zJsOut) /* Our JS code installs bindings of each WASM export. The - generated Emscripten JS file does the same using its - own framework, but we don't use those results and can - speed up lib init, and reduce memory cost - considerably, by stripping them out. */; + ps("\t@$(SQLITE.strip-createExportWrapper)"); /* - ** The above $(bin.emcc) call will write zJsOut and will create a - ** like-named .wasm file (zWasmOut). That .wasm file name gets - ** hard-coded into zJsOut so we need to, for some cases, patch - ** zJsOut to use the name sqlite3.wasm instead. Note that the + ** The above $(bin.emcc) call will write pB->zJsOut, a.k.a. $@, and + ** will create a like-named .wasm file (zWasmOut). That .wasm file + ** name gets hard-coded into $@ so we need to, for some cases, patch + ** zJsOut to use the name sqlite3.wasm instead. Note that the ** resulting .wasm file is identical for all builds for which zEmcc ** is empty. */ - if( (LIBMODE_BUNDLER_FRIENDLY & flags) - || (LIBMODE_NODEJS & flags) ){ - pf("\t@echo 'Patching $@ for %s.wasm...'; \\\n", zName); + if( (LIBMODE_BUNDLER_FRIENDLY & pB->flags) ){ + pf("\t@echo 'Patching $@ for %s.wasm...'; \\\n", pB->zName); pf("\t\trm -f %s; \\\n", zWasmOut); pf("\t\tsed -i -e 's/%s-%s.wasm/%s.wasm/g' $@ || exit;\n", - /* ^^^^^^ reminder: Mac/BSD sed has no -i flag */ - zNM, zName); + /* ^^^^^^ reminder: Mac/BSD sed has no -i flag but this + ** build process explicitly requires a Linux system. */ + zNM, pB->zName); pf("\t@ls -la $@\n"); - if( LIBMODE_BUNDLER_FRIENDLY & flags ){ + if( LIBMODE_BUNDLER_FRIENDLY & pB->flags ){ /* Avoid a 3rd occurrence of the bug fixed by 65798c09a00662a3, ** which was (in two cases) caused by makefile refactoring and ** not recognized until after a release was made with the broken - ** sqlite3-bundler-friendly.mjs: */ + ** sqlite3-bundler-friendly.mjs (which is used by the npm + ** subproject but is otherwise untested/unsupported): */ pf("\t@if grep -e '^ *importScripts(' $@; " "then echo 'ERROR: bug fixed in 65798c09a00662a3 has re-appeared'; " "exit 1; fi;\n"); } - }else{ pf("\t@ls -la %s $@\n", zWasmOut); } - if( 0==(LIBMODE_DONT_ADD_TO_ALL & flags) ){ - pf("all: %s\n", zJsOut); + if( 0==(LIBMODE_UNSUPPORTED & pB->flags) ){ + pf("all: %s\n", pB->zJsOut); } pf("# End build [%s-%s]%s", zNM, zBanner); +#undef zNM } int main(void){ int rc = 0; + const BuildDef *pB = &aBuildDefs[0]; pf("# What follows was GENERATED by %s. Edit at your own risk.\n", __FILE__); mk_prologue(); - mk_lib_mode("sqlite3", "vanilla", 0, - "$(sqlite3-api.js)", "$(sqlite3.js)", 0, 0); - mk_lib_mode("sqlite3", "esm", LIBMODE_ESM, - "$(sqlite3-api.mjs)", "$(sqlite3.mjs)", - "-Dtarget=es6-module", 0); - mk_lib_mode("sqlite3", "bundler-friendly", - LIBMODE_BUNDLER_FRIENDLY | LIBMODE_ESM, - "$(sqlite3-api-bundler-friendly.mjs)", - "$(sqlite3-bundler-friendly.mjs)", - "$(c-pp.D.sqlite3-esm) -Dtarget=es6-bundler-friendly", 0); - mk_lib_mode("sqlite3" , "node", - LIBMODE_NODEJS | LIBMODE_DONT_ADD_TO_ALL, - "$(sqlite3-api-node.mjs)", "$(sqlite3-node.mjs)", - "$(c-pp.D.sqlite3-bundler-friendly) -Dtarget=node", 0); - mk_lib_mode("sqlite3-wasmfs", "esm" , - LIBMODE_WASMFS | LIBMODE_ESM | LIBMODE_DONT_ADD_TO_ALL, - /* The sqlite3-wasmfs build is optional and needs to be invoked - ** conditionally using info we don't have here. */ - "$(sqlite3-api-wasmfs.mjs)", "$(sqlite3-wasmfs.mjs)", - "$(c-pp.D.sqlite3-bundler-friendly) -Dwasmfs", - "-sEXPORT_ES6 -sUSE_ES6_IMPORT_META"); - + for( ; pB->zName; ++pB ){ + mk_lib_mode( pB ); + } mk_fiddle(); - mk_pre_post("speedtest1","vanilla", 0, 0); - mk_pre_post("speedtest1-wasmfs","esm", 0, + mk_pre_post("speedtest1","vanilla", 0); + mk_pre_post("speedtest1-wasmfs","esm", "$(c-pp.D.sqlite3-bundler-friendly) -Dwasmfs"); return rc; } diff --git a/ext/wasm/speedtest1.html b/ext/wasm/speedtest1.html index 3bad62006..a841c7fa0 100644 --- a/ext/wasm/speedtest1.html +++ b/ext/wasm/speedtest1.html @@ -23,11 +23,10 @@ </figure> <div class="emscripten" id="module-status">Downloading...</div> <div class="emscripten"> - <progress value="0" max="100" id="module-progress" hidden='1'></progress> + <progress value="0" max="100" id="module-progress" hidden='1'></progress> </div><!-- /emscripten bits --> <div class='warning'>This page starts running the main exe when it loads, which will - block the UI until it finishes! Adding UI controls to manually configure and start it - are TODO.</div> + block the UI until it finishes!</div> </div> <div class='warning'>Achtung: running it with the dev tools open may <em>drastically</em> slow it down. For faster results, keep the dev @@ -118,7 +117,7 @@ argv.push("--vfs", vfs); log2('',"Using VFS:",vfs); if('kvvfs' === vfs){ - forceSize = 2 /* 5 uses approx. 4.96mb */; + forceSize = 2 /* >2 is too big as of mid-2025 */; dbFile = 'session'; log2('warning',"kvvfs VFS: forcing --size",forceSize, "and filename '"+dbFile+"'."); diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index 5b94c7c05..dd70024ab 100644 --- a/ext/wasm/tester1.c-pp.js +++ b/ext/wasm/tester1.c-pp.js @@ -41,7 +41,7 @@ ES6 worker module build: - ./c-pp -f tester1.c-pp.js -o tester1-esm.js -Dtarget=es6-module + ./c-pp -f tester1.c-pp.js -o tester1-esm.mjs -Dtarget=es6-module */ //#if target=es6-module import {default as sqlite3InitModule} from './jswasm/sqlite3.mjs'; @@ -221,7 +221,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; else if(filter instanceof Function) pass = filter(err); else if('string' === typeof filter) pass = (err.message === filter); if(!pass){ - throw new Error(msg || ("Filter rejected this exception: "+err.message)); + throw new Error(msg || ("Filter rejected this exception: <<"+err.message+">>")); } return this; }, @@ -1209,6 +1209,104 @@ globalThis.sqlite3InitModule = sqlite3InitModule; } } }) + + //////////////////////////////////////////////////////////////////// + .t({ + name: "oo1.DB/Stmt.wrapDbHandle()", + test: function(sqlite3){ + /* Maintenance reminder: this function is early in the list to + demonstrate that the wrappers for this.db created by this + function do not interfere with downstream tests, e.g. by + closing this.db.pointer. */ + //sqlite3.config.debug("Proxying",this.db); + const misuseMsg = "SQLITE_MISUSE: Argument must be a WASM sqlite3 pointer"; + T.mustThrowMatching(()=>sqlite3.oo1.DB.wrapHandle(this.db), misuseMsg) + .mustThrowMatching(()=>sqlite3.oo1.DB.wrapHandle(0), misuseMsg); + let dw = sqlite3.oo1.DB.wrapHandle(this.db.pointer); + //sqlite3.config.debug('dw',dw); + T.assert( dw, '!!dw' ) + .assert( dw instanceof sqlite3.oo1.DB, 'dw is-a oo1.DB' ) + .assert( dw.pointer, 'dw.pointer' ) + .assert( dw.pointer === this.db.pointer, 'dw.pointer===db.pointer' ) + .assert( dw.filename === this.db.filename, 'dw.filename===db.filename' ); + + T.assert( dw === dw.exec("select 1") ); + let q; + try { + q = dw.prepare("select 1"); + T.assert( q.step() ) + .assert( !q.step() ); + }finally{ + if( q ) q.finalize(); + } + dw.close(); + T.assert( !dw.pointer ) + .assert( this.db === this.db.exec("select 1") ); + dw = undefined; + + let pDb = 0, pStmt = 0; + const stack = wasm.pstack.pointer; + try { + const ppOut = wasm.pstack.allocPtr(); + T.assert( 0===wasm.peekPtr(ppOut) ); + let rc = capi.sqlite3_open_v2( ":memory:", ppOut, + capi.SQLITE_OPEN_CREATE + | capi.SQLITE_OPEN_READWRITE, + 0); + T.assert( 0===rc, 'open_v2()' ); + pDb = wasm.peekPtr(ppOut); + wasm.pokePtr(ppOut, 0); + T.assert( pDb>0, 'pDb>0' ); + const pTmp = pDb; + dw = sqlite3.oo1.DB.wrapHandle(pDb, true); + pDb = 0; + //sqlite3.config.debug("dw",dw); + T.assert( pTmp===dw.pointer, 'pDb===dw.pointer' ); + T.assert( dw.filename === "", "dw.filename == "+dw.filename ); + let q = dw.prepare("select 1"); + try { + T.assert( q.step(), "step()" ); + T.assert( !q.step(), "!step()" ); + }finally{ + q.finalize(); + q = undefined; + } + T.assert( dw===dw.exec("select 1") ); + dw.affirmOpen(); + const select1 = "select 1"; + rc = capi.sqlite3_prepare_v2( dw, select1, -1, ppOut, 0 ); + T.assert( 0===rc, 'prepare_v2() rc='+rc ); + pStmt = wasm.peekPtr(ppOut); + T.assert( pStmt && wasm.isPtr(pStmt), 'pStmt is valid?' ); + try { + //log( "capi.sqlite3_sql() =",capi.sqlite3_sql(pStmt)); + T.assert( select1 === capi.sqlite3_sql(pStmt), 'SQL mismatch' ); + q = sqlite3.oo1.Stmt.wrapHandle(dw, pStmt, false); + //log("q@"+pStmt+" does not own handle"); + T.assert( q.step(), "step()" ) + .assert( !q.step(), "!step()" ); + q.finalize(); + q = undefined; + T.assert( select1 === capi.sqlite3_sql(pStmt), 'SQL mismatch' + /* This will fail if we've mismanaged pStmt's lifetime */); + q = sqlite3.oo1.Stmt.wrapHandle(dw, pStmt, true); + pStmt = 0; + q.reset(); + T.assert( q.step(), "step()" ) + .assert( !q.step(), "!step()" ); + }finally{ + if( pStmt ) capi.sqlite3_finalize(pStmt) + if( q ) q.finalize(); + } + + }finally{ + wasm.pstack.restore(stack); + if( pDb ){ capi.sqlite3_close_v2(pDb); } + else if( dw ){ dw.close(); } + } + } + })/*oo1.DB/Stmt.wrapHandle()*/ + //////////////////////////////////////////////////////////////////// .t('sqlite3_db_config() and sqlite3_db_status()', function(sqlite3){ let rc = capi.sqlite3_db_config(this.db, capi.SQLITE_DBCONFIG_LEGACY_ALTER_TABLE, 0, 0); @@ -1268,6 +1366,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; /columnCount property is read-only/) .assert(1===st.columnCount) .assert(0===st.parameterCount) + .assert(0===capi.sqlite3_bind_parameter_count(st)) .mustThrow(()=>st.bind(1,null)) .assert(true===st.step()) .assert(3 === st.get(0)) @@ -1490,6 +1589,8 @@ globalThis.sqlite3InitModule = sqlite3InitModule; let st = db.prepare("update t set b=:b where a='blob'"); try { T.assert(0===st.columnCount) + .assert(1===st.parameterCount) + .assert(1===capi.sqlite3_bind_parameter_count(st)) .assert( false===st.isReadOnly() ); const ndx = st.getParamIndex(':b'); T.assert(1===ndx); @@ -3329,6 +3430,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; db.exec("create table t(a)"); const stmt = db.prepare("insert into t(a) values($a)"); T.assert( 1===capi.sqlite3_bind_parameter_count(stmt) ) + .assert( 1===stmt.parameterCount ) .assert( 1===capi.sqlite3_bind_parameter_index(stmt, "$a") ) .assert( 0===capi.sqlite3_bind_parameter_index(stmt, ":a") ) .assert( 1===stmt.getParamIndex("$a") ) diff --git a/ext/wasm/wasmfs.make b/ext/wasm/wasmfs.make index 2c6fa35bd..0d1fb4043 100644 --- a/ext/wasm/wasmfs.make +++ b/ext/wasm/wasmfs.make @@ -5,31 +5,28 @@ # sqlite3.wasm. It is intended to be "include"d from the main # GNUMakefile. ######################################################################## -MAKEFILE.wasmfs := $(lastword $(MAKEFILE_LIST)) +MAKEFILE.wasmfs = $(lastword $(MAKEFILE_LIST)) # ensure that the following message starts on line 10 or higher for proper # $(warning) alignment! ifneq (1,$(MAKING_CLEAN)) $(warning !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!) - $(warning !! The WASMFS build is not well-supported. WASMFS is a proverbial) - $(warning !! moving target, sometimes changing in incompatible ways between) - $(warning !! Emscripten versions. This build is provided for adventurous folks) - $(warning !! and is not a supported deliverable of the SQLite project.) + $(warning !! The WASMFS build is unsupported. Use at your own risk. $(warning !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!) endif -sqlite3-wasmfs.js := $(dir.wasmfs)/sqlite3-wasmfs.js -sqlite3-wasmfs.wasm := $(dir.wasmfs)/sqlite3-wasmfs.wasm +sqlite3-wasmfs.js = $(dir.wasmfs)/sqlite3-wasmfs.js +sqlite3-wasmfs.wasm = $(dir.wasmfs)/sqlite3-wasmfs.wasm ######################################################################## # emcc flags for .c/.o. -cflags.sqlite3-wasmfs := +cflags.sqlite3-wasmfs = cflags.sqlite3-wasmfs += -std=c99 -fPIC cflags.sqlite3-wasmfs += -pthread cflags.sqlite3-wasmfs += -DSQLITE_ENABLE_WASMFS ######################################################################## # emcc flags specific to building the final .js/.wasm file... -emcc.flags.sqlite3-wasmfs := +emcc.flags.sqlite3-wasmfs = emcc.flags.sqlite3-wasmfs += \ -sEXPORTED_RUNTIME_METHODS=wasmMemory # wasmMemory ==> for -sIMPORTED_MEMORY @@ -43,7 +40,7 @@ emcc.flags.sqlite3-wasmfs += -Wno-limited-postlink-optimizations emcc.flags.sqlite3-wasmfs += -sMEMORY64=0 emcc.flags.sqlite3-wasmfs += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.128) # ^^^^ 64MB is not enough for WASMFS/OPFS test runs using batch-runner.js -sqlite3-wasmfs.fsflags := -pthread -sWASMFS \ +sqlite3-wasmfs.fsflags = -pthread -sWASMFS \ -sPTHREAD_POOL_SIZE=1 \ -sERROR_ON_UNDEFINED_SYMBOLS=0 -sLLD_REPORT_UNDEFINED # ^^^^^ why undefined symbols are necessary for the wasmfs build is anyone's guess. @@ -53,10 +50,9 @@ emcc.flags.sqlite3-wasmfs += -sALLOW_MEMORY_GROWTH=0 # USE_PTHREADS + ALLOW_MEMORY_GROWTH may run non-wasm code slowly, # see https://github.com/WebAssembly/design/issues/1271 [-Wpthreads-mem-growth] # And, indeed, it runs slowly if memory is permitted to grow. -#emcc.flags.sqlite3-wasmfs.vanilla := -#emcc.flags.sqlite3-wasmfs.esm := -sEXPORT_ES6 -sUSE_ES6_IMPORT_META -all: $(sqlite3-wasmfs.mjs) -$(sqlite3-wasmfs.js) $(sqlite3-wasmfs.mjs): $(MAKEFILE.wasmfs) +#emcc.flags.sqlite3-wasmfs.vanilla = +#emcc.flags.sqlite3-wasmfs.esm = -sEXPORT_ES6 -sUSE_ES6_IMPORT_META +$(sqlite3-wasmfs.js) $(dir.wasmfs)/sqlite3-wasmfs.mjs: $(MAKEFILE.wasmfs) ######################################################################## # Build quirk: we cannot build BOTH .js and .mjs with our current # build infrastructure because the supplemental *.worker.js files get @@ -68,31 +64,31 @@ $(sqlite3-wasmfs.js) $(sqlite3-wasmfs.mjs): $(MAKEFILE.wasmfs) # names is that it means that the corresponding .wasm file is also # built/saved multiple times. It is likely that anyone wanting to use # WASMFS will want an ES6 module, so that's what we build here. -wasmfs.build.ext := mjs -$(sqlite3-wasmfs.js) $(sqlite3-wasmfs.mjs): $(SOAP.js.bld) +wasmfs.build.ext = mjs +$(sqlite3-wasmfs.js) $(dir.wasmfs)/sqlite3-wasmfs.mjs: $(SOAP.js.bld) ifeq (js,$(wasmfs.build.ext)) $(sqlite3-wasmfs.wasm): $(sqlite3-wasmfs.js) wasmfs: $(sqlite3-wasmfs.js) else - $(sqlite3-wasmfs.wasm): $(sqlite3-wasmfs.mjs) - wasmfs: $(sqlite3-wasmfs.mjs) + $(sqlite3-wasmfs.wasm): $(dir.wasmfs)/sqlite3-wasmfs.mjs + wasmfs: $(dir.wasmfs)/sqlite3-wasmfs.mjs endif +all: wasmfs ######################################################################## # speedtest1 for wasmfs. -speedtest1-wasmfs.mjs := $(dir.wasmfs)/speedtest1-wasmfs.mjs -speedtest1-wasmfs.wasm := $(subst .mjs,.wasm,$(speedtest1-wasmfs.mjs)) -emcc.flags.speedtest1-wasmfs := $(sqlite3-wasmfs.fsflags) +speedtest1-wasmfs.mjs = $(dir.wasmfs)/speedtest1-wasmfs.mjs +speedtest1-wasmfs.wasm = $(subst .mjs,.wasm,$(speedtest1-wasmfs.mjs)) +emcc.flags.speedtest1-wasmfs = $(sqlite3-wasmfs.fsflags) emcc.flags.speedtest1-wasmfs += $(SQLITE_OPT) emcc.flags.speedtest1-wasmfs += -sALLOW_MEMORY_GROWTH=0 emcc.flags.speedtest1-wasmfs += -sINITIAL_MEMORY=$(emcc.INITIAL_MEMORY.128) -#$(eval $(call call-make-pre-js,speedtest1-wasmfs,ems)) +#$(info speedtest DEPS=pre-post-sqlite3-wasmfs-esm.deps=$(pre-post-sqlite3-wasmfs-esm.deps)) $(speedtest1-wasmfs.mjs): $(speedtest1.cfiles) $(sqlite3-wasmfs.js) \ - $(MAKEFILE) $(MAKEFILE.wasmfs) \ - $(pre-post-sqlite3-wasmfs-esm.deps) \ + $(MAKEFILE) $(MAKEFILE.wasmfs) $(pre-post-sqlite3-wasmfs-esm.deps) \ $(EXPORTED_FUNCTIONS.speedtest1) @echo "Building $@ ..." - $(emcc.bin) \ + $(bin.emcc) \ $(pre-post-sqlite3-wasmfs-esm.flags) \ $(cflags.common) \ $(cflags.sqlite3-wasmfs) \ |