2024-05-27 16:46:15 +01:00
|
|
|
// Package memdb implements the "memdb" SQLite VFS.
|
|
|
|
//
|
|
|
|
// The "memdb" [vfs.VFS] allows the same in-memory database to be shared
|
|
|
|
// among multiple database connections in the same process,
|
|
|
|
// as long as the database name begins with "/".
|
|
|
|
//
|
|
|
|
// Importing package memdb registers the VFS:
|
|
|
|
//
|
|
|
|
// import _ "github.com/ncruces/go-sqlite3/vfs/memdb"
|
|
|
|
package memdb
|
|
|
|
|
|
|
|
import (
|
2024-08-15 01:30:58 +01:00
|
|
|
"fmt"
|
|
|
|
"net/url"
|
2024-05-27 16:46:15 +01:00
|
|
|
"sync"
|
2024-08-15 01:30:58 +01:00
|
|
|
"testing"
|
2024-05-27 16:46:15 +01:00
|
|
|
|
|
|
|
"github.com/ncruces/go-sqlite3/vfs"
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
vfs.Register("memdb", memVFS{})
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
memoryMtx sync.Mutex
|
|
|
|
// +checklocks:memoryMtx
|
|
|
|
memoryDBs = map[string]*memDB{}
|
|
|
|
)
|
|
|
|
|
|
|
|
// Create creates a shared memory database,
|
|
|
|
// using data as its initial contents.
|
|
|
|
// The new database takes ownership of data,
|
|
|
|
// and the caller should not use data after this call.
|
|
|
|
func Create(name string, data []byte) {
|
|
|
|
memoryMtx.Lock()
|
|
|
|
defer memoryMtx.Unlock()
|
|
|
|
|
|
|
|
db := &memDB{
|
|
|
|
refs: 1,
|
|
|
|
name: name,
|
|
|
|
size: int64(len(data)),
|
|
|
|
}
|
|
|
|
|
2024-08-15 01:30:58 +01:00
|
|
|
// Convert data from WAL/2 to rollback journal.
|
|
|
|
if len(data) >= 20 && (data[18] == 2 && data[19] == 2 ||
|
|
|
|
data[18] == 3 && data[19] == 3) {
|
2024-05-27 16:46:15 +01:00
|
|
|
data[18] = 1
|
|
|
|
data[19] = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
sectors := divRoundUp(db.size, sectorSize)
|
|
|
|
db.data = make([]*[sectorSize]byte, sectors)
|
|
|
|
for i := range db.data {
|
|
|
|
sector := data[i*sectorSize:]
|
|
|
|
if len(sector) >= sectorSize {
|
|
|
|
db.data[i] = (*[sectorSize]byte)(sector)
|
|
|
|
} else {
|
|
|
|
db.data[i] = new([sectorSize]byte)
|
|
|
|
copy((*db.data[i])[:], sector)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
memoryDBs[name] = db
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete deletes a shared memory database.
|
|
|
|
func Delete(name string) {
|
|
|
|
memoryMtx.Lock()
|
|
|
|
defer memoryMtx.Unlock()
|
|
|
|
delete(memoryDBs, name)
|
|
|
|
}
|
2024-08-15 01:30:58 +01:00
|
|
|
|
|
|
|
// TestDB creates an empty shared memory database for the test to use.
|
|
|
|
// The database is automatically deleted when the test and all its subtests complete.
|
|
|
|
// Each subsequent call to TestDB returns a unique database.
|
|
|
|
func TestDB(tb testing.TB, params ...url.Values) string {
|
|
|
|
tb.Helper()
|
|
|
|
|
|
|
|
name := fmt.Sprintf("%s_%p", tb.Name(), tb)
|
|
|
|
tb.Cleanup(func() { Delete(name) })
|
|
|
|
Create(name, nil)
|
|
|
|
|
|
|
|
p := url.Values{"vfs": {"memdb"}}
|
|
|
|
for _, v := range params {
|
|
|
|
for k, v := range v {
|
|
|
|
for _, v := range v {
|
|
|
|
p.Add(k, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (&url.URL{
|
|
|
|
Scheme: "file",
|
|
|
|
OmitHost: true,
|
|
|
|
Path: "/" + name,
|
|
|
|
RawQuery: p.Encode(),
|
|
|
|
}).String()
|
|
|
|
}
|