#include "sqlite3.h" #include "src/wal.h" #include <string.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #define LIBSQL_IMPORT(name) extern __attribute__((import_module("libsql_host"), import_name(name))) LIBSQL_IMPORT("close") int libsql_wasi_close(sqlite3_file*); LIBSQL_IMPORT("read") int libsql_wasi_read(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); LIBSQL_IMPORT("write") int libsql_wasi_write(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst); LIBSQL_IMPORT("truncate") int libsql_wasi_truncate(sqlite3_file*, sqlite3_int64 size); LIBSQL_IMPORT("sync") int libsql_wasi_sync(sqlite3_file*, int flags); LIBSQL_IMPORT("file_size") int libsql_wasi_file_size(sqlite3_file*, sqlite3_int64 *pSize); typedef struct libsql_wasi_file { const struct sqlite3_io_methods* pMethods; int64_t fd; } libsql_wasi_file; // We're running in exclusive mode, so locks are noops. // We need to handle locking in the host. static int libsql_wasi_lock(sqlite3_file* f, int eLock) { (void)f, (void)eLock; return SQLITE_OK; } static int libsql_wasi_unlock(sqlite3_file* f, int eLock) { (void)f, (void)eLock; return SQLITE_OK; } static int libsql_wasi_check_reserved_lock(sqlite3_file* f, int *pResOut) { (void)f, (void)pResOut; return SQLITE_OK; } static int libsql_wasi_device_characteristics(sqlite3_file* f) { (void)f; return SQLITE_IOCAP_ATOMIC | SQLITE_IOCAP_SAFE_APPEND | SQLITE_IOCAP_SEQUENTIAL; } static int libsql_wasi_file_control(sqlite3_file* f, int opcode, void* arg) { (void)opcode, (void)f, (void)arg; return SQLITE_NOTFOUND; } static int libsql_wasi_sector_size(sqlite3_file* f) { (void)f; return 512; } static const sqlite3_io_methods wasi_io_methods = { .iVersion = 1, .xClose = &libsql_wasi_close, .xRead = &libsql_wasi_read, .xWrite = &libsql_wasi_write, .xTruncate = &libsql_wasi_truncate, .xSync = &libsql_wasi_sync, .xFileSize = &libsql_wasi_file_size, .xLock = &libsql_wasi_lock, .xUnlock = &libsql_wasi_unlock, .xCheckReservedLock = &libsql_wasi_check_reserved_lock, .xFileControl = &libsql_wasi_file_control, .xSectorSize = &libsql_wasi_sector_size, .xDeviceCharacteristics = &libsql_wasi_device_characteristics, }; LIBSQL_IMPORT("open_fd") int64_t libsql_wasi_open_fd(const char *zName, int flags); LIBSQL_IMPORT("delete") int libsql_wasi_delete(sqlite3_vfs*, const char *zName, int syncDir); LIBSQL_IMPORT("access") int libsql_wasi_access(sqlite3_vfs*, const char *zName, int flags, int *pResOut); LIBSQL_IMPORT("full_pathname") int libsql_wasi_full_pathname(sqlite3_vfs*, const char *zName, int nOut, char *zOut); LIBSQL_IMPORT("randomness") int libsql_wasi_randomness(sqlite3_vfs*, int nByte, char *zOut); LIBSQL_IMPORT("sleep") int libsql_wasi_sleep(sqlite3_vfs*, int microseconds); LIBSQL_IMPORT("current_time") int libsql_wasi_current_time(sqlite3_vfs*, double*); LIBSQL_IMPORT("get_last_error") int libsql_wasi_get_last_error(sqlite3_vfs*, int, char*); LIBSQL_IMPORT("current_time_64") int libsql_wasi_current_time_64(sqlite3_vfs*, sqlite3_int64*); int libsql_wasi_vfs_open(sqlite3_vfs *vfs, const char *zName, sqlite3_file *file_, int flags, int *pOutFlags) { libsql_wasi_file *file = (libsql_wasi_file*)file_; file->fd = libsql_wasi_open_fd(zName, flags); if (file->fd == 0) { return SQLITE_CANTOPEN; } file->pMethods = &wasi_io_methods; return SQLITE_OK; } sqlite3_vfs libsql_wasi_vfs = { .iVersion = 2, .szOsFile = sizeof(libsql_wasi_file), .mxPathname = 100, .zName = "libsql_wasi", .xOpen = &libsql_wasi_vfs_open, .xDelete = &libsql_wasi_delete, .xAccess = &libsql_wasi_access, .xFullPathname = &libsql_wasi_full_pathname, .xRandomness = &libsql_wasi_randomness, .xSleep = &libsql_wasi_sleep, .xCurrentTime = &libsql_wasi_current_time, .xGetLastError = &libsql_wasi_get_last_error, .xCurrentTimeInt64 = &libsql_wasi_current_time_64, }; libsql_wal_methods *the_wal_methods = NULL; int libsql_wasi_wal_open(sqlite3_vfs* vfs, sqlite3_file* f, const char* path, int no_shm_mode, long long max_size, struct libsql_wal_methods* wal_methods, libsql_wal** wal) { fprintf(stderr, "Opening virtual WAL at %s: %s\n", path, wal_methods->zName); return the_wal_methods->xOpen(vfs, f, path, no_shm_mode, max_size, wal_methods, wal); } int libsql_wasi_wal_close(libsql_wal* wal, sqlite3* db, int sync_flags, int nBuf, unsigned char* zBuf) { return the_wal_methods->xClose(wal, db, sync_flags, nBuf, zBuf); } void libsql_wasi_wal_limit(libsql_wal* wal, long long limit) { return the_wal_methods->xLimit(wal, limit); } int libsql_wasi_wal_begin_read_transaction(libsql_wal* wal, int* out) { return the_wal_methods->xBeginReadTransaction(wal, out); } void libsql_wasi_wal_end_read_transaction(libsql_wal* wal) { return the_wal_methods->xEndReadTransaction(wal); } int libsql_wasi_wal_find_frame(libsql_wal* wal, unsigned int frame, unsigned int* out) { return the_wal_methods->xFindFrame(wal, frame, out); } int libsql_wasi_wal_read_frame(libsql_wal* wal, unsigned int frame, int n, unsigned char* out) { return the_wal_methods->xReadFrame(wal, frame, n, out); } unsigned int libsql_wasi_wal_dbsize(libsql_wal* wal) { return the_wal_methods->xDbsize(wal); } int libsql_wasi_wal_begin_write_transaction(libsql_wal* wal) { return the_wal_methods->xBeginWriteTransaction(wal); } int libsql_wasi_wal_end_write_transaction(libsql_wal* wal) { return the_wal_methods->xEndWriteTransaction(wal); } int libsql_wasi_wal_undo(libsql_wal* wal, int (*xUndo)(void*, unsigned int), void* pUndoCtx) { return the_wal_methods->xUndo(wal, xUndo, pUndoCtx); } void libsql_wasi_wal_savepoint(libsql_wal* wal, unsigned int* aWalData) { return the_wal_methods->xSavepoint(wal, aWalData); } int libsql_wasi_wal_savepoint_undo(libsql_wal* wal, unsigned int* aWalData) { return the_wal_methods->xSavepointUndo(wal, aWalData); } int libsql_wasi_wal_frames(libsql_wal* wal, int n, libsql_pghdr* aPgHdr, unsigned int cksum, int mode, int readonly) { return the_wal_methods->xFrames(wal, n, aPgHdr, cksum, mode, readonly, NULL); } int libsql_wasi_wal_checkpoint(libsql_wal* wal, sqlite3* db, int eMode, int (*xBusy)(void*), void* pBusyArg, int sync_flags, int nBuf, unsigned char* zBuf, int* pnLog, int* pnCkpt) { return the_wal_methods->xCheckpoint(wal, db, eMode, xBusy, pBusyArg, sync_flags, nBuf, zBuf, pnLog, pnCkpt); } int libsql_wasi_wal_callback(libsql_wal* wal) { return the_wal_methods->xCallback(wal); } int libsql_wasi_wal_exclusive_mode(libsql_wal* wal, int op) { return the_wal_methods->xExclusiveMode(wal, op); } int libsql_wasi_wal_heap_memory(libsql_wal* wal) { return the_wal_methods->xHeapMemory(wal); } int libsql_wasi_wal_snapshot_get(libsql_wal* wal, sqlite3_snapshot** snapshot) { return the_wal_methods->xSnapshotGet(wal, snapshot); } void libsql_wasi_wal_snapshot_open(libsql_wal* wal, sqlite3_snapshot* snapshot) { return the_wal_methods->xSnapshotOpen(wal, snapshot); } int libsql_wasi_wal_snapshot_recover(libsql_wal* wal) { return the_wal_methods->xSnapshotRecover(wal); } int libsql_wasi_wal_snapshot_check(libsql_wal* wal, sqlite3_snapshot* snapshot) { return the_wal_methods->xSnapshotCheck(wal, snapshot); } void libsql_wasi_wal_snapshot_unlock(libsql_wal* wal) { return the_wal_methods->xSnapshotUnlock(wal); } int libsql_wasi_wal_framesize(libsql_wal* wal) { return the_wal_methods->xFramesize(wal); } sqlite3_file *libsql_wasi_wal_file(libsql_wal* wal) { return the_wal_methods->xFile(wal); } int libsql_wasi_wal_writelock(libsql_wal* wal, int bLock) { return the_wal_methods->xWriteLock(wal, bLock); } void libsql_wasi_wal_db(libsql_wal* wal, sqlite3* db) { return the_wal_methods->xDb(wal, db); } int libsql_wasi_wal_pathname_len(int orig_len) { return the_wal_methods->xPathnameLen(orig_len); } void libsql_wasi_get_wal_pathname(char *buf, const char *orig, int len) { return the_wal_methods->xGetWalPathname(buf, orig, len); } int libsql_wasi_wal_pre_main_db_open(libsql_wal_methods *methods, const char *path) { return 0; } libsql_wal_methods libsql_wasi_wal_methods = { .iVersion = 1, .xOpen = &libsql_wasi_wal_open, .xClose = &libsql_wasi_wal_close, .xLimit = &libsql_wasi_wal_limit, .xBeginReadTransaction = &libsql_wasi_wal_begin_read_transaction, .xEndReadTransaction = &libsql_wasi_wal_end_read_transaction, .xFindFrame = &libsql_wasi_wal_find_frame, .xReadFrame = &libsql_wasi_wal_read_frame, .xDbsize = &libsql_wasi_wal_dbsize, .xBeginWriteTransaction = &libsql_wasi_wal_begin_write_transaction, .xEndWriteTransaction = &libsql_wasi_wal_end_write_transaction, .xUndo = &libsql_wasi_wal_undo, .xSavepoint = &libsql_wasi_wal_savepoint, .xSavepointUndo = &libsql_wasi_wal_savepoint_undo, .xFrames = &libsql_wasi_wal_frames, .xCheckpoint = &libsql_wasi_wal_checkpoint, .xCallback = &libsql_wasi_wal_callback, .xExclusiveMode = &libsql_wasi_wal_exclusive_mode, .xHeapMemory = &libsql_wasi_wal_heap_memory, .xSnapshotGet = &libsql_wasi_wal_snapshot_get, .xSnapshotOpen = &libsql_wasi_wal_snapshot_open, .xSnapshotRecover = &libsql_wasi_wal_snapshot_recover, .xSnapshotCheck = &libsql_wasi_wal_snapshot_check, .xSnapshotUnlock = &libsql_wasi_wal_snapshot_unlock, .xFramesize = &libsql_wasi_wal_framesize, .xFile = &libsql_wasi_wal_file, .xWriteLock = &libsql_wasi_wal_writelock, .xDb = &libsql_wasi_wal_db, .xPathnameLen = &libsql_wasi_wal_pathname_len, .xGetWalPathname = &libsql_wasi_get_wal_pathname, .xPreMainDbOpen = &libsql_wasi_wal_pre_main_db_open, .bUsesShm = 0, .zName = "libsql_wasi", .pNext = NULL, }; void libsql_wasi_init() { the_wal_methods = libsql_wal_methods_find(NULL); sqlite3_vfs_register(&libsql_wasi_vfs, 1); libsql_wal_methods_register(&libsql_wasi_wal_methods); fprintf(stderr, "WASI initialized\n"); } sqlite3 *libsql_wasi_open_db(const char *filename) { sqlite3 *db; fprintf(stderr, "opening database %s\n", filename); int rc = libsql_open(filename, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, "libsql_wasi", "libsql_wasi"); if (rc != SQLITE_OK) { fprintf(stderr, "Failed to open database: %s\n", sqlite3_errmsg(db)); return NULL; } fprintf(stderr, "opened database %s\n", filename); rc = sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL); if (rc != SQLITE_OK) { fprintf(stderr, "Failed to set journal mode: %s\n", sqlite3_errmsg(db)); return NULL; } return db; } int libsql_wasi_exec(sqlite3 *db, const char *sql) { sqlite3_stmt *stmt; int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db)); return rc; } // Step in a loop until SQLITE_DONE or error while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {} if (rc != SQLITE_DONE) { fprintf(stderr, "Failed to execute statement: %s\n", sqlite3_errmsg(db)); return rc; } return SQLITE_OK; }