Skip to content

Commit 0026bc9

Browse files
authored
MVCC memory VFS. (#309)
1 parent d84ca9d commit 0026bc9

File tree

12 files changed

+565
-0
lines changed

12 files changed

+565
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.23.0
55
toolchain go1.24.0
66

77
require (
8+
github.com/ncruces/aa v0.3.0
89
github.com/ncruces/julianday v1.0.0
910
github.com/ncruces/sort v0.1.5
1011
github.com/tetratelabs/wazero v1.9.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
22
github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
33
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
44
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
5+
github.com/ncruces/aa v0.3.0 h1:6NPcK3jsyPWRZBZWCyF7c4IzEQX4eJtkKJBA+IRKTkQ=
6+
github.com/ncruces/aa v0.3.0/go.mod h1:ctOw1LVqfuqzqg2S9LlR045bLAiXtaTiPMCL3zzl7Ik=
57
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
68
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
79
github.com/ncruces/sort v0.1.5 h1:fiFWXXAqKI8QckPf/6hu/bGFwcEPrirIOFaJqWujs4k=

tests/parallel/parallel_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/ncruces/go-sqlite3/vfs"
1919
_ "github.com/ncruces/go-sqlite3/vfs/adiantum"
2020
"github.com/ncruces/go-sqlite3/vfs/memdb"
21+
"github.com/ncruces/go-sqlite3/vfs/mvcc"
2122
_ "github.com/ncruces/go-sqlite3/vfs/xts"
2223
)
2324

@@ -98,6 +99,22 @@ func Test_memdb(t *testing.T) {
9899
testIntegrity(t, name)
99100
}
100101

102+
func Test_mvcc(t *testing.T) {
103+
var iter int
104+
if testing.Short() {
105+
iter = 1000
106+
} else {
107+
iter = 5000
108+
}
109+
110+
mvcc.Create("test.db", "")
111+
name := "file:/test.db?vfs=mvcc" +
112+
"&_pragma=busy_timeout(10000)"
113+
createDB(t, name)
114+
testParallel(t, name, iter)
115+
testIntegrity(t, name)
116+
}
117+
101118
func Test_adiantum(t *testing.T) {
102119
if !vfs.SupportsFileLocking {
103120
t.Skip("skipping without locks")
@@ -312,6 +329,16 @@ func Benchmark_memdb(b *testing.B) {
312329
testParallel(b, name, b.N)
313330
}
314331

332+
func Benchmark_mvcc(b *testing.B) {
333+
mvcc.Create("test.db", "")
334+
name := "file:/test.db?vfs=mvcc" +
335+
"&_pragma=busy_timeout(10000)"
336+
createDB(b, name)
337+
338+
b.ResetTimer()
339+
testParallel(b, name, b.N)
340+
}
341+
315342
func createDB(t testing.TB, name string) {
316343
db, err := sqlite3.Open(name)
317344
if err != nil {

vfs/mvcc/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Go `mvcc` SQLite VFS
2+
3+
This package implements the **EXPERIMENTAL** `"mvcc"` in-memory SQLite VFS.
4+
5+
It has some benefits over the [`"memdb"`](../memdb/README.md) VFS:
6+
- panics do not corrupt a shared database;
7+
- single-writer not blocked by readers,
8+
- readers never block,
9+
- instant snapshots.

vfs/mvcc/api.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Package mvcc implements the "mvcc" SQLite VFS.
2+
//
3+
// The "mvcc" [vfs.VFS] allows the same in-memory database to be shared
4+
// among multiple database connections in the same process,
5+
// as long as the database name begins with "/".
6+
//
7+
// Importing package mvcc registers the VFS:
8+
//
9+
// import _ "github.com/ncruces/go-sqlite3/vfs/mvcc"
10+
package mvcc
11+
12+
import (
13+
"sync"
14+
15+
"github.com/ncruces/go-sqlite3/vfs"
16+
)
17+
18+
func init() {
19+
vfs.Register("mvcc", mvccVFS{})
20+
}
21+
22+
var (
23+
memoryMtx sync.Mutex
24+
// +checklocks:memoryMtx
25+
memoryDBs = map[string]*mvccDB{}
26+
)
27+
28+
// Create creates a shared memory database,
29+
// using data as its initial contents.
30+
func Create(name string, data string) {
31+
memoryMtx.Lock()
32+
defer memoryMtx.Unlock()
33+
34+
db := &mvccDB{
35+
refs: 1,
36+
name: name,
37+
}
38+
memoryDBs[name] = db
39+
if len(data) == 0 {
40+
return
41+
}
42+
// Convert data from WAL/2 to rollback journal.
43+
if len(data) >= 20 && (false ||
44+
data[18] == 2 && data[19] == 2 ||
45+
data[18] == 3 && data[19] == 3) {
46+
db.data = db.data.
47+
Put(0, data[:18]).
48+
Put(18, "\001\001").
49+
Put(20, data[20:])
50+
} else {
51+
db.data = db.data.Put(0, data)
52+
}
53+
}
54+
55+
// Delete deletes a shared memory database.
56+
func Delete(name string) {
57+
memoryMtx.Lock()
58+
defer memoryMtx.Unlock()
59+
delete(memoryDBs, name)
60+
}
61+
62+
// Snapshot stores a snapshot of database src into dst.
63+
func Snapshot(dst, src string) {
64+
memoryMtx.Lock()
65+
defer memoryMtx.Unlock()
66+
memoryDBs[dst] = memoryDBs[src].fork()
67+
}

vfs/mvcc/example_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package mvcc_test
2+
3+
import (
4+
"database/sql"
5+
_ "embed"
6+
"fmt"
7+
"log"
8+
9+
_ "github.com/ncruces/go-sqlite3/driver"
10+
_ "github.com/ncruces/go-sqlite3/embed"
11+
"github.com/ncruces/go-sqlite3/vfs/mvcc"
12+
)
13+
14+
//go:embed testdata/test.db
15+
var testDB string
16+
17+
func Example() {
18+
mvcc.Create("test.db", testDB)
19+
20+
db, err := sql.Open("sqlite3", "file:/test.db?vfs=mvcc")
21+
if err != nil {
22+
log.Fatal(err)
23+
}
24+
defer db.Close()
25+
26+
_, err = db.Exec(`INSERT INTO users (id, name) VALUES (3, 'rust')`)
27+
if err != nil {
28+
log.Fatal(err)
29+
}
30+
31+
rows, err := db.Query(`SELECT id, name FROM users`)
32+
if err != nil {
33+
log.Fatal(err)
34+
}
35+
defer rows.Close()
36+
37+
for rows.Next() {
38+
var id, name string
39+
err = rows.Scan(&id, &name)
40+
if err != nil {
41+
log.Fatal(err)
42+
}
43+
fmt.Printf("%s %s\n", id, name)
44+
}
45+
// Output:
46+
// 0 go
47+
// 1 zig
48+
// 2 whatever
49+
// 3 rust
50+
}

0 commit comments

Comments
 (0)