diff --git a/Makefile.msc b/Makefile.msc index e8efe58296..6fc4c83390 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -52,6 +52,13 @@ MINIMAL_AMALGAMATION = 0 USE_STDCALL = 0 !ENDIF +# Set this non-0 to use structured exception handling (SEH) for WAL mode +# in the core library. +# +!IFNDEF USE_SEH +USE_SEH = 0 +!ENDIF + # Set this non-0 to have the shell executable link against the core dynamic # link library. # @@ -389,6 +396,13 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_MATH_FUNCTIONS OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RBU=1 !ENDIF +# Should structured exception handling (SEH) be enabled for WAL mode in +# the core library? +# +!IF $(USE_SEH)!=0 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_USE_SEH=1 +!ENDIF + # These are the "extended" SQLite compilation options used when compiling for # the Windows 10 platform. # diff --git a/autoconf/Makefile.msc b/autoconf/Makefile.msc index 09daa867ec..24154dd5d8 100644 --- a/autoconf/Makefile.msc +++ b/autoconf/Makefile.msc @@ -52,6 +52,13 @@ MINIMAL_AMALGAMATION = 0 USE_STDCALL = 0 !ENDIF +# Set this non-0 to use structured exception handling (SEH) for WAL mode +# in the core library. +# +!IFNDEF USE_SEH +USE_SEH = 0 +!ENDIF + # Set this non-0 to have the shell executable link against the core dynamic # link library. # @@ -311,6 +318,13 @@ OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_MATH_FUNCTIONS OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RBU=1 !ENDIF +# Should structured exception handling (SEH) be enabled for WAL mode in +# the core library? +# +!IF $(USE_SEH)!=0 +OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_USE_SEH=1 +!ENDIF + # These are the "extended" SQLite compilation options used when compiling for # the Windows 10 platform. # diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index 3990d227c1..ed5550e7b8 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -179,7 +179,6 @@ SQLITE_OPT = \ -DSQLITE_OMIT_DEPRECATED \ -DSQLITE_OMIT_UTF16 \ -DSQLITE_OMIT_SHARED_CACHE \ - -DSQLITE_OMIT_WAL \ -DSQLITE_THREADSAFE=0 \ -DSQLITE_TEMP_STORE=2 \ -DSQLITE_OS_KV_OPTIONAL=1 \ @@ -187,6 +186,12 @@ SQLITE_OPT = \ -DSQLITE_USE_URI=1 \ -DSQLITE_WASM_ENABLE_C_TESTS \ -DSQLITE_C=$(sqlite3.c) +#SQLITE_OPT += -DSQLITE_DEBUG +# Enabling SQLITE_DEBUG will break sqlite3_wasm_vfs_create_file() +# (and thus sqlite3_js_vfs_create_file()). Those functions are +# deprecated and alternatives are in place, but this crash behavior +# can be used to find errant uses of sqlite3_js_vfs_create_file() +# in client code. .NOTPARALLEL: $(sqlite3.h) $(sqlite3.h): diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index 572efeed5c..60050461c7 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -608,6 +608,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ["sqlite3_wasm_db_vfs", "sqlite3_vfs*", "sqlite3*","string"], ["sqlite3_wasm_vfs_create_file", "int", "sqlite3_vfs*","string","*", "int"], + ["sqlite3_wasm_posix_create_file", "int", "string","*", "int"], ["sqlite3_wasm_vfs_unlink", "int", "sqlite3_vfs*","string"] ]; diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index 3b29041c37..ca5d1c44ff 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -1357,6 +1357,74 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( }; /** + If the current environment supports the POSIX file APIs, this routine + creates (or overwrites) the given file using those APIs. This is + primarily intended for use in Emscripten-based builds where the POSIX + APIs are transparently proxied by an in-memory virtual filesystem. + It may behave diffrently in other environments. + + The first argument must be either a JS string or WASM C-string + holding the filename. Note that this routine does _not_ create + intermediary directories if the filename has a directory part. + + The 2nd argument may either a valid WASM memory pointer, an + ArrayBuffer, or a Uint8Array. The 3rd must be the length, in + bytes, of the data array to copy. If the 2nd argument is an + ArrayBuffer or Uint8Array and the 3rd is not a positive integer + then the 3rd defaults to the array's byteLength value. + + Results are undefined if data is a WASM pointer and dataLen is + exceeds data's bounds. + + Throws if any arguments are invalid or if creating or writing to + the file fails. + + Added in 3.43 as an alternative for the deprecated + sqlite3_js_vfs_create_file(). + */ + capi.sqlite3_js_posix_create_file = function(filename, data, dataLen){ + let pData; + if(data && wasm.isPtr(data)){ + pData = data; + }else if(data instanceof ArrayBuffer || data instanceof Uint8Array){ + pData = wasm.allocFromTypedArray(data); + if(arguments.length<3 || !util.isInt32(dataLen) || dataLen<0){ + dataLen = data.byteLength; + } + }else{ + SQLite3Error.toss("Invalid 2nd argument for sqlite3_js_posix_create_file()."); + } + try{ + if(!util.isInt32(dataLen) || dataLen<0){ + SQLite3Error.toss("Invalid 3rd argument for sqlite3_js_posix_create_file()."); + } + const rc = wasm.sqlite3_wasm_posix_create_file(filename, pData, dataLen); + if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code", + capi.sqlite3_js_rc_str(rc)); + }finally{ + wasm.dealloc(pData); + } + }; + + /** + Deprecation warning: this function does not work properly in + debug builds of sqlite3 because its out-of-scope use of the + sqlite3_vfs API triggers assertions in the core library. That + was unfortunately not discovered until 2023-08-11. This function + is now deprecated and should not be used in new code. + + Alternative options: + + - "unix" VFS and its variants can get equivalent functionality + with sqlite3_js_posix_create_file(). + + - OPFS: use either sqlite3.oo1.OpfsDb.importDb(), for the "opfs" + VFS, or the importDb() method of the PoolUtil object provided + by the "opfs-sahpool" OPFS (noting that its VFS name may differ + depending on client-side configuration). We cannot proxy those + from here because the former is necessarily asynchronous and + the latter requires information not available to this function. + Creates a file using the storage appropriate for the given sqlite3_vfs. The first argument may be a VFS name (JS string only, NOT a WASM C-string), WASM-managed `sqlite3_vfs*`, or @@ -1402,9 +1470,13 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( VFS nor the WASM environment imposes requirements which break it. - "opfs": uses OPFS storage and creates directory parts of the - filename. + filename. It can only be used to import an SQLite3 database + file and will fail if given anything else. */ capi.sqlite3_js_vfs_create_file = function(vfs, filename, data, dataLen){ + config.warn("sqlite3_js_vfs_create_file() is deprecated and", + "should be avoided because it can lead to C-level crashes.", + "See its documentation for alternative options."); let pData; if(data){ if(wasm.isPtr(data)){ @@ -1432,7 +1504,7 @@ globalThis.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( if(rc) SQLite3Error.toss("Creation of file failed with sqlite3 result code", capi.sqlite3_js_rc_str(rc)); }finally{ - wasm.dealloc(pData); + wasm.dealloc(pData); } }; diff --git a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js index 58c511082c..93482505ac 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js @@ -1168,11 +1168,41 @@ const installOpfsVfs = function callee(options){ doDir(opt.directory, 0); }; - //TODO to support fiddle and worker1 db upload: - //opfsUtil.createFile = function(absName, content=undefined){...} - //We have sqlite3.wasm.sqlite3_wasm_vfs_create_file() for this - //purpose but its interface and name are still under - //consideration. + /** + Asynchronously imports the given bytes (a byte array or + ArrayBuffer) into the given database file. + + It very specifically requires the input to be an SQLite3 + database and throws if that's not the case. It does so in + order to prevent this function from taking on a larger scope + than it is specifically intended to. i.e. we do not want it to + become a convenience for importing arbitrary files into OPFS. + + Throws on error. Resolves to the number of bytes written. + */ + opfsUtil.importDb = async function(filename, bytes){ + if(bytes instanceof ArrayBuffer) bytes = new Uint8Array(bytes); + const n = bytes.byteLength; + if(n<512 || n%512!=0){ + toss("Byte array size is invalid for an SQLite db."); + } + const header = "SQLite format 3"; + for(let i = 0; i < header.length; ++i){ + if( header.charCodeAt(i) !== bytes[i] ){ + toss("Input does not contain an SQLite database header."); + } + } + const [hDir, fnamePart] = await opfsUtil.getDirForFilename(filename, true); + const hFile = await hDir.getFileHandle(fnamePart, {create:true}); + const sah = await hFile.createSyncAccessHandle(); + sah.truncate(0); + const nWrote = sah.write(bytes, {at: 0}); + sah.close(); + if(nWrote != n){ + toss("Expected to write "+n+" bytes but wrote "+nWrote+"."); + } + return nWrote; + }; if(sqlite3.oo1){ const OpfsDb = function(...args){ @@ -1182,6 +1212,7 @@ const installOpfsVfs = function callee(options){ }; OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype); sqlite3.oo1.OpfsDb = OpfsDb; + OpfsDb.importDb = opfsUtil.importDb; sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql( opfsVfs.pointer, function(oo1Db, sqlite3){ diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index 94b16d7500..fcfbc06929 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -141,9 +141,6 @@ #ifndef SQLITE_OMIT_UTF16 # define SQLITE_OMIT_UTF16 1 #endif -#ifndef SQLITE_OMIT_WAL -# define SQLITE_OMIT_WAL 1 -#endif #ifndef SQLITE_OS_KV_OPTIONAL # define SQLITE_OS_KV_OPTIONAL 1 #endif @@ -295,7 +292,7 @@ SQLITE_WASM_EXPORT void * sqlite3_wasm_pstack_ptr(void){ */ SQLITE_WASM_EXPORT void sqlite3_wasm_pstack_restore(unsigned char * p){ assert(p>=PStack.pBegin && p<=PStack.pEnd && p>=PStack.pPos); - assert(0==(p & 0x7)); + assert(0==((unsigned long long)p & 0x7)); if(p>=PStack.pBegin && p<=PStack.pEnd /*&& p>=PStack.pPos*/){ PStack.pPos = p; } @@ -1353,6 +1350,13 @@ int sqlite3_wasm_db_serialize( sqlite3 *pDb, const char *zSchema, ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. ** +** ACHTUNG: it was discovered on 2023-08-11 that, with SQLITE_DEBUG, +** this function's out-of-scope use of the sqlite3_vfs/file/io_methods +** APIs leads to triggering of assertions in the core library. Its use +** is now deprecated and VFS-specific APIs for importing files need to +** be found to replace it. sqlite3_wasm_posix_create_file() is +** suitable for the "unix" family of VFSes. +** ** Creates a new file using the I/O API of the given VFS, containing ** the given number of bytes of the given data. If the file exists, it ** is truncated to the given length and populated with the given @@ -1398,7 +1402,14 @@ int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs, int rc; sqlite3_file *pFile = 0; sqlite3_io_methods const *pIo; - const int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + const int openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE +#if 0 && defined(SQLITE_DEBUG) + | SQLITE_OPEN_MAIN_JOURNAL + /* ^^^^ This is for testing a horrible workaround to avoid + triggering a specific assert() in os_unix.c:unixOpen(). Please + do not enable this in real builds. */ +#endif + ; int flagsOut = 0; int fileExisted = 0; int doUnlock = 0; @@ -1464,6 +1475,34 @@ int sqlite3_wasm_vfs_create_file( sqlite3_vfs *pVfs, return rc; } +/** +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** Creates or overwrites a file using the POSIX file API, +** i.e. Emscripten's virtual filesystem. Creates or truncates +** zFilename, appends pData bytes to it, and returns 0 on success or +** SQLITE_IOERR on error. +*/ +SQLITE_WASM_EXPORT +int sqlite3_wasm_posix_create_file( const char *zFilename, + const unsigned char * pData, + int nData ){ + int rc; + FILE * pFile = 0; + int fileExisted = 0; + size_t nWrote = 1; + + if( !zFilename || nData<0 || (pData==0 && nData>0) ) return SQLITE_MISUSE; + pFile = fopen(zFilename, "w"); + if( 0==pFile ) return SQLITE_IOERR; + if( nData>0 ){ + nWrote = fwrite(pData, (size_t)nData, 1, pFile); + } + fclose(pFile); + return 1==nWrote ? 0 : SQLITE_IOERR; +} + /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own JS/WASM bindings. diff --git a/ext/wasm/module-symbols.html b/ext/wasm/module-symbols.html index 865f1b3338..c5c6bc162b 100644 --- a/ext/wasm/module-symbols.html +++ b/ext/wasm/module-symbols.html @@ -355,6 +355,7 @@ sqlite3_js_db_vfs: 'wasm:/api-c-style.md#sqlite3_js_db_vfs', sqlite3_js_kvvfs_clear: 'wasm:/api-c-style.md#sqlite3_js_kvvfs', sqlite3_js_kvvfs_size: 'wasm:/api-c-style.md#sqlite3_js_kvvfs', + sqlite3_js_posix_create_file: 'wasm:/api-c-style.md#sqlite3_js_posix_create_file', sqlite3_js_rc_str: 'wasm:/api-c-style.md#sqlite3_js_rc_str', sqlite3_js_vfs_create_file: 'wasm:/api-c-style.md#sqlite3_js_vfs_create_file', sqlite3_js_vfs_list: 'wasm:/api-c-style.md#sqlite3_js_vfs_list', diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index c1b16d5e81..bd945dcab7 100644 --- a/ext/wasm/tester1.c-pp.js +++ b/ext/wasm/tester1.c-pp.js @@ -1657,7 +1657,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; .assert('number'===typeof rc[0]) .assert(rc[0]|0 === rc[0] /* is small integer */); }) - //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// .t({ name: 'sqlite3_js_db_export()', predicate: ()=>true, @@ -1671,13 +1671,12 @@ globalThis.sqlite3InitModule = sqlite3InitModule; } }/*sqlite3_js_db_export()*/) .t({ - name: 'sqlite3_js_vfs_create_file() with db in default VFS', + name: 'sqlite3_js_posix_create_file()', predicate: ()=>true, test: function(sqlite3){ const db = this.db; - const pVfs = capi.sqlite3_js_db_vfs(db); - const filename = "sqlite3_js_vfs_create_file().db"; - capi.sqlite3_js_vfs_create_file(pVfs, filename, this.dbExport); + const filename = "sqlite3_js_posix_create_file.db"; + capi.sqlite3_js_posix_create_file(filename, this.dbExport); delete this.dbExport; const db2 = new sqlite3.oo1.DB(filename,'r'); try { @@ -1686,7 +1685,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; T.assert(n>0 && db2.selectValue(sql) === n); }finally{ db2.close(); - wasm.sqlite3_wasm_vfs_unlink(pVfs, filename); + wasm.sqlite3_wasm_vfs_unlink(0, filename); } } }/*sqlite3_js_vfs_create_file()*/) @@ -2907,7 +2906,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; .t({ name: 'OPFS db sanity checks', test: async function(sqlite3){ - const filename = this.opfsDbFile = 'sqlite3-tester1.db'; + const filename = this.opfsDbFile = '/dir/sqlite3-tester1.db'; const pVfs = this.opfsVfs = capi.sqlite3_vfs_find('opfs'); T.assert(pVfs); const unlink = this.opfsUnlink = @@ -2935,22 +2934,23 @@ globalThis.sqlite3InitModule = sqlite3InitModule; } }/*OPFS db sanity checks*/) .t({ - name: 'OPFS export/import', + name: 'OPFS import', test: async function(sqlite3){ let db; try { const exp = this.opfsDbExport; delete this.opfsDbExport; - capi.sqlite3_js_vfs_create_file("opfs", this.opfsDbFile, exp); - const db = new sqlite3.oo1.OpfsDb(this.opfsDbFile); - T.assert(6 === db.selectValue('select count(*) from p')); + this.opfsImportSize = await sqlite3.oo1.OpfsDb.importDb(this.opfsDbFile, exp); + db = new sqlite3.oo1.OpfsDb(this.opfsDbFile); + T.assert(6 === db.selectValue('select count(*) from p')). + assert( this.opfsImportSize == exp.byteLength ); }finally{ if(db) db.close(); } } }/*OPFS export/import*/) .t({ - name: 'OPFS utility APIs and sqlite3_js_vfs_create_file()', + name: '(Internal-use) OPFS utility APIs', test: async function(sqlite3){ const filename = this.opfsDbFile; const pVfs = this.opfsVfs; @@ -2959,8 +2959,6 @@ globalThis.sqlite3InitModule = sqlite3InitModule; delete this.opfsDbFile; delete this.opfsVfs; delete this.opfsUnlink; - unlink(); - // Sanity-test sqlite3_js_vfs_create_file()... /************************************************************** ATTENTION CLIENT-SIDE USERS: sqlite3.opfs is NOT intended for client-side use. It is only for this project's own @@ -2968,39 +2966,19 @@ globalThis.sqlite3InitModule = sqlite3InitModule; any time. ***************************************************************/ const opfs = sqlite3.opfs; - const fSize = 1379; + const fSize = this.opfsImportSize; + delete this.opfsImportSize; let sh; try{ - T.assert(!(await opfs.entryExists(filename))); - capi.sqlite3_js_vfs_create_file( - pVfs, filename, null, fSize - ); T.assert(await opfs.entryExists(filename)); - let fh = await opfs.rootDirectory.getFileHandle(filename); + const [dirHandle, filenamePart] = await opfs.getDirForFilename(filename, false); + const fh = await dirHandle.getFileHandle(filenamePart); sh = await fh.createSyncAccessHandle(); T.assert(fSize === await sh.getSize()); await sh.close(); sh = undefined; unlink(); T.assert(!(await opfs.entryExists(filename))); - - const ba = new Uint8Array([1,2,3,4,5]); - capi.sqlite3_js_vfs_create_file( - "opfs", filename, ba - ); - T.assert(await opfs.entryExists(filename)); - fh = await opfs.rootDirectory.getFileHandle(filename); - sh = await fh.createSyncAccessHandle(); - T.assert(ba.byteLength === await sh.getSize()); - await sh.close(); - sh = undefined; - unlink(); - - T.mustThrowMatching(()=>{ - capi.sqlite3_js_vfs_create_file( - "no-such-vfs", filename, ba - ); - }, "SQLITE_NOTFOUND: Unknown sqlite3_vfs name: no-such-vfs"); }finally{ if(sh) await sh.close(); unlink(); @@ -3103,7 +3081,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; const conf2 = JSON.parse(JSON.stringify(sahPoolConfig)); conf2.name += '-test2'; const POther = await inst(conf2); - log("Installed second SAH instance as",conf2.name); + //log("Installed second SAH instance as",conf2.name); T.assert(0 === POther.getFileCount()) .assert(true === await POther.removeVfs()); diff --git a/manifest b/manifest index 62a3a3927c..92f7518f85 100644 --- a/manifest +++ b/manifest @@ -1,11 +1,11 @@ -C Fix\sa\smakefile\sdeps\sproblem\swhich\scaused\sext/jni\sbuild\sto\sfail\sif\ssqlite3.c/h\swere\snot\screated\sbeforehand. -D 2023-08-11T20:32:40.205 +C Merge\strunk\sinto\sjni\sbranch. +D 2023-08-11T21:24:08.276 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F Makefile.in 5ad2d1e198306bc730f06f7545d3a8832225b1bfddbc648d97c0e0b9a35f67e9 F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6 -F Makefile.msc 7248d860f71ab164b4cec3c415e6cc1bd9fee860c370d65bd8bb49e9572521e2 +F Makefile.msc daad4a19e0b3c3c3b79b64d4ddbf75e3f506405e8d3f3f604d6f48b26043c51f F README.md c1c4218efcc4071a6e26db2b517fdbc1035696a29b370edd655faddbef02b224 F VERSION c6366dc72582d3144ce87b013cc35fe48d62f6d07d5be0c9716ea33c862144aa F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 @@ -15,7 +15,7 @@ F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2 F autoconf/INSTALL 83e4a25da9fd053c7b3665eaaaf7919707915903 F autoconf/Makefile.am a8d1d24affe52ebf8d7ddcf91aa973fa0316618ab95bb68c87cabf8faf527dc8 F autoconf/Makefile.fallback 22fe523eb36dfce31e0f6349f782eb084e86a5620b2b0b4f84a2d6133f53f5ac -F autoconf/Makefile.msc 20366d19fbfc3fceecce95344a2114069eaab4fc22e4f02430da167a1e2ddf04 +F autoconf/Makefile.msc 00f11ce1f7904416fe841c33e7d789defe8c39e1df6b97e93ed2af3b1bbaf9d7 F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7 F autoconf/README.txt 42cfd21d0b19dc7d5d85fb5c405c5f3c6a4c923021c39128f6ba685355d8fd56 F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d5893b277 @@ -528,7 +528,7 @@ F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c -F ext/wasm/GNUmakefile 50a4bd40ee01a90badfc28d0042789740e47e2855d3b9acaa8801b6dc2763aba +F ext/wasm/GNUmakefile 8159bc5f9433fe21022c1a8e8c30cb1a523530ba9ef53bdf5d1e0a2186554806 F ext/wasm/README-dist.txt 6382cb9548076fca472fb3330bbdba3a55c1ea0b180ff9253f084f07ff383576 F ext/wasm/README.md a8a2962c3aebdf8d2104a9102e336c5554e78fc6072746e5daf9c61514e7d193 F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api d6a5078f48a5301ed17b9a30331075d9b2506e1360c1f0dee0c7816c10acd9ab @@ -541,16 +541,16 @@ F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08 F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62 F ext/wasm/api/pre-js.c-pp.js ad906703f7429590f2fbf5e6498513bf727a1a4f0ebfa057afb08161d7511219 F ext/wasm/api/sqlite3-api-cleanup.js d235ad237df6954145404305040991c72ef8b1881715d2a650dda7b3c2576d0e -F ext/wasm/api/sqlite3-api-glue.js cc6b0bb093bdb6279d4af259200b7b9e150e3796a8a3a4cd09a4928c43d25e56 +F ext/wasm/api/sqlite3-api-glue.js b65e546568f1dfb35205b9792feb5146a6323d71b55cda58e2ed30def6dd52f3 F ext/wasm/api/sqlite3-api-oo1.js 9678dc4d9a5d39632b6ffe6ea94a023119260815bf32f265bf5f6c36c9516db8 -F ext/wasm/api/sqlite3-api-prologue.js 76258e160bf6a89cc75a7d3c05646a054c8cab7219cd1e10bc20cacaad022131 +F ext/wasm/api/sqlite3-api-prologue.js 5f283b096b98bfb1ee2f2201e7ff0489dff00e29e1030c30953bdb4f5b87f4bd F ext/wasm/api/sqlite3-api-worker1.js 9f32af64df1a031071912eea7a201557fe39b1738645c0134562bb84e88e2fec F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 F ext/wasm/api/sqlite3-opfs-async-proxy.js 8cf8a897726f14071fae6be6648125162b256dfb4f96555b865dbb7a6b65e379 F ext/wasm/api/sqlite3-v-helper.js 7daa0eab0a513a25b05e9abae7b5beaaa39209b3ed12f86aeae9ef8d2719ed25 F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js abb69b5e008961026bf5ff433d7116cb046359af92a5daf73208af2e7ac80ae7 -F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js e7a690e0e78ff4d563f2eca468f91db69f001ff4b79c6d2304cbb6f62dca437d -F ext/wasm/api/sqlite3-wasm.c 8867f1d41c112fb4a2cfe22ff224eccaf309fcdea266cee0ec554f85db72ef0f +F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js e04fc2fda6a0200ef80efdbb4ddfa0254453558adb17ec3a230f93d2bf1d711c +F ext/wasm/api/sqlite3-wasm.c d4d4c2b349b43b7b861e6d2994299630fb79e07573ea6b61e28e8071b7d16b61 F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js bc06df0d599e625bde6a10a394e326dc68da9ff07fa5404354580f81566e591f F ext/wasm/api/sqlite3-worker1.c-pp.js da509469755035e919c015deea41b4514b5e84c12a1332e6cc8d42cb2cc1fb75 F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8 @@ -580,7 +580,7 @@ F ext/wasm/index-dist.html 22379774f0ad4edcaaa8cf9c674c82e794cc557719a8addabed74 F ext/wasm/index.html 4e7847b909f4ae0da8c829b150b79454050e53b3658431f138636257729cd42b F ext/wasm/jaccwabyt/jaccwabyt.js 1264710db3cfbcb6887d95665b7aeba60c1126eaef789ca4cf1a4a17d5bc7f54 F ext/wasm/jaccwabyt/jaccwabyt.md 37911f00db12cbcca73aa1ed72594430365f30aafae2fa9c886961de74e5e0eb -F ext/wasm/module-symbols.html 841de62fc198988b8330e238c260e70ec93028b096e1a1234db31b187a899d10 +F ext/wasm/module-symbols.html dc476b403369b26a1a23773e13b80f41b9a49f0825e81435fe3600a7cfbbe337 F ext/wasm/scratchpad-wasmfs.html a3d7388f3c4b263676b58b526846e9d02dfcb4014ff29d3a5040935286af5b96 F ext/wasm/scratchpad-wasmfs.mjs 66034b9256b218de59248aad796760a1584c1dd842231505895eff00dbd57c63 F ext/wasm/speedtest1-wasmfs.html 0e9d335a9b5b5fafe6e1bc8dc0f0ca7e22e6eb916682a2d7c36218bb7d67379d @@ -595,7 +595,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555 F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2 -F ext/wasm/tester1.c-pp.js b88dcad5424a652e8204c44a71bbc3deb22a4922c97ba792aedbabb7a6827b91 +F ext/wasm/tester1.c-pp.js 64eb0ee6e695d5638d0f758f31a0ca2231e627ca5d768de3d8b44f9f494de8d4 F ext/wasm/tests/opfs/concurrency/index.html 0802373d57034d51835ff6041cda438c7a982deea6079efd98098d3e42fbcbc1 F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 @@ -628,7 +628,7 @@ F src/btreeInt.h 91a9e0c41a0e71fa91a742ec285c63dd8dcb38b73d14fae0ed7209174ff0fdc F src/build.c a8ae3b32d9aa9bbd2c0e97d7c0dd80def9fbca408425de1608f57ee6f47f45f4 F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e -F src/ctime.c 20507cc0b0a6c19cd882fcd0eaeda32ae6a4229fb4b024cfdf3183043d9b703d +F src/ctime.c cff2b493de894383832347c3a3f936a05f4e089a92b42366838da1735f3848b5 F src/date.c f73f203b3877cef866c60ab402aec2bf89597219b60635cf50cbe3c5e4533e94 F src/dbpage.c f3eea5f7ec47e09ee7da40f42b25092ecbe961fc59566b8e5f705f34335b2387 F src/dbstat.c ec92074baa61d883de58c945162d9e666c13cd7cf3a23bc38b4d1c4d0b2c2bef @@ -643,7 +643,7 @@ F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276 -F src/json.c 9c231a853268ce6aee2e300e26d4445ba42117374a2275f8e9537b2f912909d6 +F src/json.c ae840f87b418f039f5d336b488933d09396bd31e6b31e855b93055ccaee4e255 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 176d6b2cb18a6ad73b133db17f6fc351c4d9a2d510deebdb76c22bde9cfd1465 F src/main.c fde8f13c876a658b4e8b74b77d875ca887915c174ea6a2f3122d80966f93d865 @@ -670,8 +670,8 @@ F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d87210 F src/os_unix.c 2e8b12107f75d1bd16412f312b4c5d5103191807a37836d3b81beb26436ad81b F src/os_win.c 7038223a1cda0a47e2ab4db47f63bf1833fe53ba0542f0f283a062ea13894103 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c 5ddf3a74c633a008ea6b2f5b3186167e88e2c8ca8a252ecab06ab3f1eb48e60f -F src/pager.h f82e9844166e1585f5786837ddc7709966138ced17f568c16af7ccf946c2baa3 +F src/pager.c 993445a19b611d473ca007542ab3149840661a4c7e9f2d9e1ec008b7cc2abe78 +F src/pager.h 6e326bd05970a24dd28d41d3980b6964fbaa37b4da54a2c0d4e0c5bdb06ff187 F src/parse.y aeb7760d41cfa86465e3adba506500c021597049fd55f82a30e5b7045862c28c F src/pcache.c 4cd4a0043167da9ba7e19b4d179a0e6354e7fe32c16f781ecf9bf0a5ff63b40b F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5 @@ -685,7 +685,7 @@ F src/resolve.c 37953a5f36c60bea413c3c04efcd433b6177009f508ef2ace0494728912fe2e9 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c 5f545a2c8702d4d3430bbb188cfec47d6c122d899061ef00cbe56af14591c574 F src/shell.c.in 694aaf751f00610381533d4a31c83d142cfc83ef91ef65e2aa6912ace7c39b40 -F src/sqlite.h.in 7b07a33d2af82ee974aa91e6294abce0282b2f4c5934b291d2fff961810dd867 +F src/sqlite.h.in 73a366c1c45d5ac9888cfe81c458826a44498531d106cfb4f328193ab5f6f17d F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4 F src/sqliteInt.h 025ed58a41968ef80d64cdc194caa8dd207b0256b147253d762fdac7a62408f9 @@ -745,19 +745,19 @@ F src/test_windirent.h da2e5b73c32d09905fbdd00f27cd802212a32a58ead882736fe4f5eb7 F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394ba3f F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c -F src/tokenize.c 0fb405f9adf3f757c26bfc1ae6d58ac5dccbb918917ba9e5ef0e6673a06563d3 +F src/tokenize.c 23d9f4539880b40226254ad9072f4ecf12eb1902e62aea47aac29928afafcfd5 F src/treeview.c 1d52fbc4e97161e65858d36e3424ea6e3fc045dd8a679c82b4b9593dc30de3bd F src/trigger.c ad6ab9452715fa9a8075442e15196022275b414b9141b566af8cdb7a1605f2b0 F src/update.c 0bb9171afaa4d0b100ad946873bccda7aef90ffe083ef5c63668fce08c4df9da F src/upsert.c 5303dc6c518fa7d4b280ec65170f465c7a70b7ac2b22491598f6d0b4875b3145 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 -F src/util.c b3532a95ad56db67b3acd3955e688e4cb80ebec6fd1f459a8eb51cceedd6de69 +F src/util.c a40062117e705eb3339201842717a022092816b92479eead6397cde28af32ff9 F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 F src/vdbe.c 346d848a0bf8128e3e3722c5406f4bde6c32d7093b93402c6f8e0718d19305c3 F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0 F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c F src/vdbeapi.c f37822f215740ede2a8fcae99bc13f2cc3a72dd0e1d22b81b9298c5ca67dbc38 -F src/vdbeaux.c a586f445945eef6ad1fcd7c94f700faa1baea93c0dbd446291c7cf65966c8470 +F src/vdbeaux.c e3aa5c46827cd95e0fc4d0f302fa3e901ab5f07258fdbb42709eeef40f63018d F src/vdbeblob.c 2516697b3ee8154eb8915f29466fb5d4f1ae39ee8b755ea909cefaf57ec5e2ce F src/vdbemem.c 317b9f48708139db6239ade40c7980b4bc8233168383690d588dad6d8437f722 F src/vdbesort.c 0d40dca073c94e158ead752ef4225f4fee22dee84145e8c00ca2309afb489015 @@ -765,8 +765,8 @@ F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf8 F src/vdbevtab.c 57fa8f56478e5b5cb558cb425e7878515e0a105c54f96f1d1bbf4b9433529254 F src/vtab.c 1ecf8c3745d29275688d583e12822fa984d421e0286b5ef50c137bc3bf6d7a64 F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 -F src/wal.c dd843f619ac60d5dadab7109cf402432ba74dde0c301505fd1c202add07659e3 -F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a +F src/wal.c 02e10f033a6972bc7d50122b400318003199c504cda48f61ad404564505f4e89 +F src/wal.h 04a9e53121d5076f2a173b0f2facb39d33047093fee71bd3bbe6b1f6f1f5fd4b F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 F src/where.c b8917792f1e0dbfa28fb29e6cd3d560060d69667be0ba4c491cbc772363264f5 F src/whereInt.h c7d19902863beadec1d04e66aca39c0bcd60b74f05f0eaa7422c7005dfc5d51a @@ -1272,7 +1272,7 @@ F test/json/README.md 63e3e589e1df8fd3cc1588ba1faaff659214003f8b77a15af5c6452b35 F test/json/json-generator.tcl dc0dd0f393800c98658fc4c47eaa6af29d4e17527380cd28656fb261bddc8a3f F test/json/json-q1.txt 65f9d1cdcc4cffa9823fb73ed936aae5658700cd001fde448f68bfb91c807307 F test/json/json-speed-check.sh 8b7babf530faa58bd59d6d362cec8e9036a68c5457ff46f3b1f1511d21af6737 x -F test/json101.test 94126d4291d4a00e45f6988ce885c410de69243490e46e70e9946cb6e6f9ea02 +F test/json101.test 243b0a2650218ac5eafde6ce2a92a0e9d02bf24f62aec68693b69d9a693f120a F test/json102.test 24f6f204f9cde45b971016691d0b92a9b4c58040d699e36d6b12cb165f9083ff F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 @@ -1538,7 +1538,7 @@ F test/snapshot.test a504f2e7009f512ef66c719f0ea1c55a556bdaf1e1312c80a04d46fc1a3 F test/snapshot2.test 8d6ff5dd9cc503f6e12d408a30409c3f9c653507b24408d9cd7195931c89bc54 F test/snapshot3.test 8744313270c55f6e18574283553d3c5c5fe4c5970585663613a0e75c151e599b F test/snapshot4.test d4e9347ef2fcabc491fc893506c7bbaf334da3be111d6eb4f3a97cc623b78322 -F test/snapshot_fault.test f6c5ef7cb93bf92fbb4e864ecc5c87df7d3a250064838822db5b4d3a5563ede4 +F test/snapshot_fault.test 129234ceb9b26a0e1000e8563a16e790f5c1412354e70749cbd78c3d5d07d60a F test/snapshot_up.test a0a29c4cf33475fcef07c3f8e64af795e24ab91b4cc68295863402a393cdd41c F test/soak.test 18944cf21b94a7fe0df02016a6ee1e9632bc4e8d095a0cb49d95e15d5cca2d5c F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087 @@ -1898,6 +1898,7 @@ F test/walprotocol2.test 7d3b6b4bf0b12f8007121b1e6ef714bc99101fb3b48e46371df1db8 F test/walro.test cb438d05ba0d191f10b688e39c4f0cd5b71569a1d1f4440e5bdf3c6880e08c20 F test/walro2.test 33955a6fd874dd9724005e17f77fef89d334b3171454a1256fe4941a96766cdc F test/walrofault.test c70cb6e308c443867701856cce92ad8288cd99488fa52afab77cca6cfd51af68 +F test/walseh1.test 82da37763b0d87942dccd191e58321532ce3d44b87ef36e04ff9ce13f382bbae F test/walsetlk.test 34c901443b31ab720afc463f5b236c86ca5c4134402573dce91aa0761de8db5a F test/walshared.test 42e3808582504878af237ea02c42ca793e8a0efaa19df7df26ac573370dbc7a3 F test/walslow.test c05c68d4dc2700a982f89133ce103a1a84cc285f @@ -2012,7 +2013,7 @@ F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439 F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176 F tool/mkautoconfamal.sh f62353eb6c06ab264da027fd4507d09914433dbdcab9cb011cdc18016f1ab3b8 F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x -F tool/mkctimec.tcl 38e3db33210a200aae791635125052a643a27aa0619a0debf19aa9c55e1b2dde x +F tool/mkctimec.tcl aca4b83e49aecf10368cd5d11bc4847061041ade026db5bd8da17ef201f1403b x F tool/mkkeywordhash.c b9faa0ae7e14e4dbbcd951cddd786bf46b8a65bb07b129ba8c0cfade723aaffd F tool/mkmsvcmin.tcl 8897d515ef7f94772322db95a3b6fce6c614d84fe0bdd06ba5a1c786351d5a1d F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef @@ -2090,8 +2091,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 6c8538d83495ce65dbd7417263b3b06dbbb2a649e9a61a743911944599d75ffc -R 00e08242dc8443215cfb70aa39f020b2 +P 101de670774f63757180282763730aa53e70198bd7a674c27e6044632d39d22a 8a6b0c24937e855b710f97b4aea973eff53e6d43e1182842731547aa4b37db2a +R f0a8c88fe7b9660119e5c4d560adb197 U stephan -Z 4a14af7dfae41cdfaa811266b607e2bd +Z b7f921f442de7d419e4b8b5ee576c27d # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index a98896847a..0d1edcf329 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -101de670774f63757180282763730aa53e70198bd7a674c27e6044632d39d22a \ No newline at end of file +4f0aeeba0287e846908180eab6f7080ebe1323ebe49340771864d110e1ca5b2b \ No newline at end of file diff --git a/src/ctime.c b/src/ctime.c index c853e5ba46..3687241680 100644 --- a/src/ctime.c +++ b/src/ctime.c @@ -60,9 +60,6 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_4_BYTE_ALIGNED_MALLOC "4_BYTE_ALIGNED_MALLOC", #endif -#ifdef SQLITE_64BIT_STATS - "64BIT_STATS", -#endif #ifdef SQLITE_ALLOW_COVERING_INDEX_SCAN # if SQLITE_ALLOW_COVERING_INDEX_SCAN != 1 "ALLOW_COVERING_INDEX_SCAN=" CTIMEOPT_VAL(SQLITE_ALLOW_COVERING_INDEX_SCAN), @@ -399,6 +396,9 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_INTEGRITY_CHECK_ERROR_MAX "INTEGRITY_CHECK_ERROR_MAX=" CTIMEOPT_VAL(SQLITE_INTEGRITY_CHECK_ERROR_MAX), #endif +#ifdef SQLITE_LEGACY_JSON_VALID + "LEGACY_JSON_VALID", +#endif #ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS "LIKE_DOESNT_MATCH_BLOBS", #endif diff --git a/src/json.c b/src/json.c index fcaa9d84b9..f8d4aa2a77 100644 --- a/src/json.c +++ b/src/json.c @@ -3009,7 +3009,13 @@ static void jsonValidFunc( ){ JsonParse *p; /* The parse */ UNUSED_PARAMETER(argc); - if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ +#ifdef SQLITE_LEGACY_JSON_VALID + /* Incorrect legacy behavior was to return FALSE for a NULL input */ + sqlite3_result_int(ctx, 0); +#endif + return; + } p = jsonParseCached(ctx, argv[0], 0, 0); if( p==0 || p->oom ){ sqlite3_result_error_nomem(ctx); diff --git a/src/pager.c b/src/pager.c index 7f2cb5ba76..a53dc1889a 100644 --- a/src/pager.c +++ b/src/pager.c @@ -7729,4 +7729,10 @@ int sqlite3PagerWalFramesize(Pager *pPager){ } #endif +#ifdef SQLITE_USE_SEH +int sqlite3PagerWalSystemErrno(Pager *pPager){ + return sqlite3WalSystemErrno(pPager->pWal); +} +#endif + #endif /* SQLITE_OMIT_DISKIO */ diff --git a/src/pager.h b/src/pager.h index eb1306096c..10c1acd9b1 100644 --- a/src/pager.h +++ b/src/pager.h @@ -240,4 +240,8 @@ void sqlite3PagerRekey(DbPage*, Pgno, u16); # define enable_simulated_io_errors() #endif +#ifdef SQLITE_USE_SEH +int sqlite3PagerWalSystemErrno(Pager*); +#endif + #endif /* SQLITE_PAGER_H */ diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 2af549af21..48009b1454 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -528,6 +528,7 @@ int sqlite3_exec( #define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) #define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) #define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) +#define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) diff --git a/src/tokenize.c b/src/tokenize.c index e3ca9a82a2..f4d013deea 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -433,7 +433,7 @@ int sqlite3GetToken(const unsigned char *z, int *tokenType){ testcase( z[0]=='0' ); testcase( z[0]=='1' ); testcase( z[0]=='2' ); testcase( z[0]=='3' ); testcase( z[0]=='4' ); testcase( z[0]=='5' ); testcase( z[0]=='6' ); testcase( z[0]=='7' ); testcase( z[0]=='8' ); - testcase( z[0]=='9' ); + testcase( z[0]=='9' ); testcase( z[0]=='.' ); *tokenType = TK_INTEGER; #ifndef SQLITE_OMIT_HEX_INTEGER if( z[0]=='0' && (z[1]=='x' || z[1]=='X') && sqlite3Isxdigit(z[2]) ){ diff --git a/src/util.c b/src/util.c index 25fd6ff597..e417034754 100644 --- a/src/util.c +++ b/src/util.c @@ -141,6 +141,23 @@ void sqlite3ErrorClear(sqlite3 *db){ */ void sqlite3SystemError(sqlite3 *db, int rc){ if( rc==SQLITE_IOERR_NOMEM ) return; +#ifdef SQLITE_USE_SEH + if( rc==SQLITE_IOERR_IN_PAGE ){ + int ii; + int iErr; + sqlite3BtreeEnterAll(db); + for(ii=0; iinDb; ii++){ + if( db->aDb[ii].pBt ){ + iErr = sqlite3PagerWalSystemErrno(sqlite3BtreePager(db->aDb[ii].pBt)); + if( iErr ){ + db->iSysErrno = iErr; + } + } + } + sqlite3BtreeLeaveAll(db); + return; + } +#endif rc &= 0xff; if( rc==SQLITE_CANTOPEN || rc==SQLITE_IOERR ){ db->iSysErrno = sqlite3OsGetLastError(db->pVfs); diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 44c9b1e0cd..225c8d12c9 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -3372,6 +3372,7 @@ int sqlite3VdbeHalt(Vdbe *p){ sqlite3VdbeLeave(p); return SQLITE_BUSY; }else if( rc!=SQLITE_OK ){ + sqlite3SystemError(db, rc); p->rc = rc; sqlite3RollbackAll(db, SQLITE_OK); p->nChange = 0; diff --git a/src/wal.c b/src/wal.c index 747cf099e3..c9d8eaebe5 100644 --- a/src/wal.c +++ b/src/wal.c @@ -528,7 +528,13 @@ struct Wal { u32 iReCksum; /* On commit, recalculate checksums from here */ const char *zWalName; /* Name of WAL file */ u32 nCkpt; /* Checkpoint sequence counter in the wal-header */ +#ifdef SQLITE_USE_SEH + u32 lockMask; /* Mask of locks held */ + void *pFree; /* Pointer to sqlite3_free() if exception thrown */ + int iSysErrno; /* System error code following exception */ +#endif #ifdef SQLITE_DEBUG + int nSehTry; /* Number of nested SEH_TRY{} blocks */ u8 lockError; /* True if a locking error has occurred */ #endif #ifdef SQLITE_ENABLE_SNAPSHOT @@ -610,6 +616,100 @@ struct WalIterator { sizeof(ht_slot)*HASHTABLE_NSLOT + HASHTABLE_NPAGE*sizeof(u32) \ ) +/* +** Structured Exception Handling (SEH) is a Windows-specific technique +** for catching exceptions raised while accessing memory-mapped files. +** +** The -DSQLITE_USE_SEH compile-time option means to use SEH to catch and +** deal with system-level errors that arise during WAL -shm file processing. +** Without this compile-time option, any system-level faults that appear +** while accessing the memory-mapped -shm file will cause a process-wide +** signal to be deliver, which will more than likely cause the entire +** process to exit. +*/ +#ifdef SQLITE_USE_SEH +#include + +/* Beginning of a block of code in which an exception might occur */ +# define SEH_TRY __try { \ + assert( walAssertLockmask(pWal) && pWal->nSehTry==0 ); \ + VVA_ONLY(pWal->nSehTry++); + +/* The end of a block of code in which an exception might occur */ +# define SEH_EXCEPT(X) \ + VVA_ONLY(pWal->nSehTry--); \ + assert( pWal->nSehTry==0 ); \ + } __except( sehExceptionFilter(pWal, GetExceptionCode(), GetExceptionInformation() ) ){ X } + +/* Simulate a memory-mapping fault in the -shm file for testing purposes */ +# define SEH_INJECT_FAULT sehInjectFault(pWal) + +/* +** The second argument is the return value of GetExceptionCode() for the +** current exception. Return EXCEPTION_EXECUTE_HANDLER if the exception code +** indicates that the exception may have been caused by accessing the *-shm +** file mapping. Or EXCEPTION_CONTINUE_SEARCH otherwise. +*/ +static int sehExceptionFilter(Wal *pWal, int eCode, EXCEPTION_POINTERS *p){ + VVA_ONLY(pWal->nSehTry--); + if( eCode==EXCEPTION_IN_PAGE_ERROR ){ + if( p && p->ExceptionRecord && p->ExceptionRecord->NumberParameters>=3 ){ + /* From MSDN: For this type of exception, the first element of the + ** ExceptionInformation[] array is a read-write flag - 0 if the exception + ** was thrown while reading, 1 if while writing. The second element is + ** the virtual address being accessed. The "third array element specifies + ** the underlying NTSTATUS code that resulted in the exception". */ + pWal->iSysErrno = (int)p->ExceptionRecord->ExceptionInformation[2]; + } + return EXCEPTION_EXECUTE_HANDLER; + } + return EXCEPTION_CONTINUE_SEARCH; +} + +/* +** If one is configured, invoke the xTestCallback callback with 650 as +** the argument. If it returns true, throw the same exception that is +** thrown by the system if the *-shm file mapping is accessed after it +** has been invalidated. +*/ +static void sehInjectFault(Wal *pWal){ + int res; + assert( pWal->nSehTry>0 ); + + res = sqlite3FaultSim(650); + if( res!=0 ){ + ULONG_PTR aArg[3]; + aArg[0] = 0; + aArg[1] = 0; + aArg[2] = (ULONG_PTR)res; + RaiseException(EXCEPTION_IN_PAGE_ERROR, 0, 3, (const ULONG_PTR*)aArg); + } +} + +/* +** There are two ways to use this macro. To set a pointer to be freed +** if an exception is thrown: +** +** SEH_FREE_ON_ERROR(0, pPtr); +** +** and to cancel the same: +** +** SEH_FREE_ON_ERROR(pPtr, 0); +** +** In the first case, there must not already be a pointer registered to +** be freed. In the second case, pPtr must be the registered pointer. +*/ +#define SEH_FREE_ON_ERROR(X,Y) \ + assert( (X==0 || Y==0) && pWal->pFree==X ); pWal->pFree = Y + +#else +# define SEH_TRY VVA_ONLY(pWal->nSehTry++); +# define SEH_EXCEPT(X) VVA_ONLY(pWal->nSehTry--); assert( pWal->nSehTry==0 ); +# define SEH_INJECT_FAULT assert( pWal->nSehTry>0 ); +# define SEH_FREE_ON_ERROR(X,Y) +#endif /* ifdef SQLITE_USE_SEH */ + + /* ** Obtain a pointer to the iPage'th page of the wal-index. The wal-index ** is broken into pages of WALINDEX_PGSZ bytes. Wal-index pages are @@ -682,6 +782,7 @@ static int walIndexPage( int iPage, /* The page we seek */ volatile u32 **ppPage /* Write the page pointer here */ ){ + SEH_INJECT_FAULT; if( pWal->nWiData<=iPage || (*ppPage = pWal->apWiData[iPage])==0 ){ return walIndexPageRealloc(pWal, iPage, ppPage); } @@ -693,6 +794,7 @@ static int walIndexPage( */ static volatile WalCkptInfo *walCkptInfo(Wal *pWal){ assert( pWal->nWiData>0 && pWal->apWiData[0] ); + SEH_INJECT_FAULT; return (volatile WalCkptInfo*)&(pWal->apWiData[0][sizeof(WalIndexHdr)/2]); } @@ -701,6 +803,7 @@ static volatile WalCkptInfo *walCkptInfo(Wal *pWal){ */ static volatile WalIndexHdr *walIndexHdr(Wal *pWal){ assert( pWal->nWiData>0 && pWal->apWiData[0] ); + SEH_INJECT_FAULT; return (volatile WalIndexHdr*)pWal->apWiData[0]; } @@ -958,12 +1061,18 @@ static int walLockShared(Wal *pWal, int lockIdx){ WALTRACE(("WAL%p: acquire SHARED-%s %s\n", pWal, walLockName(lockIdx), rc ? "failed" : "ok")); VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && (rc&0xFF)!=SQLITE_BUSY); ) +#ifdef SQLITE_USE_SEH + if( rc==SQLITE_OK ) pWal->lockMask |= (1 << lockIdx); +#endif return rc; } static void walUnlockShared(Wal *pWal, int lockIdx){ if( pWal->exclusiveMode ) return; (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, 1, SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED); +#ifdef SQLITE_USE_SEH + pWal->lockMask &= ~(1 << lockIdx); +#endif WALTRACE(("WAL%p: release SHARED-%s\n", pWal, walLockName(lockIdx))); } static int walLockExclusive(Wal *pWal, int lockIdx, int n){ @@ -974,12 +1083,20 @@ static int walLockExclusive(Wal *pWal, int lockIdx, int n){ WALTRACE(("WAL%p: acquire EXCLUSIVE-%s cnt=%d %s\n", pWal, walLockName(lockIdx), n, rc ? "failed" : "ok")); VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && (rc&0xFF)!=SQLITE_BUSY); ) +#ifdef SQLITE_USE_SEH + if( rc==SQLITE_OK ){ + pWal->lockMask |= (((1<exclusiveMode ) return; (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, n, SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE); +#ifdef SQLITE_USE_SEH + pWal->lockMask &= ~(((1<apWiData[0][WALINDEX_HDR_SIZE/sizeof(u32) + iFrame - 1]; } @@ -1330,6 +1448,7 @@ static int walIndexRecover(Wal *pWal){ /* Malloc a buffer to read frames into. */ szFrame = szPage + WAL_FRAME_HDRSIZE; aFrame = (u8 *)sqlite3_malloc64(szFrame + WALINDEX_PGSZ); + SEH_FREE_ON_ERROR(0, aFrame); if( !aFrame ){ rc = SQLITE_NOMEM_BKPT; goto recovery_error; @@ -1405,9 +1524,11 @@ static int walIndexRecover(Wal *pWal){ } } #endif + SEH_INJECT_FAULT; if( iFrame<=iLast ) break; } + SEH_FREE_ON_ERROR(aFrame, 0); sqlite3_free(aFrame); } @@ -1435,6 +1556,7 @@ finished: }else{ pInfo->aReadMark[i] = READMARK_NOT_USED; } + SEH_INJECT_FAULT; walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); }else if( rc!=SQLITE_BUSY ){ goto recovery_error; @@ -1827,6 +1949,7 @@ static int walIteratorInit(Wal *pWal, u32 nBackfill, WalIterator **pp){ memset(p, 0, nByte); p->nSegment = nSegment; aTmp = (ht_slot*)&(((u8*)p)[nByte]); + SEH_FREE_ON_ERROR(0, p); for(i=walFramePage(nBackfill+1); rc==SQLITE_OK && ihdr.mxFrame; mxPage = pWal->hdr.nPage; for(i=1; iaReadMark+i); + u32 y = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT; if( mxSafeFrame>y ){ assert( y<=pWal->hdr.mxFrame ); rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(i), 1); if( rc==SQLITE_OK ){ u32 iMark = (i==1 ? mxSafeFrame : READMARK_NOT_USED); - AtomicStore(pInfo->aReadMark+i, iMark); + AtomicStore(pInfo->aReadMark+i, iMark); SEH_INJECT_FAULT; walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); }else if( rc==SQLITE_BUSY ){ mxSafeFrame = y; @@ -2107,8 +2231,7 @@ static int walCheckpoint( && (rc = walBusyLock(pWal,xBusy,pBusyArg,WAL_READ_LOCK(0),1))==SQLITE_OK ){ u32 nBackfill = pInfo->nBackfill; - - pInfo->nBackfillAttempted = mxSafeFrame; + pInfo->nBackfillAttempted = mxSafeFrame; SEH_INJECT_FAULT; /* Sync the WAL to disk */ rc = sqlite3OsSync(pWal->pWalFd, CKPT_SYNC_FLAGS(sync_flags)); @@ -2139,6 +2262,7 @@ static int walCheckpoint( while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ i64 iOffset; assert( walFramePgno(pWal, iFrame)==iDbpage ); + SEH_INJECT_FAULT; if( AtomicLoad(&db->u1.isInterrupted) ){ rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT; break; @@ -2168,7 +2292,7 @@ static int walCheckpoint( } } if( rc==SQLITE_OK ){ - AtomicStore(&pInfo->nBackfill, mxSafeFrame); + AtomicStore(&pInfo->nBackfill, mxSafeFrame); SEH_INJECT_FAULT; } } @@ -2190,6 +2314,7 @@ static int walCheckpoint( */ if( rc==SQLITE_OK && eMode!=SQLITE_CHECKPOINT_PASSIVE ){ assert( pWal->writeLock ); + SEH_INJECT_FAULT; if( pInfo->nBackfillhdr.mxFrame ){ rc = SQLITE_BUSY; }else if( eMode>=SQLITE_CHECKPOINT_RESTART ){ @@ -2221,6 +2346,7 @@ static int walCheckpoint( } walcheckpoint_out: + SEH_FREE_ON_ERROR(pIter, 0); walIteratorFree(pIter); return rc; } @@ -2243,6 +2369,87 @@ static void walLimitSize(Wal *pWal, i64 nMax){ } } +#ifdef SQLITE_USE_SEH +/* +** This is the "standard" exception handler used in a few places to handle +** an exception thrown by reading from the *-shm mapping after it has become +** invalid in SQLITE_USE_SEH builds. It is used as follows: +** +** SEH_TRY { ... } +** SEH_EXCEPT( rc = walHandleException(pWal); ) +** +** This function does three things: +** +** 1) Determines the locks that should be held, based on the contents of +** the Wal.readLock, Wal.writeLock and Wal.ckptLock variables. All other +** held locks are assumed to be transient locks that would have been +** released had the exception not been thrown and are dropped. +** +** 2) Frees the pointer at Wal.pFree, if any, using sqlite3_free(). +** +** 3) Returns SQLITE_IOERR. +*/ +static int walHandleException(Wal *pWal){ + if( pWal->exclusiveMode==0 ){ + static const int S = 1; + static const int E = (1<lockMask & ~( + (pWal->readLock<0 ? 0 : (S << WAL_READ_LOCK(pWal->readLock))) + | (pWal->writeLock ? (E << WAL_WRITE_LOCK) : 0) + | (pWal->ckptLock ? (E << WAL_CKPT_LOCK) : 0) + ); + for(ii=0; iipFree); + pWal->pFree = 0; + return SQLITE_IOERR_IN_PAGE; +} + +/* +** Assert that the Wal.lockMask mask, which indicates the locks held +** by the connenction, is consistent with the Wal.readLock, Wal.writeLock +** and Wal.ckptLock variables. To be used as: +** +** assert( walAssertLockmask(pWal) ); +*/ +static int walAssertLockmask(Wal *pWal){ + if( pWal->exclusiveMode==0 ){ + static const int S = 1; + static const int E = (1<readLock<0 ? 0 : (S << WAL_READ_LOCK(pWal->readLock))) + | (pWal->writeLock ? (E << WAL_WRITE_LOCK) : 0) + | (pWal->ckptLock ? (E << WAL_CKPT_LOCK) : 0) +#ifdef SQLITE_ENABLE_SNAPSHOT + | (pWal->pSnapshot ? (pWal->lockMask & (1 << WAL_CKPT_LOCK)) : 0) +#endif + ); + assert( mExpect==pWal->lockMask ); + } + return 1; +} + +/* +** Return and zero the "system error" field set when an +** EXCEPTION_IN_PAGE_ERROR exception is caught. +*/ +int sqlite3WalSystemErrno(Wal *pWal){ + int iRet = 0; + if( pWal ){ + iRet = pWal->iSysErrno; + pWal->iSysErrno = 0; + } + return iRet; +} + +#else +# define walAssertLockmask(x) 1 +#endif /* ifdef SQLITE_USE_SEH */ + /* ** Close a connection to a log file. */ @@ -2257,6 +2464,8 @@ int sqlite3WalClose( if( pWal ){ int isDelete = 0; /* True to unlink wal and wal-index files */ + assert( walAssertLockmask(pWal) ); + /* If an EXCLUSIVE lock can be obtained on the database file (using the ** ordinary, rollback-mode locking methods, this guarantees that the ** connection associated with this log file is the only connection to @@ -2799,6 +3008,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ assert( pWal->nWiData>0 ); assert( pWal->apWiData[0]!=0 ); pInfo = walCkptInfo(pWal); + SEH_INJECT_FAULT; if( !useWal && AtomicLoad(&pInfo->nBackfill)==pWal->hdr.mxFrame #ifdef SQLITE_ENABLE_SNAPSHOT && (pWal->pSnapshot==0 || pWal->hdr.mxFrame==0) @@ -2848,7 +3058,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ } #endif for(i=1; iaReadMark+i); + u32 thisMark = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT; if( mxReadMark<=thisMark && thisMark<=mxFrame ){ assert( thisMark!=READMARK_NOT_USED ); mxReadMark = thisMark; @@ -2914,7 +3124,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ ** we can guarantee that the checkpointer that set nBackfill could not ** see any pages past pWal->hdr.mxFrame, this problem does not come up. */ - pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; + pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; SEH_INJECT_FAULT; walShmBarrier(pWal); if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) @@ -2929,6 +3139,53 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ } #ifdef SQLITE_ENABLE_SNAPSHOT +/* +** This function does the work of sqlite3WalSnapshotRecover(). +*/ +static int walSnapshotRecover( + Wal *pWal, /* WAL handle */ + void *pBuf1, /* Temp buffer pWal->szPage bytes in size */ + void *pBuf2 /* Temp buffer pWal->szPage bytes in size */ +){ + int szPage = (int)pWal->szPage; + int rc; + i64 szDb; /* Size of db file in bytes */ + + rc = sqlite3OsFileSize(pWal->pDbFd, &szDb); + if( rc==SQLITE_OK ){ + volatile WalCkptInfo *pInfo = walCkptInfo(pWal); + u32 i = pInfo->nBackfillAttempted; + for(i=pInfo->nBackfillAttempted; i>AtomicLoad(&pInfo->nBackfill); i--){ + WalHashLoc sLoc; /* Hash table location */ + u32 pgno; /* Page number in db file */ + i64 iDbOff; /* Offset of db file entry */ + i64 iWalOff; /* Offset of wal file entry */ + + rc = walHashGet(pWal, walFramePage(i), &sLoc); + if( rc!=SQLITE_OK ) break; + pgno = sLoc.aPgno[i-sLoc.iZero]; + iDbOff = (i64)(pgno-1) * szPage; + + if( iDbOff+szPage<=szDb ){ + iWalOff = walFrameOffset(i, szPage) + WAL_FRAME_HDRSIZE; + rc = sqlite3OsRead(pWal->pWalFd, pBuf1, szPage, iWalOff); + + if( rc==SQLITE_OK ){ + rc = sqlite3OsRead(pWal->pDbFd, pBuf2, szPage, iDbOff); + } + + if( rc!=SQLITE_OK || 0==memcmp(pBuf1, pBuf2, szPage) ){ + break; + } + } + + pInfo->nBackfillAttempted = i-1; + } + } + + return rc; +} + /* ** Attempt to reduce the value of the WalCkptInfo.nBackfillAttempted ** variable so that older snapshots can be accessed. To do this, loop @@ -2954,50 +3211,21 @@ int sqlite3WalSnapshotRecover(Wal *pWal){ assert( pWal->readLock>=0 ); rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); if( rc==SQLITE_OK ){ - volatile WalCkptInfo *pInfo = walCkptInfo(pWal); - int szPage = (int)pWal->szPage; - i64 szDb; /* Size of db file in bytes */ - - rc = sqlite3OsFileSize(pWal->pDbFd, &szDb); - if( rc==SQLITE_OK ){ - void *pBuf1 = sqlite3_malloc(szPage); - void *pBuf2 = sqlite3_malloc(szPage); - if( pBuf1==0 || pBuf2==0 ){ - rc = SQLITE_NOMEM; - }else{ - u32 i = pInfo->nBackfillAttempted; - for(i=pInfo->nBackfillAttempted; i>AtomicLoad(&pInfo->nBackfill); i--){ - WalHashLoc sLoc; /* Hash table location */ - u32 pgno; /* Page number in db file */ - i64 iDbOff; /* Offset of db file entry */ - i64 iWalOff; /* Offset of wal file entry */ - - rc = walHashGet(pWal, walFramePage(i), &sLoc); - if( rc!=SQLITE_OK ) break; - assert( i - sLoc.iZero - 1 >=0 ); - pgno = sLoc.aPgno[i-sLoc.iZero-1]; - iDbOff = (i64)(pgno-1) * szPage; - - if( iDbOff+szPage<=szDb ){ - iWalOff = walFrameOffset(i, szPage) + WAL_FRAME_HDRSIZE; - rc = sqlite3OsRead(pWal->pWalFd, pBuf1, szPage, iWalOff); - - if( rc==SQLITE_OK ){ - rc = sqlite3OsRead(pWal->pDbFd, pBuf2, szPage, iDbOff); - } - - if( rc!=SQLITE_OK || 0==memcmp(pBuf1, pBuf2, szPage) ){ - break; - } - } - - pInfo->nBackfillAttempted = i-1; - } + void *pBuf1 = sqlite3_malloc(pWal->szPage); + void *pBuf2 = sqlite3_malloc(pWal->szPage); + if( pBuf1==0 || pBuf2==0 ){ + rc = SQLITE_NOMEM; + }else{ + pWal->ckptLock = 1; + SEH_TRY { + rc = walSnapshotRecover(pWal, pBuf1, pBuf2); } - - sqlite3_free(pBuf1); - sqlite3_free(pBuf2); + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + pWal->ckptLock = 0; } + + sqlite3_free(pBuf1); + sqlite3_free(pBuf2); walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1); } @@ -3006,28 +3234,20 @@ int sqlite3WalSnapshotRecover(Wal *pWal){ #endif /* SQLITE_ENABLE_SNAPSHOT */ /* -** Begin a read transaction on the database. -** -** This routine used to be called sqlite3OpenSnapshot() and with good reason: -** it takes a snapshot of the state of the WAL and wal-index for the current -** instant in time. The current thread will continue to use this snapshot. -** Other threads might append new content to the WAL and wal-index but -** that extra content is ignored by the current thread. -** -** If the database contents have changes since the previous read -** transaction, then *pChanged is set to 1 before returning. The -** Pager layer will use this to know that its cache is stale and -** needs to be flushed. +** This function does the work of sqlite3WalBeginReadTransaction() (see +** below). That function simply calls this one inside an SEH_TRY{...} block. */ -int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ +static int walBeginReadTransaction(Wal *pWal, int *pChanged){ int rc; /* Return code */ int cnt = 0; /* Number of TryBeginRead attempts */ #ifdef SQLITE_ENABLE_SNAPSHOT + int ckptLock = 0; int bChanged = 0; WalIndexHdr *pSnapshot = pWal->pSnapshot; #endif assert( pWal->ckptLock==0 ); + assert( pWal->nSehTry>0 ); #ifdef SQLITE_ENABLE_SNAPSHOT if( pSnapshot ){ @@ -3050,7 +3270,7 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ if( rc!=SQLITE_OK ){ return rc; } - pWal->ckptLock = 1; + ckptLock = 1; } #endif @@ -3114,15 +3334,37 @@ int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ } /* Release the shared CKPT lock obtained above. */ - if( pWal->ckptLock ){ + if( ckptLock ){ assert( pSnapshot ); walUnlockShared(pWal, WAL_CKPT_LOCK); - pWal->ckptLock = 0; } #endif return rc; } +/* +** Begin a read transaction on the database. +** +** This routine used to be called sqlite3OpenSnapshot() and with good reason: +** it takes a snapshot of the state of the WAL and wal-index for the current +** instant in time. The current thread will continue to use this snapshot. +** Other threads might append new content to the WAL and wal-index but +** that extra content is ignored by the current thread. +** +** If the database contents have changes since the previous read +** transaction, then *pChanged is set to 1 before returning. The +** Pager layer will use this to know that its cache is stale and +** needs to be flushed. +*/ +int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ + int rc; + SEH_TRY { + rc = walBeginReadTransaction(pWal, pChanged); + } + SEH_EXCEPT( rc = walHandleException(pWal); ) + return rc; +} + /* ** Finish with a read transaction. All this does is release the ** read-lock. @@ -3143,7 +3385,7 @@ void sqlite3WalEndReadTransaction(Wal *pWal){ ** Return SQLITE_OK if successful, or an error code if an error occurs. If an ** error does occur, the final value of *piRead is undefined. */ -int sqlite3WalFindFrame( +static int walFindFrame( Wal *pWal, /* WAL handle */ Pgno pgno, /* Database page number to read data for */ u32 *piRead /* OUT: Frame number (or zero) */ @@ -3206,6 +3448,7 @@ int sqlite3WalFindFrame( } nCollide = HASHTABLE_NSLOT; iKey = walHash(pgno); + SEH_INJECT_FAULT; while( (iH = AtomicLoad(&sLoc.aHash[iKey]))!=0 ){ u32 iFrame = iH + sLoc.iZero; if( iFrame<=iLast && iFrame>=pWal->minFrame && sLoc.aPgno[iH-1]==pgno ){ @@ -3242,6 +3485,30 @@ int sqlite3WalFindFrame( return SQLITE_OK; } +/* +** Search the wal file for page pgno. If found, set *piRead to the frame that +** contains the page. Otherwise, if pgno is not in the wal file, set *piRead +** to zero. +** +** Return SQLITE_OK if successful, or an error code if an error occurs. If an +** error does occur, the final value of *piRead is undefined. +** +** The difference between this function and walFindFrame() is that this +** function wraps walFindFrame() in an SEH_TRY{...} block. +*/ +int sqlite3WalFindFrame( + Wal *pWal, /* WAL handle */ + Pgno pgno, /* Database page number to read data for */ + u32 *piRead /* OUT: Frame number (or zero) */ +){ + int rc; + SEH_TRY { + rc = walFindFrame(pWal, pgno, piRead); + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + return rc; +} + /* ** Read the contents of frame iRead from the wal file into buffer pOut ** (which is nOut bytes in size). Return SQLITE_OK if successful, or an @@ -3323,12 +3590,17 @@ int sqlite3WalBeginWriteTransaction(Wal *pWal){ ** time the read transaction on this connection was started, then ** the write is disallowed. */ - if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){ + SEH_TRY { + if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){ + rc = SQLITE_BUSY_SNAPSHOT; + } + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + + if( rc!=SQLITE_OK ){ walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); pWal->writeLock = 0; - rc = SQLITE_BUSY_SNAPSHOT; } - return rc; } @@ -3364,30 +3636,33 @@ int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx){ Pgno iMax = pWal->hdr.mxFrame; Pgno iFrame; - /* Restore the clients cache of the wal-index header to the state it - ** was in before the client began writing to the database. - */ - memcpy(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr)); - - for(iFrame=pWal->hdr.mxFrame+1; - ALWAYS(rc==SQLITE_OK) && iFrame<=iMax; - iFrame++ - ){ - /* This call cannot fail. Unless the page for which the page number - ** is passed as the second argument is (a) in the cache and - ** (b) has an outstanding reference, then xUndo is either a no-op - ** (if (a) is false) or simply expels the page from the cache (if (b) - ** is false). - ** - ** If the upper layer is doing a rollback, it is guaranteed that there - ** are no outstanding references to any page other than page 1. And - ** page 1 is never written to the log until the transaction is - ** committed. As a result, the call to xUndo may not fail. + SEH_TRY { + /* Restore the clients cache of the wal-index header to the state it + ** was in before the client began writing to the database. */ - assert( walFramePgno(pWal, iFrame)!=1 ); - rc = xUndo(pUndoCtx, walFramePgno(pWal, iFrame)); + memcpy(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr)); + + for(iFrame=pWal->hdr.mxFrame+1; + ALWAYS(rc==SQLITE_OK) && iFrame<=iMax; + iFrame++ + ){ + /* This call cannot fail. Unless the page for which the page number + ** is passed as the second argument is (a) in the cache and + ** (b) has an outstanding reference, then xUndo is either a no-op + ** (if (a) is false) or simply expels the page from the cache (if (b) + ** is false). + ** + ** If the upper layer is doing a rollback, it is guaranteed that there + ** are no outstanding references to any page other than page 1. And + ** page 1 is never written to the log until the transaction is + ** committed. As a result, the call to xUndo may not fail. + */ + assert( walFramePgno(pWal, iFrame)!=1 ); + rc = xUndo(pUndoCtx, walFramePgno(pWal, iFrame)); + } + if( iMax!=pWal->hdr.mxFrame ) walCleanupHash(pWal); } - if( iMax!=pWal->hdr.mxFrame ) walCleanupHash(pWal); + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) } return rc; } @@ -3431,7 +3706,10 @@ int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){ pWal->hdr.mxFrame = aWalData[0]; pWal->hdr.aFrameCksum[0] = aWalData[1]; pWal->hdr.aFrameCksum[1] = aWalData[2]; - walCleanupHash(pWal); + SEH_TRY { + walCleanupHash(pWal); + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) } return rc; @@ -3612,7 +3890,7 @@ static int walRewriteChecksums(Wal *pWal, u32 iLast){ ** Write a set of frames to the log. The caller must hold the write-lock ** on the log file (obtained using sqlite3WalBeginWriteTransaction()). */ -int sqlite3WalFrames( +static int walFrames( Wal *pWal, /* Wal handle to write to */ int szPage, /* Database page-size in bytes */ PgHdr *pList, /* List of dirty pages to write */ @@ -3723,7 +4001,7 @@ int sqlite3WalFrames( ** checksums must be recomputed when the transaction is committed. */ if( iFirst && (p->pDirty || isCommit==0) ){ u32 iWrite = 0; - VVA_ONLY(rc =) sqlite3WalFindFrame(pWal, p->pgno, &iWrite); + VVA_ONLY(rc =) walFindFrame(pWal, p->pgno, &iWrite); assert( rc==SQLITE_OK || iWrite==0 ); if( iWrite>=iFirst ){ i64 iOff = walFrameOffset(iWrite, szPage) + WAL_FRAME_HDRSIZE; @@ -3842,6 +4120,29 @@ int sqlite3WalFrames( return rc; } +/* +** Write a set of frames to the log. The caller must hold the write-lock +** on the log file (obtained using sqlite3WalBeginWriteTransaction()). +** +** The difference between this function and walFrames() is that this +** function wraps walFrames() in an SEH_TRY{...} block. +*/ +int sqlite3WalFrames( + Wal *pWal, /* Wal handle to write to */ + int szPage, /* Database page-size in bytes */ + PgHdr *pList, /* List of dirty pages to write */ + Pgno nTruncate, /* Database size after this commit */ + int isCommit, /* True if this is a commit */ + int sync_flags /* Flags to pass to OsSync() (or 0) */ +){ + int rc; + SEH_TRY { + rc = walFrames(pWal, szPage, pList, nTruncate, isCommit, sync_flags); + } + SEH_EXCEPT( rc = walHandleException(pWal); ) + return rc; +} + /* ** This routine is called to implement sqlite3_wal_checkpoint() and ** related interfaces. @@ -3921,30 +4222,33 @@ int sqlite3WalCheckpoint( /* Read the wal-index header. */ - if( rc==SQLITE_OK ){ - walDisableBlocking(pWal); - rc = walIndexReadHdr(pWal, &isChanged); - (void)walEnableBlocking(pWal); - if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ - sqlite3OsUnfetch(pWal->pDbFd, 0, 0); - } - } - - /* Copy data from the log to the database file. */ - if( rc==SQLITE_OK ){ - - if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ - rc = SQLITE_CORRUPT_BKPT; - }else{ - rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags, zBuf); - } - - /* If no error occurred, set the output variables. */ - if( rc==SQLITE_OK || rc==SQLITE_BUSY ){ - if( pnLog ) *pnLog = (int)pWal->hdr.mxFrame; - if( pnCkpt ) *pnCkpt = (int)(walCkptInfo(pWal)->nBackfill); + SEH_TRY { + if( rc==SQLITE_OK ){ + walDisableBlocking(pWal); + rc = walIndexReadHdr(pWal, &isChanged); + (void)walEnableBlocking(pWal); + if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ + sqlite3OsUnfetch(pWal->pDbFd, 0, 0); + } + } + + /* Copy data from the log to the database file. */ + if( rc==SQLITE_OK ){ + if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ + rc = SQLITE_CORRUPT_BKPT; + }else{ + rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags,zBuf); + } + + /* If no error occurred, set the output variables. */ + if( rc==SQLITE_OK || rc==SQLITE_BUSY ){ + if( pnLog ) *pnLog = (int)pWal->hdr.mxFrame; + SEH_INJECT_FAULT; + if( pnCkpt ) *pnCkpt = (int)(walCkptInfo(pWal)->nBackfill); + } } } + SEH_EXCEPT( rc = walHandleException(pWal); ) if( isChanged ){ /* If a new wal-index header was loaded before the checkpoint was @@ -4021,7 +4325,9 @@ int sqlite3WalExclusiveMode(Wal *pWal, int op){ ** locks are taken in this case). Nor should the pager attempt to ** upgrade to exclusive-mode following such an error. */ +#ifndef SQLITE_USE_SEH assert( pWal->readLock>=0 || pWal->lockError ); +#endif assert( pWal->readLock>=0 || (op<=0 && pWal->exclusiveMode==0) ); if( op==0 ){ @@ -4122,16 +4428,19 @@ int sqlite3_snapshot_cmp(sqlite3_snapshot *p1, sqlite3_snapshot *p2){ */ int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot){ int rc; - rc = walLockShared(pWal, WAL_CKPT_LOCK); - if( rc==SQLITE_OK ){ - WalIndexHdr *pNew = (WalIndexHdr*)pSnapshot; - if( memcmp(pNew->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)) - || pNew->mxFramenBackfillAttempted - ){ - rc = SQLITE_ERROR_SNAPSHOT; - walUnlockShared(pWal, WAL_CKPT_LOCK); + SEH_TRY { + rc = walLockShared(pWal, WAL_CKPT_LOCK); + if( rc==SQLITE_OK ){ + WalIndexHdr *pNew = (WalIndexHdr*)pSnapshot; + if( memcmp(pNew->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)) + || pNew->mxFramenBackfillAttempted + ){ + rc = SQLITE_ERROR_SNAPSHOT; + walUnlockShared(pWal, WAL_CKPT_LOCK); + } } } + SEH_EXCEPT( rc = walHandleException(pWal); ) return rc; } diff --git a/src/wal.h b/src/wal.h index 02e2bab360..d39bb50f3d 100644 --- a/src/wal.h +++ b/src/wal.h @@ -151,5 +151,9 @@ int sqlite3WalWriteLock(Wal *pWal, int bLock); void sqlite3WalDb(Wal *pWal, sqlite3 *db); #endif +#ifdef SQLITE_USE_SEH +int sqlite3WalSystemErrno(Wal*); +#endif + #endif /* ifndef SQLITE_OMIT_WAL */ #endif /* SQLITE_WAL_H */ diff --git a/test/json101.test b/test/json101.test index 543e4c71e7..8bb133ec73 100644 --- a/test/json101.test +++ b/test/json101.test @@ -923,9 +923,15 @@ do_execsql_test json-20.3 { # a NULL value as the JSON input. # db null NULL -do_execsql_test json-21.1 { - SELECT json_valid(NULL); -} NULL +if {[db exists {SELECT * FROM pragma_compile_options WHERE compile_options LIKE '%legacy_json_valid%'}]} { + do_execsql_test json-21.1-legacy { + SELECT json_valid(NULL); + } 0 +} else { + do_execsql_test json-21.1-correct { + SELECT json_valid(NULL); + } NULL +} do_execsql_test json-21.2 { SELECT json_error_position(NULL); } NULL diff --git a/test/snapshot_fault.test b/test/snapshot_fault.test index 2adb793650..10c5094594 100644 --- a/test/snapshot_fault.test +++ b/test/snapshot_fault.test @@ -23,6 +23,7 @@ set testprefix snapshot_fault # checkpointing the db. # do_faultsim_test 1.0 -prep { + catch { db2 close } faultsim_delete_and_reopen sqlite3 db2 test.db db2 eval { diff --git a/test/walseh1.test b/test/walseh1.test new file mode 100644 index 0000000000..c3a655f534 --- /dev/null +++ b/test/walseh1.test @@ -0,0 +1,150 @@ +# 2021 August 16 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl +set testprefix walseh1 + +set ::seh_countdown 0 +set ::seh_errno 10 +proc seh_faultsim_callback {iFault} { + if {$iFault==650} { + incr ::seh_countdown -1 + if {$::seh_countdown==0} { return $::seh_errno } + } + return 0 +} + +proc seh_injectinstall {} { + sqlite3_test_control_fault_install seh_faultsim_callback +} +proc seh_injectuninstall {} { + sqlite3_test_control_fault_install +} +proc seh_injectstart {iFail} { + set ::seh_errno [expr 2+$iFail*10] + set ::seh_countdown $iFail +} +proc seh_injectstop {} { + set res [expr $::seh_countdown<=0] + set ::seh_countdown 0 + set res +} + +set FAULTSIM(seh) [list \ + -injectinstall seh_injectinstall \ + -injectstart seh_injectstart \ + -injectstop seh_injectstop \ + -injecterrlist {{1 {disk I/O error}}} \ + -injectuninstall seh_injectuninstall \ +] + +proc test_system_errno {db expect} { + set serrno [sqlite3_system_errno $db] + if {$serrno!=$expect} { + error "surprising system_errno. Expected $expect, got $serrno" + } +} + +do_execsql_test 1.0 { + PRAGMA journal_mode = wal; + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); +} {wal} +faultsim_save_and_close + +do_faultsim_test 1 -faults seh -prep { + catch { db2 close } + faultsim_restore_and_reopen + execsql { SELECT * FROM sqlite_schema } + sqlite3 db2 test.db +} -body { + execsql { SELECT * FROM t1 } db2 +} -test { + faultsim_test_result {0 {1 2 3 4}} + if {$testrc} { test_system_errno db2 $::seh_errno } +} +catch { db2 close } + +faultsim_save_and_close + +do_faultsim_test 2 -faults seh -prep { + catch { db close } + faultsim_restore_and_reopen +} -body { + execsql { SELECT * FROM t1 } +} -test { + faultsim_test_result {0 {1 2 3 4}} + if {$testrc} { test_system_errno db $::seh_errno } +} + +do_faultsim_test 3 -faults seh -prep { + catch { db close } + faultsim_restore_and_reopen +} -body { + execsql { INSERT INTO t1 VALUES(5, 6) } + execsql { SELECT * FROM t1 } +} -test { + faultsim_test_result {0 {1 2 3 4 5 6}} + if {$testrc} { test_system_errno db $::seh_errno } +} + +do_faultsim_test 4 -faults seh -prep { + catch { db close } + faultsim_restore_and_reopen +} -body { + execsql { PRAGMA wal_checkpoint } + execsql { INSERT INTO t1 VALUES(7, 8) } + execsql { SELECT * FROM t1 } +} -test { + faultsim_test_result {0 {1 2 3 4 7 8}} + if {$testrc} { test_system_errno db $::seh_errno } +} +catch { db close } + +do_faultsim_test 5 -faults seh -prep { + catch { db close } + faultsim_restore_and_reopen + execsql { + PRAGMA cache_size = 5; + BEGIN; + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<50 + ) + INSERT INTO t1 SELECT randomblob(500), randomblob(500) FROM s; + } +} -body { + execsql ROLLBACK +} -test { + faultsim_test_result {0 {}} + if {$testrc} { test_system_errno db $::seh_errno } +} +catch { db close } + +do_faultsim_test 6 -faults seh -prep { + catch { db close } + faultsim_restore_and_reopen +} -body { + execsql { PRAGMA wal_checkpoint = TRUNCATE } + execsql { INSERT INTO t1 VALUES(7, 8) } + execsql { SELECT * FROM t1 } +} -test { + faultsim_test_result {0 {1 2 3 4 7 8}} + if {$testrc} { test_system_errno db $::seh_errno } +} +catch { db close } + +finish_test + + diff --git a/tool/mkctimec.tcl b/tool/mkctimec.tcl index bcb1a54bb8..317f30a35f 100755 --- a/tool/mkctimec.tcl +++ b/tool/mkctimec.tcl @@ -180,6 +180,7 @@ set boolean_defnil_options { SQLITE_IGNORE_FLOCK_LOCK_ERRORS SQLITE_INLINE_MEMCPY SQLITE_INT64_TYPE + SQLITE_LEGACY_JSON_VALID SQLITE_LIKE_DOESNT_MATCH_BLOBS SQLITE_LOCK_TRACE SQLITE_LOG_CACHE_SPILL