Refactor.

This commit is contained in:
Nuno Cruces
2023-03-31 13:48:19 +01:00
parent 8509e0b6c8
commit c38382fd8e
26 changed files with 110 additions and 111 deletions

View File

@@ -19,7 +19,7 @@ embeds a build of SQLite into your application.
### Caveats
This module replaces the SQLite [OS Interface](https://www.sqlite.org/vfs.html) (aka VFS)
with a pure Go implementation.
with a [pure Go](internal/vfs/) implementation.
This has numerous benefits, but also comes with some drawbacks.
#### Write-Ahead Logging

View File

@@ -9,8 +9,7 @@ const (
_UTF8 = 1
_MAX_STRING = 512 // Used for short strings: names, error messages…
_MAX_PATHNAME = 512
_MAX_STRING = 512 // Used for short strings: names, error messages…
_MAX_ALLOCATION_SIZE = 0x7ffffeff

View File

@@ -1,11 +1,8 @@
package vfs
const (
_MAX_PATHNAME = 512
ptrlen = 4
)
const _MAX_PATHNAME = 512
// https://www.sqlite.org/rescode.html
type _ErrorCode uint32
const (
@@ -55,9 +52,10 @@ const (
_CANTOPEN_CONVPATH = _CANTOPEN | (4 << 8)
_CANTOPEN_DIRTYWAL = _CANTOPEN | (5 << 8) /* Not Used */
_CANTOPEN_SYMLINK = _CANTOPEN | (6 << 8)
_OK_SYMLINK = (_OK | (2 << 8)) /* internal use only */
_OK_SYMLINK = _OK | (2 << 8) /* internal use only */
)
// https://www.sqlite.org/c3ref/c_open_autoproxy.html
type _OpenFlag uint32
const (
@@ -85,6 +83,7 @@ const (
_OPEN_EXRESCODE _OpenFlag = 0x02000000 /* Extended result codes */
)
// https://www.sqlite.org/c3ref/c_access_exists.html
type _AccessFlag uint32
const (
@@ -93,6 +92,7 @@ const (
_ACCESS_READ _AccessFlag = 2 /* Unused */
)
// https://www.sqlite.org/c3ref/c_sync_dataonly.html
type _SyncFlag uint32
const (
@@ -101,6 +101,51 @@ const (
_SYNC_DATAONLY _SyncFlag = 0x00010
)
// https://www.sqlite.org/c3ref/c_lock_exclusive.html
type _LockLevel uint32
const (
// No locks are held on the database.
// The database may be neither read nor written.
// Any internally cached data is considered suspect and subject to
// verification against the database file before being used.
// Other processes can read or write the database as their own locking
// states permit.
// This is the default state.
_LOCK_NONE _LockLevel = 0 /* xUnlock() only */
// The database may be read but not written.
// Any number of processes can hold SHARED locks at the same time,
// hence there can be many simultaneous readers.
// But no other thread or process is allowed to write to the database file
// while one or more SHARED locks are active.
_LOCK_SHARED _LockLevel = 1 /* xLock() or xUnlock() */
// A RESERVED lock means that the process is planning on writing to the
// database file at some point in the future but that it is currently just
// reading from the file.
// Only a single RESERVED lock may be active at one time,
// though multiple SHARED locks can coexist with a single RESERVED lock.
// RESERVED differs from PENDING in that new SHARED locks can be acquired
// while there is a RESERVED lock.
_LOCK_RESERVED _LockLevel = 2 /* xLock() only */
// A PENDING lock means that the process holding the lock wants to write to
// the database as soon as possible and is just waiting on all current
// SHARED locks to clear so that it can get an EXCLUSIVE lock.
// No new SHARED locks are permitted against the database if a PENDING lock
// is active, though existing SHARED locks are allowed to continue.
_LOCK_PENDING _LockLevel = 3 /* internal use only */
// An EXCLUSIVE lock is needed in order to write to the database file.
// Only one EXCLUSIVE lock is allowed on the file and no other locks of any
// kind are allowed to coexist with an EXCLUSIVE lock.
// In order to maximize concurrency, SQLite works to minimize the amount of
// time that EXCLUSIVE locks are held.
_LOCK_EXCLUSIVE _LockLevel = 4 /* xLock() only */
)
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
type _FcntlOpcode uint32
const (

View File

@@ -16,13 +16,10 @@ import (
"sync/atomic"
"testing"
_ "unsafe"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
_ "github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/vfs"
)

View File

@@ -13,12 +13,10 @@ import (
"testing"
_ "embed"
_ "unsafe"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
_ "github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/vfs"
)

View File

@@ -76,16 +76,17 @@ func vfsLocaltime(ctx context.Context, mod api.Module, pTm uint32, t int64) _Err
isdst = 1
}
const size = 32 / 8
// https://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html
util.WriteUint32(mod, pTm+0*ptrlen, uint32(tm.Second()))
util.WriteUint32(mod, pTm+1*ptrlen, uint32(tm.Minute()))
util.WriteUint32(mod, pTm+2*ptrlen, uint32(tm.Hour()))
util.WriteUint32(mod, pTm+3*ptrlen, uint32(tm.Day()))
util.WriteUint32(mod, pTm+4*ptrlen, uint32(tm.Month()-time.January))
util.WriteUint32(mod, pTm+5*ptrlen, uint32(tm.Year()-1900))
util.WriteUint32(mod, pTm+6*ptrlen, uint32(tm.Weekday()-time.Sunday))
util.WriteUint32(mod, pTm+7*ptrlen, uint32(tm.YearDay()-1))
util.WriteUint32(mod, pTm+8*ptrlen, uint32(isdst))
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
}

View File

@@ -53,11 +53,11 @@ func getOSFile(ctx context.Context, mod api.Module, pFile uint32) *os.File {
return vfs.files[id]
}
func getFileLock(ctx context.Context, mod api.Module, pFile uint32) vfsLockState {
return vfsLockState(util.ReadUint8(mod, pFile+vfsFileLockOffset))
func getFileLock(ctx context.Context, mod api.Module, pFile uint32) _LockLevel {
return _LockLevel(util.ReadUint8(mod, pFile+vfsFileLockOffset))
}
func setFileLock(ctx context.Context, mod api.Module, pFile uint32, lock vfsLockState) {
func setFileLock(ctx context.Context, mod api.Module, pFile uint32, lock _LockLevel) {
util.WriteUint8(mod, pFile+vfsFileLockOffset, uint8(lock))
}

View File

@@ -10,56 +10,15 @@ import (
)
const (
// No locks are held on the database.
// The database may be neither read nor written.
// Any internally cached data is considered suspect and subject to
// verification against the database file before being used.
// Other processes can read or write the database as their own locking
// states permit.
// This is the default state.
_NO_LOCK = 0
// The database may be read but not written.
// Any number of processes can hold SHARED locks at the same time,
// hence there can be many simultaneous readers.
// But no other thread or process is allowed to write to the database file
// while one or more SHARED locks are active.
_SHARED_LOCK = 1
// A RESERVED lock means that the process is planning on writing to the
// database file at some point in the future but that it is currently just
// reading from the file.
// Only a single RESERVED lock may be active at one time,
// though multiple SHARED locks can coexist with a single RESERVED lock.
// RESERVED differs from PENDING in that new SHARED locks can be acquired
// while there is a RESERVED lock.
_RESERVED_LOCK = 2
// A PENDING lock means that the process holding the lock wants to write to
// the database as soon as possible and is just waiting on all current
// SHARED locks to clear so that it can get an EXCLUSIVE lock.
// No new SHARED locks are permitted against the database if a PENDING lock
// is active, though existing SHARED locks are allowed to continue.
_PENDING_LOCK = 3
// An EXCLUSIVE lock is needed in order to write to the database file.
// Only one EXCLUSIVE lock is allowed on the file and no other locks of any
// kind are allowed to coexist with an EXCLUSIVE lock.
// In order to maximize concurrency, SQLite works to minimize the amount of
// time that EXCLUSIVE locks are held.
_EXCLUSIVE_LOCK = 4
_PENDING_BYTE = 0x40000000
_RESERVED_BYTE = (_PENDING_BYTE + 1)
_SHARED_FIRST = (_PENDING_BYTE + 2)
_SHARED_SIZE = 510
)
type vfsLockState uint32
func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) _ErrorCode {
func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock _LockLevel) _ErrorCode {
// Argument check. SQLite never explicitly requests a pending lock.
if eLock != _SHARED_LOCK && eLock != _RESERVED_LOCK && eLock != _EXCLUSIVE_LOCK {
if eLock != _LOCK_SHARED && eLock != _LOCK_RESERVED && eLock != _LOCK_EXCLUSIVE {
panic(util.AssertErr())
}
@@ -69,13 +28,13 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockSta
readOnly := getFileReadOnly(ctx, mod, pFile)
switch {
case cLock < _NO_LOCK || cLock > _EXCLUSIVE_LOCK:
case cLock < _LOCK_NONE || cLock > _LOCK_EXCLUSIVE:
// Connection state check.
panic(util.AssertErr())
case cLock == _NO_LOCK && eLock > _SHARED_LOCK:
case cLock == _LOCK_NONE && eLock > _LOCK_SHARED:
// We never move from unlocked to anything higher than a shared lock.
panic(util.AssertErr())
case cLock != _SHARED_LOCK && eLock == _RESERVED_LOCK:
case cLock != _LOCK_SHARED && eLock == _LOCK_RESERVED:
// A shared lock is always held when a reserved lock is requested.
panic(util.AssertErr())
}
@@ -86,49 +45,49 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockSta
}
// Do not allow any kind of write-lock on a read-only database.
if readOnly && eLock > _RESERVED_LOCK {
if readOnly && eLock > _LOCK_RESERVED {
return _IOERR_LOCK
}
switch eLock {
case _SHARED_LOCK:
case _LOCK_SHARED:
// Must be unlocked to get SHARED.
if cLock != _NO_LOCK {
if cLock != _LOCK_NONE {
panic(util.AssertErr())
}
if rc := osGetSharedLock(file, timeout); rc != _OK {
return rc
}
setFileLock(ctx, mod, pFile, _SHARED_LOCK)
setFileLock(ctx, mod, pFile, _LOCK_SHARED)
return _OK
case _RESERVED_LOCK:
case _LOCK_RESERVED:
// Must be SHARED to get RESERVED.
if cLock != _SHARED_LOCK {
if cLock != _LOCK_SHARED {
panic(util.AssertErr())
}
if rc := osGetReservedLock(file, timeout); rc != _OK {
return rc
}
setFileLock(ctx, mod, pFile, _RESERVED_LOCK)
setFileLock(ctx, mod, pFile, _LOCK_RESERVED)
return _OK
case _EXCLUSIVE_LOCK:
case _LOCK_EXCLUSIVE:
// Must be SHARED, RESERVED or PENDING to get EXCLUSIVE.
if cLock <= _NO_LOCK || cLock >= _EXCLUSIVE_LOCK {
if cLock <= _LOCK_NONE || cLock >= _LOCK_EXCLUSIVE {
panic(util.AssertErr())
}
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
if cLock < _PENDING_LOCK {
if cLock < _LOCK_PENDING {
if rc := osGetPendingLock(file); rc != _OK {
return rc
}
setFileLock(ctx, mod, pFile, _PENDING_LOCK)
setFileLock(ctx, mod, pFile, _LOCK_PENDING)
}
if rc := osGetExclusiveLock(file, timeout); rc != _OK {
return rc
}
setFileLock(ctx, mod, pFile, _EXCLUSIVE_LOCK)
setFileLock(ctx, mod, pFile, _LOCK_EXCLUSIVE)
return _OK
default:
@@ -136,9 +95,9 @@ func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockSta
}
}
func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) _ErrorCode {
func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock _LockLevel) _ErrorCode {
// Argument check.
if eLock != _NO_LOCK && eLock != _SHARED_LOCK {
if eLock != _LOCK_NONE && eLock != _LOCK_SHARED {
panic(util.AssertErr())
}
@@ -146,7 +105,7 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
cLock := getFileLock(ctx, mod, pFile)
// Connection state check.
if cLock < _NO_LOCK || cLock > _EXCLUSIVE_LOCK {
if cLock < _LOCK_NONE || cLock > _LOCK_EXCLUSIVE {
panic(util.AssertErr())
}
@@ -156,16 +115,16 @@ func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockS
}
switch eLock {
case _SHARED_LOCK:
case _LOCK_SHARED:
if rc := osDowngradeLock(file, cLock); rc != _OK {
return rc
}
setFileLock(ctx, mod, pFile, _SHARED_LOCK)
setFileLock(ctx, mod, pFile, _LOCK_SHARED)
return _OK
case _NO_LOCK:
case _LOCK_NONE:
rc := osReleaseLock(file, cLock)
setFileLock(ctx, mod, pFile, _NO_LOCK)
setFileLock(ctx, mod, pFile, _LOCK_NONE)
return rc
default:
@@ -178,13 +137,13 @@ func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ui
cLock := getFileLock(ctx, mod, pFile)
// Connection state check.
if cLock < _NO_LOCK || cLock > _EXCLUSIVE_LOCK {
if cLock < _LOCK_NONE || cLock > _LOCK_EXCLUSIVE {
panic(util.AssertErr())
}
var locked bool
var rc _ErrorCode
if cLock >= _RESERVED_LOCK {
if cLock >= _LOCK_RESERVED {
locked = true
} else {
locked, rc = osCheckReservedLock(file)

View File

@@ -61,7 +61,7 @@ func Test_vfsLock(t *testing.T) {
t.Error("file was locked")
}
rc = vfsLock(ctx, mod, pFile2, _SHARED_LOCK)
rc = vfsLock(ctx, mod, pFile2, _LOCK_SHARED)
if rc != _OK {
t.Fatal("returned", rc)
}
@@ -81,11 +81,11 @@ func Test_vfsLock(t *testing.T) {
t.Error("file was locked")
}
rc = vfsLock(ctx, mod, pFile2, _RESERVED_LOCK)
rc = vfsLock(ctx, mod, pFile2, _LOCK_RESERVED)
if rc != _OK {
t.Fatal("returned", rc)
}
rc = vfsLock(ctx, mod, pFile2, _SHARED_LOCK)
rc = vfsLock(ctx, mod, pFile2, _LOCK_SHARED)
if rc != _OK {
t.Fatal("returned", rc)
}
@@ -105,7 +105,7 @@ func Test_vfsLock(t *testing.T) {
t.Error("file wasn't locked")
}
rc = vfsLock(ctx, mod, pFile2, _EXCLUSIVE_LOCK)
rc = vfsLock(ctx, mod, pFile2, _LOCK_EXCLUSIVE)
if rc != _OK {
t.Fatal("returned", rc)
}
@@ -125,7 +125,7 @@ func Test_vfsLock(t *testing.T) {
t.Error("file wasn't locked")
}
rc = vfsLock(ctx, mod, pFile1, _SHARED_LOCK)
rc = vfsLock(ctx, mod, pFile1, _LOCK_SHARED)
if rc == _OK {
t.Fatal("returned", rc)
}
@@ -145,7 +145,7 @@ func Test_vfsLock(t *testing.T) {
t.Error("file wasn't locked")
}
rc = vfsUnlock(ctx, mod, pFile2, _SHARED_LOCK)
rc = vfsUnlock(ctx, mod, pFile2, _LOCK_SHARED)
if rc != _OK {
t.Fatal("returned", rc)
}
@@ -165,7 +165,7 @@ func Test_vfsLock(t *testing.T) {
t.Error("file was locked")
}
rc = vfsLock(ctx, mod, pFile1, _SHARED_LOCK)
rc = vfsLock(ctx, mod, pFile1, _LOCK_SHARED)
if rc != _OK {
t.Fatal("returned", rc)
}

View File

@@ -43,8 +43,8 @@ func osGetExclusiveLock(file *os.File, timeout time.Duration) _ErrorCode {
return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
}
func osDowngradeLock(file *os.File, state vfsLockState) _ErrorCode {
if state >= _EXCLUSIVE_LOCK {
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
@@ -59,7 +59,7 @@ func osDowngradeLock(file *os.File, state vfsLockState) _ErrorCode {
return osUnlock(file, _PENDING_BYTE, 2)
}
func osReleaseLock(file *os.File, _ vfsLockState) _ErrorCode {
func osReleaseLock(file *os.File, _ _LockLevel) _ErrorCode {
// Release all locks.
return osUnlock(file, 0, 0)
}

View File

@@ -79,8 +79,8 @@ func osGetExclusiveLock(file *os.File, timeout time.Duration) _ErrorCode {
return rc
}
func osDowngradeLock(file *os.File, state vfsLockState) _ErrorCode {
if state >= _EXCLUSIVE_LOCK {
func osDowngradeLock(file *os.File, state _LockLevel) _ErrorCode {
if state >= _LOCK_EXCLUSIVE {
// Release the SHARED lock.
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
@@ -93,24 +93,24 @@ func osDowngradeLock(file *os.File, state vfsLockState) _ErrorCode {
}
// Release the PENDING and RESERVED locks.
if state >= _RESERVED_LOCK {
if state >= _LOCK_RESERVED {
osUnlock(file, _RESERVED_BYTE, 1)
}
if state >= _PENDING_LOCK {
if state >= _LOCK_PENDING {
osUnlock(file, _PENDING_BYTE, 1)
}
return _OK
}
func osReleaseLock(file *os.File, state vfsLockState) _ErrorCode {
func osReleaseLock(file *os.File, state _LockLevel) _ErrorCode {
// Release all locks.
if state >= _RESERVED_LOCK {
if state >= _LOCK_RESERVED {
osUnlock(file, _RESERVED_BYTE, 1)
}
if state >= _SHARED_LOCK {
if state >= _LOCK_SHARED {
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
}
if state >= _PENDING_LOCK {
if state >= _LOCK_PENDING {
osUnlock(file, _PENDING_BYTE, 1)
}
return _OK