diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index fd52b95..ebd588e 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -15,6 +15,8 @@ jobs: steps: - uses: actions/checkout@v3 + with: + lfs: 'true' - name: Set up Go uses: actions/setup-go@v3 diff --git a/.gitignore b/.gitignore index 58b5f43..c8b2376 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,4 @@ # Dependency directories (remove the comment below to include it) # vendor/ -tools - -# Project -sqlite3/sqlite3* \ No newline at end of file +tools \ No newline at end of file diff --git a/embed/build.sh b/embed/build.sh index 02a9c38..363881a 100755 --- a/embed/build.sh +++ b/embed/build.sh @@ -13,51 +13,4 @@ zig cc --target=wasm32-wasi -flto -g0 -Os \ -mbulk-memory -mreference-types \ -mnontrapping-fptoint -msign-ext \ -D_HAVE_SQLITE_CONFIG_H \ - -Wl,--export=free \ - -Wl,--export=malloc \ - -Wl,--export=malloc_destructor \ - -Wl,--export=sqlite3_errcode \ - -Wl,--export=sqlite3_errstr \ - -Wl,--export=sqlite3_errmsg \ - -Wl,--export=sqlite3_error_offset \ - -Wl,--export=sqlite3_open_v2 \ - -Wl,--export=sqlite3_close \ - -Wl,--export=sqlite3_prepare_v3 \ - -Wl,--export=sqlite3_finalize \ - -Wl,--export=sqlite3_reset \ - -Wl,--export=sqlite3_step \ - -Wl,--export=sqlite3_exec \ - -Wl,--export=sqlite3_clear_bindings \ - -Wl,--export=sqlite3_bind_parameter_count \ - -Wl,--export=sqlite3_bind_parameter_index \ - -Wl,--export=sqlite3_bind_parameter_name \ - -Wl,--export=sqlite3_bind_null \ - -Wl,--export=sqlite3_bind_int64 \ - -Wl,--export=sqlite3_bind_double \ - -Wl,--export=sqlite3_bind_text64 \ - -Wl,--export=sqlite3_bind_blob64 \ - -Wl,--export=sqlite3_bind_zeroblob64 \ - -Wl,--export=sqlite3_column_count \ - -Wl,--export=sqlite3_column_name \ - -Wl,--export=sqlite3_column_type \ - -Wl,--export=sqlite3_column_int64 \ - -Wl,--export=sqlite3_column_double \ - -Wl,--export=sqlite3_column_text \ - -Wl,--export=sqlite3_column_blob \ - -Wl,--export=sqlite3_column_bytes \ - -Wl,--export=sqlite3_blob_open \ - -Wl,--export=sqlite3_blob_close \ - -Wl,--export=sqlite3_blob_bytes \ - -Wl,--export=sqlite3_blob_read \ - -Wl,--export=sqlite3_blob_write \ - -Wl,--export=sqlite3_blob_reopen \ - -Wl,--export=sqlite3_get_autocommit \ - -Wl,--export=sqlite3_last_insert_rowid \ - -Wl,--export=sqlite3_changes64 \ - -Wl,--export=sqlite3_unlock_notify \ - -Wl,--export=sqlite3_backup_init \ - -Wl,--export=sqlite3_backup_step \ - -Wl,--export=sqlite3_backup_finish \ - -Wl,--export=sqlite3_backup_remaining \ - -Wl,--export=sqlite3_backup_pagecount \ - -Wl,--export=sqlite3_interrupt_offset \ \ No newline at end of file + $(awk '{print "-Wl,--export="$0}' ../sqlite3/exports.txt) \ No newline at end of file diff --git a/sqlite3/.gitignore b/sqlite3/.gitignore new file mode 100644 index 0000000..2ebf02e --- /dev/null +++ b/sqlite3/.gitignore @@ -0,0 +1,3 @@ +sqlite3.c +sqlite3.h +sqlite3ext.h \ No newline at end of file diff --git a/sqlite3/exports.txt b/sqlite3/exports.txt new file mode 100644 index 0000000..0a538db --- /dev/null +++ b/sqlite3/exports.txt @@ -0,0 +1,48 @@ +free +malloc +malloc_destructor +sqlite3_errcode +sqlite3_errstr +sqlite3_errmsg +sqlite3_error_offset +sqlite3_open_v2 +sqlite3_close +sqlite3_prepare_v3 +sqlite3_finalize +sqlite3_reset +sqlite3_step +sqlite3_exec +sqlite3_clear_bindings +sqlite3_bind_parameter_count +sqlite3_bind_parameter_index +sqlite3_bind_parameter_name +sqlite3_bind_null +sqlite3_bind_int64 +sqlite3_bind_double +sqlite3_bind_text64 +sqlite3_bind_blob64 +sqlite3_bind_zeroblob64 +sqlite3_column_count +sqlite3_column_name +sqlite3_column_type +sqlite3_column_int64 +sqlite3_column_double +sqlite3_column_text +sqlite3_column_blob +sqlite3_column_bytes +sqlite3_blob_open +sqlite3_blob_close +sqlite3_blob_bytes +sqlite3_blob_read +sqlite3_blob_write +sqlite3_blob_reopen +sqlite3_get_autocommit +sqlite3_last_insert_rowid +sqlite3_changes64 +sqlite3_unlock_notify +sqlite3_backup_init +sqlite3_backup_step +sqlite3_backup_finish +sqlite3_backup_remaining +sqlite3_backup_pagecount +sqlite3_interrupt_offset \ No newline at end of file diff --git a/sqlite3/sqlite_cfg.h b/sqlite3/sqlite_cfg.h index 14746c8..32950f6 100644 --- a/sqlite3/sqlite_cfg.h +++ b/sqlite3/sqlite_cfg.h @@ -33,7 +33,9 @@ // We set the default locking mode to EXCLUSIVE instead. // https://www.sqlite.org/wal.html#noshm #undef SQLITE_OMIT_WAL +#ifndef SQLITE_DEFAULT_LOCKING_MODE #define SQLITE_DEFAULT_LOCKING_MODE 1 +#endif // Recommended Extensions diff --git a/tests/mptest/mptest_test.go b/tests/mptest/mptest_test.go new file mode 100644 index 0000000..17db366 --- /dev/null +++ b/tests/mptest/mptest_test.go @@ -0,0 +1,166 @@ +package mptest + +import ( + "bytes" + "context" + "crypto/rand" + "embed" + "io/fs" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "sync/atomic" + "testing" + + _ "unsafe" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" + + _ "github.com/ncruces/go-sqlite3" +) + +//go:embed testdata/mptest.wasm +var binary []byte + +//go:embed testdata/*.*test +var scripts embed.FS + +//go:linkname vfsNewEnvModuleBuilder github.com/ncruces/go-sqlite3.vfsNewEnvModuleBuilder +func vfsNewEnvModuleBuilder(r wazero.Runtime) wazero.HostModuleBuilder + +var ( + rt wazero.Runtime + module wazero.CompiledModule + config wazero.ModuleConfig + instances atomic.Uint64 + log *logger +) + +func init() { + ctx := context.TODO() + + rt = wazero.NewRuntime(ctx) + wasi_snapshot_preview1.MustInstantiate(ctx, rt) + env := vfsNewEnvModuleBuilder(rt) + env.NewFunctionBuilder().WithFunc(system).Export("system") + _, err := env.Instantiate(ctx) + if err != nil { + panic(err) + } + + module, err = rt.CompileModule(ctx, binary) + if err != nil { + panic(err) + } + + fs, err := fs.Sub(scripts, "testdata") + if err != nil { + panic(err) + } + + config = wazero.NewModuleConfig().WithFS(fs). + WithSysWalltime().WithSysNanotime().WithSysNanosleep(). + WithOsyield(runtime.Gosched). + WithRandSource(rand.Reader) +} + +func system(ctx context.Context, mod api.Module, ptr uint32) uint32 { + buf, _ := mod.Memory().Read(ptr, mod.Memory().Size()-ptr) + buf = buf[:bytes.IndexByte(buf, 0)] + + args := strings.Split(string(buf), " ") + for i := range args { + a, err := strconv.Unquote(args[i]) + if err == nil { + args[i] = a + } + } + args = args[:len(args)-1] + + cfg := config.WithArgs(args...). + WithStdout(log).WithStderr(log).WithName(instanceName()) + go rt.InstantiateModule(ctx, module, cfg) + return 0 +} + +func Test_config01(t *testing.T) { + log = &logger{T: t} + ctx := context.TODO() + name := filepath.Join(t.TempDir(), "test.db") + cfg := config.WithArgs("mptest", name, "config01.test"). + WithStdout(log).WithStderr(log).WithName(instanceName()) + _, err := rt.InstantiateModule(ctx, module, cfg) + if err != nil { + t.Error(err) + } +} + +func Test_config02(t *testing.T) { + t.Skip() // TODO: remove + log = &logger{T: t} + ctx := context.TODO() + name := filepath.Join(t.TempDir(), "test.db") + cfg := config.WithArgs("mptest", name, "config02.test"). + WithStdout(log).WithStderr(log).WithName(instanceName()) + _, err := rt.InstantiateModule(ctx, module, cfg) + if err != nil { + t.Error(err) + } +} + +func Test_crash01(t *testing.T) { + t.Skip() // TODO: remove + log = &logger{T: t} + ctx := context.TODO() + name := filepath.Join(t.TempDir(), "test.db") + cfg := config.WithArgs("mptest", name, "crash01.test"). + WithStdout(log).WithStderr(log).WithName(instanceName()) + _, err := rt.InstantiateModule(ctx, module, cfg) + if err != nil { + t.Error(err) + } +} + +func Test_multiwrite01(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip() // TODO: remove + } + log = &logger{T: t} + ctx := context.TODO() + name := filepath.Join(t.TempDir(), "test.db") + cfg := config.WithArgs("mptest", name, "multiwrite01.test"). + WithStdout(log).WithStderr(log).WithName(instanceName()) + _, err := rt.InstantiateModule(ctx, module, cfg) + if err != nil { + t.Error(err) + } +} + +type logger struct { + *testing.T + buf []byte + mtx sync.Mutex +} + +func (l *logger) Write(p []byte) (n int, err error) { + l.mtx.Lock() + defer l.mtx.Unlock() + + l.buf = append(l.buf, p...) + for { + before, after, found := bytes.Cut(l.buf, []byte("\n")) + if !found { + return len(p), nil + } + l.Logf("%s", before) + l.buf = after + } +} + +func instanceName() string { + return strconv.FormatUint(instances.Add(1), 10) +} diff --git a/tests/mptest/testdata/.gitattributes b/tests/mptest/testdata/.gitattributes new file mode 100644 index 0000000..b2e214e --- /dev/null +++ b/tests/mptest/testdata/.gitattributes @@ -0,0 +1 @@ +mptest.wasm filter=lfs diff=lfs merge=lfs -text diff --git a/tests/mptest/testdata/.gitignore b/tests/mptest/testdata/.gitignore new file mode 100644 index 0000000..2937a24 --- /dev/null +++ b/tests/mptest/testdata/.gitignore @@ -0,0 +1 @@ +mptest.c \ No newline at end of file diff --git a/tests/mptest/testdata/build.sh b/tests/mptest/testdata/build.sh new file mode 100755 index 0000000..56e51cd --- /dev/null +++ b/tests/mptest/testdata/build.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -eo pipefail + +cd -P -- "$(dirname -- "$0")" + +if [ ! -f "mptest.c" ]; then + curl -sOL "https://github.com/sqlite/sqlite/raw/master/mptest/mptest.c" + curl -sOL "https://github.com/sqlite/sqlite/raw/master/mptest/config01.test" + curl -sOL "https://github.com/sqlite/sqlite/raw/master/mptest/config02.test" + curl -sOL "https://github.com/sqlite/sqlite/raw/master/mptest/crash01.test" + curl -sOL "https://github.com/sqlite/sqlite/raw/master/mptest/crash02.subtest" + curl -sOL "https://github.com/sqlite/sqlite/raw/master/mptest/multiwrite01.test" +fi + +zig cc --target=wasm32-wasi -flto -g0 -Os \ + -o mptest.wasm main.c test.c \ + -I../../../sqlite3 \ + -mmutable-globals \ + -mbulk-memory -mreference-types \ + -mnontrapping-fptoint -msign-ext \ + -D_HAVE_SQLITE_CONFIG_H \ + -DSQLITE_DEFAULT_LOCKING_MODE=0 \ + -DHAVE_USLEEP -DSQLITE_NO_SYNC \ + -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \ + -D_WASI_EMULATED_GETPID -lwasi-emulated-getpid \ + $(awk '{print "-Wl,--export="$0}' ../../../sqlite3/exports.txt) \ No newline at end of file diff --git a/tests/mptest/testdata/config01.test b/tests/mptest/testdata/config01.test new file mode 100644 index 0000000..683ee91 --- /dev/null +++ b/tests/mptest/testdata/config01.test @@ -0,0 +1,46 @@ +/* +** Configure five tasks in different ways, then run tests. +*/ +--if vfsname() GLOB 'unix' +PRAGMA page_size=8192; +--task 1 + PRAGMA journal_mode=PERSIST; + PRAGMA mmap_size=0; +--end +--task 2 + PRAGMA journal_mode=TRUNCATE; + PRAGMA mmap_size=28672; +--end +--task 3 + PRAGMA journal_mode=MEMORY; +--end +--task 4 + PRAGMA journal_mode=OFF; +--end +--task 4 + PRAGMA mmap_size(268435456); +--end +--source multiwrite01.test +--wait all +PRAGMA page_size=16384; +VACUUM; +CREATE TABLE pgsz(taskid, sz INTEGER); +--task 1 + INSERT INTO pgsz VALUES(1, eval('PRAGMA page_size')); +--end +--task 2 + INSERT INTO pgsz VALUES(2, eval('PRAGMA page_size')); +--end +--task 3 + INSERT INTO pgsz VALUES(3, eval('PRAGMA page_size')); +--end +--task 4 + INSERT INTO pgsz VALUES(4, eval('PRAGMA page_size')); +--end +--task 5 + INSERT INTO pgsz VALUES(5, eval('PRAGMA page_size')); +--end +--source multiwrite01.test +--wait all +SELECT sz FROM pgsz; +--match 16384 16384 16384 16384 16384 diff --git a/tests/mptest/testdata/config02.test b/tests/mptest/testdata/config02.test new file mode 100644 index 0000000..7d4b278 --- /dev/null +++ b/tests/mptest/testdata/config02.test @@ -0,0 +1,123 @@ +/* +** Configure five tasks in different ways, then run tests. +*/ +PRAGMA page_size=512; +--task 1 + PRAGMA mmap_size=0; +--end +--task 2 + PRAGMA mmap_size=28672; +--end +--task 3 + PRAGMA mmap_size=8192; +--end +--task 4 + PRAGMA mmap_size=65536; +--end +--task 5 + PRAGMA mmap_size=268435456; +--end +--source multiwrite01.test +--source crash02.subtest +PRAGMA page_size=1024; +VACUUM; +CREATE TABLE pgsz(taskid, sz INTEGER); +--task 1 + INSERT INTO pgsz VALUES(1, eval('PRAGMA page_size')); +--end +--task 2 + INSERT INTO pgsz VALUES(2, eval('PRAGMA page_size')); +--end +--task 3 + INSERT INTO pgsz VALUES(3, eval('PRAGMA page_size')); +--end +--task 4 + INSERT INTO pgsz VALUES(4, eval('PRAGMA page_size')); +--end +--task 5 + INSERT INTO pgsz VALUES(5, eval('PRAGMA page_size')); +--end +--source multiwrite01.test +--source crash02.subtest +--wait all +SELECT sz FROM pgsz; +--match 1024 1024 1024 1024 1024 +PRAGMA page_size=2048; +VACUUM; +DELETE FROM pgsz; +--task 1 + INSERT INTO pgsz VALUES(1, eval('PRAGMA page_size')); +--end +--task 2 + INSERT INTO pgsz VALUES(2, eval('PRAGMA page_size')); +--end +--task 3 + INSERT INTO pgsz VALUES(3, eval('PRAGMA page_size')); +--end +--task 4 + INSERT INTO pgsz VALUES(4, eval('PRAGMA page_size')); +--end +--task 5 + INSERT INTO pgsz VALUES(5, eval('PRAGMA page_size')); +--end +--source multiwrite01.test +--source crash02.subtest +--wait all +SELECT sz FROM pgsz; +--match 2048 2048 2048 2048 2048 +PRAGMA page_size=8192; +VACUUM; +DELETE FROM pgsz; +--task 1 + INSERT INTO pgsz VALUES(1, eval('PRAGMA page_size')); +--end +--task 2 + INSERT INTO pgsz VALUES(2, eval('PRAGMA page_size')); +--end +--task 3 + INSERT INTO pgsz VALUES(3, eval('PRAGMA page_size')); +--end +--task 4 + INSERT INTO pgsz VALUES(4, eval('PRAGMA page_size')); +--end +--task 5 + INSERT INTO pgsz VALUES(5, eval('PRAGMA page_size')); +--end +--source multiwrite01.test +--source crash02.subtest +--wait all +SELECT sz FROM pgsz; +--match 8192 8192 8192 8192 8192 +PRAGMA page_size=16384; +VACUUM; +DELETE FROM pgsz; +--task 1 + INSERT INTO pgsz VALUES(1, eval('PRAGMA page_size')); +--end +--task 2 + INSERT INTO pgsz VALUES(2, eval('PRAGMA page_size')); +--end +--task 3 + INSERT INTO pgsz VALUES(3, eval('PRAGMA page_size')); +--end +--task 4 + INSERT INTO pgsz VALUES(4, eval('PRAGMA page_size')); +--end +--task 5 + INSERT INTO pgsz VALUES(5, eval('PRAGMA page_size')); +--end +--source multiwrite01.test +--source crash02.subtest +--wait all +SELECT sz FROM pgsz; +--match 16384 16384 16384 16384 16384 +PRAGMA auto_vacuum=FULL; +VACUUM; +--source multiwrite01.test +--source crash02.subtest +--wait all +PRAGMA auto_vacuum=FULL; +PRAGMA page_size=512; +VACUUM; +--source multiwrite01.test +--source crash02.subtest diff --git a/tests/mptest/testdata/crash01.test b/tests/mptest/testdata/crash01.test new file mode 100644 index 0000000..f1483df --- /dev/null +++ b/tests/mptest/testdata/crash01.test @@ -0,0 +1,106 @@ +/* Test cases involving incomplete transactions that must be rolled back. +*/ +--task 1 + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + --sleep 1 + INSERT INTO t1 VALUES(1, randomblob(2000)); + INSERT INTO t1 VALUES(2, randomblob(1000)); + --sleep 1 + INSERT INTO t1 SELECT a+2, randomblob(1500) FROM t1; + INSERT INTO t1 SELECT a+4, randomblob(1500) FROM t1; + INSERT INTO t1 SELECT a+8, randomblob(1500) FROM t1; + --sleep 1 + INSERT INTO t1 SELECT a+16, randomblob(1500) FROM t1; + --sleep 1 + INSERT INTO t1 SELECT a+32, randomblob(1500) FROM t1; + SELECT count(*) FROM t1; + --match 64 + SELECT avg(length(b)) FROM t1; + --match 1500.0 + --sleep 2 + UPDATE t1 SET b='x'||a||'y'; + SELECT sum(length(b)) FROM t1; + --match 247 + SELECT a FROM t1 WHERE b='x17y'; + --match 17 + CREATE INDEX t1b ON t1(b); + SELECT a FROM t1 WHERE b='x17y'; + --match 17 + SELECT a FROM t1 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end +--wait 1 +--task 2 + DROP TABLE IF EXISTS t2; + CREATE TABLE t2(a INTEGER PRIMARY KEY, b); + INSERT INTO t2 SELECT a, b FROM t1; + UPDATE t1 SET b='x'||a||'y'; + SELECT sum(length(b)) FROM t2; + --match 247 + SELECT a FROM t2 WHERE b='x17y'; + --match 17 + CREATE INDEX t2b ON t2(b); + SELECT a FROM t2 WHERE b='x17y'; + --match 17 + SELECT a FROM t2 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end +--task 3 + DROP TABLE IF EXISTS t3; + CREATE TABLE t3(a INTEGER PRIMARY KEY, b); + INSERT INTO t3 SELECT a, b FROM t1; + UPDATE t1 SET b='x'||a||'y'; + SELECT sum(length(b)) FROM t3; + --match 247 + SELECT a FROM t3 WHERE b='x17y'; + --match 17 + CREATE INDEX t3b ON t3(b); + SELECT a FROM t3 WHERE b='x17y'; + --match 17 + SELECT a FROM t3 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end +--task 4 + DROP TABLE IF EXISTS t4; + CREATE TABLE t4(a INTEGER PRIMARY KEY, b); + INSERT INTO t4 SELECT a, b FROM t1; + UPDATE t1 SET b='x'||a||'y'; + SELECT sum(length(b)) FROM t4; + --match 247 + SELECT a FROM t4 WHERE b='x17y'; + --match 17 + CREATE INDEX t4b ON t4(b); + SELECT a FROM t4 WHERE b='x17y'; + --match 17 + SELECT a FROM t4 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end +--task 5 + DROP TABLE IF EXISTS t5; + CREATE TABLE t5(a INTEGER PRIMARY KEY, b); + INSERT INTO t5 SELECT a, b FROM t1; + UPDATE t1 SET b='x'||a||'y'; + SELECT sum(length(b)) FROM t5; + --match 247 + SELECT a FROM t5 WHERE b='x17y'; + --match 17 + CREATE INDEX t5b ON t5(b); + SELECT a FROM t5 WHERE b='x17y'; + --match 17 + SELECT a FROM t5 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end + +--wait all +/* After the database file has been set up, run the crash2 subscript +** multiple times. */ +--source crash02.subtest +--source crash02.subtest +--source crash02.subtest +--source crash02.subtest +--source crash02.subtest +--source crash02.subtest +--source crash02.subtest +--source crash02.subtest +--source crash02.subtest diff --git a/tests/mptest/testdata/crash02.subtest b/tests/mptest/testdata/crash02.subtest new file mode 100644 index 0000000..86f64dd --- /dev/null +++ b/tests/mptest/testdata/crash02.subtest @@ -0,0 +1,53 @@ +/* +** This script is called from crash01.test and config02.test and perhaps other +** script. After the database file has been set up, make a big rollback +** journal in client 1, then crash client 1. +** Then in the other clients, do an integrity check. +*/ +--task 1 leave-hot-journal + --sleep 5 + --finish + PRAGMA cache_size=10; + BEGIN; + UPDATE t1 SET b=randomblob(20000); + UPDATE t2 SET b=randomblob(20000); + UPDATE t3 SET b=randomblob(20000); + UPDATE t4 SET b=randomblob(20000); + UPDATE t5 SET b=randomblob(20000); + UPDATE t1 SET b=NULL; + UPDATE t2 SET b=NULL; + UPDATE t3 SET b=NULL; + UPDATE t4 SET b=NULL; + UPDATE t5 SET b=NULL; + --print Task one crashing an incomplete transaction + --exit 1 +--end +--task 2 integrity_check-2 + SELECT count(*) FROM t1; + --match 64 + --sleep 100 + PRAGMA integrity_check(10); + --match ok +--end +--task 3 integrity_check-3 + SELECT count(*) FROM t1; + --match 64 + --sleep 100 + PRAGMA integrity_check(10); + --match ok +--end +--task 4 integrity_check-4 + SELECT count(*) FROM t1; + --match 64 + --sleep 100 + PRAGMA integrity_check(10); + --match ok +--end +--task 5 integrity_check-5 + SELECT count(*) FROM t1; + --match 64 + --sleep 100 + PRAGMA integrity_check(10); + --match ok +--end +--wait all diff --git a/tests/mptest/testdata/main.c b/tests/mptest/testdata/main.c new file mode 100644 index 0000000..690f9a9 --- /dev/null +++ b/tests/mptest/testdata/main.c @@ -0,0 +1,22 @@ +#include +#include + +#include "os.c" +#include "qsort.c" +#include "sqlite3.c" + +sqlite3_destructor_type malloc_destructor = &free; +size_t sqlite3_interrupt_offset = offsetof(sqlite3, u1.isInterrupted); + +void __attribute__((constructor)) premain() { sqlite3_initialize(); } + +int sqlite3_enable_load_extension(sqlite3 *db, int onoff) { return SQLITE_OK; } + +void *sqlite3_trace(sqlite3 *db, void (*xTrace)(void *, const char *), + void *pArg) { + return NULL; +} + +int sqlite3_os_init() { + return sqlite3_vfs_register(os_vfs(), /*default=*/true); +} \ No newline at end of file diff --git a/tests/mptest/testdata/mptest.wasm b/tests/mptest/testdata/mptest.wasm new file mode 100755 index 0000000..de8cafe --- /dev/null +++ b/tests/mptest/testdata/mptest.wasm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:430afdac4d212a6e4b662db46c1bbb5bfb3d601770ae493beb0420a302dc3131 +size 1080243 diff --git a/tests/mptest/testdata/multiwrite01.test b/tests/mptest/testdata/multiwrite01.test new file mode 100644 index 0000000..7062ae0 --- /dev/null +++ b/tests/mptest/testdata/multiwrite01.test @@ -0,0 +1,415 @@ +/* +** This script sets up five different tasks all writing and updating +** the database at the same time, but each in its own table. +*/ +--task 1 build-t1 + DROP TABLE IF EXISTS t1; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + --sleep 1 + INSERT INTO t1 VALUES(1, randomblob(2000)); + INSERT INTO t1 VALUES(2, randomblob(1000)); + --sleep 1 + INSERT INTO t1 SELECT a+2, randomblob(1500) FROM t1; + INSERT INTO t1 SELECT a+4, randomblob(1500) FROM t1; + INSERT INTO t1 SELECT a+8, randomblob(1500) FROM t1; + --sleep 1 + INSERT INTO t1 SELECT a+16, randomblob(1500) FROM t1; + --sleep 1 + INSERT INTO t1 SELECT a+32, randomblob(1500) FROM t1; + SELECT count(*) FROM t1; + --match 64 + SELECT avg(length(b)) FROM t1; + --match 1500.0 + --sleep 2 + UPDATE t1 SET b='x'||a||'y'; + SELECT sum(length(b)) FROM t1; + --match 247 + SELECT a FROM t1 WHERE b='x17y'; + --match 17 + CREATE INDEX t1b ON t1(b); + SELECT a FROM t1 WHERE b='x17y'; + --match 17 + SELECT a FROM t1 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end + + +--task 2 build-t2 + DROP TABLE IF EXISTS t2; + CREATE TABLE t2(a INTEGER PRIMARY KEY, b); + --sleep 1 + INSERT INTO t2 VALUES(1, randomblob(2000)); + INSERT INTO t2 VALUES(2, randomblob(1000)); + --sleep 1 + INSERT INTO t2 SELECT a+2, randomblob(1500) FROM t2; + INSERT INTO t2 SELECT a+4, randomblob(1500) FROM t2; + INSERT INTO t2 SELECT a+8, randomblob(1500) FROM t2; + --sleep 1 + INSERT INTO t2 SELECT a+16, randomblob(1500) FROM t2; + --sleep 1 + INSERT INTO t2 SELECT a+32, randomblob(1500) FROM t2; + SELECT count(*) FROM t2; + --match 64 + SELECT avg(length(b)) FROM t2; + --match 1500.0 + --sleep 2 + UPDATE t2 SET b='x'||a||'y'; + SELECT sum(length(b)) FROM t2; + --match 247 + SELECT a FROM t2 WHERE b='x17y'; + --match 17 + CREATE INDEX t2b ON t2(b); + SELECT a FROM t2 WHERE b='x17y'; + --match 17 + SELECT a FROM t2 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end + +--task 3 build-t3 + DROP TABLE IF EXISTS t3; + CREATE TABLE t3(a INTEGER PRIMARY KEY, b); + --sleep 1 + INSERT INTO t3 VALUES(1, randomblob(2000)); + INSERT INTO t3 VALUES(2, randomblob(1000)); + --sleep 1 + INSERT INTO t3 SELECT a+2, randomblob(1500) FROM t3; + INSERT INTO t3 SELECT a+4, randomblob(1500) FROM t3; + INSERT INTO t3 SELECT a+8, randomblob(1500) FROM t3; + --sleep 1 + INSERT INTO t3 SELECT a+16, randomblob(1500) FROM t3; + --sleep 1 + INSERT INTO t3 SELECT a+32, randomblob(1500) FROM t3; + SELECT count(*) FROM t3; + --match 64 + SELECT avg(length(b)) FROM t3; + --match 1500.0 + --sleep 2 + UPDATE t3 SET b='x'||a||'y'; + SELECT sum(length(b)) FROM t3; + --match 247 + SELECT a FROM t3 WHERE b='x17y'; + --match 17 + CREATE INDEX t3b ON t3(b); + SELECT a FROM t3 WHERE b='x17y'; + --match 17 + SELECT a FROM t3 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end + +--task 4 build-t4 + DROP TABLE IF EXISTS t4; + CREATE TABLE t4(a INTEGER PRIMARY KEY, b); + --sleep 1 + INSERT INTO t4 VALUES(1, randomblob(2000)); + INSERT INTO t4 VALUES(2, randomblob(1000)); + --sleep 1 + INSERT INTO t4 SELECT a+2, randomblob(1500) FROM t4; + INSERT INTO t4 SELECT a+4, randomblob(1500) FROM t4; + INSERT INTO t4 SELECT a+8, randomblob(1500) FROM t4; + --sleep 1 + INSERT INTO t4 SELECT a+16, randomblob(1500) FROM t4; + --sleep 1 + INSERT INTO t4 SELECT a+32, randomblob(1500) FROM t4; + SELECT count(*) FROM t4; + --match 64 + SELECT avg(length(b)) FROM t4; + --match 1500.0 + --sleep 2 + UPDATE t4 SET b='x'||a||'y'; + SELECT sum(length(b)) FROM t4; + --match 247 + SELECT a FROM t4 WHERE b='x17y'; + --match 17 + CREATE INDEX t4b ON t4(b); + SELECT a FROM t4 WHERE b='x17y'; + --match 17 + SELECT a FROM t4 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end + +--task 5 build-t5 + DROP TABLE IF EXISTS t5; + CREATE TABLE t5(a INTEGER PRIMARY KEY, b); + --sleep 1 + INSERT INTO t5 VALUES(1, randomblob(2000)); + INSERT INTO t5 VALUES(2, randomblob(1000)); + --sleep 1 + INSERT INTO t5 SELECT a+2, randomblob(1500) FROM t5; + INSERT INTO t5 SELECT a+4, randomblob(1500) FROM t5; + INSERT INTO t5 SELECT a+8, randomblob(1500) FROM t5; + --sleep 1 + INSERT INTO t5 SELECT a+16, randomblob(1500) FROM t5; + --sleep 1 + INSERT INTO t5 SELECT a+32, randomblob(1500) FROM t5; + SELECT count(*) FROM t5; + --match 64 + SELECT avg(length(b)) FROM t5; + --match 1500.0 + --sleep 2 + UPDATE t5 SET b='x'||a||'y'; + SELECT sum(length(b)) FROM t5; + --match 247 + SELECT a FROM t5 WHERE b='x17y'; + --match 17 + CREATE INDEX t5b ON t5(b); + SELECT a FROM t5 WHERE b='x17y'; + --match 17 + SELECT a FROM t5 WHERE b GLOB 'x2?y' ORDER BY b DESC LIMIT 5; + --match 29 28 27 26 25 +--end + +--wait all +SELECT count(*), sum(length(b)) FROM t1; +--match 64 247 +SELECT count(*), sum(length(b)) FROM t2; +--match 64 247 +SELECT count(*), sum(length(b)) FROM t3; +--match 64 247 +SELECT count(*), sum(length(b)) FROM t4; +--match 64 247 +SELECT count(*), sum(length(b)) FROM t5; +--match 64 247 + +--task 1 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 5 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 3 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 2 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 4 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--wait all + +--task 5 + DROP INDEX t5b; + --sleep 5 + PRAGMA integrity_check(10); + --match ok + CREATE INDEX t5b ON t5(b DESC); +--end +--task 3 + DROP INDEX t3b; + --sleep 5 + PRAGMA integrity_check(10); + --match ok + CREATE INDEX t3b ON t3(b DESC); +--end +--task 1 + DROP INDEX t1b; + --sleep 5 + PRAGMA integrity_check(10); + --match ok + CREATE INDEX t1b ON t1(b DESC); +--end +--task 2 + DROP INDEX t2b; + --sleep 5 + PRAGMA integrity_check(10); + --match ok + CREATE INDEX t2b ON t2(b DESC); +--end +--task 4 + DROP INDEX t4b; + --sleep 5 + PRAGMA integrity_check(10); + --match ok + CREATE INDEX t4b ON t4(b DESC); +--end +--wait all + +--task 1 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 5 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 3 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 2 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--task 4 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 +--end +--wait all + +VACUUM; +PRAGMA integrity_check(10); +--match ok + +--task 1 + UPDATE t1 SET b=randomblob(20000); + --sleep 5 + UPDATE t1 SET b='x'||a||'y'; + SELECT a FROM t1 WHERE b='x63y'; + --match 63 +--end +--task 2 + UPDATE t2 SET b=randomblob(20000); + --sleep 5 + UPDATE t2 SET b='x'||a||'y'; + SELECT a FROM t2 WHERE b='x63y'; + --match 63 +--end +--task 3 + UPDATE t3 SET b=randomblob(20000); + --sleep 5 + UPDATE t3 SET b='x'||a||'y'; + SELECT a FROM t3 WHERE b='x63y'; + --match 63 +--end +--task 4 + UPDATE t4 SET b=randomblob(20000); + --sleep 5 + UPDATE t4 SET b='x'||a||'y'; + SELECT a FROM t4 WHERE b='x63y'; + --match 63 +--end +--task 5 + UPDATE t5 SET b=randomblob(20000); + --sleep 5 + UPDATE t5 SET b='x'||a||'y'; + SELECT a FROM t5 WHERE b='x63y'; + --match 63 +--end +--wait all + +--task 1 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 + PRAGMA integrity_check; + --match ok +--end +--task 5 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 + PRAGMA integrity_check; + --match ok +--end +--task 3 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 + PRAGMA integrity_check; + --match ok +--end +--task 2 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 + PRAGMA integrity_check; + --match ok +--end +--task 4 + SELECT t1.a FROM t1, t2 + WHERE t2.b GLOB 'x3?y' AND t1.b=('x'||(t2.a+3)||'y') + ORDER BY t1.a LIMIT 4 + --match 33 34 35 36 + SELECT t3.a FROM t3, t4 + WHERE t4.b GLOB 'x4?y' AND t3.b=('x'||(t4.a+5)||'y') + ORDER BY t3.a LIMIT 7 + --match 45 46 47 48 49 50 51 + PRAGMA integrity_check; + --match ok +--end +--wait all diff --git a/tests/mptest/testdata/test.c b/tests/mptest/testdata/test.c new file mode 100644 index 0000000..8f204d9 --- /dev/null +++ b/tests/mptest/testdata/test.c @@ -0,0 +1,5 @@ +#define unlink dont_unlink + +#include "mptest.c" + +int dont_unlink(const char *pathname) { return 0; } \ No newline at end of file diff --git a/vfs.go b/vfs.go index 02368ba..af16d0b 100644 --- a/vfs.go +++ b/vfs.go @@ -26,6 +26,14 @@ func vfsInstantiate(ctx context.Context, r wazero.Runtime) { panic(err) } + env := vfsNewEnvModuleBuilder(r) + _, err = env.Instantiate(ctx) + if err != nil { + panic(err) + } +} + +func vfsNewEnvModuleBuilder(r wazero.Runtime) wazero.HostModuleBuilder { env := r.NewHostModuleBuilder("env") env.NewFunctionBuilder().WithFunc(vfsLocaltime).Export("os_localtime") env.NewFunctionBuilder().WithFunc(vfsRandomness).Export("os_randomness") @@ -46,10 +54,7 @@ func vfsInstantiate(ctx context.Context, r wazero.Runtime) { env.NewFunctionBuilder().WithFunc(vfsUnlock).Export("os_unlock") env.NewFunctionBuilder().WithFunc(vfsCheckReservedLock).Export("os_check_reserved_lock") env.NewFunctionBuilder().WithFunc(vfsFileControl).Export("os_file_control") - _, err = env.Instantiate(ctx) - if err != nil { - panic(err) - } + return env } type vfsOSMethods bool