diff options
Diffstat (limited to 'ext/wasm/api/sqlite3-wasm.c')
-rw-r--r-- | ext/wasm/api/sqlite3-wasm.c | 414 |
1 files changed, 316 insertions, 98 deletions
diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index 6a81da3e5..da8f5c32e 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -1,4 +1,89 @@ -#include "sqlite3.c" +/* +** This file requires access to sqlite3.c static state in order to +** implement certain WASM-specific features, and thus directly +** includes that file. Unlike the rest of sqlite3.c, this file +** requires compiling with -std=c99 (or equivalent, or a later C +** version) because it makes use of features not available in C89. +** +** At it's simplest, to build sqlite3.wasm either place this file +** in the same directory as sqlite3.c/h before compilation or use the +** -I/path flag to tell the compiler where to find both of those +** files, then compile this file. For example: +** +** emcc -o sqlite3.wasm ... -I/path/to/sqlite3-c-and-h sqlite3-wasm.c +*/ + +/* +** Threading and file locking: JS is single-threaded. Each Worker +** thread is a separate instance of the JS engine so can never access +** the same db handle as another thread, thus multi-threading support +** is unnecessary in the library. Because the filesystems are virtual +** and local to a given wasm runtime instance, two Workers can never +** access the same db file at once, with the exception of OPFS. As of +** this writing (2022-09-30), OPFS exclusively locks a file when +** opening it, so two Workers can never open the same OPFS-backed file +** at once. That situation will change if and when lower-level locking +** features are added to OPFS (as is currently planned, per folks +** involved with its development). +** +** Summary: except for the case of future OPFS, which supports +** locking, and any similar future filesystems, threading and file +** locking support are unnecessary in the wasm build. +*/ +#undef SQLITE_OMIT_DESERIALIZE +#ifndef SQLITE_DEFAULT_UNIX_VFS +# define SQLITE_DEFAULT_UNIX_VFS "unix-none" +#endif +#ifndef SQLITE_OMIT_DEPRECATED +# define SQLITE_OMIT_DEPRECATED +#endif +#ifndef SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION +#endif +#ifndef SQLITE_OMIT_SHARED_CACHE +# define SQLITE_OMIT_SHARED_CACHE +#endif +#ifndef SQLITE_OMIT_UTF16 +# define SQLITE_OMIT_UTF16 +#endif +#ifndef SQLITE_OS_KV_OPTIONAL +# define SQLITE_OS_KV_OPTIONAL 1 +#endif +#ifndef SQLITE_TEMP_STORE +# define SQLITE_TEMP_STORE 3 +#endif +#ifndef SQLITE_THREADSAFE +# define SQLITE_THREADSAFE 0 +#endif + +#include "sqlite3.c" /* yes, .c instead of .h. */ + +/* +** WASM_KEEP is identical to EMSCRIPTEN_KEEPALIVE but is not +** Emscripten-specific. It explicitly marks functions for export into +** the target wasm file without requiring explicit listing of those +** functions in Emscripten's -sEXPORTED_FUNCTIONS=... list (or +** equivalent in other build platforms). Any function with neither +** this attribute nor which is listed as an explicit export will not +** be exported from the wasm file (but may still be used internally +** within the wasm file). +** +** The functions in this file (sqlite3-wasm.c) which require exporting +** are marked with this flag. They may also be added to any explicit +** build-time export list but need not be. All of these APIs are +** intended for use only within the project's own JS/WASM code, and +** not by client code, so an argument can be made for reducing their +** visibility by not including them in any build-time export lists. +** +** 2022-09-11: it's not yet _proven_ that this approach works in +** non-Emscripten builds. If not, such builds will need to export +** those using the --export=... wasm-ld flag (or equivalent). As of +** this writing we are tied to Emscripten for various reasons +** and cannot test the library with other build environments. +*/ +#define WASM_KEEP __attribute__((used,visibility("default"))) +// See also: +//__attribute__((export_name("theExportedName"), used, visibility("default"))) /* ** This function is NOT part of the sqlite3 public API. It is strictly @@ -14,8 +99,8 @@ ** ** Returns err_code. */ -int sqlite3_wasm_db_error(sqlite3*db, int err_code, - const char *zMsg){ +WASM_KEEP +int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ if(0!=zMsg){ const int nMsg = sqlite3Strlen30(zMsg); sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); @@ -40,8 +125,9 @@ int sqlite3_wasm_db_error(sqlite3*db, int err_code, ** buffer is not large enough for the generated JSON. In debug builds ** that will trigger an assert(). */ +WASM_KEEP const char * sqlite3_wasm_enum_json(void){ - static char strBuf[1024 * 8] = {0} /* where the JSON goes */; + static char strBuf[1024 * 12] = {0} /* where the JSON goes */; int n = 0, childCount = 0, structCount = 0 /* output counters for figuring out where commas go */; char * pos = &strBuf[1] /* skip first byte for now to help protect @@ -76,10 +162,137 @@ const char * sqlite3_wasm_enum_json(void){ outf("%s\"%s\": \"%s\"", (n++ ? ", " : ""), #KEY, KEY) #define _DefGroup CloseBrace(1) - DefGroup(version) { - DefInt(SQLITE_VERSION_NUMBER); - DefStr(SQLITE_VERSION); - DefStr(SQLITE_SOURCE_ID); + /* The following groups are sorted alphabetic by group name. */ + DefGroup(access){ + DefInt(SQLITE_ACCESS_EXISTS); + DefInt(SQLITE_ACCESS_READWRITE); + DefInt(SQLITE_ACCESS_READ)/*docs say this is unused*/; + } _DefGroup; + + DefGroup(blobFinalizers) { + /* SQLITE_STATIC/TRANSIENT need to be handled explicitly as + ** integers to avoid casting-related warnings. */ + out("\"SQLITE_STATIC\":0, \"SQLITE_TRANSIENT\":-1"); + } _DefGroup; + + DefGroup(dataTypes) { + DefInt(SQLITE_INTEGER); + DefInt(SQLITE_FLOAT); + DefInt(SQLITE_TEXT); + DefInt(SQLITE_BLOB); + DefInt(SQLITE_NULL); + } _DefGroup; + + DefGroup(encodings) { + /* Noting that the wasm binding only aims to support UTF-8. */ + DefInt(SQLITE_UTF8); + DefInt(SQLITE_UTF16LE); + DefInt(SQLITE_UTF16BE); + DefInt(SQLITE_UTF16); + /*deprecated DefInt(SQLITE_ANY); */ + DefInt(SQLITE_UTF16_ALIGNED); + } _DefGroup; + + DefGroup(fcntl) { + DefInt(SQLITE_FCNTL_LOCKSTATE); + DefInt(SQLITE_FCNTL_GET_LOCKPROXYFILE); + DefInt(SQLITE_FCNTL_SET_LOCKPROXYFILE); + DefInt(SQLITE_FCNTL_LAST_ERRNO); + DefInt(SQLITE_FCNTL_SIZE_HINT); + DefInt(SQLITE_FCNTL_CHUNK_SIZE); + DefInt(SQLITE_FCNTL_FILE_POINTER); + DefInt(SQLITE_FCNTL_SYNC_OMITTED); + DefInt(SQLITE_FCNTL_WIN32_AV_RETRY); + DefInt(SQLITE_FCNTL_PERSIST_WAL); + DefInt(SQLITE_FCNTL_OVERWRITE); + DefInt(SQLITE_FCNTL_VFSNAME); + DefInt(SQLITE_FCNTL_POWERSAFE_OVERWRITE); + DefInt(SQLITE_FCNTL_PRAGMA); + DefInt(SQLITE_FCNTL_BUSYHANDLER); + DefInt(SQLITE_FCNTL_TEMPFILENAME); + DefInt(SQLITE_FCNTL_MMAP_SIZE); + DefInt(SQLITE_FCNTL_TRACE); + DefInt(SQLITE_FCNTL_HAS_MOVED); + DefInt(SQLITE_FCNTL_SYNC); + DefInt(SQLITE_FCNTL_COMMIT_PHASETWO); + DefInt(SQLITE_FCNTL_WIN32_SET_HANDLE); + DefInt(SQLITE_FCNTL_WAL_BLOCK); + DefInt(SQLITE_FCNTL_ZIPVFS); + DefInt(SQLITE_FCNTL_RBU); + DefInt(SQLITE_FCNTL_VFS_POINTER); + DefInt(SQLITE_FCNTL_JOURNAL_POINTER); + DefInt(SQLITE_FCNTL_WIN32_GET_HANDLE); + DefInt(SQLITE_FCNTL_PDB); + DefInt(SQLITE_FCNTL_BEGIN_ATOMIC_WRITE); + DefInt(SQLITE_FCNTL_COMMIT_ATOMIC_WRITE); + DefInt(SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE); + DefInt(SQLITE_FCNTL_LOCK_TIMEOUT); + DefInt(SQLITE_FCNTL_DATA_VERSION); + DefInt(SQLITE_FCNTL_SIZE_LIMIT); + DefInt(SQLITE_FCNTL_CKPT_DONE); + DefInt(SQLITE_FCNTL_RESERVE_BYTES); + DefInt(SQLITE_FCNTL_CKPT_START); + DefInt(SQLITE_FCNTL_EXTERNAL_READER); + DefInt(SQLITE_FCNTL_CKSM_FILE); + } _DefGroup; + + DefGroup(flock) { + DefInt(SQLITE_LOCK_NONE); + DefInt(SQLITE_LOCK_SHARED); + DefInt(SQLITE_LOCK_RESERVED); + DefInt(SQLITE_LOCK_PENDING); + DefInt(SQLITE_LOCK_EXCLUSIVE); + } _DefGroup; + + DefGroup(ioCap) { + DefInt(SQLITE_IOCAP_ATOMIC); + DefInt(SQLITE_IOCAP_ATOMIC512); + DefInt(SQLITE_IOCAP_ATOMIC1K); + DefInt(SQLITE_IOCAP_ATOMIC2K); + DefInt(SQLITE_IOCAP_ATOMIC4K); + DefInt(SQLITE_IOCAP_ATOMIC8K); + DefInt(SQLITE_IOCAP_ATOMIC16K); + DefInt(SQLITE_IOCAP_ATOMIC32K); + DefInt(SQLITE_IOCAP_ATOMIC64K); + DefInt(SQLITE_IOCAP_SAFE_APPEND); + DefInt(SQLITE_IOCAP_SEQUENTIAL); + DefInt(SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN); + DefInt(SQLITE_IOCAP_POWERSAFE_OVERWRITE); + DefInt(SQLITE_IOCAP_IMMUTABLE); + DefInt(SQLITE_IOCAP_BATCH_ATOMIC); + } _DefGroup; + + DefGroup(openFlags) { + /* Noting that not all of these will have any effect in + ** WASM-space. */ + DefInt(SQLITE_OPEN_READONLY); + DefInt(SQLITE_OPEN_READWRITE); + DefInt(SQLITE_OPEN_CREATE); + DefInt(SQLITE_OPEN_URI); + DefInt(SQLITE_OPEN_MEMORY); + DefInt(SQLITE_OPEN_NOMUTEX); + DefInt(SQLITE_OPEN_FULLMUTEX); + DefInt(SQLITE_OPEN_SHAREDCACHE); + DefInt(SQLITE_OPEN_PRIVATECACHE); + DefInt(SQLITE_OPEN_EXRESCODE); + DefInt(SQLITE_OPEN_NOFOLLOW); + /* OPEN flags for use with VFSes... */ + DefInt(SQLITE_OPEN_MAIN_DB); + DefInt(SQLITE_OPEN_MAIN_JOURNAL); + DefInt(SQLITE_OPEN_TEMP_DB); + DefInt(SQLITE_OPEN_TEMP_JOURNAL); + DefInt(SQLITE_OPEN_TRANSIENT_DB); + DefInt(SQLITE_OPEN_SUBJOURNAL); + DefInt(SQLITE_OPEN_SUPER_JOURNAL); + DefInt(SQLITE_OPEN_WAL); + DefInt(SQLITE_OPEN_DELETEONCLOSE); + DefInt(SQLITE_OPEN_EXCLUSIVE); + } _DefGroup; + + DefGroup(prepareFlags) { + DefInt(SQLITE_PREPARE_PERSISTENT); + DefInt(SQLITE_PREPARE_NORMALIZE); + DefInt(SQLITE_PREPARE_NO_VTAB); } _DefGroup; DefGroup(resultCodes) { @@ -114,7 +327,6 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_WARNING); DefInt(SQLITE_ROW); DefInt(SQLITE_DONE); - // Extended Result Codes DefInt(SQLITE_ERROR_MISSING_COLLSEQ); DefInt(SQLITE_ERROR_RETRY); @@ -193,60 +405,10 @@ const char * sqlite3_wasm_enum_json(void){ //DefInt(SQLITE_OK_SYMLINK) /* internal use only */; } _DefGroup; - DefGroup(dataTypes) { - DefInt(SQLITE_INTEGER); - DefInt(SQLITE_FLOAT); - DefInt(SQLITE_TEXT); - DefInt(SQLITE_BLOB); - DefInt(SQLITE_NULL); - } _DefGroup; - - DefGroup(encodings) { - /* Noting that the wasm binding only aims to support UTF-8. */ - DefInt(SQLITE_UTF8); - DefInt(SQLITE_UTF16LE); - DefInt(SQLITE_UTF16BE); - DefInt(SQLITE_UTF16); - /*deprecated DefInt(SQLITE_ANY); */ - DefInt(SQLITE_UTF16_ALIGNED); - } _DefGroup; - - DefGroup(blobFinalizers) { - /* SQLITE_STATIC/TRANSIENT need to be handled explicitly as - ** integers to avoid casting-related warnings. */ - out("\"SQLITE_STATIC\":0, \"SQLITE_TRANSIENT\":-1"); - } _DefGroup; - - DefGroup(udfFlags) { - DefInt(SQLITE_DETERMINISTIC); - DefInt(SQLITE_DIRECTONLY); - DefInt(SQLITE_INNOCUOUS); - } _DefGroup; - - DefGroup(openFlags) { - /* Noting that not all of these will have any effect in WASM-space. */ - DefInt(SQLITE_OPEN_READONLY); - DefInt(SQLITE_OPEN_READWRITE); - DefInt(SQLITE_OPEN_CREATE); - DefInt(SQLITE_OPEN_URI); - DefInt(SQLITE_OPEN_MEMORY); - DefInt(SQLITE_OPEN_NOMUTEX); - DefInt(SQLITE_OPEN_FULLMUTEX); - DefInt(SQLITE_OPEN_SHAREDCACHE); - DefInt(SQLITE_OPEN_PRIVATECACHE); - DefInt(SQLITE_OPEN_EXRESCODE); - DefInt(SQLITE_OPEN_NOFOLLOW); - /* OPEN flags for use with VFSes... */ - DefInt(SQLITE_OPEN_MAIN_DB); - DefInt(SQLITE_OPEN_MAIN_JOURNAL); - DefInt(SQLITE_OPEN_TEMP_DB); - DefInt(SQLITE_OPEN_TEMP_JOURNAL); - DefInt(SQLITE_OPEN_TRANSIENT_DB); - DefInt(SQLITE_OPEN_SUBJOURNAL); - DefInt(SQLITE_OPEN_SUPER_JOURNAL); - DefInt(SQLITE_OPEN_WAL); - DefInt(SQLITE_OPEN_DELETEONCLOSE); - DefInt(SQLITE_OPEN_EXCLUSIVE); + DefGroup(serialize){ + DefInt(SQLITE_DESERIALIZE_FREEONCLOSE); + DefInt(SQLITE_DESERIALIZE_READONLY); + DefInt(SQLITE_DESERIALIZE_RESIZEABLE); } _DefGroup; DefGroup(syncFlags) { @@ -255,42 +417,16 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_SYNC_DATAONLY); } _DefGroup; - DefGroup(prepareFlags) { - DefInt(SQLITE_PREPARE_PERSISTENT); - DefInt(SQLITE_PREPARE_NORMALIZE); - DefInt(SQLITE_PREPARE_NO_VTAB); - } _DefGroup; - - DefGroup(flock) { - DefInt(SQLITE_LOCK_NONE); - DefInt(SQLITE_LOCK_SHARED); - DefInt(SQLITE_LOCK_RESERVED); - DefInt(SQLITE_LOCK_PENDING); - DefInt(SQLITE_LOCK_EXCLUSIVE); - } _DefGroup; - - DefGroup(ioCap) { - DefInt(SQLITE_IOCAP_ATOMIC); - DefInt(SQLITE_IOCAP_ATOMIC512); - DefInt(SQLITE_IOCAP_ATOMIC1K); - DefInt(SQLITE_IOCAP_ATOMIC2K); - DefInt(SQLITE_IOCAP_ATOMIC4K); - DefInt(SQLITE_IOCAP_ATOMIC8K); - DefInt(SQLITE_IOCAP_ATOMIC16K); - DefInt(SQLITE_IOCAP_ATOMIC32K); - DefInt(SQLITE_IOCAP_ATOMIC64K); - DefInt(SQLITE_IOCAP_SAFE_APPEND); - DefInt(SQLITE_IOCAP_SEQUENTIAL); - DefInt(SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN); - DefInt(SQLITE_IOCAP_POWERSAFE_OVERWRITE); - DefInt(SQLITE_IOCAP_IMMUTABLE); - DefInt(SQLITE_IOCAP_BATCH_ATOMIC); + DefGroup(udfFlags) { + DefInt(SQLITE_DETERMINISTIC); + DefInt(SQLITE_DIRECTONLY); + DefInt(SQLITE_INNOCUOUS); } _DefGroup; - DefGroup(access){ - DefInt(SQLITE_ACCESS_EXISTS); - DefInt(SQLITE_ACCESS_READWRITE); - DefInt(SQLITE_ACCESS_READ)/*docs say this is unused*/; + DefGroup(version) { + DefInt(SQLITE_VERSION_NUMBER); + DefStr(SQLITE_VERSION); + DefStr(SQLITE_SOURCE_ID); } _DefGroup; #undef DefGroup @@ -391,7 +527,7 @@ const char * sqlite3_wasm_enum_json(void){ #define CurrentStruct sqlite3_file StructBinder { - M(pMethods,"P"); + M(pMethods,"p"); } _StructBinder; #undef CurrentStruct @@ -411,3 +547,85 @@ const char * sqlite3_wasm_enum_json(void){ #undef outf #undef lenCheck } + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** This function invokes the xDelete method of the default VFS, +** passing on the given filename. If zName is NULL, no default VFS is +** found, or it has no xDelete method, SQLITE_MISUSE is returned, else +** the result of the xDelete() call is returned. +*/ +WASM_KEEP +int sqlite3_wasm_vfs_unlink(const char * zName){ + int rc = SQLITE_MISUSE /* ??? */; + sqlite3_vfs * const pVfs = sqlite3_vfs_find(0); + if( zName && pVfs && pVfs->xDelete ){ + rc = pVfs->xDelete(pVfs, zName, 1); + } + return rc; +} + +#if defined(__EMSCRIPTEN__) +#include <emscripten/console.h> +#if defined(SQLITE_WASM_WASMFS) +#include <emscripten/wasmfs.h> + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings, specifically +** only when building with Emscripten's WASMFS support. +** +** This function should only be called if the JS side detects the +** existence of the Origin-Private FileSystem (OPFS) APIs in the +** client. The first time it is called, this function instantiates a +** WASMFS backend impl for OPFS. On success, subsequent calls are +** no-ops. +** +** This function may be passed a "mount point" name, which must have a +** leading "/" and is currently restricted to a single path component, +** e.g. "/foo" is legal but "/foo/" and "/foo/bar" are not. If it is +** NULL or empty, it defaults to "/persistent". +** +** Returns 0 on success, SQLITE_NOMEM if instantiation of the backend +** object fails, SQLITE_IOERR if mkdir() of the zMountPoint dir in +** the virtual FS fails. In builds compiled without SQLITE_WASM_WASMFS +** defined, SQLITE_NOTFOUND is returned without side effects. +*/ +WASM_KEEP +int sqlite3_wasm_init_wasmfs(const char *zMountPoint){ + static backend_t pOpfs = 0; + if( !zMountPoint || !*zMountPoint ) zMountPoint = "/opfs"; + if( !pOpfs ){ + pOpfs = wasmfs_create_opfs_backend(); + if( pOpfs ){ + emscripten_console_log("Created WASMFS OPFS backend."); + } + } + /** It's not enough to instantiate the backend. We have to create a + mountpoint in the VFS and attach the backend to it. */ + if( pOpfs && 0!=access(zMountPoint, F_OK) ){ + /* mkdir() simply hangs when called from fiddle app. Cause is + not yet determined but the hypothesis is an init-order + issue. */ + /* Note that this check and is not robust but it will + hypothetically suffice for the transient wasm-based virtual + filesystem we're currently running in. */ + const int rc = wasmfs_create_directory(zMountPoint, 0777, pOpfs); + emscripten_console_logf("OPFS mkdir(%s) rc=%d", zMountPoint, rc); + if(rc) return SQLITE_IOERR; + } + return pOpfs ? 0 : SQLITE_NOMEM; +} +#else +WASM_KEEP +int sqlite3_wasm_init_wasmfs(const char *zUnused){ + emscripten_console_warn("WASMFS OPFS is not compiled in."); + if(zUnused){/*unused*/} + return SQLITE_NOTFOUND; +} +#endif /* __EMSCRIPTEN__ && SQLITE_WASM_WASMFS */ +#endif + +#undef WASM_KEEP |