Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -333,6 +333,7 @@ # in various forms. # version-info$(T.exe): $(TOP)/tool/version-info.c Makefile sqlite3.h $(T.link) $(ST_OPT) -o $@ $(TOP)/tool/version-info.c +IS_CROSS_COMPILING = @IS_CROSS_COMPILING@ include $(TOP)/main.mk Index: Makefile.msc ================================================================== --- Makefile.msc +++ Makefile.msc @@ -2332,11 +2332,11 @@ copy /Y $(TOP)\src\parse.y . copy /B parse.y +,, .\lemon.exe $(REQ_FEATURE_FLAGS) $(OPT_FEATURE_FLAGS) $(EXT_FEATURE_FLAGS) $(OPTS) -S parse.y $(SQLITE3H): $(TOP)\src\sqlite.h.in $(TOP)\manifest mksourceid.exe $(TOP)\VERSION $(JIM_TCLSH) - $(JIM_TCLSH) $(TOP)\tool\mksqlite3h.tcl "$(TOP:\=/)" > $(SQLITE3H) $(MKSQLITE3H_ARGS) + $(JIM_TCLSH) $(TOP)\tool\mksqlite3h.tcl "$(TOP:\=/)" -o $(SQLITE3H) $(MKSQLITE3H_ARGS) sqlite3ext.h: .target_source !IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0 type tsrc\sqlite3ext.h | $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "\(\*\)" "(SQLITE_CALLBACK *)" \ | $(TCLSH_CMD) $(TOP)\tool\replace.tcl regsub "\(\*" "(SQLITE_APICALL *" > sqlite3ext.h @@ -2396,11 +2396,11 @@ SHELL_DEP = $(SHELL_DEP) $(TOP)\ext\misc\sqlar.c SHELL_DEP = $(SHELL_DEP) $(TOP)\ext\misc\zipfile.c !ENDIF shell.c: $(SHELL_DEP) $(TOP)\tool\mkshellc.tcl $(JIM_TCLSH) - $(JIM_TCLSH) $(TOP)\tool\mkshellc.tcl > shell.c + $(JIM_TCLSH) $(TOP)\tool\mkshellc.tcl shell.c zlib: pushd $(ZLIBDIR) && $(MAKE) /f win32\Makefile.msc clean $(ZLIBLIB) && popd # Rules to build the extension objects. @@ -2809,11 +2809,12 @@ del /Q $(SQLITE3C) $(SQLITE3H) 2>NUL # <> clean: del /Q *.exp *.lo *.ilk *.lib *.obj *.ncb *.pdb *.sdf *.suo 2>NUL - del /Q *.bsc *.def *.cod *.da *.bb *.bbg *.vc gmon.out 2>NUL + del /Q *.bsc *.cod *.da *.bb *.bbg *.vc gmon.out 2>NUL + del /Q sqlite3.def tclsqlite3.def 2>NUL del /Q $(SQLITE3EXE) $(SQLITE3DLL) Replace.exe 2>NUL # <> del /Q $(SQLITE3TCLDLL) pkgIndex.tcl 2>NUL del /Q opcodes.c opcodes.h 2>NUL del /Q lemon.* lempar.c parse.* 2>NUL Index: VERSION ================================================================== --- VERSION +++ VERSION @@ -1,1 +1,1 @@ -3.48.0 +3.48.1 Index: auto.def ================================================================== --- auto.def +++ auto.def @@ -323,20 +323,32 @@ # which are to run on the build system (in autosetup it's called # CC_FOR_BUILD and in Makefile.in it's $(B.cc)) and the one used for # compiling binaries for the target system (CC a.k.a. $(T.cc)). # Normally they're the same, but they will differ when # cross-compiling. -define CFLAGS [proj-get-env CFLAGS {-g -O2}] +# +# When cross-compiling we default to not using the -g flag, based on a +# /chat discussion prompted by +# https://sqlite.org/forum/forumpost/9a67df63eda9925c +set defaultCFlags {-O2} +if {!$isCrossCompiling} { + lappend defaultCFlags -g +} +define CFLAGS [proj-get-env CFLAGS $defaultCFlags] +# BUILD_CFLAGS is the CFLAGS for CC_FOR_BUILD. define BUILD_CFLAGS [proj-get-env BUILD_CFLAGS {-g}] +unset defaultCFlags proj-if-opt-truthy dev { # --enable-dev needs to come early so that the downstream tests # which check for the following flags use their updated state. proj-opt-set all 1 proj-opt-set debug 1 proj-opt-set amalgamation 0 define CFLAGS [get-env CFLAGS {-O0 -g}] + # -------------^^^^^^^ intentionally using [get-env] instead of + # [proj-get-env] here. } ######################################################################## # Handle --with-wasi-sdk=DIR # @@ -473,11 +485,10 @@ # soname. set soname none } switch -exact -- $soname { none - "" { return 0 } - auto { set soname libsqlite3.so.3 } legacy { set soname libsqlite3.so.0 } default { if {[string match libsqlite3.* $soname]} { # use it as-is } else { @@ -602,13 +613,11 @@ set doConfigLookup 1 ; # set to 0 to test the tclConfig.sh-not-found cases if {"" ne $with_tclsh} { # --with-tclsh was provided or found above. Validate it and use it # to trump any value passed via --with-tcl=DIR. - if {![file isfile $with_tclsh]} { - proj-fatal "TCL shell $with_tclsh is not a file" - } elseif {![file-isexec $with_tclsh]} { + if {![file-isexec $with_tclsh]} { proj-fatal "TCL shell $with_tclsh is not executable" } else { define TCLSH_CMD $with_tclsh #msg-result "Using tclsh: $with_tclsh" } @@ -668,11 +677,13 @@ } define TCL_CONFIG_SH $cfg # Export a subset of tclConfig.sh to the current TCL-space. If $cfg # is an empty string, this emits empty-string entries for the # various options we're interested in. - eval [exec "${srcdir}/tool/tclConfigShToAutoDef.sh" "$cfg"] + eval [exec sh "$srcdir/tool/tclConfigShToAutoDef.sh" "$cfg"] + # ---------^^ a Windows/msys workaround, without which it cannot + # exec a .sh file: https://sqlite.org/forum/forumpost/befb352a42a7cd6d if {"" eq $with_tclsh && $cfg ne ""} { # We have tclConfig.sh but no tclsh. Attempt to locate a tclsh # based on info from tclConfig.sh. proj-assert {"" ne [get-define TCL_EXEC_PREFIX]} @@ -1337,13 +1348,33 @@ } else { sqlite-add-feature-flag $featureFlag msg-result " - $boolFlag" } } + +######################################################################## +# Copy all CFLAGS entries matching -DSQLITE_OMIT* and +# -DSQLITE_ENABLE* to OPT_FEATURE_FLAGS. This behavior is derived +# from the legacy build and was missing the 3.48.0 release (the +# initial Autosetup port). +# https://sqlite.org/forum/forumpost/9801e54665afd728 +# +# If any configure flags for features are in conflict with +# CFLAGS-specified feature flags, all bets are off. There are no +# guarantees about which one will take precedence. +foreach cf [get-define CFLAGS ""] { + switch -glob -- $cf { + -DSQLITE_OMIT* - + -DSQLITE_ENABLE* { + sqlite-add-feature-flag $cf + } + } +} ######################################################################### -# Show the final feature flag sets: +# Remove duplicates from the final feature flag sets and show them to +# the user: apply {{} { set oFF [get-define OPT_FEATURE_FLAGS] if {"" ne $oFF} { define OPT_FEATURE_FLAGS [lsort -unique $oFF] msg-result "Library feature flags: [get-define OPT_FEATURE_FLAGS]" @@ -1351,12 +1382,16 @@ set oFF [get-define OPT_SHELL] if {"" ne $oFF} { define OPT_SHELL [lsort -unique $oFF] msg-result "Shell options: [get-define OPT_SHELL]" } - unset oFF }} + +######################################################################## +# When cross-compiling, we have to avoid using the -s flag to +# /usr/bin/install: https://sqlite.org/forum/forumpost/9a67df63eda9925c +define IS_CROSS_COMPILING $isCrossCompiling ######################################################################## # "Re-export" the autoconf-conventional --XYZdir flags into something # which is more easily overridable from a make invocation. See the docs # for [proj-remap-autoconf-dir-vars] for the explanation of why. Index: autoconf/Makefile.msc ================================================================== --- autoconf/Makefile.msc +++ autoconf/Makefile.msc @@ -1085,7 +1085,8 @@ !ENDIF clean: del /Q *.exp *.lo *.ilk *.lib *.obj *.ncb *.pdb *.sdf *.suo 2>NUL - del /Q *.bsc *.def *.cod *.da *.bb *.bbg *.vc gmon.out 2>NUL + del /Q *.bsc *.cod *.da *.bb *.bbg *.vc gmon.out 2>NUL + del /Q sqlite3.def tclsqlite3.def 2>NUL del /Q $(SQLITE3EXE) $(SQLITE3DLL) Replace.exe 2>NUL Index: autoconf/tea/configure.ac ================================================================== --- autoconf/tea/configure.ac +++ autoconf/tea/configure.ac @@ -17,11 +17,11 @@ # so you can encode the package version directly into the source files. # This will also define a special symbol for Windows (BUILD_ # so that we create the export library with the dll. #----------------------------------------------------------------------- -AC_INIT([sqlite],[3.48.0]) +AC_INIT([sqlite],[3.48.1]) #-------------------------------------------------------------------- # Call TEA_INIT as the first TEA_ macro to set up initial vars. # This will define a ${TEA_PLATFORM} variable == "unix" or "windows" # as well as PKG_LIB_FILE and PKG_STUB_LIB_FILE. Index: autosetup/jimsh0.c ================================================================== --- autosetup/jimsh0.c +++ autosetup/jimsh0.c @@ -73,10 +73,13 @@ #endif #if defined(_WIN32) || defined(WIN32) +#ifndef STDIN_FILENO +#define STDIN_FILENO 0 +#endif #define HAVE_DLOPEN void *dlopen(const char *path, int mode); int dlclose(void *handle); void *dlsym(void *handle, const char *symbol); char *dlerror(void); @@ -1862,11 +1865,11 @@ " }\n" " -bu* {\n" " $f buffering $v\n" " }\n" " -tr* {\n" -"\n" +" $f translation $v\n" " }\n" " default {\n" " return -code error \"fconfigure: unknown option $n\"\n" " }\n" " }\n" @@ -2933,10 +2936,32 @@ } Jim_SetResult(interp, resultObj); return JIM_OK; } + +static int aio_cmd_translation(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + enum {OPT_BINARY, OPT_TEXT}; + static const char * const options[] = { + "binary", + "text", + NULL + }; + int opt; + + if (Jim_GetEnum(interp, argv[0], options, &opt, NULL, JIM_ERRMSG) != JIM_OK) { + return JIM_ERR; + } +#if defined(_setmode) && defined(O_BINARY) + else { + AioFile *af = Jim_CmdPrivData(interp); + _setmode(af->fh, opt == OPT_BINARY ? O_BINARY : O_TEXT); + } +#endif + return JIM_OK; +} static int aio_cmd_readsize(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { AioFile *af = Jim_CmdPrivData(interp); @@ -3143,10 +3168,17 @@ "?none|line|full? ?size?", aio_cmd_buffering, 0, 2, + }, + { "translation", + "binary|text", + aio_cmd_translation, + 1, + 1, + }, { "readsize", "?size?", aio_cmd_readsize, 0, @@ -24340,10 +24372,14 @@ #include #include #include +#ifdef HAVE_UNISTD_H +#include +#endif + extern int Jim_initjimshInit(Jim_Interp *interp); static void JimSetArgv(Jim_Interp *interp, int argc, char *const argv[]) { @@ -24423,10 +24459,14 @@ if (retcode == JIM_ERR) { JimPrintErrorMessage(interp); } if (retcode != JIM_EXIT) { JimSetArgv(interp, 0, NULL); + if (!isatty(STDIN_FILENO)) { + + goto eval_stdin; + } retcode = Jim_InteractivePrompt(interp); } } else { @@ -24445,10 +24485,11 @@ } else { Jim_SetVariableStr(interp, "argv0", Jim_NewStringObj(interp, argv[1], -1)); JimSetArgv(interp, argc - 2, argv + 2); if (strcmp(argv[1], "-") == 0) { +eval_stdin: retcode = Jim_Eval(interp, "eval [info source [stdin read] stdin 1]"); } else { retcode = Jim_EvalFile(interp, argv[1]); } } Index: ext/fts5/fts5_index.c ================================================================== --- ext/fts5/fts5_index.c +++ ext/fts5/fts5_index.c @@ -776,15 +776,17 @@ } /* ** Close the read-only blob handle, if it is open. */ -void sqlite3Fts5IndexCloseReader(Fts5Index *p){ +static void fts5IndexCloseReader(Fts5Index *p){ if( p->pReader ){ + int rc; sqlite3_blob *pReader = p->pReader; p->pReader = 0; - sqlite3_blob_close(pReader); + rc = sqlite3_blob_close(pReader); + if( p->rc==SQLITE_OK ) p->rc = rc; } } /* ** Retrieve a record from the %_data table. @@ -805,11 +807,11 @@ p->pReader = 0; rc = sqlite3_blob_reopen(pBlob, iRowid); assert( p->pReader==0 ); p->pReader = pBlob; if( rc!=SQLITE_OK ){ - sqlite3Fts5IndexCloseReader(p); + fts5IndexCloseReader(p); } if( rc==SQLITE_ABORT ) rc = SQLITE_OK; } /* If the blob handle is not open at this point, open it and seek @@ -5006,10 +5008,18 @@ static int fts5IndexReturn(Fts5Index *p){ int rc = p->rc; p->rc = SQLITE_OK; return rc; } + +/* +** Close the read-only blob handle, if it is open. +*/ +void sqlite3Fts5IndexCloseReader(Fts5Index *p){ + fts5IndexCloseReader(p); + fts5IndexReturn(p); +} typedef struct Fts5FlushCtx Fts5FlushCtx; struct Fts5FlushCtx { Fts5Index *pIdx; Fts5SegWriter writer; @@ -6728,11 +6738,11 @@ ** Commit data to disk. */ int sqlite3Fts5IndexSync(Fts5Index *p){ assert( p->rc==SQLITE_OK ); fts5IndexFlush(p); - sqlite3Fts5IndexCloseReader(p); + fts5IndexCloseReader(p); return fts5IndexReturn(p); } /* ** Discard any data stored in the in-memory hash tables. Do not write it @@ -6739,15 +6749,14 @@ ** to the database. Additionally, assume that the contents of the %_data ** table may have changed on disk. So any in-memory caches of %_data ** records must be invalidated. */ int sqlite3Fts5IndexRollback(Fts5Index *p){ - sqlite3Fts5IndexCloseReader(p); + fts5IndexCloseReader(p); fts5IndexDiscardData(p); fts5StructureInvalidate(p); - /* assert( p->rc==SQLITE_OK ); */ - return SQLITE_OK; + return fts5IndexReturn(p); } /* ** The %_data table is completely empty when this function is called. This ** function populates it with the initial structure objects for each index, @@ -6943,10 +6952,20 @@ */ static void fts5SegIterSetEOF(Fts5SegIter *pSeg){ fts5DataRelease(pSeg->pLeaf); pSeg->pLeaf = 0; } + +static void fts5IterClose(Fts5IndexIter *pIndexIter){ + if( pIndexIter ){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; + Fts5Index *pIndex = pIter->pIndex; + fts5TokendataIterDelete(pIter->pTokenDataIter); + fts5MultiIterFree(pIter); + fts5IndexCloseReader(pIndex); + } +} /* ** This function appends iterator pAppend to Fts5TokenDataIter pIn and ** returns the result. */ @@ -6971,11 +6990,11 @@ pNew->nIterAlloc = nAlloc; } } } if( p->rc ){ - sqlite3Fts5IterClose((Fts5IndexIter*)pAppend); + fts5IterClose((Fts5IndexIter*)pAppend); }else{ pRet->apIter[pRet->nIter++] = pAppend; } assert( pRet==0 || pRet->nIter<=pRet->nIterAlloc ); @@ -7184,11 +7203,11 @@ fts5BufferAppendBlob(&p->rc, &bSeek, 1, (const u8*)"\0"); }else{ fts5BufferSet(&p->rc, &bSeek, nToken, pToken); } if( p->rc ){ - sqlite3Fts5IterClose((Fts5IndexIter*)pNew); + fts5IterClose((Fts5IndexIter*)pNew); break; } pNewIter = &pNew->aSeg[0]; pPrevIter = (pPrev ? &pPrev->aSeg[0] : 0); @@ -7249,11 +7268,11 @@ /* If pSmall is still NULL at this point, then the new iterator does ** not point to any terms that match the query. So delete it and break ** out of the loop - all required iterators have been collected. */ if( pSmall==0 ){ - sqlite3Fts5IterClose((Fts5IndexIter*)pNew); + fts5IterClose((Fts5IndexIter*)pNew); break; } /* Append this iterator to the set and continue. */ pSet = fts5AppendTokendataIter(p, pSet, pNew); @@ -7378,13 +7397,13 @@ } } } if( p->rc ){ - sqlite3Fts5IterClose((Fts5IndexIter*)pRet); + fts5IterClose((Fts5IndexIter*)pRet); pRet = 0; - sqlite3Fts5IndexCloseReader(p); + fts5IndexCloseReader(p); } *ppIter = (Fts5IndexIter*)pRet; sqlite3Fts5BufferFree(&buf); } @@ -7630,15 +7649,13 @@ /* ** Close an iterator opened by an earlier call to sqlite3Fts5IndexQuery(). */ void sqlite3Fts5IterClose(Fts5IndexIter *pIndexIter){ if( pIndexIter ){ - Fts5Iter *pIter = (Fts5Iter*)pIndexIter; - Fts5Index *pIndex = pIter->pIndex; - fts5TokendataIterDelete(pIter->pTokenDataIter); - fts5MultiIterFree(pIter); - sqlite3Fts5IndexCloseReader(pIndex); + Fts5Index *pIndex = ((Fts5Iter*)pIndexIter)->pIndex; + fts5IterClose(pIndexIter); + fts5IndexReturn(pIndex); } } /* ** Read and decode the "averages" record from the database. @@ -8164,11 +8181,11 @@ } if( rc==SQLITE_OK ){ rc = sqlite3Fts5IterNext(pIter); } } - sqlite3Fts5IterClose(pIter); + fts5IterClose(pIter); *pCksum = cksum; return rc; } Index: ext/fts5/test/fts5faultI.test ================================================================== --- ext/fts5/test/fts5faultI.test +++ ext/fts5/test/fts5faultI.test @@ -287,8 +287,43 @@ INSERT INTO f1(rowid, content) SELECT id, content FROM g1; } } -test { faultsim_test_result {0 {}} } + +#------------------------------------------------------------------------- +reset_db + +ifcapable foreignkey { + do_execsql_test 12.0 { + CREATE VIRTUAL TABLE f1 USING fts5(content); + CREATE TABLE p1(a INTEGER PRIMARY KEY); + CREATE TABLE c1(b REFERENCES p1 DEFERRABLE INITIALLY DEFERRED); + } + + faultsim_save_and_close + + do_faultsim_test 11 -faults oom* -prep { + faultsim_restore_and_reopen + execsql { + PRAGMA foreign_keys = 1; + BEGIN; + INSERT INTO c1 VALUES(123); + SAVEPOINT xyz; + } + } -body { + execsql { + INSERT INTO f1 VALUES('a b c'); + ROLLBACK TO xyz; + COMMIT; + } + } -test { + execsql { SELECT 123 } + faultsim_test_result \ + {1 {FOREIGN KEY constraint failed}} \ + {1 {out of memory}} \ + {1 {constraint failed}} + } +} finish_test Index: ext/fts5/test/fts5misc.test ================================================================== --- ext/fts5/test/fts5misc.test +++ ext/fts5/test/fts5misc.test @@ -589,11 +589,10 @@ do_execsql_test 21.2 { PRAGMA integrity_check } {ok} -breakpoint sqlite3_db_config db DEFENSIVE 1 do_execsql_test 21.3 { CREATE TABLE xyz_notashadow(x, y); DROP TABLE xyz_notashadow; } @@ -662,8 +661,29 @@ } do_execsql_test 25.0 { SELECT fts5_test_poslist(t1) FROM t1('b') ORDER BY rank; } {{}} + +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 26.0 { + PRAGMA foreign_keys = ON; + CREATE TABLE t1(x INTEGER PRIMARY KEY); + CREATE TABLE t2(y INTEGER PRIMARY KEY, + z INTEGER REFERENCES t1(x) DEFERRABLE INITIALLY DEFERRED + ); + CREATE VIRTUAL TABLE t3 USING fts5(a, b, content='', tokendata=1); +} + +do_execsql_test 26.1 { + BEGIN; + INSERT INTO t2 VALUES(1,111); + INSERT INTO t3 VALUES(3,3); + PRAGMA defer_foreign_keys=ON; + DELETE FROM t2 WHERE y+1; + COMMIT; +} finish_test Index: ext/misc/sqlite3_stdio.c ================================================================== --- ext/misc/sqlite3_stdio.c +++ ext/misc/sqlite3_stdio.c @@ -44,10 +44,15 @@ ** ** The default behavior, if neither of the above is defined is to ** use O_U8TEXT when writing to the Windows console (or anything ** else for which _isatty() returns true) and to use O_BINARY or O_TEXT ** for all other output channels. +** +** The SQLITE_USE_W32_FOR_CONSOLE_IO macro is also available. If +** defined, it forces the use of Win32 APIs for all console I/O, both +** input and output. This is necessary for some non-Microsoft run-times +** that implement stdio differently from Microsoft/Visual-Studio. */ #if defined(SQLITE_U8TEXT_ONLY) # define UseWtextForOutput(fd) 1 # define UseWtextForInput(fd) 1 # define IsConsole(fd) _isatty(_fileno(fd)) @@ -146,14 +151,14 @@ ** that into UTF-8. Otherwise, non-ASCII characters all get translated ** into '?'. */ wchar_t *b1 = sqlite3_malloc( sz*sizeof(wchar_t) ); if( b1==0 ) return 0; -#ifndef SQLITE_USE_STDIO_FOR_CONSOLE +#ifdef SQLITE_USE_W32_FOR_CONSOLE_IO DWORD nRead = 0; if( IsConsole(in) - && ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), b1, sz, &nRead, 0) + && ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), b1, sz-1, &nRead, 0) ){ b1[nRead] = 0; }else #endif { @@ -224,22 +229,23 @@ wchar_t *b1 = sqlite3_malloc( (sz+1)*sizeof(wchar_t) ); if( b1==0 ) return 0; sz = MultiByteToWideChar(CP_UTF8, 0, z, sz, b1, sz); b1[sz] = 0; -#ifndef SQLITE_STDIO_FOR_CONSOLE +#ifdef SQLITE_USE_W32_FOR_CONSOLE_IO DWORD nWr = 0; if( IsConsole(out) && WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),b1,sz,&nWr,0) ){ /* If writing to the console, then the WriteConsoleW() is all we ** need to do. */ }else #endif { - /* For non-console I/O, or if SQLITE_USE_STDIO_FOR_CONSOLE is defined - ** then write using the standard library. */ + /* As long as SQLITE_USE_W32_FOR_CONSOLE_IO is not defined, or for + ** non-console I/O even if that macro is defined, write using the + ** standard library. */ _setmode(_fileno(out), _O_U8TEXT); if( UseBinaryWText(out) ){ piecemealOutput(b1, sz, out); }else{ fputws(b1, out); Index: ext/misc/vfstrace.c ================================================================== --- ext/misc/vfstrace.c +++ ext/misc/vfstrace.c @@ -1031,11 +1031,11 @@ */ static void vfstraceDlClose(sqlite3_vfs *pVfs, void *pHandle){ vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData; sqlite3_vfs *pRoot = pInfo->pRootVfs; vfstraceOnOff(pInfo, VTR_DLCLOSE); - vfstrace_printf(pInfo, "%s.xDlOpen()\n", pInfo->zVfsName); + vfstrace_printf(pInfo, "%s.xDlClose()\n", pInfo->zVfsName); pRoot->xDlClose(pRoot, pHandle); } /* ** Populate the buffer pointed to by zBufOut with nByte bytes of Index: ext/session/session1.test ================================================================== --- ext/session/session1.test +++ ext/session/session1.test @@ -283,11 +283,11 @@ {DELETE t2 NOTFOUND {i 3 t three}} {DELETE t2 DATA {i 4 t four} {i 4 t five}} {FOREIGN_KEY 1} } do_execsql_test $tn.3.2.4 "SELECT * FROM t2" {} -do_db2_test $tn.3.2.5 "SELECT * FROM t2" {1 one 2 two 4 five} +do_db2_test $tn.3.2.5 "SELECT * FROM t2" {4 five} # Test UPDATE changesets. # do_execsql_test $tn.3.3.1 { CREATE TABLE t4(a, b, c, PRIMARY KEY(b, c))%WR%; Index: ext/session/session9.test ================================================================== --- ext/session/session9.test +++ ext/session/session9.test @@ -78,11 +78,11 @@ 6 3 0 {FOREIGN_KEY 1} ABORT 7 2 1 {FOREIGN_KEY 1} ABORT 8 3 1 {FOREIGN_KEY 1} ABORT } { - set A(OMIT,0) {1 SQLITE_CONSTRAINT} + set A(OMIT,0) {0 {}} set A(OMIT,1) {0 {}} set A(ABORT,0) {1 SQLITE_CONSTRAINT} set A(ABORT,1) {1 SQLITE_CONSTRAINT} do_test 1.2.$tn.1 { populate_db @@ -93,11 +93,11 @@ list [catch {sqlite3changeset_apply db $::cc xConflict} msg] $msg } $A($conflictret,$trans) do_test 1.2.$tn.2 { set ::xConflict } $conflictargs - set A(OMIT,0) {0 0} + set A(OMIT,0) {1 1} set A(OMIT,1) {1 1} set A(ABORT,0) {0 0} set A(ABORT,1) {0 0} do_test 1.2.$tn.3 { Index: ext/session/sessionnoact.test ================================================================== --- ext/session/sessionnoact.test +++ ext/session/sessionnoact.test @@ -57,11 +57,11 @@ } set ::nConflict 0 proc conflict {args} { incr ::nConflict - return "OMIT" + return "ABORT" } sqlite3changeset_apply_v2 db $C conflict do_execsql_test 1.3 { @@ -109,10 +109,13 @@ #------------------------------------------------------------------------- # Check that a changeset that causes an FK violation may not be applied, # even if SQLITE_CHANGESETAPPLY_FKNOACTION is specified. # +# UPDATE: Unless the conflict-handler returns OMIT. In that case it can +# be committed. See test cases 3.* in this file. +# reset_db do_execsql_test 2.0 { CREATE TABLE p1(a INTEGER PRIMARY KEY, b, c UNIQUE); INSERT INTO p1 VALUES(1, 1, 'one'); INSERT INTO p1 VALUES(2, 2, 'two'); @@ -161,8 +164,69 @@ SELECT * FROM p1; } {1 1 one 2 2 two} do_execsql_test 2.8 { SELECT * FROM c1; } {two} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 3.0 { + CREATE TABLE p1(a INTEGER PRIMARY KEY, b, c UNIQUE); + INSERT INTO p1 VALUES(1, 1, 'one'); + INSERT INTO p1 VALUES(2, 2, 'two'); + + CREATE TABLE c1(x REFERENCES p1(c) ON DELETE CASCADE); + INSERT INTO c1 VALUES('two'); +} + +set ::nConflict 0 +proc conflict {args} { + incr ::nConflict + return "OMIT" +} + +db_save + +set C [changeset_from_sql { + DELETE FROM p1 WHERE a=2; +}] + +db_restore_and_reopen + +do_test 3.1 { + sqlite3changeset_apply_v2 -noaction db $C conflict +} {} +do_execsql_test 3.2 { + SELECT * FROM p1 +} {1 1 one} + +db_restore_and_reopen +db eval { PRAGMA foreign_keys = 1 } + +do_test 3.3 { + list [catch { sqlite3changeset_apply_v2 -noaction db $C conflict } msg] $msg +} {0 {}} +do_execsql_test 3.4 { + SELECT * FROM p1; +} {1 1 one} +do_execsql_test 3.5 { + SELECT * FROM c1; +} {two} + +db_restore_and_reopen +db eval { PRAGMA foreign_keys = 1 } + +do_test 3.6 { + list [catch { + sqlite3changeset_apply_v2 -ignorenoop -noaction db $C conflict + } msg] $msg +} {0 {}} +do_execsql_test 3.7 { + SELECT * FROM p1; +} {1 1 one} +do_execsql_test 3.8 { + SELECT * FROM c1; +} {two} finish_test Index: ext/session/sqlite3session.c ================================================================== --- ext/session/sqlite3session.c +++ ext/session/sqlite3session.c @@ -5281,10 +5281,15 @@ if( res!=SQLITE_CHANGESET_OMIT ){ rc = SQLITE_CONSTRAINT; } } } + + { + int rc2 = sqlite3_exec(db, "PRAGMA defer_foreign_keys = 0", 0, 0, 0); + if( rc==SQLITE_OK ) rc = rc2; + } if( (flags & SQLITE_CHANGESETAPPLY_NOSAVEPOINT)==0 ){ if( rc==SQLITE_OK ){ rc = sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0); } Index: ext/wasm/api/extern-post-js.c-pp.js ================================================================== --- ext/wasm/api/extern-post-js.c-pp.js +++ ext/wasm/api/extern-post-js.c-pp.js @@ -10,10 +10,11 @@ global scope. */ //#if target=es6-module const toExportForESM = //#endif (function(){ + //console.warn("this is extern-post-js"); /** In order to hide the sqlite3InitModule()'s resulting Emscripten module from downstream clients (and simplify our documentation by being able to elide those details), we hide that function and expose a hand-written sqlite3InitModule() to return @@ -60,10 +61,21 @@ } globalThis.sqlite3InitModule = function ff(...args){ //console.warn("Using replaced sqlite3InitModule()",globalThis.location); return originalInit(...args).then((EmscriptenModule)=>{ + //console.warn("originalInit() then() arg =",EmscriptenModule); + //console.warn("initModuleState =",initModuleState); + if( EmscriptenModule.postRun && EmscriptenModule.postRun.length ){ + /* Emscripten 4.0.0 changes the order in which our Module.postRun handler + runs. In 3.x postRun would have run by now, and our code relies + heavily on that order, so we'll work around that difference here. + + https://github.com/emscripten-core/emscripten/issues/23420 */ + //console.warn("Emscripten did not run postRun: running them now!"); + EmscriptenModule.postRun.shift()(EmscriptenModule); + } //#if wasmfs if('undefined'!==typeof WorkerGlobalScope && EmscriptenModule['ENVIRONMENT_IS_PTHREAD']){ /** Workaround for wasmfs-generated worker, which calls this routine from each individual thread and requires that its @@ -72,11 +84,10 @@ not public Emscripten details. */ //console.warn("sqlite3InitModule() returning E-module.",EmscriptenModule); return EmscriptenModule; } //#endif - //console.warn("sqlite3InitModule() returning sqlite3 object."); const s = EmscriptenModule.sqlite3; s.scriptInfo = initModuleState; //console.warn("sqlite3.scriptInfo =",s.scriptInfo); if(ff.__isUnderTest) s.__isUnderTest = true; const f = s.asyncPostInit; Index: ext/wasm/api/post-js-footer.js ================================================================== --- ext/wasm/api/post-js-footer.js +++ ext/wasm/api/post-js-footer.js @@ -1,4 +1,6 @@ /* The current function scope was opened via post-js-header.js, which gets prepended to this at build-time. This file closes that scope. */ +//console.warn("This is the end of the Module.postRun handler."); })/*postRun.push(...)*/; +//console.warn("This is the end of the setup of the (pending) Module.postRun"); Index: ext/wasm/api/post-js-header.js ================================================================== --- ext/wasm/api/post-js-header.js +++ ext/wasm/api/post-js-header.js @@ -8,10 +8,11 @@ point the sqlite3 JS API bits will get set up. */ if(!Module.postRun) Module.postRun = []; Module.postRun.push(function(Module/*the Emscripten-style module object*/){ 'use strict'; + //console.warn("This is the start of the Module.postRun handler."); /* This function will contain at least the following: - post-js-header.js (this file) - sqlite3-api-prologue.js => Bootstrapping bits to attach the rest to - common/whwasmutil.js => Replacements for much of Emscripten's glue Index: ext/wasm/api/sqlite3-api-cleanup.js ================================================================== --- ext/wasm/api/sqlite3-api-cleanup.js +++ ext/wasm/api/sqlite3-api-cleanup.js @@ -12,10 +12,13 @@ This file is the tail end of the sqlite3-api.js constellation, intended to be appended after all other sqlite3-api-*.js files so that it can finalize any setup and clean up any global symbols temporarily used for setting up the API's various subsystems. + + In Emscripten builds it's run in the context of a Module.postRun + handler. */ 'use strict'; if('undefined' !== typeof Module){ // presumably an Emscripten build /** Install a suitable default configuration for sqlite3ApiBootstrap(). Index: ext/wasm/api/sqlite3-api-glue.c-pp.js ================================================================== --- ext/wasm/api/sqlite3-api-glue.c-pp.js +++ ext/wasm/api/sqlite3-api-glue.c-pp.js @@ -230,11 +230,11 @@ ]], ["sqlite3_set_auxdata", undefined, [ "sqlite3_context*", "int", "*", new wasm.xWrap.FuncPtrAdapter({ name: 'xDestroyAuxData', - signature: 'v(*)', + signature: 'v(p)', contextKey: (argv, argIndex)=>argv[0/* sqlite3_context* */] }) ]], ["sqlite3_shutdown", undefined], ["sqlite3_sourceid", "string"], Index: ext/wasm/dist.make ================================================================== --- ext/wasm/dist.make +++ ext/wasm/dist.make @@ -95,10 +95,20 @@ # files which are _not_ cleaned up by the clean target. # # Note that we require $(bin.version-info) in order to figure out the # dist file's name, so cannot (without a recursive make) have the # target name equal to the archive name. +# +# 2025-01-15: Emsdk 4.0.0 introduces, in its generated code, a regex +# which contains the pattern /*. That, of course, confuses any C-style +# comment-stripper which is not specifically JS-aware and smart enough +# 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 +# ^^^ shell command true or false dist: \ $(bin.stripccomments) $(bin.version-info) \ $(dist.build) $(STRIP_K1.js) $(STRIP_K2.js) \ $(dist.jswasm.extras) $(dist.common.extras) \ $(MAKEFILE) $(MAKEFILE.dist) @@ -107,12 +117,12 @@ @mkdir -p $(dist-dir.jswasm) $(dist-dir.common) @cp -p $(dist.top.extras) $(dist-dir.top) @cp -p README-dist.txt $(dist-dir.top)/README.txt @cp -p index-dist.html $(dist-dir.top)/index.html @cp -p $(dist.jswasm.extras) $(dist-dir.jswasm) - @$(foreach JS,$(STRIP_K1.js),$(call DIST_STRIP_COMMENTS,$(JS),-k)) - @$(foreach JS,$(STRIP_K2.js),$(call DIST_STRIP_COMMENTS,$(JS),-k -k)) + @if $(apply_comment_stripper); then $(foreach JS,$(STRIP_K1.js),$(call DIST_STRIP_COMMENTS,$(JS),-k)) fi + @if $(apply_comment_stripper); then $(foreach JS,$(STRIP_K2.js),$(call DIST_STRIP_COMMENTS,$(JS),-k -k)) fi @cp -p $(dist.common.extras) $(dist-dir.common) @set -e; \ vnum=$$($(bin.version-info) --download-version); \ vdir=$(dist-name-prefix)-$$vnum; \ arczip=$$vdir.zip; \ Index: main.mk ================================================================== --- main.mk +++ main.mk @@ -1064,20 +1064,20 @@ $(B.cc) -o $@ $(TOP)/tool/mksourceid.c sqlite3.h: $(MAKE_SANITY_CHECK) $(TOP)/src/sqlite.h.in \ $(TOP)/manifest mksourceid$(B.exe) \ $(TOP)/VERSION $(B.tclsh) - $(B.tclsh) $(TOP)/tool/mksqlite3h.tcl $(TOP) >sqlite3.h + $(B.tclsh) $(TOP)/tool/mksqlite3h.tcl $(TOP) -o sqlite3.h sqlite3.c: .target_source sqlite3.h $(TOP)/tool/mksqlite3c.tcl src-verify$(B.exe) \ $(B.tclsh) $(B.tclsh) $(TOP)/tool/mksqlite3c.tcl $(AMALGAMATION_GEN_FLAGS) $(EXTRA_SRC) cp tsrc/sqlite3ext.h . cp $(TOP)/ext/session/sqlite3session.h . sqlite3r.h: sqlite3.h $(B.tclsh) - $(B.tclsh) $(TOP)/tool/mksqlite3h.tcl $(TOP) --enable-recover >sqlite3r.h + $(B.tclsh) $(TOP)/tool/mksqlite3h.tcl $(TOP) --enable-recover -o sqlite3r.h sqlite3r.c: sqlite3.c sqlite3r.h $(B.tclsh) cp $(TOP)/ext/recover/sqlite3recover.c tsrc/ cp $(TOP)/ext/recover/sqlite3recover.h tsrc/ cp $(TOP)/ext/recover/dbdata.c tsrc/ @@ -2015,11 +2015,11 @@ shell.c $(LIBOBJS0) \ $(CFLAGS.readline) $(SHELL_OPT) \ $(LDFLAGS.libsqlite3) $(LDFLAGS.readline) install-shell-0: sqlite3$(T.exe) $(install-dir.bin) - $(INSTALL) -s sqlite3$(T.exe) "$(install-dir.bin)" + $(INSTALL) sqlite3$(T.exe) "$(install-dir.bin)" install-shell-1: install: install-shell-$(HAVE_WASI_SDK) # How to build sqldiff$(T.exe) depends on $(LINK_TOOLS_DYNAMICALLY) # @@ -2029,11 +2029,11 @@ sqldiff.1.rules = $(T.link) -o $@ $(TOP)/tool/sqldiff.c -L. -lsqlite3 $(LDFLAGS.configure) sqldiff$(T.exe): $(sqldiff.$(LINK_TOOLS_DYNAMICALLY).deps) $(sqldiff.$(LINK_TOOLS_DYNAMICALLY).rules) install-diff: sqldiff$(T.exe) $(install-dir.bin) - $(INSTALL) -s sqldiff$(T.exe) "$(install-dir.bin)" + $(INSTALL) sqldiff$(T.exe) "$(install-dir.bin)" #install: install-diff dbhash$(T.exe): $(TOP)/tool/dbhash.c sqlite3.o sqlite3.h $(T.link) -o $@ $(TOP)/tool/dbhash.c sqlite3.o $(LDFLAGS.libsqlite3) xbin: dbhash$(T.exe) @@ -2208,11 +2208,11 @@ $(TOP)/ext/recover/sqlite3recover.h \ $(TOP)/src/test_windirent.c \ $(TOP)/src/test_windirent.h shell.c: $(SHELL_DEP) $(TOP)/tool/mkshellc.tcl $(B.tclsh) - $(B.tclsh) $(TOP)/tool/mkshellc.tcl >shell.c + $(B.tclsh) $(TOP)/tool/mkshellc.tcl shell.c # # Rules to build the extension objects. # DEPS_EXT_COMMON = $(DEPS_OBJ_COMMON) $(EXTHDR) Index: src/date.c ================================================================== --- src/date.c +++ src/date.c @@ -220,10 +220,13 @@ ms = ms*10.0 + *zDate - '0'; rScale *= 10.0; zDate++; } ms /= rScale; + /* Truncate to avoid problems with sub-milliseconds + ** rounding. https://sqlite.org/forum/forumpost/766a2c9231 */ + if( ms>0.999 ) ms = 0.999; } }else{ s = 0; } p->validJD = 0; @@ -1427,11 +1430,11 @@ sqlite3_str_appendf(&sRes, cf=='d' ? "%02d" : "%2d", x.D); break; } case 'f': { /* Fractional seconds. (Non-standard) */ double s = x.s; - if( s>59.999 ) s = 59.999; + if( NEVER(s>59.999) ) s = 59.999; sqlite3_str_appendf(&sRes, "%06.3f", s); break; } case 'F': { sqlite3_str_appendf(&sRes, "%04d-%02d-%02d", x.Y, x.M, x.D); Index: src/dbpage.c ================================================================== --- src/dbpage.c +++ src/dbpage.c @@ -422,10 +422,11 @@ } sqlite3PagerUnref(pDbPage); return rc; update_fail: + pTab->pgnoTrunc = 0; sqlite3_free(pVtab->zErrMsg); pVtab->zErrMsg = sqlite3_mprintf("%s", zErr); return SQLITE_ERROR; } Index: src/expr.c ================================================================== --- src/expr.c +++ src/expr.c @@ -3463,10 +3463,11 @@ for(; pOpp4type!=P4_SUBRTNSIG ) continue; assert( pOp->opcode==OP_BeginSubrtn ); pSig = pOp->p4.pSubrtnSig; assert( pSig!=0 ); + if( !pSig->bComplete ) continue; if( pNewSig->selId!=pSig->selId ) continue; if( strcmp(pNewSig->zAff,pSig->zAff)!=0 ) continue; pExpr->y.sub.iAddr = pSig->iAddr; pExpr->y.sub.regReturn = pSig->regReturn; pExpr->iTable = pSig->iTable; @@ -3509,10 +3510,11 @@ int addr; /* Address of OP_OpenEphemeral instruction */ Expr *pLeft; /* the LHS of the IN operator */ KeyInfo *pKeyInfo = 0; /* Key information */ int nVal; /* Size of vector pLeft */ Vdbe *v; /* The prepared statement under construction */ + SubrtnSig *pSig = 0; /* Signature for this subroutine */ v = pParse->pVdbe; assert( v!=0 ); /* The evaluation of the IN must be repeated every time it @@ -3529,11 +3531,10 @@ /* Reuse of the RHS is allowed ** ** Compute a signature for the RHS of the IN operator to facility ** finding and reusing prior instances of the same IN operator. */ - SubrtnSig *pSig = 0; assert( !ExprUseXSelect(pExpr) || pExpr->x.pSelect!=0 ); if( ExprUseXSelect(pExpr) && (pExpr->x.pSelect->selFlags & SF_All)==0 ){ pSig = sqlite3DbMallocRawNN(pParse->db, sizeof(pSig[0])); if( pSig ){ pSig->selId = pExpr->x.pSelect->selId; @@ -3572,10 +3573,11 @@ assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); pExpr->y.sub.regReturn = ++pParse->nMem; pExpr->y.sub.iAddr = sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pExpr->y.sub.regReturn) + 1; if( pSig ){ + pSig->bComplete = 0; pSig->iAddr = pExpr->y.sub.iAddr; pSig->regReturn = pExpr->y.sub.regReturn; pSig->iTable = iTab; pParse->mSubrtnSig = 1 << (pSig->selId&7); sqlite3VdbeChangeP4(v, -1, (const char*)pSig, P4_SUBRTNSIG); @@ -3707,10 +3709,11 @@ sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r2, r1, 1); } sqlite3ReleaseTempReg(pParse, r1); sqlite3ReleaseTempReg(pParse, r2); } + if( pSig ) pSig->bComplete = 1; if( pKeyInfo ){ sqlite3VdbeChangeP4(v, addr, (void *)pKeyInfo, P4_KEYINFO); } if( addrOnce ){ sqlite3VdbeAddOp1(v, OP_NullRow, iTab); Index: src/os_unix.c ================================================================== --- src/os_unix.c +++ src/os_unix.c @@ -1662,11 +1662,11 @@ assert( pInode!=0 ); assert( sqlite3_mutex_held(pInode->pLockMutex) ); if( (pFile->ctrlFlags & (UNIXFILE_EXCL|UNIXFILE_RDONLY))==UNIXFILE_EXCL ){ if( pInode->bProcessLock==0 ){ struct flock lock; - assert( pInode->nLock==0 ); + /* assert( pInode->nLock==0 ); <-- Not true if unix-excl READONLY used */ lock.l_whence = SEEK_SET; lock.l_start = SHARED_FIRST; lock.l_len = SHARED_SIZE; lock.l_type = F_WRLCK; rc = osSetPosixAdvisoryLock(pFile->h, &lock, pFile); Index: src/pager.c ================================================================== --- src/pager.c +++ src/pager.c @@ -806,11 +806,11 @@ if( sqlite3PCacheIsDirty(pPager->pPCache) ) return 0; /* Failed (3) */ #ifndef SQLITE_OMIT_WAL if( pPager->pWal ){ u32 iRead = 0; (void)sqlite3WalFindFrame(pPager->pWal, pgno, &iRead); - return iRead==0; /* Condition (4) */ + if( iRead ) return 0; /* Case (4) */ } #endif assert( pPager->fd->pMethods->xDeviceCharacteristics!=0 ); if( (pPager->fd->pMethods->xDeviceCharacteristics(pPager->fd) & SQLITE_IOCAP_SUBPAGE_READ)==0 ){ Index: src/vdbe.h ================================================================== --- src/vdbe.h +++ src/vdbe.h @@ -38,10 +38,11 @@ ** A signature for a reusable subroutine that materializes the RHS of ** an IN operator. */ struct SubrtnSig { int selId; /* SELECT-id for the SELECT statement on the RHS */ + u8 bComplete; /* True if fully coded and available for reusable */ char *zAff; /* Affinity of the overall IN expression */ int iTable; /* Ephemeral table generated by the subroutine */ int iAddr; /* Subroutine entry address */ int regReturn; /* Register used to hold return address */ }; Index: src/vdbeapi.c ================================================================== --- src/vdbeapi.c +++ src/vdbeapi.c @@ -61,11 +61,10 @@ */ static SQLITE_NOINLINE void invokeProfileCallback(sqlite3 *db, Vdbe *p){ sqlite3_int64 iNow; sqlite3_int64 iElapse; assert( p->startTime>0 ); - assert( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0 ); assert( db->init.busy==0 ); assert( p->zSql!=0 ); sqlite3OsCurrentTimeInt64(db->pVfs, &iNow); iElapse = (iNow - p->startTime)*1000000; #ifndef SQLITE_OMIT_DEPRECATED @@ -781,11 +780,11 @@ if( db->nVdbeActive==0 ){ AtomicStore(&db->u1.isInterrupted, 0); } assert( db->nVdbeWrite>0 || db->autoCommit==0 - || (db->nDeferredCons==0 && db->nDeferredImmCons==0) + || ((db->nDeferredCons + db->nDeferredImmCons)==0) ); #ifndef SQLITE_OMIT_TRACE if( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0 && !db->init.busy && p->zSql ){ Index: src/whereexpr.c ================================================================== --- src/whereexpr.c +++ src/whereexpr.c @@ -217,24 +217,26 @@ }else if( op==TK_STRING ){ assert( !ExprHasProperty(pRight, EP_IntValue) ); z = (u8*)pRight->u.zToken; } if( z ){ - /* Count the number of prefix bytes prior to the first wildcard. - ** or U+fffd character. If the underlying database has a UTF16LE - ** encoding, then only consider ASCII characters. Note that the - ** encoding of z[] is UTF8 - we are dealing with only UTF8 here in - ** this code, but the database engine itself might be processing - ** content using a different encoding. */ + /* Count the number of prefix bytes prior to the first wildcard, + ** U+fffd character, or malformed utf-8. If the underlying database + ** has a UTF16LE encoding, then only consider ASCII characters. Note that + ** the encoding of z[] is UTF8 - we are dealing with only UTF8 here in this + ** code, but the database engine itself might be processing content using a + ** different encoding. */ cnt = 0; while( (c=z[cnt])!=0 && c!=wc[0] && c!=wc[1] && c!=wc[2] ){ cnt++; if( c==wc[3] && z[cnt]>0 && z[cnt]<0x80 ){ cnt++; }else if( c>=0x80 ){ const u8 *z2 = z+cnt-1; - if( sqlite3Utf8Read(&z2)==0xfffd || ENC(db)==SQLITE_UTF16LE ){ + if( sqlite3Utf8Read(&z2)==0xfffd || c==0xFF /* bad utf-8 */ + || ENC(db)==SQLITE_UTF16LE + ){ cnt--; break; }else{ cnt = (int)(z2-z); } @@ -1382,24 +1384,30 @@ pStr2->u.zToken[i] = sqlite3Tolower(c); } } if( !db->mallocFailed ){ - u8 c, *pC; /* Last character before the first wildcard */ + u8 *pC; /* Last character before the first wildcard */ pC = (u8*)&pStr2->u.zToken[sqlite3Strlen30(pStr2->u.zToken)-1]; - c = *pC; if( noCase ){ /* The point is to increment the last character before the first ** wildcard. But if we increment '@', that will push it into the ** alphabetic range where case conversions will mess up the ** inequality. To avoid this, make sure to also run the full ** LIKE on all candidate expressions by clearing the isComplete flag */ - if( c=='A'-1 ) isComplete = 0; - c = sqlite3UpperToLower[c]; + if( *pC=='A'-1 ) isComplete = 0; + *pC = sqlite3UpperToLower[*pC]; + } + + /* Increment the value of the last utf8 character in the prefix. */ + while( *pC==0xBF && pC>(u8*)pStr2->u.zToken ){ + *pC = 0x80; + pC--; } - *pC = c + 1; + assert( *pC!=0xFF ); /* isLikeOrGlob() guarantees this */ + (*pC)++; } zCollSeqName = noCase ? "NOCASE" : sqlite3StrBINARY; pNewExpr1 = sqlite3ExprDup(db, pLeft, 0); pNewExpr1 = sqlite3PExpr(pParse, TK_GE, sqlite3ExprAddCollateString(pParse,pNewExpr1,zCollSeqName), Index: test/date.test ================================================================== --- test/date.test +++ test/date.test @@ -649,7 +649,15 @@ datetest 19.50 {date('2000-08-31','+0023-06-00','floor')} {2024-02-29} datetest 19.51 {date('2000-08-31','+0022-06-00','floor')} {2023-02-28} datetest 19.52 {date('2000-08-31','+0023-06-00','ceiling')} {2024-03-02} datetest 19.53 {date('2000-08-31','+0022-06-00','ceiling')} {2023-03-03} +# 2025-01-21 +# https://sqlite.org/forum/forumpost/766a2c9231 +# +datetest 20.1 {datetime('2024-12-31 23:59:59.9990')} {2024-12-31 23:59:59} +datetest 20.2 {datetime('2024-12-31 23:59:59.9999999999999')} \ + {2024-12-31 23:59:59} +datetest 20.3 {datetime('2024-12-31 23:59:59.9995')} {2024-12-31 23:59:59} +datetest 20.4 {datetime('2024-12-31 23:59:58.9995')} {2024-12-31 23:59:58} finish_test Index: test/fkey6.test ================================================================== --- test/fkey6.test +++ test/fkey6.test @@ -248,9 +248,24 @@ do_catchsql_test 4.2 { COMMIT; } {1 {FOREIGN KEY constraint failed}} +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 5.0 { + PRAGMA foreign_keys = 1; + CREATE TABLE p1(a INTEGER PRIMARY KEY, b); + CREATE TABLE c1(x REFERENCES p1 DEFERRABLE INITIALLY DEFERRED); +} +do_execsql_test 5.1 { + BEGIN; + INSERT INTO c1 VALUES(123); + PRAGMA defer_foreign_keys = 1; + INSERT INTO p1 VALUES(123, 'one two three'); + COMMIT; +} finish_test Index: test/in7.test ================================================================== --- test/in7.test +++ test/in7.test @@ -216,7 +216,36 @@ SELECT t1.a, t2.b FROM t1, t2 WHERE (t1.a, t2.b) = (1, 2); } {1 2} do_execsql_test 3.8 { SELECT t1.a, t2.b FROM t1, t2 WHERE (t1.a, t2.b) IN ((1, 2)); } {1 2} + +# 2025-01-30 Inifinite loop in byte-code discovered by dbsqlfuzz +# having to do with SubrtnSig logic. The code was using a Subroutine +# from within itself resulting in infinite recursion. +# +# This test will spin forever if the bug has not been fixed, or if +# it reappears. +# +reset_db +do_execsql_test 4.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + INSERT INTO t1 VALUES(1,x'1111'); + CREATE TABLE t2(c); + CREATE TABLE t3(d); + CREATE TRIGGER t1tr UPDATE ON t1 BEGIN + UPDATE t1 SET b=x'2222' FROM t2; + UPDATE t1 + SET b = (SELECT a IN (SELECT a + FROM t1 + WHERE (b,a) IN (SELECT rowid, d + FROM t3 + ) + ) + FROM t1 NATURAL RIGHT JOIN t1 + ); + END; + UPDATE t1 SET b=x'3333'; + SELECT quote(b) FROM t1; +} {X'3333'} finish_test Index: test/like3.test ================================================================== --- test/like3.test +++ test/like3.test @@ -272,7 +272,87 @@ } { QUERY PLAN `--SEARCH t2 USING INDEX t2path2 (path>? AND path= char(0x307F) AND word < char(0x3080); + } {み} +} + +#------------------------------------------------------------------------- +reset_db + +foreach enc { + UTF-8 + UTF-16le + UTF-16be +} { + foreach {tn expr} { + 1 "CAST (X'FF' AS TEXT)" + 2 "CAST (X'FFBF' AS TEXT)" + 3 "CAST (X'FFBFBF' AS TEXT)" + 4 "CAST (X'FFBFBFBF' AS TEXT)" + + 5 "'abc' || CAST (X'FF' AS TEXT)" + 6 "'def' || CAST (X'FFBF' AS TEXT)" + 7 "'ghi' || CAST (X'FFBFBF' AS TEXT)" + 8 "'jkl' || CAST (X'FFBFBFBF' AS TEXT)" + } { + reset_db + execsql "PRAGMA encoding = '$enc'" + do_execsql_test like3-8.$tn.0 { + CREATE TABLE t1(x); + } + + do_execsql_test like3-8.$tn.1 { + PRAGMA encoding + } $enc + + do_execsql_test like3-8.$tn.1 " + INSERT INTO t1 VALUES( $expr ) + " + + do_execsql_test like3-8.$tn.2 { + SELECT typeof(x) FROM t1 + } {text} + + set x [db one {SELECT x || '%' FROM t1}] + + do_execsql_test like3-8.$tn.3 { + SELECT rowid FROM t1 WHERE x LIKE $x + } 1 + + do_execsql_test like3-8.$tn.4 { + CREATE INDEX i1 ON t1(x); + } + + do_execsql_test like3-8.$tn.5 { + SELECT rowid FROM t1 WHERE x LIKE $x + } 1 + } +} finish_test + Index: test/trace3.test ================================================================== --- test/trace3.test +++ test/trace3.test @@ -340,7 +340,23 @@ do_test 12.1.2 { sqlite3_finalize $STMT } {SQLITE_OK} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 13.0 { + CREATE TABLE T1(a, b); + INSERT INTO t1 VALUES(1, 2), (3, 4); +} + +proc trace_callback {args} {} +db trace_v2 trace_callback profile + +do_test 13.1 { + db eval { SELECT * FROM t1 } { + db trace_v2 "" "" + } + set {} {} +} {} finish_test Index: tool/emcc.sh.in ================================================================== --- tool/emcc.sh.in +++ tool/emcc.sh.in @@ -61,6 +61,6 @@ echo "emcc not found in PATH. Normally that's set up by ${EMSDK_ENV_SH}." 1>&2 exit 4 fi fi -exec emcc "$@" +exec $emcc "$@" Index: tool/mkshellc.tcl ================================================================== --- tool/mkshellc.tcl +++ tool/mkshellc.tcl @@ -10,10 +10,13 @@ # and $DIR/../ext/misc. # set topdir [file dir [file dir [file normal $argv0]]] set out stdout fconfigure stdout -translation binary +if {[lindex $argv 0]!=""} { + set out [open [lindex $argv 0] wb] +} puts $out {/* DO NOT EDIT! ** This file is automatically generated by the script in the canonical ** SQLite source tree at tool/mkshellc.tcl. That script combines source ** code from various constituent source files of SQLite into this single ** "shell.c" file used to implement the SQLite command-line shell. Index: tool/mksqlite3c-noext.tcl ================================================================== --- tool/mksqlite3c-noext.tcl +++ tool/mksqlite3c-noext.tcl @@ -55,11 +55,11 @@ # Open the output file and write a header comment at the beginning # of the file. # set out [open sqlite3.c w] # Force the output to use unix line endings, even on Windows. -fconfigure $out -translation lf +fconfigure $out -translation binary set today [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S UTC" -gmt 1] puts $out [subst \ {/****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite ** version $VERSION. By combining all the individual C code files into this Index: tool/mksqlite3c.tcl ================================================================== --- tool/mksqlite3c.tcl +++ tool/mksqlite3c.tcl @@ -86,11 +86,11 @@ # set fname sqlite3.c if {$enable_recover} { set fname sqlite3r.c } set out [open $fname wb] # Force the output to use unix line endings, even on Windows. -fconfigure $out -translation lf +fconfigure $out -translation binary set today [clock format [clock seconds] -format "%Y-%m-%d %H:%M:%S UTC" -gmt 1] puts $out [subst \ {/****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite ** version $VERSION. By combining all the individual C code files into this @@ -128,11 +128,11 @@ if {[llength $res]==1} { puts $out "." } else { puts $out " with changes in files:\n**" foreach f [lrange $res 1 end] { - puts $out "** $f" + puts $out "** [string trim $f]" } } } else { puts $out "** The origin of the sources used to build this amalgamation" puts $out "** is unknown." Index: tool/mksqlite3h.tcl ================================================================== --- tool/mksqlite3h.tcl +++ tool/mksqlite3h.tcl @@ -22,30 +22,49 @@ # 5) Replaces the string --SOURCE-ID-- with the date and time and sha1 # hash of the fossil-scm manifest for the source tree. # 6) Adds the SQLITE_CALLBACK calling convention macro in front of all # callback declarations. # -# This script outputs to stdout. +# This script outputs to stdout unless the -o FILENAME option is used. # # Example usage: # -# tclsh mksqlite3h.tcl ../sqlite >sqlite3.h +# tclsh mksqlite3h.tcl ../sqlite [OPTIONS] +# ^^^^^^^^^ +# Root of source tree +# +# Where options are: +# +# --enable-recover Include the sqlite3recover extension +# -o FILENAME Write results to FILENAME instead of stdout +# --useapicall SQLITE_APICALL instead of SQLITE_CDECL # +# Default output stream +set out stdout # Get the source tree root directory from the command-line # set TOP [lindex $argv 0] + +# If the -o FILENAME option is present, use FILENAME for output. +# +set x [lsearch $argv -o] +if {$x>0} { + incr x + set out [open [lindex $argv $x] wb] +} # Enable use of SQLITE_APICALL macros at the right points? # set useapicall 0 # Include sqlite3recover.h? # set enable_recover 0 +# Process command-line arguments if {[lsearch -regexp [lrange $argv 1 end] {^-+useapicall}] != -1} { set useapicall 1 } if {[lsearch -regexp [lrange $argv 1 end] {^-+enable-recover}] != -1} { set enable_recover 1 @@ -86,11 +105,11 @@ set declpattern5 \ {^ *([a-zA-Z][a-zA-Z_0-9 ]+ \**)(sqlite3rebaser_[_a-zA-Z0-9]+)(\(.*)$} # Force the output to use unix line endings, even on Windows. -fconfigure stdout -translation lf +fconfigure stdout -translation binary set filelist [subst { $TOP/src/sqlite.h.in $TOP/ext/rtree/sqlite3rtree.h $TOP/ext/session/sqlite3session.h @@ -116,11 +135,11 @@ # Process the source files. # foreach file $filelist { set in [open $file rb] if {![regexp {sqlite\.h\.in} $file]} { - puts "/******** Begin file [file tail $file] *********/" + puts $out "/******** Begin file [file tail $file] *********/" } while {![eof $in]} { set line [string trimright [gets $in]] @@ -159,13 +178,13 @@ if {$useapicall} { set line [string map [list (*sqlite3_syscall_ptr) \ "(SQLITE_SYSAPI *sqlite3_syscall_ptr)"] $line] regsub {\(\*} $line {(SQLITE_CALLBACK *} line } - puts $line + puts $out $line } close $in if {![regexp {sqlite\.h\.in} $file]} { - puts "/******** End of [file tail $file] *********/" + puts $out "/******** End of [file tail $file] *********/" } } -puts "#endif /* SQLITE3_H */" +puts $out "#endif /* SQLITE3_H */" Index: tool/split-sqlite3c.tcl ================================================================== --- tool/split-sqlite3c.tcl +++ tool/split-sqlite3c.tcl @@ -13,11 +13,11 @@ set BEGIN {^/\*+ Begin file ([a-zA-Z0-9_.]+) \*+/} set END {^/\*+ End of %s \*+/} set in [open sqlite3.c] set out1 [open sqlite3-all.c w] -fconfigure $out1 -translation lf +fconfigure $out1 -translation binary # Copy the header from sqlite3.c into sqlite3-all.c # while {[gets $in line]} { if {[regexp $BEGIN $line]} break Index: tool/stripccomments.c ================================================================== --- tool/stripccomments.c +++ tool/stripccomments.c @@ -109,11 +109,28 @@ } break; } else if(slash == ch){ /* MARKER(("state 0 ==> 1 @ %d:%d\n", line, col)); */ - state = S_SLASH1; + if( '\\'==prev ){ + /** + JS regexes may contain slash-asterisks, as happened at: + + https://github.com/emscripten-core/emscripten/issues/23412 + + Such regexes will always necessarily be preceeded by a + backslash, though. + + It is hypothetically possible for a legitimate comment + slash-asterisk to appear immediately before a + backslash, but that seems like an even rarer corner + case than the JS regex case. + */ + fputc(ch, out); + }else{ + state = S_SLASH1; + } break; } fputc(ch, out); break; case S_SLASH1: /* 1 slash */