aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm/api/sqlite3-wasm.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/wasm/api/sqlite3-wasm.c')
-rw-r--r--ext/wasm/api/sqlite3-wasm.c414
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