mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-12 05:59:14 +00:00
Refactor VFS.
This commit is contained in:
@@ -1,170 +0,0 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/sqlite3vfs"
|
||||
)
|
||||
|
||||
const (
|
||||
_MAX_STRING = 512 // Used for short strings: names, error messages…
|
||||
_MAX_PATHNAME = 512
|
||||
_DEFAULT_SECTOR_SIZE = 4096
|
||||
)
|
||||
|
||||
// https://www.sqlite.org/rescode.html
|
||||
type _ErrorCode uint32
|
||||
|
||||
func (e _ErrorCode) Error() string {
|
||||
return util.ErrorCodeString(uint32(e))
|
||||
}
|
||||
|
||||
const (
|
||||
_OK _ErrorCode = util.OK
|
||||
_PERM _ErrorCode = util.PERM
|
||||
_BUSY _ErrorCode = util.BUSY
|
||||
_IOERR _ErrorCode = util.IOERR
|
||||
_NOTFOUND _ErrorCode = util.NOTFOUND
|
||||
_CANTOPEN _ErrorCode = util.CANTOPEN
|
||||
_IOERR_READ _ErrorCode = util.IOERR_READ
|
||||
_IOERR_SHORT_READ _ErrorCode = util.IOERR_SHORT_READ
|
||||
_IOERR_WRITE _ErrorCode = util.IOERR_WRITE
|
||||
_IOERR_FSYNC _ErrorCode = util.IOERR_FSYNC
|
||||
_IOERR_DIR_FSYNC _ErrorCode = util.IOERR_DIR_FSYNC
|
||||
_IOERR_TRUNCATE _ErrorCode = util.IOERR_TRUNCATE
|
||||
_IOERR_FSTAT _ErrorCode = util.IOERR_FSTAT
|
||||
_IOERR_UNLOCK _ErrorCode = util.IOERR_UNLOCK
|
||||
_IOERR_RDLOCK _ErrorCode = util.IOERR_RDLOCK
|
||||
_IOERR_DELETE _ErrorCode = util.IOERR_DELETE
|
||||
_IOERR_ACCESS _ErrorCode = util.IOERR_ACCESS
|
||||
_IOERR_CHECKRESERVEDLOCK _ErrorCode = util.IOERR_CHECKRESERVEDLOCK
|
||||
_IOERR_LOCK _ErrorCode = util.IOERR_LOCK
|
||||
_IOERR_CLOSE _ErrorCode = util.IOERR_CLOSE
|
||||
_IOERR_SEEK _ErrorCode = util.IOERR_SEEK
|
||||
_IOERR_DELETE_NOENT _ErrorCode = util.IOERR_DELETE_NOENT
|
||||
_CANTOPEN_FULLPATH _ErrorCode = util.CANTOPEN_FULLPATH
|
||||
_OK_SYMLINK _ErrorCode = util.OK_SYMLINK
|
||||
)
|
||||
|
||||
// https://www.sqlite.org/c3ref/c_open_autoproxy.html
|
||||
type _OpenFlag = sqlite3vfs.OpenFlag
|
||||
|
||||
const (
|
||||
_OPEN_READONLY = sqlite3vfs.OPEN_READONLY
|
||||
_OPEN_READWRITE = sqlite3vfs.OPEN_READWRITE
|
||||
_OPEN_CREATE = sqlite3vfs.OPEN_CREATE
|
||||
_OPEN_DELETEONCLOSE = sqlite3vfs.OPEN_DELETEONCLOSE
|
||||
_OPEN_EXCLUSIVE = sqlite3vfs.OPEN_EXCLUSIVE
|
||||
_OPEN_AUTOPROXY = sqlite3vfs.OPEN_AUTOPROXY
|
||||
_OPEN_URI = sqlite3vfs.OPEN_URI
|
||||
_OPEN_MEMORY = sqlite3vfs.OPEN_MEMORY
|
||||
_OPEN_MAIN_DB = sqlite3vfs.OPEN_MAIN_DB
|
||||
_OPEN_TEMP_DB = sqlite3vfs.OPEN_TEMP_DB
|
||||
_OPEN_TRANSIENT_DB = sqlite3vfs.OPEN_TRANSIENT_DB
|
||||
_OPEN_MAIN_JOURNAL = sqlite3vfs.OPEN_MAIN_JOURNAL
|
||||
_OPEN_TEMP_JOURNAL = sqlite3vfs.OPEN_TEMP_JOURNAL
|
||||
_OPEN_SUBJOURNAL = sqlite3vfs.OPEN_SUBJOURNAL
|
||||
_OPEN_SUPER_JOURNAL = sqlite3vfs.OPEN_SUPER_JOURNAL
|
||||
_OPEN_NOMUTEX = sqlite3vfs.OPEN_NOMUTEX
|
||||
_OPEN_FULLMUTEX = sqlite3vfs.OPEN_FULLMUTEX
|
||||
_OPEN_SHAREDCACHE = sqlite3vfs.OPEN_SHAREDCACHE
|
||||
_OPEN_PRIVATECACHE = sqlite3vfs.OPEN_PRIVATECACHE
|
||||
_OPEN_WAL = sqlite3vfs.OPEN_WAL
|
||||
_OPEN_NOFOLLOW = sqlite3vfs.OPEN_NOFOLLOW
|
||||
)
|
||||
|
||||
// https://www.sqlite.org/c3ref/c_access_exists.html
|
||||
type _AccessFlag = sqlite3vfs.AccessFlag
|
||||
|
||||
const (
|
||||
_ACCESS_EXISTS = sqlite3vfs.ACCESS_EXISTS
|
||||
_ACCESS_READWRITE = sqlite3vfs.ACCESS_READWRITE
|
||||
_ACCESS_READ = sqlite3vfs.ACCESS_READ
|
||||
)
|
||||
|
||||
// https://www.sqlite.org/c3ref/c_sync_dataonly.html
|
||||
type _SyncFlag = sqlite3vfs.SyncFlag
|
||||
|
||||
const (
|
||||
_SYNC_NORMAL = sqlite3vfs.SYNC_NORMAL
|
||||
_SYNC_FULL = sqlite3vfs.SYNC_FULL
|
||||
_SYNC_DATAONLY = sqlite3vfs.SYNC_DATAONLY
|
||||
)
|
||||
|
||||
// https://www.sqlite.org/c3ref/c_lock_exclusive.html
|
||||
type _LockLevel = sqlite3vfs.LockLevel
|
||||
|
||||
const (
|
||||
_LOCK_NONE = sqlite3vfs.LOCK_NONE
|
||||
_LOCK_SHARED = sqlite3vfs.LOCK_SHARED
|
||||
_LOCK_RESERVED = sqlite3vfs.LOCK_RESERVED
|
||||
_LOCK_PENDING = sqlite3vfs.LOCK_PENDING
|
||||
_LOCK_EXCLUSIVE = sqlite3vfs.LOCK_EXCLUSIVE
|
||||
)
|
||||
|
||||
// https://www.sqlite.org/c3ref/c_iocap_atomic.html
|
||||
type _DeviceCharacteristic = sqlite3vfs.DeviceCharacteristic
|
||||
|
||||
const (
|
||||
_IOCAP_ATOMIC = sqlite3vfs.IOCAP_ATOMIC
|
||||
_IOCAP_ATOMIC512 = sqlite3vfs.IOCAP_ATOMIC512
|
||||
_IOCAP_ATOMIC1K = sqlite3vfs.IOCAP_ATOMIC1K
|
||||
_IOCAP_ATOMIC2K = sqlite3vfs.IOCAP_ATOMIC2K
|
||||
_IOCAP_ATOMIC4K = sqlite3vfs.IOCAP_ATOMIC4K
|
||||
_IOCAP_ATOMIC8K = sqlite3vfs.IOCAP_ATOMIC8K
|
||||
_IOCAP_ATOMIC16K = sqlite3vfs.IOCAP_ATOMIC16K
|
||||
_IOCAP_ATOMIC32K = sqlite3vfs.IOCAP_ATOMIC32K
|
||||
_IOCAP_ATOMIC64K = sqlite3vfs.IOCAP_ATOMIC64K
|
||||
_IOCAP_SAFE_APPEND = sqlite3vfs.IOCAP_SAFE_APPEND
|
||||
_IOCAP_SEQUENTIAL = sqlite3vfs.IOCAP_SEQUENTIAL
|
||||
_IOCAP_UNDELETABLE_WHEN_OPEN = sqlite3vfs.IOCAP_UNDELETABLE_WHEN_OPEN
|
||||
_IOCAP_POWERSAFE_OVERWRITE = sqlite3vfs.IOCAP_POWERSAFE_OVERWRITE
|
||||
_IOCAP_IMMUTABLE = sqlite3vfs.IOCAP_IMMUTABLE
|
||||
_IOCAP_BATCH_ATOMIC = sqlite3vfs.IOCAP_BATCH_ATOMIC
|
||||
)
|
||||
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
|
||||
type _FcntlOpcode uint32
|
||||
|
||||
const (
|
||||
_FCNTL_LOCKSTATE _FcntlOpcode = 1
|
||||
_FCNTL_GET_LOCKPROXYFILE _FcntlOpcode = 2
|
||||
_FCNTL_SET_LOCKPROXYFILE _FcntlOpcode = 3
|
||||
_FCNTL_LAST_ERRNO _FcntlOpcode = 4
|
||||
_FCNTL_SIZE_HINT _FcntlOpcode = 5
|
||||
_FCNTL_CHUNK_SIZE _FcntlOpcode = 6
|
||||
_FCNTL_FILE_POINTER _FcntlOpcode = 7
|
||||
_FCNTL_SYNC_OMITTED _FcntlOpcode = 8
|
||||
_FCNTL_WIN32_AV_RETRY _FcntlOpcode = 9
|
||||
_FCNTL_PERSIST_WAL _FcntlOpcode = 10
|
||||
_FCNTL_OVERWRITE _FcntlOpcode = 11
|
||||
_FCNTL_VFSNAME _FcntlOpcode = 12
|
||||
_FCNTL_POWERSAFE_OVERWRITE _FcntlOpcode = 13
|
||||
_FCNTL_PRAGMA _FcntlOpcode = 14
|
||||
_FCNTL_BUSYHANDLER _FcntlOpcode = 15
|
||||
_FCNTL_TEMPFILENAME _FcntlOpcode = 16
|
||||
_FCNTL_MMAP_SIZE _FcntlOpcode = 18
|
||||
_FCNTL_TRACE _FcntlOpcode = 19
|
||||
_FCNTL_HAS_MOVED _FcntlOpcode = 20
|
||||
_FCNTL_SYNC _FcntlOpcode = 21
|
||||
_FCNTL_COMMIT_PHASETWO _FcntlOpcode = 22
|
||||
_FCNTL_WIN32_SET_HANDLE _FcntlOpcode = 23
|
||||
_FCNTL_WAL_BLOCK _FcntlOpcode = 24
|
||||
_FCNTL_ZIPVFS _FcntlOpcode = 25
|
||||
_FCNTL_RBU _FcntlOpcode = 26
|
||||
_FCNTL_VFS_POINTER _FcntlOpcode = 27
|
||||
_FCNTL_JOURNAL_POINTER _FcntlOpcode = 28
|
||||
_FCNTL_WIN32_GET_HANDLE _FcntlOpcode = 29
|
||||
_FCNTL_PDB _FcntlOpcode = 30
|
||||
_FCNTL_BEGIN_ATOMIC_WRITE _FcntlOpcode = 31
|
||||
_FCNTL_COMMIT_ATOMIC_WRITE _FcntlOpcode = 32
|
||||
_FCNTL_ROLLBACK_ATOMIC_WRITE _FcntlOpcode = 33
|
||||
_FCNTL_LOCK_TIMEOUT _FcntlOpcode = 34
|
||||
_FCNTL_DATA_VERSION _FcntlOpcode = 35
|
||||
_FCNTL_SIZE_LIMIT _FcntlOpcode = 36
|
||||
_FCNTL_CKPT_DONE _FcntlOpcode = 37
|
||||
_FCNTL_RESERVE_BYTES _FcntlOpcode = 38
|
||||
_FCNTL_CKPT_START _FcntlOpcode = 39
|
||||
_FCNTL_EXTERNAL_READER _FcntlOpcode = 40
|
||||
_FCNTL_CKSM_FILE _FcntlOpcode = 41
|
||||
_FCNTL_RESET_CACHE _FcntlOpcode = 42
|
||||
)
|
||||
@@ -1,179 +0,0 @@
|
||||
package mptest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"embed"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/vfs"
|
||||
)
|
||||
|
||||
//go:embed testdata/mptest.wasm
|
||||
var binary []byte
|
||||
|
||||
//go:embed testdata/*.*test
|
||||
var scripts embed.FS
|
||||
|
||||
var (
|
||||
rt wazero.Runtime
|
||||
module wazero.CompiledModule
|
||||
instances atomic.Uint64
|
||||
)
|
||||
|
||||
func init() {
|
||||
ctx := context.TODO()
|
||||
|
||||
rt = wazero.NewRuntime(ctx)
|
||||
wasi_snapshot_preview1.MustInstantiate(ctx, rt)
|
||||
|
||||
env := vfs.Export(rt.NewHostModuleBuilder("env"))
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func config(ctx context.Context) wazero.ModuleConfig {
|
||||
name := strconv.FormatUint(instances.Add(1), 10)
|
||||
log := ctx.Value(logger{}).(io.Writer)
|
||||
fs, err := fs.Sub(scripts, "testdata")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return wazero.NewModuleConfig().
|
||||
WithName(name).WithStdout(log).WithStderr(log).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 {
|
||||
args[i] = strings.Trim(args[i], `"`)
|
||||
}
|
||||
args = args[:len(args)-1]
|
||||
|
||||
cfg := config(ctx).WithArgs(args...)
|
||||
go func() {
|
||||
ctx, vfs := vfs.Context(ctx)
|
||||
rt.InstantiateModule(ctx, module, cfg)
|
||||
vfs.Close()
|
||||
}()
|
||||
return 0
|
||||
}
|
||||
|
||||
func Test_config01(t *testing.T) {
|
||||
ctx, vfs := vfs.Context(newContext(t))
|
||||
name := filepath.Join(t.TempDir(), "test.db")
|
||||
cfg := config(ctx).WithArgs("mptest", name, "config01.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
vfs.Close()
|
||||
mod.Close(ctx)
|
||||
}
|
||||
|
||||
func Test_config02(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
if os.Getenv("CI") != "" {
|
||||
t.Skip("skipping in CI")
|
||||
}
|
||||
|
||||
ctx, vfs := vfs.Context(newContext(t))
|
||||
name := filepath.Join(t.TempDir(), "test.db")
|
||||
cfg := config(ctx).WithArgs("mptest", name, "config02.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
vfs.Close()
|
||||
mod.Close(ctx)
|
||||
}
|
||||
|
||||
func Test_crash01(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
|
||||
ctx, vfs := vfs.Context(newContext(t))
|
||||
name := filepath.Join(t.TempDir(), "test.db")
|
||||
cfg := config(ctx).WithArgs("mptest", name, "crash01.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
vfs.Close()
|
||||
mod.Close(ctx)
|
||||
}
|
||||
|
||||
func Test_multiwrite01(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
|
||||
ctx, vfs := vfs.Context(newContext(t))
|
||||
name := filepath.Join(t.TempDir(), "test.db")
|
||||
cfg := config(ctx).WithArgs("mptest", name, "multiwrite01.test")
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
vfs.Close()
|
||||
mod.Close(ctx)
|
||||
}
|
||||
|
||||
func newContext(t *testing.T) context.Context {
|
||||
return context.WithValue(context.Background(), logger{}, &testWriter{T: t})
|
||||
}
|
||||
|
||||
type logger struct{}
|
||||
|
||||
type testWriter struct {
|
||||
*testing.T
|
||||
buf []byte
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func (l *testWriter) 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
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
mptest.wasm filter=lfs diff=lfs merge=lfs -text
|
||||
*.*test -crlf
|
||||
@@ -1 +0,0 @@
|
||||
mptest.c
|
||||
28
internal/vfs/tests/mptest/testdata/build.sh
vendored
28
internal/vfs/tests/mptest/testdata/build.sh
vendored
@@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
ROOT=../../../../../
|
||||
BINARYEN="$ROOT/tools/binaryen-version_112/bin"
|
||||
WASI_SDK="$ROOT/tools/wasi-sdk-20.0/bin"
|
||||
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -flto -g0 -O2 \
|
||||
-o mptest.wasm main.c \
|
||||
-I"$ROOT/sqlite3" \
|
||||
-mmutable-globals \
|
||||
-mbulk-memory -mreference-types \
|
||||
-mnontrapping-fptoint -msign-ext \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
-DSQLITE_DEFAULT_SYNCHRONOUS=0 \
|
||||
-DSQLITE_DEFAULT_LOCKING_MODE=0 \
|
||||
-DHAVE_USLEEP -DSQLITE_NO_SYNC \
|
||||
-DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \
|
||||
-D_WASI_EMULATED_GETPID -lwasi-emulated-getpid
|
||||
|
||||
"$BINARYEN/wasm-opt" -g -O2 mptest.wasm -o mptest.tmp \
|
||||
--enable-multivalue --enable-mutable-globals \
|
||||
--enable-bulk-memory --enable-reference-types \
|
||||
--enable-nontrapping-float-to-int --enable-sign-ext
|
||||
mv mptest.tmp mptest.wasm
|
||||
46
internal/vfs/tests/mptest/testdata/config01.test
vendored
46
internal/vfs/tests/mptest/testdata/config01.test
vendored
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
** 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
|
||||
123
internal/vfs/tests/mptest/testdata/config02.test
vendored
123
internal/vfs/tests/mptest/testdata/config02.test
vendored
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
** 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
|
||||
106
internal/vfs/tests/mptest/testdata/crash01.test
vendored
106
internal/vfs/tests/mptest/testdata/crash01.test
vendored
@@ -1,106 +0,0 @@
|
||||
/* 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
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
** 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
|
||||
18
internal/vfs/tests/mptest/testdata/main.c
vendored
18
internal/vfs/tests/mptest/testdata/main.c
vendored
@@ -1,18 +0,0 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Configuration
|
||||
#include "sqlite_cfg.h"
|
||||
// Amalgamation
|
||||
#include "sqlite3.c"
|
||||
// VFS
|
||||
#include "vfs.c"
|
||||
|
||||
__attribute__((constructor)) void init() { sqlite3_initialize(); }
|
||||
|
||||
static int dont_unlink(const char *pathname) { return 0; }
|
||||
#define sqlite3_enable_load_extension(...)
|
||||
#define sqlite3_trace(...)
|
||||
#define unlink dont_unlink
|
||||
#undef UNUSED_PARAMETER
|
||||
#include "mptest.c"
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:aafb873c26d32dbf7498e4c4dd243eb6411c056432818121463d8cb49bc15f70
|
||||
size 1479293
|
||||
415
internal/vfs/tests/mptest/testdata/multiwrite01.test
vendored
415
internal/vfs/tests/mptest/testdata/multiwrite01.test
vendored
@@ -1,415 +0,0 @@
|
||||
/*
|
||||
** 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
|
||||
@@ -1,85 +0,0 @@
|
||||
package speedtest1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/vfs"
|
||||
)
|
||||
|
||||
//go:embed testdata/speedtest1.wasm
|
||||
var binary []byte
|
||||
|
||||
var (
|
||||
rt wazero.Runtime
|
||||
module wazero.CompiledModule
|
||||
output bytes.Buffer
|
||||
options []string
|
||||
)
|
||||
|
||||
func init() {
|
||||
ctx := context.TODO()
|
||||
|
||||
rt = wazero.NewRuntime(ctx)
|
||||
wasi_snapshot_preview1.MustInstantiate(ctx, rt)
|
||||
env := vfs.Export(rt.NewHostModuleBuilder("env"))
|
||||
_, err := env.Instantiate(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
module, err = rt.CompileModule(ctx, binary)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
i := 1
|
||||
options = append(options, "speedtest1")
|
||||
for _, arg := range os.Args[1:] {
|
||||
if strings.HasPrefix(arg, "-test.") {
|
||||
os.Args[i] = arg
|
||||
i++
|
||||
} else {
|
||||
options = append(options, arg)
|
||||
}
|
||||
}
|
||||
os.Args = os.Args[:i]
|
||||
|
||||
code := m.Run()
|
||||
io.Copy(os.Stderr, &output)
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func Benchmark_speedtest1(b *testing.B) {
|
||||
output.Reset()
|
||||
ctx, vfs := vfs.Context(context.Background())
|
||||
name := filepath.Join(b.TempDir(), "test.db")
|
||||
args := append(options, "--size", strconv.Itoa(b.N), name)
|
||||
cfg := wazero.NewModuleConfig().
|
||||
WithArgs(args...).WithName("speedtest1").
|
||||
WithStdout(&output).WithStderr(&output).
|
||||
WithSysWalltime().WithSysNanotime().WithSysNanosleep().
|
||||
WithOsyield(runtime.Gosched).
|
||||
WithRandSource(rand.Reader)
|
||||
mod, err := rt.InstantiateModule(ctx, module, cfg)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
vfs.Close()
|
||||
mod.Close(ctx)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
speedtest1.wasm filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -1 +0,0 @@
|
||||
speedtest1.c
|
||||
23
internal/vfs/tests/speedtest1/testdata/build.sh
vendored
23
internal/vfs/tests/speedtest1/testdata/build.sh
vendored
@@ -1,23 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
ROOT=../../../../../
|
||||
BINARYEN="$ROOT/tools/binaryen-version_112/bin"
|
||||
WASI_SDK="$ROOT/tools/wasi-sdk-20.0/bin"
|
||||
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -flto -g0 -O2 \
|
||||
-o speedtest1.wasm main.c \
|
||||
-I"$ROOT/sqlite3" \
|
||||
-mmutable-globals \
|
||||
-mbulk-memory -mreference-types \
|
||||
-mnontrapping-fptoint -msign-ext \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined
|
||||
|
||||
"$BINARYEN/wasm-opt" -g -O2 speedtest1.wasm -o speedtest1.tmp \
|
||||
--enable-multivalue --enable-mutable-globals \
|
||||
--enable-bulk-memory --enable-reference-types \
|
||||
--enable-nontrapping-float-to-int --enable-sign-ext
|
||||
mv speedtest1.tmp speedtest1.wasm
|
||||
12
internal/vfs/tests/speedtest1/testdata/main.c
vendored
12
internal/vfs/tests/speedtest1/testdata/main.c
vendored
@@ -1,12 +0,0 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Configuration
|
||||
#include "sqlite_cfg.h"
|
||||
// Amalgamation
|
||||
#include "sqlite3.c"
|
||||
// VFS
|
||||
#include "vfs.c"
|
||||
|
||||
#define randomFunc(args...) randomFunc2(args)
|
||||
#include "speedtest1.c"
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2638f5458f93b60f97006456f60e3e294cffb9ac22b20dba748fbd12e34f056d
|
||||
size 1515841
|
||||
@@ -1,327 +0,0 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/sqlite3vfs"
|
||||
"github.com/ncruces/julianday"
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
func Export(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
|
||||
util.RegisterFuncII(env, "go_vfs_find", vfsFind)
|
||||
util.RegisterFuncIIJ(env, "go_localtime", vfsLocaltime)
|
||||
util.RegisterFuncIIII(env, "go_randomness", vfsRandomness)
|
||||
util.RegisterFuncIII(env, "go_sleep", vfsSleep)
|
||||
util.RegisterFuncIII(env, "go_current_time", vfsCurrentTime)
|
||||
util.RegisterFuncIII(env, "go_current_time_64", vfsCurrentTime64)
|
||||
util.RegisterFuncIIIII(env, "go_full_pathname", vfsFullPathname)
|
||||
util.RegisterFuncIIII(env, "go_delete", vfsDelete)
|
||||
util.RegisterFuncIIIII(env, "go_access", vfsAccess)
|
||||
util.RegisterFuncIIIIII(env, "go_open", vfsOpen)
|
||||
util.RegisterFuncII(env, "go_close", vfsClose)
|
||||
util.RegisterFuncIIIIJ(env, "go_read", vfsRead)
|
||||
util.RegisterFuncIIIIJ(env, "go_write", vfsWrite)
|
||||
util.RegisterFuncIIJ(env, "go_truncate", vfsTruncate)
|
||||
util.RegisterFuncIII(env, "go_sync", vfsSync)
|
||||
util.RegisterFuncIII(env, "go_file_size", vfsFileSize)
|
||||
util.RegisterFuncIIII(env, "go_file_control", vfsFileControl)
|
||||
util.RegisterFuncII(env, "go_sector_size", vfsSectorSize)
|
||||
util.RegisterFuncII(env, "go_device_characteristics", vfsDeviceCharacteristics)
|
||||
util.RegisterFuncIII(env, "go_lock", vfsLock)
|
||||
util.RegisterFuncIII(env, "go_unlock", vfsUnlock)
|
||||
util.RegisterFuncIII(env, "go_check_reserved_lock", vfsCheckReservedLock)
|
||||
return env
|
||||
}
|
||||
|
||||
type vfsKey struct{}
|
||||
type vfsState struct {
|
||||
files []sqlite3vfs.File
|
||||
}
|
||||
|
||||
func Context(ctx context.Context) (context.Context, io.Closer) {
|
||||
vfs := &vfsState{}
|
||||
return context.WithValue(ctx, vfsKey{}, vfs), vfs
|
||||
}
|
||||
|
||||
func (vfs *vfsState) Close() error {
|
||||
for _, f := range vfs.files {
|
||||
if f != nil {
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
vfs.files = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func vfsFind(ctx context.Context, mod api.Module, zVfsName uint32) uint32 {
|
||||
name := util.ReadString(mod, zVfsName, _MAX_STRING)
|
||||
if sqlite3vfs.Find(name) != nil {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func vfsLocaltime(ctx context.Context, mod api.Module, pTm uint32, t int64) _ErrorCode {
|
||||
tm := time.Unix(t, 0)
|
||||
var isdst int
|
||||
if tm.IsDST() {
|
||||
isdst = 1
|
||||
}
|
||||
|
||||
const size = 32 / 8
|
||||
// https://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html
|
||||
util.WriteUint32(mod, pTm+0*size, uint32(tm.Second()))
|
||||
util.WriteUint32(mod, pTm+1*size, uint32(tm.Minute()))
|
||||
util.WriteUint32(mod, pTm+2*size, uint32(tm.Hour()))
|
||||
util.WriteUint32(mod, pTm+3*size, uint32(tm.Day()))
|
||||
util.WriteUint32(mod, pTm+4*size, uint32(tm.Month()-time.January))
|
||||
util.WriteUint32(mod, pTm+5*size, uint32(tm.Year()-1900))
|
||||
util.WriteUint32(mod, pTm+6*size, uint32(tm.Weekday()-time.Sunday))
|
||||
util.WriteUint32(mod, pTm+7*size, uint32(tm.YearDay()-1))
|
||||
util.WriteUint32(mod, pTm+8*size, uint32(isdst))
|
||||
return _OK
|
||||
}
|
||||
|
||||
func vfsRandomness(ctx context.Context, mod api.Module, pVfs, nByte, zByte uint32) uint32 {
|
||||
mem := util.View(mod, zByte, uint64(nByte))
|
||||
n, _ := rand.Reader.Read(mem)
|
||||
return uint32(n)
|
||||
}
|
||||
|
||||
func vfsSleep(ctx context.Context, mod api.Module, pVfs, nMicro uint32) _ErrorCode {
|
||||
time.Sleep(time.Duration(nMicro) * time.Microsecond)
|
||||
return _OK
|
||||
}
|
||||
|
||||
func vfsCurrentTime(ctx context.Context, mod api.Module, pVfs, prNow uint32) _ErrorCode {
|
||||
day := julianday.Float(time.Now())
|
||||
util.WriteFloat64(mod, prNow, day)
|
||||
return _OK
|
||||
}
|
||||
|
||||
func vfsCurrentTime64(ctx context.Context, mod api.Module, pVfs, piNow uint32) _ErrorCode {
|
||||
day, nsec := julianday.Date(time.Now())
|
||||
msec := day*86_400_000 + nsec/1_000_000
|
||||
util.WriteUint64(mod, piNow, uint64(msec))
|
||||
return _OK
|
||||
}
|
||||
|
||||
func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative, nFull, zFull uint32) _ErrorCode {
|
||||
vfs := vfsAPIGet(mod, pVfs)
|
||||
path := util.ReadString(mod, zRelative, _MAX_PATHNAME)
|
||||
|
||||
path, err := vfs.FullPathname(path)
|
||||
|
||||
size := uint64(len(path) + 1)
|
||||
if size > uint64(nFull) {
|
||||
return _CANTOPEN_FULLPATH
|
||||
}
|
||||
mem := util.View(mod, zFull, size)
|
||||
mem[len(path)] = 0
|
||||
copy(mem, path)
|
||||
|
||||
return vfsAPIErrorCode(err, _CANTOPEN_FULLPATH)
|
||||
}
|
||||
|
||||
func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath, syncDir uint32) _ErrorCode {
|
||||
vfs := vfsAPIGet(mod, pVfs)
|
||||
path := util.ReadString(mod, zPath, _MAX_PATHNAME)
|
||||
|
||||
err := vfs.Delete(path, syncDir != 0)
|
||||
return vfsAPIErrorCode(err, _IOERR_DELETE)
|
||||
}
|
||||
|
||||
func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath uint32, flags _AccessFlag, pResOut uint32) _ErrorCode {
|
||||
vfs := vfsAPIGet(mod, pVfs)
|
||||
path := util.ReadString(mod, zPath, _MAX_PATHNAME)
|
||||
|
||||
ok, err := vfs.Access(path, flags)
|
||||
var res uint32
|
||||
if ok {
|
||||
res = 1
|
||||
}
|
||||
|
||||
util.WriteUint32(mod, pResOut, res)
|
||||
return vfsAPIErrorCode(err, _IOERR_ACCESS)
|
||||
}
|
||||
|
||||
func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, flags _OpenFlag, pOutFlags uint32) _ErrorCode {
|
||||
vfs := vfsAPIGet(mod, pVfs)
|
||||
|
||||
var path string
|
||||
if zPath != 0 {
|
||||
path = util.ReadString(mod, zPath, _MAX_PATHNAME)
|
||||
}
|
||||
|
||||
file, flags, err := vfs.Open(path, flags)
|
||||
if err != nil {
|
||||
return vfsAPIErrorCode(err, _CANTOPEN)
|
||||
}
|
||||
|
||||
vfsFileRegister(ctx, mod, pFile, file)
|
||||
if pOutFlags != 0 {
|
||||
util.WriteUint32(mod, pOutFlags, uint32(flags))
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
|
||||
func vfsClose(ctx context.Context, mod api.Module, pFile uint32) _ErrorCode {
|
||||
err := vfsFileClose(ctx, mod, pFile)
|
||||
if err != nil {
|
||||
return _IOERR_CLOSE
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
|
||||
func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfst int64) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
buf := util.View(mod, zBuf, uint64(iAmt))
|
||||
|
||||
n, err := file.ReadAt(buf, iOfst)
|
||||
if n == int(iAmt) {
|
||||
return _OK
|
||||
}
|
||||
if n == 0 && err != io.EOF {
|
||||
return _IOERR_READ
|
||||
}
|
||||
for i := range buf[n:] {
|
||||
buf[n+i] = 0
|
||||
}
|
||||
return _IOERR_SHORT_READ
|
||||
}
|
||||
|
||||
func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOfst int64) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
buf := util.View(mod, zBuf, uint64(iAmt))
|
||||
|
||||
_, err := file.WriteAt(buf, iOfst)
|
||||
if err != nil {
|
||||
return _IOERR_WRITE
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
|
||||
func vfsTruncate(ctx context.Context, mod api.Module, pFile uint32, nByte int64) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
err := file.Truncate(nByte)
|
||||
return vfsAPIErrorCode(err, _IOERR_TRUNCATE)
|
||||
}
|
||||
|
||||
func vfsSync(ctx context.Context, mod api.Module, pFile uint32, flags _SyncFlag) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
err := file.Sync(flags)
|
||||
return vfsAPIErrorCode(err, _IOERR_FSYNC)
|
||||
}
|
||||
|
||||
func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize uint32) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
size, err := file.FileSize()
|
||||
util.WriteUint64(mod, pSize, uint64(size))
|
||||
return vfsAPIErrorCode(err, _IOERR_SEEK)
|
||||
}
|
||||
|
||||
func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock _LockLevel) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
err := file.Lock(eLock)
|
||||
return vfsAPIErrorCode(err, _IOERR_LOCK)
|
||||
}
|
||||
|
||||
func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock _LockLevel) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
err := file.Unlock(eLock)
|
||||
return vfsAPIErrorCode(err, _IOERR_UNLOCK)
|
||||
}
|
||||
|
||||
func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
locked, err := file.CheckReservedLock()
|
||||
|
||||
var res uint32
|
||||
if locked {
|
||||
res = 1
|
||||
}
|
||||
|
||||
util.WriteUint32(mod, pResOut, res)
|
||||
return vfsAPIErrorCode(err, _IOERR_CHECKRESERVEDLOCK)
|
||||
}
|
||||
|
||||
func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _FcntlOpcode, pArg uint32) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
|
||||
switch op {
|
||||
case _FCNTL_LOCKSTATE:
|
||||
if file, ok := file.(sqlite3vfs.FileLockState); ok {
|
||||
util.WriteUint32(mod, pArg, uint32(file.LockState()))
|
||||
return _OK
|
||||
}
|
||||
|
||||
case _FCNTL_LOCK_TIMEOUT:
|
||||
if file, ok := file.(*vfsFile); ok {
|
||||
millis := file.lockTimeout.Milliseconds()
|
||||
file.lockTimeout = time.Duration(util.ReadUint32(mod, pArg)) * time.Millisecond
|
||||
util.WriteUint32(mod, pArg, uint32(millis))
|
||||
return _OK
|
||||
}
|
||||
|
||||
case _FCNTL_POWERSAFE_OVERWRITE:
|
||||
if file, ok := file.(sqlite3vfs.FilePowersafeOverwrite); ok {
|
||||
switch util.ReadUint32(mod, pArg) {
|
||||
case 0:
|
||||
file.SetPowersafeOverwrite(false)
|
||||
case 1:
|
||||
file.SetPowersafeOverwrite(true)
|
||||
default:
|
||||
if file.PowersafeOverwrite() {
|
||||
util.WriteUint32(mod, pArg, 1)
|
||||
} else {
|
||||
util.WriteUint32(mod, pArg, 0)
|
||||
}
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
|
||||
case _FCNTL_SIZE_HINT:
|
||||
if file, ok := file.(sqlite3vfs.FileSizeHint); ok {
|
||||
size := util.ReadUint64(mod, pArg)
|
||||
err := file.SizeHint(int64(size))
|
||||
return vfsAPIErrorCode(err, _IOERR_TRUNCATE)
|
||||
}
|
||||
|
||||
case _FCNTL_HAS_MOVED:
|
||||
if file, ok := file.(sqlite3vfs.FileHasMoved); ok {
|
||||
moved, err := file.HasMoved()
|
||||
|
||||
var res uint32
|
||||
if moved {
|
||||
res = 1
|
||||
}
|
||||
|
||||
util.WriteUint32(mod, pArg, res)
|
||||
return vfsAPIErrorCode(err, _IOERR_FSTAT)
|
||||
}
|
||||
}
|
||||
|
||||
// Consider also implementing these opcodes (in use by SQLite):
|
||||
// _FCNTL_BUSYHANDLER
|
||||
// _FCNTL_COMMIT_PHASETWO
|
||||
// _FCNTL_PDB
|
||||
// _FCNTL_PRAGMA
|
||||
// _FCNTL_SYNC
|
||||
return _NOTFOUND
|
||||
}
|
||||
|
||||
func vfsSectorSize(ctx context.Context, mod api.Module, pFile uint32) uint32 {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
return uint32(file.SectorSize())
|
||||
}
|
||||
|
||||
func vfsDeviceCharacteristics(ctx context.Context, mod api.Module, pFile uint32) _DeviceCharacteristic {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
return file.DeviceCharacteristics()
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/sqlite3vfs"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
func vfsAPIGet(mod api.Module, pVfs uint32) sqlite3vfs.VFS {
|
||||
if pVfs != 0 {
|
||||
name := util.ReadString(mod, util.ReadUint32(mod, pVfs+16), _MAX_STRING)
|
||||
if vfs := sqlite3vfs.Find(name); vfs != nil {
|
||||
return vfs
|
||||
}
|
||||
}
|
||||
return vfsOS{}
|
||||
}
|
||||
|
||||
func vfsAPIErrorCode(err error, def _ErrorCode) _ErrorCode {
|
||||
if err == nil {
|
||||
return _OK
|
||||
}
|
||||
switch v := reflect.ValueOf(err); v.Kind() {
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
return _ErrorCode(v.Uint())
|
||||
}
|
||||
return def
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/sqlite3vfs"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
type vfsOS struct{}
|
||||
|
||||
func (vfsOS) FullPathname(path string) (string, error) {
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fi, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return path, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
if fi.Mode()&fs.ModeSymlink != 0 {
|
||||
err = _OK_SYMLINK
|
||||
}
|
||||
return path, err
|
||||
}
|
||||
|
||||
func (vfsOS) Delete(path string, syncDir bool) error {
|
||||
err := os.Remove(path)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return _IOERR_DELETE_NOENT
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if runtime.GOOS != "windows" && syncDir {
|
||||
f, err := os.Open(filepath.Dir(path))
|
||||
if err != nil {
|
||||
return _OK
|
||||
}
|
||||
defer f.Close()
|
||||
err = osSync(f, false, false)
|
||||
if err != nil {
|
||||
return _IOERR_DIR_FSYNC
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vfsOS) Access(name string, flags sqlite3vfs.AccessFlag) (bool, error) {
|
||||
err := osAccess(name, flags)
|
||||
if flags == _ACCESS_EXISTS {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return false, nil
|
||||
}
|
||||
} else {
|
||||
if errors.Is(err, fs.ErrPermission) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
func (vfsOS) Open(name string, flags sqlite3vfs.OpenFlag) (sqlite3vfs.File, sqlite3vfs.OpenFlag, error) {
|
||||
var oflags int
|
||||
if flags&_OPEN_EXCLUSIVE != 0 {
|
||||
oflags |= os.O_EXCL
|
||||
}
|
||||
if flags&_OPEN_CREATE != 0 {
|
||||
oflags |= os.O_CREATE
|
||||
}
|
||||
if flags&_OPEN_READONLY != 0 {
|
||||
oflags |= os.O_RDONLY
|
||||
}
|
||||
if flags&_OPEN_READWRITE != 0 {
|
||||
oflags |= os.O_RDWR
|
||||
}
|
||||
|
||||
var err error
|
||||
var f *os.File
|
||||
if name == "" {
|
||||
f, err = os.CreateTemp("", "*.db")
|
||||
} else {
|
||||
f, err = osOpenFile(name, oflags, 0666)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, flags, err
|
||||
}
|
||||
|
||||
if flags&_OPEN_DELETEONCLOSE != 0 {
|
||||
os.Remove(f.Name())
|
||||
}
|
||||
|
||||
file := vfsFile{
|
||||
File: f,
|
||||
psow: true,
|
||||
readOnly: flags&_OPEN_READONLY != 0,
|
||||
syncDir: runtime.GOOS != "windows" &&
|
||||
flags&(_OPEN_CREATE) != 0 &&
|
||||
flags&(_OPEN_MAIN_JOURNAL|_OPEN_SUPER_JOURNAL|_OPEN_WAL) != 0,
|
||||
}
|
||||
return &file, flags, nil
|
||||
}
|
||||
|
||||
type vfsFile struct {
|
||||
*os.File
|
||||
lockTimeout time.Duration
|
||||
lock _LockLevel
|
||||
psow bool
|
||||
syncDir bool
|
||||
readOnly bool
|
||||
}
|
||||
|
||||
var (
|
||||
// Ensure these interfaces are implemented:
|
||||
_ sqlite3vfs.FileLockState = &vfsFile{}
|
||||
_ sqlite3vfs.FileHasMoved = &vfsFile{}
|
||||
_ sqlite3vfs.FileSizeHint = &vfsFile{}
|
||||
_ sqlite3vfs.FilePowersafeOverwrite = &vfsFile{}
|
||||
)
|
||||
|
||||
func vfsFileNew(vfs *vfsState, file sqlite3vfs.File) uint32 {
|
||||
// Find an empty slot.
|
||||
for id, f := range vfs.files {
|
||||
if f == nil {
|
||||
vfs.files[id] = file
|
||||
return uint32(id)
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new slot.
|
||||
vfs.files = append(vfs.files, file)
|
||||
return uint32(len(vfs.files) - 1)
|
||||
}
|
||||
|
||||
func vfsFileRegister(ctx context.Context, mod api.Module, pFile uint32, file sqlite3vfs.File) {
|
||||
id := vfsFileNew(ctx.Value(vfsKey{}).(*vfsState), file)
|
||||
util.WriteUint32(mod, pFile+4, id)
|
||||
}
|
||||
|
||||
func vfsFileGet(ctx context.Context, mod api.Module, pFile uint32) sqlite3vfs.File {
|
||||
vfs := ctx.Value(vfsKey{}).(*vfsState)
|
||||
id := util.ReadUint32(mod, pFile+4)
|
||||
return vfs.files[id]
|
||||
}
|
||||
|
||||
func vfsFileClose(ctx context.Context, mod api.Module, pFile uint32) error {
|
||||
vfs := ctx.Value(vfsKey{}).(*vfsState)
|
||||
id := util.ReadUint32(mod, pFile+4)
|
||||
file := vfs.files[id]
|
||||
vfs.files[id] = nil
|
||||
return file.Close()
|
||||
}
|
||||
|
||||
func (f *vfsFile) Sync(flags sqlite3vfs.SyncFlag) error {
|
||||
dataonly := (flags & _SYNC_DATAONLY) != 0
|
||||
fullsync := (flags & 0x0f) == _SYNC_FULL
|
||||
|
||||
err := osSync(f.File, fullsync, dataonly)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if runtime.GOOS != "windows" && f.syncDir {
|
||||
f.syncDir = false
|
||||
d, err := os.Open(filepath.Dir(f.File.Name()))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer d.Close()
|
||||
err = osSync(d, false, false)
|
||||
if err != nil {
|
||||
return _IOERR_DIR_FSYNC
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *vfsFile) FileSize() (int64, error) {
|
||||
return f.Seek(0, io.SeekEnd)
|
||||
}
|
||||
|
||||
func (*vfsFile) SectorSize() int {
|
||||
return _DEFAULT_SECTOR_SIZE
|
||||
}
|
||||
|
||||
func (f *vfsFile) DeviceCharacteristics() sqlite3vfs.DeviceCharacteristic {
|
||||
if f.psow {
|
||||
return _IOCAP_POWERSAFE_OVERWRITE
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *vfsFile) SizeHint(size int64) error {
|
||||
return osAllocate(f.File, size)
|
||||
}
|
||||
|
||||
func (f *vfsFile) HasMoved() (bool, error) {
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
pi, err := os.Stat(f.Name())
|
||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
return false, err
|
||||
}
|
||||
return !os.SameFile(fi, pi), nil
|
||||
}
|
||||
|
||||
func (f *vfsFile) LockState() sqlite3vfs.LockLevel { return f.lock }
|
||||
func (f *vfsFile) PowersafeOverwrite() bool { return f.psow }
|
||||
func (f *vfsFile) SetPowersafeOverwrite(psow bool) { f.psow = psow }
|
||||
@@ -1,151 +0,0 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/sqlite3vfs"
|
||||
)
|
||||
|
||||
const (
|
||||
_PENDING_BYTE = 0x40000000
|
||||
_RESERVED_BYTE = (_PENDING_BYTE + 1)
|
||||
_SHARED_FIRST = (_PENDING_BYTE + 2)
|
||||
_SHARED_SIZE = 510
|
||||
)
|
||||
|
||||
func (file *vfsFile) Lock(eLock sqlite3vfs.LockLevel) error {
|
||||
// Argument check. SQLite never explicitly requests a pending lock.
|
||||
if eLock != _LOCK_SHARED && eLock != _LOCK_RESERVED && eLock != _LOCK_EXCLUSIVE {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
|
||||
switch {
|
||||
case file.lock < _LOCK_NONE || file.lock > _LOCK_EXCLUSIVE:
|
||||
// Connection state check.
|
||||
panic(util.AssertErr())
|
||||
case file.lock == _LOCK_NONE && eLock > _LOCK_SHARED:
|
||||
// We never move from unlocked to anything higher than a shared lock.
|
||||
panic(util.AssertErr())
|
||||
case file.lock != _LOCK_SHARED && eLock == _LOCK_RESERVED:
|
||||
// A shared lock is always held when a reserved lock is requested.
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
|
||||
// If we already have an equal or more restrictive lock, do nothing.
|
||||
if file.lock >= eLock {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not allow any kind of write-lock on a read-only database.
|
||||
if file.readOnly && eLock >= _LOCK_RESERVED {
|
||||
return _IOERR_LOCK
|
||||
}
|
||||
|
||||
switch eLock {
|
||||
case _LOCK_SHARED:
|
||||
// Must be unlocked to get SHARED.
|
||||
if file.lock != _LOCK_NONE {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
if rc := osGetSharedLock(file.File, file.lockTimeout); rc != _OK {
|
||||
return rc
|
||||
}
|
||||
file.lock = _LOCK_SHARED
|
||||
return nil
|
||||
|
||||
case _LOCK_RESERVED:
|
||||
// Must be SHARED to get RESERVED.
|
||||
if file.lock != _LOCK_SHARED {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
if rc := osGetReservedLock(file.File, file.lockTimeout); rc != _OK {
|
||||
return rc
|
||||
}
|
||||
file.lock = _LOCK_RESERVED
|
||||
return nil
|
||||
|
||||
case _LOCK_EXCLUSIVE:
|
||||
// Must be SHARED, RESERVED or PENDING to get EXCLUSIVE.
|
||||
if file.lock <= _LOCK_NONE || file.lock >= _LOCK_EXCLUSIVE {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
|
||||
if file.lock < _LOCK_PENDING {
|
||||
if rc := osGetPendingLock(file.File); rc != _OK {
|
||||
return rc
|
||||
}
|
||||
file.lock = _LOCK_PENDING
|
||||
}
|
||||
if rc := osGetExclusiveLock(file.File, file.lockTimeout); rc != _OK {
|
||||
return rc
|
||||
}
|
||||
file.lock = _LOCK_EXCLUSIVE
|
||||
return nil
|
||||
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
}
|
||||
|
||||
func (file *vfsFile) Unlock(eLock sqlite3vfs.LockLevel) error {
|
||||
// Argument check.
|
||||
if eLock != _LOCK_NONE && eLock != _LOCK_SHARED {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
|
||||
// Connection state check.
|
||||
if file.lock < _LOCK_NONE || file.lock > _LOCK_EXCLUSIVE {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
|
||||
// If we don't have a more restrictive lock, do nothing.
|
||||
if file.lock <= eLock {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch eLock {
|
||||
case _LOCK_SHARED:
|
||||
if rc := osDowngradeLock(file.File, file.lock); rc != _OK {
|
||||
return rc
|
||||
}
|
||||
file.lock = _LOCK_SHARED
|
||||
return nil
|
||||
|
||||
case _LOCK_NONE:
|
||||
rc := osReleaseLock(file.File, file.lock)
|
||||
file.lock = _LOCK_NONE
|
||||
return rc
|
||||
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
}
|
||||
|
||||
func (file *vfsFile) CheckReservedLock() (bool, error) {
|
||||
// Connection state check.
|
||||
if file.lock < _LOCK_NONE || file.lock > _LOCK_EXCLUSIVE {
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
|
||||
if file.lock >= _LOCK_RESERVED {
|
||||
return true, nil
|
||||
}
|
||||
return osCheckReservedLock(file.File)
|
||||
}
|
||||
|
||||
func osGetReservedLock(file *os.File, timeout time.Duration) _ErrorCode {
|
||||
// Acquire the RESERVED lock.
|
||||
return osWriteLock(file, _RESERVED_BYTE, 1, timeout)
|
||||
}
|
||||
|
||||
func osGetPendingLock(file *os.File) _ErrorCode {
|
||||
// Acquire the PENDING lock.
|
||||
return osWriteLock(file, _PENDING_BYTE, 1, 0)
|
||||
}
|
||||
|
||||
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
|
||||
// Test the RESERVED lock.
|
||||
return osCheckLock(file, _RESERVED_BYTE, 1)
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
func Test_vfsLock(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "linux", "darwin", "windows":
|
||||
break
|
||||
default:
|
||||
t.Skip("OS lacks OFD locks")
|
||||
}
|
||||
|
||||
name := filepath.Join(t.TempDir(), "test.db")
|
||||
|
||||
// Create a temporary file.
|
||||
file1, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer file1.Close()
|
||||
|
||||
// Open the temporary file again.
|
||||
file2, err := os.OpenFile(name, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer file2.Close()
|
||||
|
||||
const (
|
||||
pFile1 = 4
|
||||
pFile2 = 16
|
||||
pOutput = 32
|
||||
)
|
||||
mod := util.NewMockModule(128)
|
||||
ctx, vfs := Context(context.TODO())
|
||||
defer vfs.Close()
|
||||
|
||||
vfsFileRegister(ctx, mod, pFile1, &vfsFile{File: file1})
|
||||
vfsFileRegister(ctx, mod, pFile2, &vfsFile{File: file2})
|
||||
|
||||
rc := vfsCheckReservedLock(ctx, mod, pFile1, pOutput)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, pOutput); got != 0 {
|
||||
t.Error("file was locked")
|
||||
}
|
||||
rc = vfsCheckReservedLock(ctx, mod, pFile2, pOutput)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, pOutput); got != 0 {
|
||||
t.Error("file was locked")
|
||||
}
|
||||
|
||||
rc = vfsLock(ctx, mod, pFile2, _LOCK_SHARED)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
rc = vfsCheckReservedLock(ctx, mod, pFile1, pOutput)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, pOutput); got != 0 {
|
||||
t.Error("file was locked")
|
||||
}
|
||||
rc = vfsCheckReservedLock(ctx, mod, pFile2, pOutput)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, pOutput); got != 0 {
|
||||
t.Error("file was locked")
|
||||
}
|
||||
|
||||
rc = vfsLock(ctx, mod, pFile2, _LOCK_RESERVED)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
rc = vfsLock(ctx, mod, pFile2, _LOCK_SHARED)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
rc = vfsCheckReservedLock(ctx, mod, pFile1, pOutput)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, pOutput); got == 0 {
|
||||
t.Error("file wasn't locked")
|
||||
}
|
||||
rc = vfsCheckReservedLock(ctx, mod, pFile2, pOutput)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, pOutput); got == 0 {
|
||||
t.Error("file wasn't locked")
|
||||
}
|
||||
|
||||
rc = vfsLock(ctx, mod, pFile2, _LOCK_EXCLUSIVE)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
rc = vfsCheckReservedLock(ctx, mod, pFile1, pOutput)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, pOutput); got == 0 {
|
||||
t.Error("file wasn't locked")
|
||||
}
|
||||
rc = vfsCheckReservedLock(ctx, mod, pFile2, pOutput)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, pOutput); got == 0 {
|
||||
t.Error("file wasn't locked")
|
||||
}
|
||||
|
||||
rc = vfsLock(ctx, mod, pFile1, _LOCK_SHARED)
|
||||
if rc == _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
rc = vfsCheckReservedLock(ctx, mod, pFile1, pOutput)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, pOutput); got == 0 {
|
||||
t.Error("file wasn't locked")
|
||||
}
|
||||
rc = vfsCheckReservedLock(ctx, mod, pFile2, pOutput)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, pOutput); got == 0 {
|
||||
t.Error("file wasn't locked")
|
||||
}
|
||||
|
||||
rc = vfsUnlock(ctx, mod, pFile2, _LOCK_SHARED)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
rc = vfsCheckReservedLock(ctx, mod, pFile1, pOutput)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, pOutput); got != 0 {
|
||||
t.Error("file was locked")
|
||||
}
|
||||
rc = vfsCheckReservedLock(ctx, mod, pFile2, pOutput)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, pOutput); got != 0 {
|
||||
t.Error("file was locked")
|
||||
}
|
||||
|
||||
rc = vfsLock(ctx, mod, pFile1, _LOCK_SHARED)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
//go:build freebsd || openbsd || netbsd || dragonfly || (darwin && sqlite3_bsd)
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func osUnlock(file *os.File, start, len int64) _ErrorCode {
|
||||
if start == 0 && len == 0 {
|
||||
err := unix.Flock(int(file.Fd()), unix.LOCK_UN)
|
||||
if err != nil {
|
||||
return _IOERR_UNLOCK
|
||||
}
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
|
||||
func osLock(file *os.File, how int, timeout time.Duration, def _ErrorCode) _ErrorCode {
|
||||
var err error
|
||||
for {
|
||||
err = unix.Flock(int(file.Fd()), how)
|
||||
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
|
||||
break
|
||||
}
|
||||
if timeout < time.Millisecond {
|
||||
break
|
||||
}
|
||||
timeout -= time.Millisecond
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
return osLockErrorCode(err, def)
|
||||
}
|
||||
|
||||
func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||
return osLock(file, unix.LOCK_SH|unix.LOCK_NB, timeout, _IOERR_RDLOCK)
|
||||
}
|
||||
|
||||
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||
return osLock(file, unix.LOCK_EX|unix.LOCK_NB, timeout, _IOERR_LOCK)
|
||||
}
|
||||
|
||||
func osCheckLock(file *os.File, start, len int64) (bool, _ErrorCode) {
|
||||
lock := unix.Flock_t{
|
||||
Type: unix.F_RDLCK,
|
||||
Start: start,
|
||||
Len: len,
|
||||
}
|
||||
if unix.FcntlFlock(file.Fd(), unix.F_GETLK, &lock) != nil {
|
||||
return false, _IOERR_CHECKRESERVEDLOCK
|
||||
}
|
||||
return lock.Type != unix.F_UNLCK, _OK
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
//go:build !sqlite3_bsd
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
|
||||
_F_OFD_SETLK = 90
|
||||
_F_OFD_SETLKW = 91
|
||||
_F_OFD_GETLK = 92
|
||||
_F_OFD_SETLKWTIMEOUT = 93
|
||||
)
|
||||
|
||||
type flocktimeout_t struct {
|
||||
fl unix.Flock_t
|
||||
timeout unix.Timespec
|
||||
}
|
||||
|
||||
func osSync(file *os.File, fullsync, dataonly bool) error {
|
||||
if fullsync {
|
||||
return file.Sync()
|
||||
}
|
||||
return unix.Fsync(int(file.Fd()))
|
||||
}
|
||||
|
||||
func osAllocate(file *os.File, size int64) error {
|
||||
off, err := file.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if size <= off {
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/11497568/867786
|
||||
store := unix.Fstore_t{
|
||||
Flags: unix.F_ALLOCATECONTIG,
|
||||
Posmode: unix.F_PEOFPOSMODE,
|
||||
Offset: 0,
|
||||
Length: size,
|
||||
}
|
||||
|
||||
// Try to get a continous chunk of disk space.
|
||||
err = unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store)
|
||||
if err != nil {
|
||||
// OK, perhaps we are too fragmented, allocate non-continuous.
|
||||
store.Flags = unix.F_ALLOCATEALL
|
||||
unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store)
|
||||
}
|
||||
return file.Truncate(size)
|
||||
}
|
||||
|
||||
func osUnlock(file *os.File, start, len int64) _ErrorCode {
|
||||
err := unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &unix.Flock_t{
|
||||
Type: unix.F_UNLCK,
|
||||
Start: start,
|
||||
Len: len,
|
||||
})
|
||||
if err != nil {
|
||||
return _IOERR_UNLOCK
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
|
||||
func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode {
|
||||
lock := flocktimeout_t{fl: unix.Flock_t{
|
||||
Type: typ,
|
||||
Start: start,
|
||||
Len: len,
|
||||
}}
|
||||
var err error
|
||||
if timeout == 0 {
|
||||
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &lock.fl)
|
||||
} else {
|
||||
lock.timeout = unix.NsecToTimespec(int64(timeout / time.Nanosecond))
|
||||
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKWTIMEOUT, &lock.fl)
|
||||
}
|
||||
return osLockErrorCode(err, def)
|
||||
}
|
||||
|
||||
func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||
return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK)
|
||||
}
|
||||
|
||||
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||
return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK)
|
||||
}
|
||||
|
||||
func osCheckLock(file *os.File, start, len int64) (bool, _ErrorCode) {
|
||||
lock := unix.Flock_t{
|
||||
Type: unix.F_RDLCK,
|
||||
Start: start,
|
||||
Len: len,
|
||||
}
|
||||
if unix.FcntlFlock(file.Fd(), _F_OFD_GETLK, &lock) != nil {
|
||||
return false, _IOERR_CHECKRESERVEDLOCK
|
||||
}
|
||||
return lock.Type != unix.F_UNLCK, _OK
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func osSync(file *os.File, fullsync, dataonly bool) error {
|
||||
if dataonly {
|
||||
_, _, err := unix.Syscall(unix.SYS_FDATASYNC, file.Fd(), 0, 0)
|
||||
if err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return file.Sync()
|
||||
}
|
||||
|
||||
func osAllocate(file *os.File, size int64) error {
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
return unix.Fallocate(int(file.Fd()), 0, 0, size)
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
//go:build linux || illumos
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func osUnlock(file *os.File, start, len int64) _ErrorCode {
|
||||
err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &unix.Flock_t{
|
||||
Type: unix.F_UNLCK,
|
||||
Start: start,
|
||||
Len: len,
|
||||
})
|
||||
if err != nil {
|
||||
return _IOERR_UNLOCK
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
|
||||
func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode {
|
||||
lock := unix.Flock_t{
|
||||
Type: typ,
|
||||
Start: start,
|
||||
Len: len,
|
||||
}
|
||||
var err error
|
||||
for {
|
||||
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
|
||||
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
|
||||
break
|
||||
}
|
||||
if timeout < time.Millisecond {
|
||||
break
|
||||
}
|
||||
timeout -= time.Millisecond
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
return osLockErrorCode(err, def)
|
||||
}
|
||||
|
||||
func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||
return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK)
|
||||
}
|
||||
|
||||
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||
return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK)
|
||||
}
|
||||
|
||||
func osCheckLock(file *os.File, start, len int64) (bool, _ErrorCode) {
|
||||
lock := unix.Flock_t{
|
||||
Type: unix.F_RDLCK,
|
||||
Start: start,
|
||||
Len: len,
|
||||
}
|
||||
if unix.FcntlFlock(file.Fd(), unix.F_OFD_GETLK, &lock) != nil {
|
||||
return false, _IOERR_CHECKRESERVEDLOCK
|
||||
}
|
||||
return lock.Type != unix.F_UNLCK, _OK
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
//go:build !linux && (!darwin || sqlite3_bsd)
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func osSync(file *os.File, fullsync, dataonly bool) error {
|
||||
return file.Sync()
|
||||
}
|
||||
|
||||
func osAllocate(file *os.File, size int64) error {
|
||||
off, err := file.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if size <= off {
|
||||
return nil
|
||||
}
|
||||
return file.Truncate(size)
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
//go:build unix
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func osOpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
|
||||
return os.OpenFile(name, flag, perm)
|
||||
}
|
||||
|
||||
func osAccess(path string, flags _AccessFlag) error {
|
||||
var access uint32 // unix.F_OK
|
||||
switch flags {
|
||||
case _ACCESS_READWRITE:
|
||||
access = unix.R_OK | unix.W_OK
|
||||
case _ACCESS_READ:
|
||||
access = unix.R_OK
|
||||
}
|
||||
return unix.Access(path, access)
|
||||
}
|
||||
|
||||
func osGetSharedLock(file *os.File, timeout time.Duration) _ErrorCode {
|
||||
// Test the PENDING lock before acquiring a new SHARED lock.
|
||||
if pending, _ := osCheckLock(file, _PENDING_BYTE, 1); pending {
|
||||
return _ErrorCode(_BUSY)
|
||||
}
|
||||
// Acquire the SHARED lock.
|
||||
return osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
|
||||
}
|
||||
|
||||
func osGetExclusiveLock(file *os.File, timeout time.Duration) _ErrorCode {
|
||||
if timeout == 0 {
|
||||
timeout = time.Millisecond
|
||||
}
|
||||
|
||||
// Acquire the EXCLUSIVE lock.
|
||||
return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
|
||||
}
|
||||
|
||||
func osDowngradeLock(file *os.File, state _LockLevel) _ErrorCode {
|
||||
if state >= _LOCK_EXCLUSIVE {
|
||||
// Downgrade to a SHARED lock.
|
||||
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
|
||||
// In theory, the downgrade to a SHARED cannot fail because another
|
||||
// process is holding an incompatible lock. If it does, this
|
||||
// indicates that the other process is not following the locking
|
||||
// protocol. If this happens, return _IOERR_RDLOCK. Returning
|
||||
// BUSY would confuse the upper layer.
|
||||
return _IOERR_RDLOCK
|
||||
}
|
||||
}
|
||||
// Release the PENDING and RESERVED locks.
|
||||
return osUnlock(file, _PENDING_BYTE, 2)
|
||||
}
|
||||
|
||||
func osReleaseLock(file *os.File, _ _LockLevel) _ErrorCode {
|
||||
// Release all locks.
|
||||
return osUnlock(file, 0, 0)
|
||||
}
|
||||
|
||||
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
|
||||
if err == nil {
|
||||
return _OK
|
||||
}
|
||||
if errno, ok := err.(unix.Errno); ok {
|
||||
switch errno {
|
||||
case
|
||||
unix.EACCES,
|
||||
unix.EAGAIN,
|
||||
unix.EBUSY,
|
||||
unix.EINTR,
|
||||
unix.ENOLCK,
|
||||
unix.EDEADLK,
|
||||
unix.ETIMEDOUT:
|
||||
return _ErrorCode(_BUSY)
|
||||
case unix.EPERM:
|
||||
return _ErrorCode(_PERM)
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// osOpenFile is a simplified copy of [os.openFileNolog]
|
||||
// that uses syscall.FILE_SHARE_DELETE.
|
||||
// https://go.dev/src/os/file_windows.go
|
||||
//
|
||||
// See: https://go.dev/issue/32088
|
||||
func osOpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
|
||||
if name == "" {
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOENT}
|
||||
}
|
||||
r, e := syscallOpen(name, flag, uint32(perm.Perm()))
|
||||
if e != nil {
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: e}
|
||||
}
|
||||
return os.NewFile(uintptr(r), name), nil
|
||||
}
|
||||
|
||||
func osAccess(path string, flags _AccessFlag) error {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if flags == _ACCESS_EXISTS {
|
||||
return nil
|
||||
}
|
||||
|
||||
var want fs.FileMode = windows.S_IRUSR
|
||||
if flags == _ACCESS_READWRITE {
|
||||
want |= windows.S_IWUSR
|
||||
}
|
||||
if fi.IsDir() {
|
||||
want |= windows.S_IXUSR
|
||||
}
|
||||
if fi.Mode()&want != want {
|
||||
return fs.ErrPermission
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func osGetSharedLock(file *os.File, timeout time.Duration) _ErrorCode {
|
||||
// Acquire the PENDING lock temporarily before acquiring a new SHARED lock.
|
||||
rc := osReadLock(file, _PENDING_BYTE, 1, timeout)
|
||||
|
||||
if rc == _OK {
|
||||
// Acquire the SHARED lock.
|
||||
rc = osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
|
||||
|
||||
// Release the PENDING lock.
|
||||
osUnlock(file, _PENDING_BYTE, 1)
|
||||
}
|
||||
return rc
|
||||
}
|
||||
|
||||
func osGetExclusiveLock(file *os.File, timeout time.Duration) _ErrorCode {
|
||||
if timeout == 0 {
|
||||
timeout = time.Millisecond
|
||||
}
|
||||
|
||||
// Release the SHARED lock.
|
||||
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
|
||||
|
||||
// Acquire the EXCLUSIVE lock.
|
||||
rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
|
||||
|
||||
if rc != _OK {
|
||||
// Reacquire the SHARED lock.
|
||||
osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
|
||||
}
|
||||
return rc
|
||||
}
|
||||
|
||||
func osDowngradeLock(file *os.File, state _LockLevel) _ErrorCode {
|
||||
if state >= _LOCK_EXCLUSIVE {
|
||||
// Release the SHARED lock.
|
||||
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
|
||||
|
||||
// Reacquire the SHARED lock.
|
||||
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
|
||||
// This should never happen.
|
||||
// We should always be able to reacquire the read lock.
|
||||
return _IOERR_RDLOCK
|
||||
}
|
||||
}
|
||||
|
||||
// Release the PENDING and RESERVED locks.
|
||||
if state >= _LOCK_RESERVED {
|
||||
osUnlock(file, _RESERVED_BYTE, 1)
|
||||
}
|
||||
if state >= _LOCK_PENDING {
|
||||
osUnlock(file, _PENDING_BYTE, 1)
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
|
||||
func osReleaseLock(file *os.File, state _LockLevel) _ErrorCode {
|
||||
// Release all locks.
|
||||
if state >= _LOCK_RESERVED {
|
||||
osUnlock(file, _RESERVED_BYTE, 1)
|
||||
}
|
||||
if state >= _LOCK_SHARED {
|
||||
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
|
||||
}
|
||||
if state >= _LOCK_PENDING {
|
||||
osUnlock(file, _PENDING_BYTE, 1)
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
|
||||
func osUnlock(file *os.File, start, len uint32) _ErrorCode {
|
||||
err := windows.UnlockFileEx(windows.Handle(file.Fd()),
|
||||
0, len, 0, &windows.Overlapped{Offset: start})
|
||||
if err == windows.ERROR_NOT_LOCKED {
|
||||
return _OK
|
||||
}
|
||||
if err != nil {
|
||||
return _IOERR_UNLOCK
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
|
||||
func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode {
|
||||
var err error
|
||||
for {
|
||||
err = windows.LockFileEx(windows.Handle(file.Fd()), flags,
|
||||
0, len, 0, &windows.Overlapped{Offset: start})
|
||||
if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION {
|
||||
break
|
||||
}
|
||||
if timeout < time.Millisecond {
|
||||
break
|
||||
}
|
||||
timeout -= time.Millisecond
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
return osLockErrorCode(err, def)
|
||||
}
|
||||
|
||||
func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
|
||||
return osLock(file,
|
||||
windows.LOCKFILE_FAIL_IMMEDIATELY,
|
||||
start, len, timeout, _IOERR_RDLOCK)
|
||||
}
|
||||
|
||||
func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
|
||||
return osLock(file,
|
||||
windows.LOCKFILE_FAIL_IMMEDIATELY|windows.LOCKFILE_EXCLUSIVE_LOCK,
|
||||
start, len, timeout, _IOERR_LOCK)
|
||||
}
|
||||
|
||||
func osCheckLock(file *os.File, start, len uint32) (bool, _ErrorCode) {
|
||||
rc := osLock(file,
|
||||
windows.LOCKFILE_FAIL_IMMEDIATELY,
|
||||
start, len, 0, _IOERR_CHECKRESERVEDLOCK)
|
||||
if rc == _BUSY {
|
||||
return true, _OK
|
||||
}
|
||||
if rc == _OK {
|
||||
osUnlock(file, start, len)
|
||||
}
|
||||
return false, rc
|
||||
}
|
||||
|
||||
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
|
||||
if err == nil {
|
||||
return _OK
|
||||
}
|
||||
if errno, ok := err.(windows.Errno); ok {
|
||||
// https://devblogs.microsoft.com/oldnewthing/20140905-00/?p=63
|
||||
switch errno {
|
||||
case
|
||||
windows.ERROR_LOCK_VIOLATION,
|
||||
windows.ERROR_IO_PENDING,
|
||||
windows.ERROR_OPERATION_ABORTED:
|
||||
return _BUSY
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// syscallOpen is a simplified copy of [syscall.Open]
|
||||
// that uses syscall.FILE_SHARE_DELETE.
|
||||
// https://go.dev/src/syscall/syscall_windows.go
|
||||
func syscallOpen(path string, mode int, perm uint32) (fd syscall.Handle, err error) {
|
||||
if len(path) == 0 {
|
||||
return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
|
||||
}
|
||||
pathp, err := syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return syscall.InvalidHandle, err
|
||||
}
|
||||
var access uint32
|
||||
switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
|
||||
case syscall.O_RDONLY:
|
||||
access = syscall.GENERIC_READ
|
||||
case syscall.O_WRONLY:
|
||||
access = syscall.GENERIC_WRITE
|
||||
case syscall.O_RDWR:
|
||||
access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
|
||||
}
|
||||
if mode&syscall.O_CREAT != 0 {
|
||||
access |= syscall.GENERIC_WRITE
|
||||
}
|
||||
if mode&syscall.O_APPEND != 0 {
|
||||
access &^= syscall.GENERIC_WRITE
|
||||
access |= syscall.FILE_APPEND_DATA
|
||||
}
|
||||
sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE | syscall.FILE_SHARE_DELETE)
|
||||
var createmode uint32
|
||||
switch {
|
||||
case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
|
||||
createmode = syscall.CREATE_NEW
|
||||
case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC):
|
||||
createmode = syscall.CREATE_ALWAYS
|
||||
case mode&syscall.O_CREAT == syscall.O_CREAT:
|
||||
createmode = syscall.OPEN_ALWAYS
|
||||
case mode&syscall.O_TRUNC == syscall.O_TRUNC:
|
||||
createmode = syscall.TRUNCATE_EXISTING
|
||||
default:
|
||||
createmode = syscall.OPEN_EXISTING
|
||||
}
|
||||
var attrs uint32 = syscall.FILE_ATTRIBUTE_NORMAL
|
||||
if perm&syscall.S_IWRITE == 0 {
|
||||
attrs = syscall.FILE_ATTRIBUTE_READONLY
|
||||
}
|
||||
if createmode == syscall.OPEN_EXISTING && access == syscall.GENERIC_READ {
|
||||
// Necessary for opening directory handles.
|
||||
attrs |= syscall.FILE_FLAG_BACKUP_SEMANTICS
|
||||
}
|
||||
return syscall.CreateFile(pathp, access, sharemode, nil, createmode, attrs, 0)
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/julianday"
|
||||
)
|
||||
|
||||
func Test_vfsLocaltime(t *testing.T) {
|
||||
mod := util.NewMockModule(128)
|
||||
ctx := context.TODO()
|
||||
|
||||
tm := time.Now()
|
||||
rc := vfsLocaltime(ctx, mod, 4, tm.Unix())
|
||||
if rc != 0 {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
if s := util.ReadUint32(mod, 4+0*4); int(s) != tm.Second() {
|
||||
t.Error("wrong second")
|
||||
}
|
||||
if m := util.ReadUint32(mod, 4+1*4); int(m) != tm.Minute() {
|
||||
t.Error("wrong minute")
|
||||
}
|
||||
if h := util.ReadUint32(mod, 4+2*4); int(h) != tm.Hour() {
|
||||
t.Error("wrong hour")
|
||||
}
|
||||
if d := util.ReadUint32(mod, 4+3*4); int(d) != tm.Day() {
|
||||
t.Error("wrong day")
|
||||
}
|
||||
if m := util.ReadUint32(mod, 4+4*4); time.Month(1+m) != tm.Month() {
|
||||
t.Error("wrong month")
|
||||
}
|
||||
if y := util.ReadUint32(mod, 4+5*4); 1900+int(y) != tm.Year() {
|
||||
t.Error("wrong year")
|
||||
}
|
||||
if w := util.ReadUint32(mod, 4+6*4); time.Weekday(w) != tm.Weekday() {
|
||||
t.Error("wrong weekday")
|
||||
}
|
||||
if d := util.ReadUint32(mod, 4+7*4); int(d) != tm.YearDay()-1 {
|
||||
t.Error("wrong yearday")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_vfsRandomness(t *testing.T) {
|
||||
mod := util.NewMockModule(128)
|
||||
ctx := context.TODO()
|
||||
|
||||
rc := vfsRandomness(ctx, mod, 0, 16, 4)
|
||||
if rc != 16 {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
var zero [16]byte
|
||||
if got := util.View(mod, 4, 16); bytes.Equal(got, zero[:]) {
|
||||
t.Fatal("all zero")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_vfsSleep(t *testing.T) {
|
||||
mod := util.NewMockModule(128)
|
||||
ctx := context.TODO()
|
||||
|
||||
now := time.Now()
|
||||
rc := vfsSleep(ctx, mod, 0, 123456)
|
||||
if rc != 0 {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
want := 123456 * time.Microsecond
|
||||
if got := time.Since(now); got < want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_vfsCurrentTime(t *testing.T) {
|
||||
mod := util.NewMockModule(128)
|
||||
ctx := context.TODO()
|
||||
|
||||
now := time.Now()
|
||||
rc := vfsCurrentTime(ctx, mod, 0, 4)
|
||||
if rc != 0 {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
want := julianday.Float(now)
|
||||
if got := util.ReadFloat64(mod, 4); float32(got) != float32(want) {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_vfsCurrentTime64(t *testing.T) {
|
||||
mod := util.NewMockModule(128)
|
||||
ctx := context.TODO()
|
||||
|
||||
now := time.Now()
|
||||
time.Sleep(time.Millisecond)
|
||||
rc := vfsCurrentTime64(ctx, mod, 0, 4)
|
||||
if rc != 0 {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
day, nsec := julianday.Date(now)
|
||||
want := day*86_400_000 + nsec/1_000_000
|
||||
if got := util.ReadUint64(mod, 4); float32(got) != float32(want) {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_vfsFullPathname(t *testing.T) {
|
||||
mod := util.NewMockModule(128 + _MAX_PATHNAME)
|
||||
util.WriteString(mod, 4, ".")
|
||||
ctx := context.TODO()
|
||||
|
||||
rc := vfsFullPathname(ctx, mod, 0, 4, 0, 8)
|
||||
if rc != _CANTOPEN_FULLPATH {
|
||||
t.Errorf("returned %d, want %d", rc, _CANTOPEN_FULLPATH)
|
||||
}
|
||||
|
||||
rc = vfsFullPathname(ctx, mod, 0, 4, _MAX_PATHNAME, 8)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
want, _ := filepath.Abs(".")
|
||||
if got := util.ReadString(mod, 8, _MAX_PATHNAME); got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_vfsDelete(t *testing.T) {
|
||||
name := filepath.Join(t.TempDir(), "test.db")
|
||||
|
||||
file, err := os.Create(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
file.Close()
|
||||
|
||||
mod := util.NewMockModule(128 + _MAX_PATHNAME)
|
||||
util.WriteString(mod, 4, name)
|
||||
ctx := context.TODO()
|
||||
|
||||
rc := vfsDelete(ctx, mod, 0, 4, 1)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(name); !errors.Is(err, fs.ErrNotExist) {
|
||||
t.Fatal("did not delete the file")
|
||||
}
|
||||
|
||||
rc = vfsDelete(ctx, mod, 0, 4, 1)
|
||||
if rc != _IOERR_DELETE_NOENT {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_vfsAccess(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
file := filepath.Join(t.TempDir(), "test.db")
|
||||
if f, err := os.Create(file); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
f.Close()
|
||||
}
|
||||
if err := os.Chmod(file, syscall.S_IRUSR); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mod := util.NewMockModule(128 + _MAX_PATHNAME)
|
||||
util.WriteString(mod, 8, dir)
|
||||
ctx := context.TODO()
|
||||
|
||||
rc := vfsAccess(ctx, mod, 0, 8, _ACCESS_EXISTS, 4)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, 4); got != 1 {
|
||||
t.Error("directory did not exist")
|
||||
}
|
||||
|
||||
rc = vfsAccess(ctx, mod, 0, 8, _ACCESS_READWRITE, 4)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, 4); got != 1 {
|
||||
t.Error("can't access directory")
|
||||
}
|
||||
|
||||
util.WriteString(mod, 8, file)
|
||||
rc = vfsAccess(ctx, mod, 0, 8, _ACCESS_READWRITE, 4)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, 4); got != 0 {
|
||||
t.Error("can access file")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_vfsFile(t *testing.T) {
|
||||
mod := util.NewMockModule(128)
|
||||
ctx, vfs := Context(context.TODO())
|
||||
defer vfs.Close()
|
||||
|
||||
// Open a temporary file.
|
||||
rc := vfsOpen(ctx, mod, 0, 0, 4, _OPEN_CREATE|_OPEN_EXCLUSIVE|_OPEN_READWRITE|_OPEN_DELETEONCLOSE, 0)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
// Write stuff.
|
||||
text := "Hello world!"
|
||||
util.WriteString(mod, 16, text)
|
||||
rc = vfsWrite(ctx, mod, 4, 16, uint32(len(text)), 0)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
// Check file size.
|
||||
rc = vfsFileSize(ctx, mod, 4, 16)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, 16); got != uint32(len(text)) {
|
||||
t.Errorf("got %d", got)
|
||||
}
|
||||
|
||||
// Partial read at offset.
|
||||
rc = vfsRead(ctx, mod, 4, 16, uint32(len(text)), 4)
|
||||
if rc != _IOERR_SHORT_READ {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadString(mod, 16, 64); got != text[4:] {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
|
||||
// Truncate the file.
|
||||
rc = vfsTruncate(ctx, mod, 4, 4)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
|
||||
// Check file size.
|
||||
rc = vfsFileSize(ctx, mod, 4, 16)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadUint32(mod, 16); got != 4 {
|
||||
t.Errorf("got %d", got)
|
||||
}
|
||||
|
||||
// Read at offset.
|
||||
rc = vfsRead(ctx, mod, 4, 32, 4, 0)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
if got := util.ReadString(mod, 32, 64); got != text[:4] {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
|
||||
// Close the file.
|
||||
rc = vfsClose(ctx, mod, 4)
|
||||
if rc != _OK {
|
||||
t.Fatal("returned", rc)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user