mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-12 22:19:14 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4764fb2fa | ||
|
|
42df71f3ff | ||
|
|
91e969c06b | ||
|
|
8ab0ddf53e | ||
|
|
74d22ded0a | ||
|
|
d962611796 | ||
|
|
7df3814c34 | ||
|
|
c5f49b835a | ||
|
|
0e55451a0b | ||
|
|
ea9a58ab19 | ||
|
|
0b46e74ea6 | ||
|
|
8dca850bee | ||
|
|
5b78823416 | ||
|
|
1764a571da | ||
|
|
9837310af7 | ||
|
|
ec8961a621 | ||
|
|
8c37aa2d97 | ||
|
|
ca93c498e7 | ||
|
|
15e9087fa8 |
33
.github/workflows/test.yml
vendored
33
.github/workflows/test.yml
vendored
@@ -68,8 +68,8 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
go work init .
|
||||
go work use -r embed gormlite
|
||||
go test ./embed/bcw2/...
|
||||
go work use -r embed/bcw2 gormlite
|
||||
go test ./embed/bcw2 ./gormlite
|
||||
|
||||
- name: Test GORM
|
||||
shell: bash
|
||||
@@ -93,25 +93,28 @@ jobs:
|
||||
github.event_name == 'push' &&
|
||||
matrix.os == 'ubuntu-latest'
|
||||
|
||||
test-bsd:
|
||||
test-cross:
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- name: freebsd
|
||||
version: '14.3'
|
||||
version: '15.0'
|
||||
- name: netbsd
|
||||
version: '10.1'
|
||||
- name: freebsd
|
||||
arch: arm64
|
||||
version: '14.3'
|
||||
tflags: '-test.short'
|
||||
- name: netbsd
|
||||
arch: arm64
|
||||
version: '10.1'
|
||||
tflags: '-test.short'
|
||||
- name: illumos
|
||||
action: omnios
|
||||
version: 'r151056'
|
||||
- name: openbsd
|
||||
version: '7.8'
|
||||
tflags: '-test.short'
|
||||
- name: freebsd
|
||||
arch: arm64
|
||||
version: '15.0'
|
||||
tflags: '-test.short'
|
||||
- name: netbsd
|
||||
arch: arm64
|
||||
version: '10.1'
|
||||
tflags: '-test.short'
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
@@ -126,9 +129,9 @@ jobs:
|
||||
run: .github/workflows/build-test.sh
|
||||
|
||||
- name: Test
|
||||
uses: cross-platform-actions/action@v0.30.0
|
||||
uses: cross-platform-actions/action@v0.32.0
|
||||
with:
|
||||
operating_system: ${{ matrix.os.name }}
|
||||
operating_system: ${{ matrix.os.action || matrix.os.name }}
|
||||
architecture: ${{ matrix.os.arch }}
|
||||
version: ${{ matrix.os.version }}
|
||||
shell: bash
|
||||
@@ -142,7 +145,7 @@ jobs:
|
||||
- name: dragonfly
|
||||
action: 'vmactions/dragonflybsd-vm@v1'
|
||||
- name: illumos
|
||||
action: 'vmactions/omnios-vm@v1'
|
||||
action: 'vmactions/openindiana-vm@v0'
|
||||
- name: solaris
|
||||
action: 'vmactions/solaris-vm@v1'
|
||||
bflags: '-tags sqlite3_dotlk'
|
||||
|
||||
@@ -440,22 +440,6 @@ func (c *conn) CheckNamedValue(arg *driver.NamedValue) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: for Litestream use only; may be removed at any time.
|
||||
func (c *conn) FileControlPersistWAL(schema string, mode int) (int, error) {
|
||||
// notest
|
||||
arg := make([]any, 1)
|
||||
if mode >= 0 {
|
||||
arg[0] = mode > 0
|
||||
} else {
|
||||
arg = arg[:0]
|
||||
}
|
||||
res, err := c.Conn.FileControl(schema, sqlite3.FCNTL_PERSIST_WAL, arg...)
|
||||
if res == true {
|
||||
return 1, err
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
type stmt struct {
|
||||
*sqlite3.Stmt
|
||||
tmWrite sqlite3.TimeFormat
|
||||
@@ -669,14 +653,12 @@ type rows struct {
|
||||
names []string
|
||||
types []string
|
||||
scans []scantype
|
||||
dest []driver.Value
|
||||
}
|
||||
|
||||
var (
|
||||
// Ensure these interfaces are implemented:
|
||||
_ driver.RowsColumnTypeDatabaseTypeName = &rows{}
|
||||
_ driver.RowsColumnTypeNullable = &rows{}
|
||||
// _ driver.RowsColumnScanner = &rows{}
|
||||
)
|
||||
|
||||
func (r *rows) Close() error {
|
||||
@@ -716,17 +698,23 @@ func (r *rows) loadColumnMetadata() {
|
||||
types := make([]string, count)
|
||||
scans := make([]scantype, count)
|
||||
for i := range types {
|
||||
var notnull bool
|
||||
if col := r.Stmt.ColumnOriginName(i); col != "" {
|
||||
types[i], _, notnull, _, _, _ = c.TableColumnMetadata(
|
||||
var declType string
|
||||
var notNull, autoInc bool
|
||||
if column := r.Stmt.ColumnOriginName(i); column != "" {
|
||||
declType, _, notNull, _, autoInc, _ = c.TableColumnMetadata(
|
||||
r.Stmt.ColumnDatabaseName(i),
|
||||
r.Stmt.ColumnTableName(i),
|
||||
col)
|
||||
types[i] = strings.ToUpper(types[i])
|
||||
scans[i] = scanFromDecl(types[i])
|
||||
if notnull {
|
||||
scans[i] |= _NOT_NULL
|
||||
}
|
||||
column)
|
||||
} else {
|
||||
declType = r.Stmt.ColumnDeclType(i)
|
||||
}
|
||||
if declType != "" {
|
||||
declType = strings.ToUpper(declType)
|
||||
scans[i] = scanFromDecl(declType)
|
||||
types[i] = declType
|
||||
}
|
||||
if notNull || autoInc {
|
||||
scans[i] |= _NOT_NULL
|
||||
}
|
||||
}
|
||||
r.types = types
|
||||
@@ -796,7 +784,6 @@ func (r *rows) ColumnTypeScanType(index int) (typ reflect.Type) {
|
||||
}
|
||||
|
||||
func (r *rows) Next(dest []driver.Value) error {
|
||||
r.dest = nil
|
||||
c := r.Stmt.Conn()
|
||||
if old := c.SetInterrupt(r.ctx); old != r.ctx {
|
||||
defer c.SetInterrupt(old)
|
||||
@@ -846,33 +833,5 @@ func (r *rows) Next(dest []driver.Value) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
r.dest = dest
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rows) ScanColumn(dest any, index int) (err error) {
|
||||
// notest // Go 1.26
|
||||
var tm *time.Time
|
||||
var ok *bool
|
||||
switch d := dest.(type) {
|
||||
case *time.Time:
|
||||
tm = d
|
||||
case *sql.NullTime:
|
||||
tm = &d.Time
|
||||
ok = &d.Valid
|
||||
case *sql.Null[time.Time]:
|
||||
tm = &d.V
|
||||
ok = &d.Valid
|
||||
default:
|
||||
return driver.ErrSkip
|
||||
}
|
||||
value := r.dest[index]
|
||||
*tm, err = r.tmRead.Decode(value)
|
||||
if ok != nil {
|
||||
*ok = err == nil
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"math"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -521,39 +520,6 @@ func Test_ColumnType_ScanType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_rows_ScanColumn(t *testing.T) {
|
||||
t.Parallel()
|
||||
dsn := memdb.TestDB(t)
|
||||
|
||||
db, err := Open(dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var tm time.Time
|
||||
err = db.QueryRow(`SELECT NULL`).Scan(&tm)
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
// Go 1.26
|
||||
err = db.QueryRow(`SELECT datetime()`).Scan(&tm)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "sql: Scan error") {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var nt sql.NullTime
|
||||
err = db.QueryRow(`SELECT NULL`).Scan(&nt)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// Go 1.26
|
||||
err = db.QueryRow(`SELECT datetime()`).Scan(&nt)
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "sql: Scan error") {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_loop(b *testing.B) {
|
||||
db, err := Open(":memory:")
|
||||
if err != nil {
|
||||
|
||||
@@ -23,6 +23,7 @@ if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
|
||||
MSYS_NO_PATHCONV=1 nmake /f makefile.msc sqlite3.c "OPTS=-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES"
|
||||
else
|
||||
sh configure --enable-update-limit
|
||||
make verify-source
|
||||
OPTS=-DSQLITE_ENABLE_ORDERED_SET_AGGREGATES make sqlite3.c
|
||||
fi
|
||||
cd ~-
|
||||
|
||||
@@ -2,11 +2,11 @@ module github.com/ncruces/go-sqlite3/embed/bcw2
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require github.com/ncruces/go-sqlite3 v0.30.2
|
||||
require github.com/ncruces/go-sqlite3 v0.30.3
|
||||
|
||||
require (
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/ncruces/sort v0.1.6 // indirect
|
||||
github.com/tetratelabs/wazero v1.10.1 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.11.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
github.com/ncruces/go-sqlite3 v0.30.2 h1:1GVbHAkKAOwjJd3JYl8ldrYROudfZUOah7oXPD7VZbQ=
|
||||
github.com/ncruces/go-sqlite3 v0.30.2/go.mod h1:AxKu9sRxkludimFocbktlY6LiYSkxiI5gTA8r+os/Nw=
|
||||
github.com/ncruces/go-sqlite3 v0.30.3 h1:X/CgWW9GzmIAkEPrifhKqf0cC15DuOVxAJaHFTTAURQ=
|
||||
github.com/ncruces/go-sqlite3 v0.30.3/go.mod h1:AxKu9sRxkludimFocbktlY6LiYSkxiI5gTA8r+os/Nw=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/ncruces/sort v0.1.6 h1:TrsJfGRH1AoWoaeB4/+gCohot9+cA6u/INaH5agIhNk=
|
||||
github.com/ncruces/sort v0.1.6/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk=
|
||||
github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
|
||||
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
|
||||
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/util/sql3util"
|
||||
)
|
||||
|
||||
// Register registers the bloom_filter virtual table:
|
||||
@@ -34,6 +35,8 @@ type bloom struct {
|
||||
hashes int
|
||||
}
|
||||
|
||||
const vtab = `CREATE TABLE x(present, word TEXT HIDDEN NOT NULL PRIMARY KEY) WITHOUT ROWID`
|
||||
|
||||
func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom, err error) {
|
||||
b := bloom{
|
||||
db: db,
|
||||
@@ -55,11 +58,9 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
|
||||
}
|
||||
|
||||
if len(arg) > 1 {
|
||||
b.prob, err = strconv.ParseFloat(arg[1], 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b.prob <= 0 || b.prob >= 1 {
|
||||
var ok bool
|
||||
b.prob, ok = sql3util.ParseFloat(arg[1])
|
||||
if !ok || b.prob <= 0 || b.prob >= 1 {
|
||||
return nil, util.ErrorString("bloom: probability must be in the range (0,1)")
|
||||
}
|
||||
} else {
|
||||
@@ -80,8 +81,7 @@ func create(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom,
|
||||
|
||||
b.bytes = numBytes(nelem, b.prob)
|
||||
|
||||
err = db.DeclareVTab(
|
||||
`CREATE TABLE x(present, word HIDDEN NOT NULL PRIMARY KEY) WITHOUT ROWID`)
|
||||
err = db.DeclareVTab(vtab)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -115,8 +115,7 @@ func connect(db *sqlite3.Conn, _, schema, table string, arg ...string) (_ *bloom
|
||||
storage: table + "_storage",
|
||||
}
|
||||
|
||||
err = db.DeclareVTab(
|
||||
`CREATE TABLE x(present, word HIDDEN NOT NULL PRIMARY KEY) WITHOUT ROWID`)
|
||||
err = db.DeclareVTab(vtab)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func Register(db *sqlite3.Conn) error {
|
||||
done.Add(key)
|
||||
}
|
||||
|
||||
err := db.DeclareVTab(`CREATE TABLE x(id,depth,root HIDDEN,tablename HIDDEN,idcolumn HIDDEN,parentcolumn HIDDEN)`)
|
||||
err := db.DeclareVTab(`CREATE TABLE x(id INT,depth INT,root HIDDEN,tablename TEXT HIDDEN,idcolumn TEXT HIDDEN,parentcolumn TEXT HIDDEN)`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -254,19 +254,15 @@ func (c *cursor) Column(ctx sqlite3.Context, col int) error {
|
||||
|
||||
switch typ {
|
||||
case numeric, integer:
|
||||
if strings.TrimLeft(txt, "+-0123456789") == "" {
|
||||
if i, err := strconv.ParseInt(txt, 10, 64); err == nil {
|
||||
ctx.ResultInt64(i)
|
||||
return nil
|
||||
}
|
||||
if i, err := strconv.ParseInt(txt, 10, 64); err == nil {
|
||||
ctx.ResultInt64(i)
|
||||
return nil
|
||||
}
|
||||
fallthrough
|
||||
case real:
|
||||
if strings.TrimLeft(txt, "+-.0123456789Ee") == "" {
|
||||
if f, err := strconv.ParseFloat(txt, 64); err == nil {
|
||||
ctx.ResultFloat(f)
|
||||
return nil
|
||||
}
|
||||
if f, ok := sql3util.ParseFloat(txt); ok {
|
||||
ctx.ResultFloat(f)
|
||||
return nil
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
|
||||
@@ -30,7 +30,7 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
|
||||
db.CreateFunction("readfile", 1, sqlite3.DIRECTONLY, readfile(fsys)),
|
||||
db.CreateFunction("lsmode", 1, sqlite3.DETERMINISTIC, lsmode),
|
||||
sqlite3.CreateModule(db, "fsdir", nil, func(db *sqlite3.Conn, _, _, _ string, _ ...string) (fsdir, error) {
|
||||
err := db.DeclareVTab(`CREATE TABLE x(name,mode,mtime TIMESTAMP,data,path HIDDEN,dir HIDDEN)`)
|
||||
err := db.DeclareVTab(`CREATE TABLE x(name TEXT,mode INT,mtime TIMESTAMP,data BLOB,path HIDDEN,dir HIDDEN)`)
|
||||
if err == nil {
|
||||
err = db.VTabConfig(sqlite3.VTAB_DIRECTONLY)
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (ret *table, err e
|
||||
t.keys[i] = name
|
||||
create.WriteString(sep)
|
||||
create.WriteString(name)
|
||||
create.WriteString(" ")
|
||||
create.WriteString(stmt.ColumnDeclType(i))
|
||||
sep = ","
|
||||
}
|
||||
stmt.Close()
|
||||
@@ -71,8 +73,11 @@ func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (ret *table, err e
|
||||
for stmt.Step() {
|
||||
name := sqlite3.QuoteIdentifier(stmt.ColumnText(1))
|
||||
t.cols = append(t.cols, stmt.ColumnValue(0).Dup())
|
||||
create.WriteString(",")
|
||||
create.WriteString(sep)
|
||||
create.WriteString(name)
|
||||
create.WriteString(" ")
|
||||
create.WriteString(stmt.ColumnDeclType(1))
|
||||
sep = ","
|
||||
}
|
||||
stmt.Close()
|
||||
|
||||
|
||||
12
go.mod
12
go.mod
@@ -5,18 +5,18 @@ go 1.24.0
|
||||
require (
|
||||
github.com/ncruces/julianday v1.0.0
|
||||
github.com/ncruces/sort v0.1.6
|
||||
github.com/ncruces/wbt v0.2.0
|
||||
github.com/tetratelabs/wazero v1.10.1
|
||||
golang.org/x/sys v0.38.0
|
||||
github.com/ncruces/wbt v1.0.0
|
||||
github.com/tetratelabs/wazero v1.11.0
|
||||
golang.org/x/sys v0.40.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dchest/siphash v1.2.3 // ext/bloom
|
||||
github.com/google/uuid v1.6.0 // ext/uuid
|
||||
github.com/psanford/httpreadat v0.1.0 // example
|
||||
golang.org/x/crypto v0.45.0 // vfs/adiantum vfs/xts
|
||||
golang.org/x/sync v0.18.0 // test
|
||||
golang.org/x/text v0.31.0 // ext/unicode
|
||||
golang.org/x/crypto v0.46.0 // vfs/adiantum vfs/xts
|
||||
golang.org/x/sync v0.19.0 // test
|
||||
golang.org/x/text v0.32.0 // ext/unicode
|
||||
lukechampine.com/adiantum v1.1.1 // vfs/adiantum
|
||||
)
|
||||
|
||||
|
||||
24
go.sum
24
go.sum
@@ -6,19 +6,19 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/ncruces/sort v0.1.6 h1:TrsJfGRH1AoWoaeB4/+gCohot9+cA6u/INaH5agIhNk=
|
||||
github.com/ncruces/sort v0.1.6/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk=
|
||||
github.com/ncruces/wbt v0.2.0 h1:Q9zlKOBSZc7Yy/R2cGa35g6RKUUE3BjNIW3tfGC4F04=
|
||||
github.com/ncruces/wbt v0.2.0/go.mod h1:DtF92amvMxH69EmBFUSFWRDAlo6hOEfoNQnClxj9C/c=
|
||||
github.com/ncruces/wbt v1.0.0 h1:8iBE7UPjTLUpzu3/FCRjAmuQjWzgxo10RGBgt3ooLSc=
|
||||
github.com/ncruces/wbt v1.0.0/go.mod h1:DtF92amvMxH69EmBFUSFWRDAlo6hOEfoNQnClxj9C/c=
|
||||
github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIwZLUE=
|
||||
github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE=
|
||||
github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
|
||||
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
|
||||
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
lukechampine.com/adiantum v1.1.1 h1:4fp6gTxWCqpEbLy40ExiYDDED3oUNWx5cTqBCtPdZqA=
|
||||
lukechampine.com/adiantum v1.1.1/go.mod h1:LrAYVnTYLnUtE/yMp5bQr0HstAf060YUF8nM0B6+rUw=
|
||||
|
||||
@@ -3,7 +3,7 @@ module github.com/ncruces/go-sqlite3/gormlite
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/ncruces/go-sqlite3 v0.30.2
|
||||
github.com/ncruces/go-sqlite3 v0.30.3
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.10.1 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.11.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
)
|
||||
|
||||
@@ -2,15 +2,15 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/ncruces/go-sqlite3 v0.30.2 h1:1GVbHAkKAOwjJd3JYl8ldrYROudfZUOah7oXPD7VZbQ=
|
||||
github.com/ncruces/go-sqlite3 v0.30.2/go.mod h1:AxKu9sRxkludimFocbktlY6LiYSkxiI5gTA8r+os/Nw=
|
||||
github.com/ncruces/go-sqlite3 v0.30.3 h1:X/CgWW9GzmIAkEPrifhKqf0cC15DuOVxAJaHFTTAURQ=
|
||||
github.com/ncruces/go-sqlite3 v0.30.3/go.mod h1:AxKu9sRxkludimFocbktlY6LiYSkxiI5gTA8r+os/Nw=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
|
||||
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
|
||||
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
# Litestream in-process replication and lightweight read-replicas
|
||||
|
||||
This package adds **EXPERIMENTAL** support for in-process [Litestream](https://litestream.io/).
|
||||
|
||||
## Lightweight read-replicas
|
||||
|
||||
The `"litestream"` SQLite VFS implements Litestream
|
||||
[lightweight read-replicas](https://fly.io/blog/litestream-revamped/#lightweight-read-replicas).
|
||||
|
||||
See the [example](example_test.go) for how to use.
|
||||
|
||||
To improve performance, increase `PollInterval` as much as you can,
|
||||
and set [`PRAGMA cache_size=N`](https://www.sqlite.org/pragma.html#pragma_cache_size)
|
||||
(or use `_pragma=cache_size(N)`).
|
||||
|
||||
## In-process replication
|
||||
|
||||
For disaster recovery, it is probably best if you run Litestream as a separate background process,
|
||||
as recommended by the [tutorial](https://litestream.io/getting-started/).
|
||||
|
||||
However, running Litestream as a background process requires
|
||||
compatible locking and cross-process shared memory WAL
|
||||
(see our [support matrix](https://github.com/ncruces/go-sqlite3/wiki/Support-matrix)).
|
||||
|
||||
If your OS lacks locking or shared memory support,
|
||||
you can use `NewPrimary` with the `sqlite3_dotlk` build tag to setup in-process replication.
|
||||
@@ -1,101 +0,0 @@
|
||||
// Package litestream implements a Litestream lightweight read-replica VFS.
|
||||
package litestream
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/litestream"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
)
|
||||
|
||||
const (
|
||||
// The default poll interval.
|
||||
DefaultPollInterval = 1 * time.Second
|
||||
|
||||
// The default cache size: 10 MiB.
|
||||
DefaultCacheSize = 10 * 1024 * 1024
|
||||
)
|
||||
|
||||
func init() {
|
||||
vfs.Register("litestream", liteVFS{})
|
||||
}
|
||||
|
||||
var (
|
||||
liteMtx sync.RWMutex
|
||||
// +checklocks:liteMtx
|
||||
liteDBs = map[string]*liteDB{}
|
||||
)
|
||||
|
||||
// ReplicaOptions represents options for [NewReplica].
|
||||
type ReplicaOptions struct {
|
||||
// Where to log error messages. May be nil.
|
||||
Logger *slog.Logger
|
||||
|
||||
// Replica poll interval.
|
||||
// Should be less than the compaction interval
|
||||
// used by the replica at MinLevel+1.
|
||||
PollInterval time.Duration
|
||||
|
||||
// CacheSize is the maximum size of the page cache in bytes.
|
||||
// Zero means DefaultCacheSize, negative disables caching.
|
||||
CacheSize int
|
||||
}
|
||||
|
||||
// NewReplica creates a read-replica from a Litestream client.
|
||||
func NewReplica(name string, client ReplicaClient, options ReplicaOptions) {
|
||||
if options.Logger != nil {
|
||||
options.Logger = options.Logger.With("name", name)
|
||||
} else {
|
||||
options.Logger = slog.New(slog.DiscardHandler)
|
||||
}
|
||||
if options.PollInterval <= 0 {
|
||||
options.PollInterval = DefaultPollInterval
|
||||
}
|
||||
if options.CacheSize == 0 {
|
||||
options.CacheSize = DefaultCacheSize
|
||||
}
|
||||
|
||||
liteMtx.Lock()
|
||||
defer liteMtx.Unlock()
|
||||
liteDBs[name] = &liteDB{
|
||||
client: client,
|
||||
opts: options,
|
||||
cache: pageCache{size: options.CacheSize},
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveReplica removes a replica by name.
|
||||
func RemoveReplica(name string) {
|
||||
liteMtx.Lock()
|
||||
defer liteMtx.Unlock()
|
||||
delete(liteDBs, name)
|
||||
}
|
||||
|
||||
// NewPrimary creates a new primary that replicates through client.
|
||||
// If restore is not nil, the database is first restored.
|
||||
func NewPrimary(ctx context.Context, path string, client ReplicaClient, restore *RestoreOptions) (*litestream.DB, error) {
|
||||
lsdb := litestream.NewDB(path)
|
||||
lsdb.Replica = litestream.NewReplicaWithClient(lsdb, client)
|
||||
|
||||
if restore != nil {
|
||||
err := lsdb.Replica.Restore(ctx, *restore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err := lsdb.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return lsdb, nil
|
||||
}
|
||||
|
||||
type (
|
||||
ReplicaClient = litestream.ReplicaClient
|
||||
RestoreOptions = litestream.RestoreOptions
|
||||
)
|
||||
@@ -1,104 +0,0 @@
|
||||
package litestream
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/litestream"
|
||||
"github.com/benbjohnson/litestream/file"
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
func Test_integration(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
dbpath := filepath.Join(dir, "test.db")
|
||||
backup := filepath.Join(dir, "backup", "test.db")
|
||||
|
||||
db, err := driver.Open(dbpath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
client := file.NewReplicaClient(backup)
|
||||
NewReplica("test.db", client, ReplicaOptions{})
|
||||
|
||||
if err := setupPrimary(t, dbpath, client); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
replica, err := driver.Open("file:test.db?vfs=litestream")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer replica.Close()
|
||||
|
||||
_, err = db.ExecContext(t.Context(), `CREATE TABLE users (id INT, name VARCHAR(10))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = db.ExecContext(t.Context(),
|
||||
`INSERT INTO users (id, name) VALUES (0, 'go'), (1, 'zig'), (2, 'whatever')`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
time.Sleep(DefaultPollInterval + litestream.DefaultMonitorInterval)
|
||||
|
||||
rows, err := replica.QueryContext(t.Context(), `SELECT id, name FROM users`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
row := 0
|
||||
ids := []int{0, 1, 2}
|
||||
names := []string{"go", "zig", "whatever"}
|
||||
for ; rows.Next(); row++ {
|
||||
var id int
|
||||
var name string
|
||||
err := rows.Scan(&id, &name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if id != ids[row] {
|
||||
t.Errorf("got %d, want %d", id, ids[row])
|
||||
}
|
||||
if name != names[row] {
|
||||
t.Errorf("got %q, want %q", name, names[row])
|
||||
}
|
||||
}
|
||||
if row != 3 {
|
||||
t.Errorf("got %d, want %d", row, len(ids))
|
||||
}
|
||||
|
||||
var lag int
|
||||
err = replica.QueryRowContext(t.Context(), `PRAGMA litestream_lag`).Scan(&lag)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if lag < 0 || lag > 2 {
|
||||
t.Errorf("got %d", lag)
|
||||
}
|
||||
|
||||
var txid string
|
||||
err = replica.QueryRowContext(t.Context(), `PRAGMA litestream_txid`).Scan(&txid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if txid != "0000000000000001" {
|
||||
t.Errorf("got %q", txid)
|
||||
}
|
||||
}
|
||||
|
||||
func setupPrimary(tb testing.TB, path string, client ReplicaClient) error {
|
||||
db, err := NewPrimary(tb.Context(), path, client, nil)
|
||||
if err == nil {
|
||||
tb.Cleanup(func() { db.Close(tb.Context()) })
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package litestream
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
"github.com/superfly/ltx"
|
||||
)
|
||||
|
||||
type pageCache struct {
|
||||
single singleflight.Group
|
||||
pages map[uint32]cachedPage // +checklocks:mtx
|
||||
size int
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
type cachedPage struct {
|
||||
data []byte
|
||||
txid ltx.TXID
|
||||
}
|
||||
|
||||
func (c *pageCache) getOrFetch(pgno uint32, maxTXID ltx.TXID, fetch func() (any, error)) ([]byte, error) {
|
||||
if c.size >= 0 {
|
||||
c.mtx.Lock()
|
||||
if c.pages == nil {
|
||||
c.pages = map[uint32]cachedPage{}
|
||||
}
|
||||
page := c.pages[pgno]
|
||||
c.mtx.Unlock()
|
||||
|
||||
if page.txid == maxTXID {
|
||||
return page.data, nil
|
||||
}
|
||||
}
|
||||
|
||||
var key [12]byte
|
||||
binary.LittleEndian.PutUint32(key[0:], pgno)
|
||||
binary.LittleEndian.PutUint64(key[4:], uint64(maxTXID))
|
||||
v, err, _ := c.single.Do(string(key[:]), fetch)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
page := cachedPage{v.([]byte), maxTXID}
|
||||
if c.size >= 0 {
|
||||
c.mtx.Lock()
|
||||
c.evict(len(page.data))
|
||||
c.pages[pgno] = page
|
||||
c.mtx.Unlock()
|
||||
}
|
||||
return page.data, nil
|
||||
}
|
||||
|
||||
// +checklocks:c.mtx
|
||||
func (c *pageCache) evict(pageSize int) {
|
||||
// Evict random keys until we're under the maximum size.
|
||||
// SQLite has its own page cache, which it will use for each connection.
|
||||
// Since this is a second layer of shared cache,
|
||||
// random eviction is probably good enough.
|
||||
if pageSize*len(c.pages) < c.size {
|
||||
return
|
||||
}
|
||||
for key := range c.pages {
|
||||
delete(c.pages, key)
|
||||
if pageSize*len(c.pages) < c.size {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package litestream_test
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/litestream/s3"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/litestream"
|
||||
)
|
||||
|
||||
func ExampleNewReplica() {
|
||||
client := s3.NewReplicaClient()
|
||||
client.Bucket = "test-bucket"
|
||||
client.Path = "fruits.db"
|
||||
|
||||
litestream.NewReplica("fruits.db", client, litestream.ReplicaOptions{
|
||||
PollInterval: 5 * time.Second,
|
||||
})
|
||||
|
||||
db, err := driver.Open("file:fruits.db?vfs=litestream")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
rows, err := db.Query("SELECT * FROM fruits")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var name, color string
|
||||
err := rows.Scan(&name, &color)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
log.Println(name, color)
|
||||
}
|
||||
|
||||
log.Println("===")
|
||||
rows.Close()
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
module github.com/ncruces/go-sqlite3/litestream
|
||||
|
||||
go 1.24.4
|
||||
|
||||
require (
|
||||
github.com/benbjohnson/litestream v0.5.3-0.20251109214555-48dc960260f0
|
||||
github.com/ncruces/go-sqlite3 v0.30.2
|
||||
github.com/ncruces/wbt v0.2.0
|
||||
github.com/superfly/ltx v0.5.0
|
||||
golang.org/x/sync v0.18.0
|
||||
)
|
||||
|
||||
// github.com/ncruces/go-sqlite3
|
||||
require (
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.10.1 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
)
|
||||
|
||||
// github.com/superfly/ltx
|
||||
require github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
|
||||
// github.com/benbjohnson/litestream
|
||||
require (
|
||||
filippo.io/age v1.2.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.4 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/psanford/sqlite3vfs v0.0.0-20251127171934-4e34e03a991a // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
modernc.org/sqlite v1.40.1 // indirect
|
||||
)
|
||||
|
||||
// github.com/benbjohnson/litestream/s3
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2 v1.37.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.30.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.18.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.85.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.35.1 // indirect
|
||||
github.com/aws/smithy-go v1.22.5 // indirect
|
||||
)
|
||||
|
||||
replace modernc.org/sqlite => github.com/ncruces/go-sqlite3/litestream/modernc v0.30.1
|
||||
@@ -1,199 +0,0 @@
|
||||
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
|
||||
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
|
||||
cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM=
|
||||
cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU=
|
||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
|
||||
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
|
||||
cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8=
|
||||
cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
|
||||
filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o=
|
||||
filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HRiRH3CR3Mj8pxqCcdD5A=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 h1:MhRfI58HblXzCtWEZCO0feHs8LweePB3s90r7WaR1KU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0/go.mod h1:okZ+ZURbArNdlJ+ptXoyHNuOETzOl1Oww19rm8I2WLA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 h1:FwladfywkNirM+FZYLBR2kBz5C8Tg0fw5w5Y7meRXWI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2/go.mod h1:vv5Ad0RrIoT1lJFdWBZwt4mB1+j+V8DUroixmKDTCdk=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.37.1 h1:SMUxeNz3Z6nqGsXv0JuJXc8w5YMtrQMuIBmDx//bBDY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.37.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 h1:6GMWV6CNpA/6fbFHnoAjrv4+LGfyTqZz2LtCHnspgDg=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0/go.mod h1:/mXlTIVG9jbxkqDnr5UQNQxW1HRYxeGklkM9vAFeabg=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.30.2 h1:YE1BmSc4fFYqFgN1mN8uzrtc7R9x+7oSWeX8ckoltAw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.30.2/go.mod h1:UNrLGZ6jfAVjgVJpkIxjLufRJqTXCVYOpkeVf83kwBo=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.2 h1:mfm0GKY/PHLhs7KO0sUaOtFnIQ15Qqxt+wXbO/5fIfs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.2/go.mod h1:v0SdJX6ayPeZFQxgXUKw5RhLpAoZUuynxWDfh8+Eknc=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1 h1:owmNBboeA0kHKDcdF8KiSXmrIuXZustfMGGytv6OMkM=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.1/go.mod h1:Bg1miN59SGxrZqlP8vJZSmXW+1N8Y1MjQDq1OfuNod8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.18.2 h1:YFX4DvH1CPQXgQR8935b46Om+L7+6jus4aTdKqyDR84=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.18.2/go.mod h1:DgMPy7GqxcV0RSyaITnI3rw8HC3lIHB87U3KPQKDxHg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1 h1:ksZXBYv80EFTcgc8OJO48aQ8XDWXIQL7gGasPeCoTzI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.1/go.mod h1:HSksQyyJETVZS7uM54cir0IgxttTD+8aEoJMPGepHBI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1 h1:+dn/xF/05utS7tUhjIcndbuaPjfll2LhbH1cCDGLYUQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.1/go.mod h1:hyAGz30LHdm5KBZDI58MXx5lDVZ5CUfvfTZvMu4HCZo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.1 h1:4HbnOGE9491a9zYJ9VpPh1ApgEq6ZlD4Kuv1PJenFpc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.1/go.mod h1:Z6QnHC6TmpJWUxAy8FI4JzA7rTwl6EIANkyK9OR5z5w=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.1 h1:ps3nrmBWdWwakZBydGX1CxeYFK80HsQ79JLMwm7Y4/c=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.1/go.mod h1:bAdfrfxENre68Hh2swNaGEVuFYE74o0SaSCAlaG9E74=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1 h1:ky79ysLMxhwk5rxJtS+ILd3Mc8kC5fhsLBrP27r6h4I=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.1/go.mod h1:+2MmkvFvPYM1vsozBWduoLJUi5maxFk5B7KJFECujhY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.1 h1:MdVYlN5pcQu1t1OYx4Ajo3fKl1IEhzgdPQbYFCRjYS8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.1/go.mod h1:iikmNLrvHm2p4a3/4BPeix2S9P+nW8yM1IZW73x8bFA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.85.1 h1:Hsqo8+dFxSdDvv9B2PgIx1AJAnDpqgS0znVI+R+MoGY=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.85.1/go.mod h1:8Q0TAPXD68Z8YqlcIGHs/UNIDHsxErV9H4dl4vJEpgw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.26.1 h1:uWaz3DoNK9MNhm7i6UGxqufwu3BEuJZm72WlpGwyVtY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.26.1/go.mod h1:ILpVNjL0BO+Z3Mm0SbEeUoYS9e0eJWV1BxNppp0fcb8=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1 h1:XdG6/o1/ZDmn3wJU5SRAejHaWgKS4zHv0jBamuKuS2k=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.1/go.mod h1:oiotGTKadCOCl3vg/tYh4k45JlDF81Ka8rdumNhEnIQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.35.1 h1:iF4Xxkc0H9c/K2dS0zZw3SCkj0Z7n6AMnUiiyoJND+I=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.35.1/go.mod h1:0bxIatfN0aLq4mjoLDeBpOjOke68OsFlXPDFJ7V0MYw=
|
||||
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
|
||||
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/benbjohnson/litestream v0.5.3-0.20251109214555-48dc960260f0 h1:190qM2axbs1p7NyRm5/ygqrDdcIYhc5QXPTK04Cgyno=
|
||||
github.com/benbjohnson/litestream v0.5.3-0.20251109214555-48dc960260f0/go.mod h1:0gjSLi7Qm5INdtdo6YJMFMsS5e/KM2s3CFYF3OtLmY8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
||||
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nats-io/nats.go v1.44.0 h1:ECKVrDLdh/kDPV1g0gAQ+2+m2KprqZK5O/eJAyAnH2M=
|
||||
github.com/nats-io/nats.go v1.44.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/ncruces/go-sqlite3 v0.30.2 h1:1GVbHAkKAOwjJd3JYl8ldrYROudfZUOah7oXPD7VZbQ=
|
||||
github.com/ncruces/go-sqlite3 v0.30.2/go.mod h1:AxKu9sRxkludimFocbktlY6LiYSkxiI5gTA8r+os/Nw=
|
||||
github.com/ncruces/go-sqlite3/litestream/modernc v0.30.1 h1:3SNAOrm+qmLprkZybcvBrVNHyt0QYHljUGxmOXnL+K0=
|
||||
github.com/ncruces/go-sqlite3/litestream/modernc v0.30.1/go.mod h1:GSM2gXEOb9HIFFtsl0IUtnpvpDmVi7Kbp8z5GzwA0Tw=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/ncruces/wbt v0.2.0 h1:Q9zlKOBSZc7Yy/R2cGa35g6RKUUE3BjNIW3tfGC4F04=
|
||||
github.com/ncruces/wbt v0.2.0/go.mod h1:DtF92amvMxH69EmBFUSFWRDAlo6hOEfoNQnClxj9C/c=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
|
||||
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
|
||||
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
|
||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||
github.com/psanford/sqlite3vfs v0.0.0-20251127171934-4e34e03a991a h1:r4YWl0uVObCbBFvj1VsIlyHzgZwZOHvY1KdRaQjzzUc=
|
||||
github.com/psanford/sqlite3vfs v0.0.0-20251127171934-4e34e03a991a/go.mod h1:iW4cSew5PAb1sMZiTEkVJAIBNrepaB6jTYjeP47WtI0=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/studio-b12/gowebdav v0.11.0 h1:qbQzq4USxY28ZYsGJUfO5jR+xkFtcnwWgitp4Zp1irU=
|
||||
github.com/studio-b12/gowebdav v0.11.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
||||
github.com/superfly/ltx v0.5.0 h1:dXNrcT3ZtMb6iKZopIV7z5UBscnapg0b0F02loQsk5o=
|
||||
github.com/superfly/ltx v0.5.0/go.mod h1:Nf50QAIXU/ET4ua3AuQ2fh31MbgNQZA7r/DYx6Os77s=
|
||||
github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
|
||||
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
|
||||
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
|
||||
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
|
||||
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
|
||||
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
|
||||
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
||||
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.154.0 h1:X7QkVKZBskztmpPKWQXgjJRPA2dJYrL6r+sYPRLj050=
|
||||
google.golang.org/api v0.154.0/go.mod h1:qhSMkM85hgqiokIYsrRyKxrjfBeIhgl4Z2JmeRkYylc=
|
||||
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
|
||||
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA=
|
||||
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
|
||||
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -1,11 +0,0 @@
|
||||
module modernc.org/sqlite
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require github.com/ncruces/go-sqlite3 v0.30.2
|
||||
|
||||
require (
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.10.1 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
)
|
||||
@@ -1,10 +0,0 @@
|
||||
github.com/ncruces/go-sqlite3 v0.30.2 h1:1GVbHAkKAOwjJd3JYl8ldrYROudfZUOah7oXPD7VZbQ=
|
||||
github.com/ncruces/go-sqlite3 v0.30.2/go.mod h1:AxKu9sRxkludimFocbktlY6LiYSkxiI5gTA8r+os/Nw=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
|
||||
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
@@ -1,20 +0,0 @@
|
||||
// Package sqlite provides a shim that allows Litestream to work with the ncruces SQLite driver.
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"slices"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !slices.Contains(sql.Drivers(), "sqlite") {
|
||||
sql.Register("sqlite", &driver.SQLite{})
|
||||
}
|
||||
}
|
||||
|
||||
type FileControl interface {
|
||||
FileControlPersistWAL(string, int) (int, error)
|
||||
}
|
||||
@@ -1,322 +0,0 @@
|
||||
package litestream
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/litestream"
|
||||
"github.com/superfly/ltx"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/util/vfsutil"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
"github.com/ncruces/wbt"
|
||||
)
|
||||
|
||||
type liteVFS struct{}
|
||||
|
||||
func (liteVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||
// Temp journals, as used by the sorter, use SliceFile.
|
||||
if flags&vfs.OPEN_TEMP_JOURNAL != 0 {
|
||||
return &vfsutil.SliceFile{}, flags | vfs.OPEN_MEMORY, nil
|
||||
}
|
||||
// Refuse to open all other file types.
|
||||
if flags&vfs.OPEN_MAIN_DB == 0 {
|
||||
return nil, flags, sqlite3.CANTOPEN
|
||||
}
|
||||
|
||||
liteMtx.RLock()
|
||||
defer liteMtx.RUnlock()
|
||||
if db, ok := liteDBs[name]; ok {
|
||||
// Build the page index so we can lookup individual pages.
|
||||
if err := db.buildIndex(context.Background()); err != nil {
|
||||
db.opts.Logger.Error("build index", "error", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return &liteFile{db: db}, flags | vfs.OPEN_READONLY, nil
|
||||
}
|
||||
return nil, flags, sqlite3.CANTOPEN
|
||||
}
|
||||
|
||||
func (liteVFS) Delete(name string, dirSync bool) error {
|
||||
// notest // used to delete journals
|
||||
return sqlite3.IOERR_DELETE_NOENT
|
||||
}
|
||||
|
||||
func (liteVFS) Access(name string, flag vfs.AccessFlag) (bool, error) {
|
||||
// notest // used to check for journals
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (liteVFS) FullPathname(name string) (string, error) {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
type liteFile struct {
|
||||
db *liteDB
|
||||
conn *sqlite3.Conn
|
||||
pages *pageIndex
|
||||
txid ltx.TXID
|
||||
pageSize uint32
|
||||
}
|
||||
|
||||
func (f *liteFile) Close() error { return nil }
|
||||
|
||||
func (f *liteFile) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
ctx := f.context()
|
||||
pages, txid := f.pages, f.txid
|
||||
if pages == nil {
|
||||
pages, txid, err = f.db.pollReplica(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
pgno := uint32(1)
|
||||
if off >= 512 {
|
||||
pgno += uint32(off / int64(f.pageSize))
|
||||
}
|
||||
|
||||
elem, ok := pages.Get(pgno)
|
||||
if !ok {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
data, err := f.db.cache.getOrFetch(pgno, elem.MaxTXID, func() (any, error) {
|
||||
_, data, err := litestream.FetchPage(ctx, f.db.client, elem.Level, elem.MinTXID, elem.MaxTXID, elem.Offset, elem.Size)
|
||||
return data, err
|
||||
})
|
||||
if err != nil {
|
||||
f.db.opts.Logger.Error("fetch page", "error", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Update the first page to pretend we are in journal mode,
|
||||
// load the page size and track changes to the database.
|
||||
if pgno == 1 && len(data) >= 100 &&
|
||||
data[18] >= 1 && data[19] >= 1 &&
|
||||
data[18] <= 3 && data[19] <= 3 {
|
||||
data[18], data[19] = 0x01, 0x01
|
||||
binary.BigEndian.PutUint32(data[24:28], uint32(txid))
|
||||
f.pageSize = uint32(256 * binary.LittleEndian.Uint16(data[16:18]))
|
||||
}
|
||||
|
||||
n = copy(p, data[off%int64(len(data)):])
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (f *liteFile) WriteAt(b []byte, off int64) (n int, err error) {
|
||||
// notest // OPEN_READONLY
|
||||
return 0, sqlite3.IOERR_WRITE
|
||||
}
|
||||
|
||||
func (f *liteFile) Truncate(size int64) error {
|
||||
// notest // OPEN_READONLY
|
||||
return sqlite3.IOERR_TRUNCATE
|
||||
}
|
||||
|
||||
func (f *liteFile) Sync(flag vfs.SyncFlag) error {
|
||||
// notest // OPEN_READONLY
|
||||
return sqlite3.IOERR_FSYNC
|
||||
}
|
||||
|
||||
func (f *liteFile) Size() (size int64, err error) {
|
||||
if max := f.pages.Max(); max != nil {
|
||||
size = int64(max.Key()) * int64(f.pageSize)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *liteFile) Lock(lock vfs.LockLevel) (err error) {
|
||||
if lock >= vfs.LOCK_RESERVED {
|
||||
return sqlite3.IOERR_LOCK
|
||||
}
|
||||
f.pages, f.txid, err = f.db.pollReplica(f.context())
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *liteFile) Unlock(lock vfs.LockLevel) error {
|
||||
f.pages, f.txid = nil, 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *liteFile) CheckReservedLock() (bool, error) {
|
||||
// notest // used to check for hot journals
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (f *liteFile) SectorSize() int {
|
||||
// notest // safe default
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *liteFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
|
||||
// notest // safe default
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *liteFile) Pragma(name, value string) (string, error) {
|
||||
switch name {
|
||||
case "litestream_txid":
|
||||
txid := f.txid
|
||||
if txid == 0 {
|
||||
// Outside transaction.
|
||||
f.db.mtx.Lock()
|
||||
txid = f.db.txids[0]
|
||||
f.db.mtx.Unlock()
|
||||
}
|
||||
return txid.String(), nil
|
||||
|
||||
case "litestream_lag":
|
||||
f.db.mtx.Lock()
|
||||
lastPoll := f.db.lastPoll
|
||||
f.db.mtx.Unlock()
|
||||
|
||||
if lastPoll.IsZero() {
|
||||
// Never polled successfully.
|
||||
return "-1", nil
|
||||
}
|
||||
lag := time.Since(lastPoll) / time.Second
|
||||
return strconv.FormatInt(int64(lag), 10), nil
|
||||
}
|
||||
|
||||
return "", sqlite3.NOTFOUND
|
||||
}
|
||||
|
||||
func (f *liteFile) SetDB(conn any) {
|
||||
f.conn = conn.(*sqlite3.Conn)
|
||||
}
|
||||
|
||||
func (f *liteFile) context() context.Context {
|
||||
if f.conn != nil {
|
||||
return f.conn.GetInterrupt()
|
||||
}
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
type liteDB struct {
|
||||
client litestream.ReplicaClient
|
||||
opts ReplicaOptions
|
||||
cache pageCache
|
||||
pages *pageIndex // +checklocks:mtx
|
||||
lastPoll time.Time // +checklocks:mtx
|
||||
txids levelTXIDs // +checklocks:mtx
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func (f *liteDB) buildIndex(ctx context.Context) error {
|
||||
f.mtx.Lock()
|
||||
defer f.mtx.Unlock()
|
||||
|
||||
// Skip if we already have an index.
|
||||
if f.pages != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build the index from scratch from a Litestream restore plan.
|
||||
infos, err := litestream.CalcRestorePlan(ctx, f.client, 0, time.Time{}, f.opts.Logger)
|
||||
if err != nil {
|
||||
if !errors.Is(err, litestream.ErrTxNotAvailable) {
|
||||
return fmt.Errorf("calc restore plan: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, info := range infos {
|
||||
err := f.updateInfo(ctx, info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
f.lastPoll = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *liteDB) pollReplica(ctx context.Context) (*pageIndex, ltx.TXID, error) {
|
||||
f.mtx.Lock()
|
||||
defer f.mtx.Unlock()
|
||||
|
||||
// Limit polling interval.
|
||||
if time.Since(f.lastPoll) < f.opts.PollInterval {
|
||||
return f.pages, f.txids[0], nil
|
||||
}
|
||||
|
||||
for level := range []int{0, 1, litestream.SnapshotLevel} {
|
||||
if err := f.updateLevel(ctx, level); err != nil {
|
||||
f.opts.Logger.Error("cannot poll replica", "error", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
f.lastPoll = time.Now()
|
||||
return f.pages, f.txids[0], nil
|
||||
}
|
||||
|
||||
// +checklocks:f.mtx
|
||||
func (f *liteDB) updateLevel(ctx context.Context, level int) error {
|
||||
var nextTXID ltx.TXID
|
||||
// Snapshots must start from scratch,
|
||||
// other levels can start from where they were left.
|
||||
if level != litestream.SnapshotLevel {
|
||||
nextTXID = f.txids[level] + 1
|
||||
}
|
||||
|
||||
// Start reading from the next LTX file after the current position.
|
||||
itr, err := f.client.LTXFiles(ctx, level, nextTXID, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ltx files: %w", err)
|
||||
}
|
||||
defer itr.Close()
|
||||
|
||||
// Build an update across all new LTX files.
|
||||
for itr.Next() {
|
||||
info := itr.Item()
|
||||
|
||||
// Skip LTX files already fully loaded into the index.
|
||||
if info.MaxTXID <= f.txids[level] {
|
||||
continue
|
||||
}
|
||||
|
||||
err := f.updateInfo(ctx, info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := itr.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return itr.Close()
|
||||
}
|
||||
|
||||
// +checklocks:f.mtx
|
||||
func (f *liteDB) updateInfo(ctx context.Context, info *ltx.FileInfo) error {
|
||||
idx, err := litestream.FetchPageIndex(ctx, f.client, info)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch page index: %w", err)
|
||||
}
|
||||
|
||||
// Replace pages in the index with new pages.
|
||||
for k, v := range idx {
|
||||
// Patch avoids mutating the index for an unmodified page.
|
||||
f.pages = f.pages.Patch(k, func(node *pageIndex) (ltx.PageIndexElem, bool) {
|
||||
return v, node == nil || v != node.Value()
|
||||
})
|
||||
}
|
||||
|
||||
// Track the MaxTXID for each level.
|
||||
maxTXID := &f.txids[info.Level]
|
||||
*maxTXID = max(*maxTXID, info.MaxTXID)
|
||||
f.txids[0] = max(f.txids[0], *maxTXID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type aliases; these are a mouthful.
|
||||
type pageIndex = wbt.Tree[uint32, ltx.PageIndexElem]
|
||||
type levelTXIDs = [litestream.SnapshotLevel + 1]ltx.TXID
|
||||
@@ -14,8 +14,8 @@ var (
|
||||
// https://sqlite.org/c3ref/auto_extension.html
|
||||
func AutoExtension(entryPoint func(*Conn) error) {
|
||||
extRegistryMtx.Lock()
|
||||
defer extRegistryMtx.Unlock()
|
||||
extRegistry = append(extRegistry, entryPoint)
|
||||
extRegistryMtx.Unlock()
|
||||
}
|
||||
|
||||
func initExtensions(c *Conn) error {
|
||||
|
||||
15
time.go
15
time.go
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/util/sql3util"
|
||||
"github.com/ncruces/julianday"
|
||||
)
|
||||
|
||||
@@ -157,11 +158,13 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
|
||||
|
||||
case TimeFormatUnix, TimeFormatUnixFrac:
|
||||
if s, ok := v.(string); ok {
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
if i, err := strconv.ParseInt(s, 10, 64); err == nil {
|
||||
v = i
|
||||
} else if f, ok := sql3util.ParseFloat(s); ok {
|
||||
v = f
|
||||
} else {
|
||||
return time.Time{}, util.TimeErr
|
||||
}
|
||||
v = f
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case float64:
|
||||
@@ -234,8 +237,8 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
|
||||
v = i
|
||||
break
|
||||
}
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err == nil {
|
||||
f, ok := sql3util.ParseFloat(s)
|
||||
if ok {
|
||||
v = f
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package sql3util
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NamedArg splits an named arg into a key and value,
|
||||
// around an equals sign.
|
||||
@@ -63,3 +67,124 @@ func ParseBool(s string) (b, ok bool) {
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
// ParseFloat parses a decimal floating point number.
|
||||
func ParseFloat(s string) (f float64, ok bool) {
|
||||
if strings.TrimLeft(s, "+-.0123456789Ee") != "" {
|
||||
return
|
||||
}
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
return f, err == nil
|
||||
}
|
||||
|
||||
// ParseTimeShift parses a time shift modifier,
|
||||
// also the output of timediff.
|
||||
//
|
||||
// https://sqlite.org/lang_datefunc.html
|
||||
func ParseTimeShift(s string) (years, months, days int, duration time.Duration, ok bool) {
|
||||
// Sign part: ±
|
||||
neg := strings.HasPrefix(s, "-")
|
||||
sign := neg || strings.HasPrefix(s, "+")
|
||||
if sign {
|
||||
s = s[1:]
|
||||
}
|
||||
|
||||
if ok = len(s) >= 5; !ok {
|
||||
return // !ok
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if neg {
|
||||
years = -years
|
||||
months = -months
|
||||
days = -days
|
||||
duration = -duration
|
||||
}
|
||||
}()
|
||||
|
||||
// Date part: YYYY-MM-DD
|
||||
if s[4] == '-' {
|
||||
if ok = sign && len(s) >= 10 && s[7] == '-'; !ok {
|
||||
return // !ok
|
||||
}
|
||||
if years, ok = parseInt(s[0:4], 0); !ok {
|
||||
return // !ok
|
||||
}
|
||||
if months, ok = parseInt(s[5:7], 12); !ok {
|
||||
return // !ok
|
||||
}
|
||||
if days, ok = parseInt(s[8:10], 31); !ok {
|
||||
return // !ok
|
||||
}
|
||||
if len(s) == 10 {
|
||||
return
|
||||
}
|
||||
if ok = s[10] == ' '; !ok {
|
||||
return // !ok
|
||||
}
|
||||
s = s[11:]
|
||||
}
|
||||
|
||||
// Time part: HH:MM
|
||||
if ok = len(s) >= 5 && s[2] == ':'; !ok {
|
||||
return // !ok
|
||||
}
|
||||
|
||||
var hours, minutes int
|
||||
if hours, ok = parseInt(s[0:2], 24); !ok {
|
||||
return
|
||||
}
|
||||
if minutes, ok = parseInt(s[3:5], 60); !ok {
|
||||
return
|
||||
}
|
||||
duration = time.Duration(hours)*time.Hour + time.Duration(minutes)*time.Minute
|
||||
|
||||
if len(s) == 5 {
|
||||
return
|
||||
}
|
||||
if ok = len(s) >= 8 && s[5] == ':'; !ok {
|
||||
return // !ok
|
||||
}
|
||||
|
||||
// Seconds part: HH:MM:SS
|
||||
var seconds int
|
||||
if seconds, ok = parseInt(s[6:8], 60); !ok {
|
||||
return
|
||||
}
|
||||
duration += time.Duration(seconds) * time.Second
|
||||
|
||||
if len(s) == 8 {
|
||||
return
|
||||
}
|
||||
if ok = len(s) >= 10 && s[8] == '.'; !ok {
|
||||
return // !ok
|
||||
}
|
||||
s = s[9:]
|
||||
|
||||
// Nanosecond part: HH:MM:SS.SSS
|
||||
var nanos int
|
||||
if nanos, ok = parseInt(s[0:min(9, len(s))], 0); !ok {
|
||||
return
|
||||
}
|
||||
for i := len(s); i < 9; i++ {
|
||||
nanos *= 10
|
||||
}
|
||||
duration += time.Duration(nanos)
|
||||
|
||||
// Subnanosecond part.
|
||||
if len(s) > 9 {
|
||||
_, ok = parseInt(s[9:], 0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseInt(s string, max int) (i int, _ bool) {
|
||||
for _, r := range []byte(s) {
|
||||
r -= '0'
|
||||
if r > 9 {
|
||||
return
|
||||
}
|
||||
i = i*10 + int(r)
|
||||
}
|
||||
return i, max == 0 || i < max
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ package sql3util_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
"github.com/ncruces/go-sqlite3/util/sql3util"
|
||||
)
|
||||
|
||||
@@ -53,3 +56,112 @@ func TestParseBool(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTimeShift(t *testing.T) {
|
||||
epoch := time.Unix(0, 0)
|
||||
tests := []struct {
|
||||
str string
|
||||
val time.Time
|
||||
ok bool
|
||||
}{
|
||||
{"", epoch, false},
|
||||
{"0001-11-30", epoch, false},
|
||||
{"+_001-11-30", epoch, false},
|
||||
{"+0001-_1-30", epoch.AddDate(1, 0, 0), false},
|
||||
{"+0001-11-_0", epoch.AddDate(1, 11, 0), false},
|
||||
{"+0001-11-30", epoch.AddDate(1, 11, 30), true},
|
||||
{"-0001-11-30", epoch.AddDate(-1, -11, -30), true},
|
||||
{"+0001-11-30T", epoch.AddDate(1, 11, 30), false},
|
||||
{"+0001-11-30 12", epoch.AddDate(1, 11, 30), false},
|
||||
{"+0001-11-30 _2:30", epoch.AddDate(1, 11, 30), false},
|
||||
{"+0001-11-30 12:_0", epoch.AddDate(1, 11, 30), false},
|
||||
{"+0001-11-30 12:30", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute), true},
|
||||
{"+0001-11-30 12:30:", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute), false},
|
||||
{"+0001-11-30 12:30:_0", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute), false},
|
||||
{"+0001-11-30 12:30:59", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second), true},
|
||||
{"+0001-11-30 12:30:59.", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second), false},
|
||||
{"+0001-11-30 12:30:59._", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second), false},
|
||||
{"+0001-11-30 12:30:59.1", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second + 100*time.Millisecond), true},
|
||||
{"+0001-11-30 12:30:59.123456789_", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second + 123456789), false},
|
||||
{"+0001-11-30 12:30:59.1234567890", epoch.AddDate(1, 11, 30).Add(12*time.Hour + 30*time.Minute + 59*time.Second + 123456789), true},
|
||||
{"-12:30:59.1234567890", epoch.Add(-12*time.Hour - 30*time.Minute - 59*time.Second - 123456789), true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.str, func(t *testing.T) {
|
||||
years, months, days, duration, gotOK := sql3util.ParseTimeShift(tt.str)
|
||||
gotVal := epoch.AddDate(years, months, days).Add(duration)
|
||||
if !gotVal.Equal(tt.val) || gotOK != tt.ok {
|
||||
t.Errorf("ParseTimeShift(%q) = (%v, %v) want (%v, %v)", tt.str, gotVal, gotOK, tt.val, tt.ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzParseTimeShift(f *testing.F) {
|
||||
f.Add("")
|
||||
f.Add("0001-12-30")
|
||||
f.Add("+_001-12-30")
|
||||
f.Add("+0001-_2-30")
|
||||
f.Add("+0001-12-_0")
|
||||
f.Add("+0001-12-30")
|
||||
f.Add("-0001-12-30")
|
||||
f.Add("+0001-12-30T")
|
||||
f.Add("+0001-12-30 12")
|
||||
f.Add("+0001-12-30 _2:30")
|
||||
f.Add("+0001-12-30 12:_0")
|
||||
f.Add("+0001-12-30 12:30")
|
||||
f.Add("+0001-12-30 12:30:")
|
||||
f.Add("+0001-12-30 12:30:_0")
|
||||
f.Add("+0001-12-30 12:30:60")
|
||||
f.Add("+0001-12-30 12:30:60.")
|
||||
f.Add("+0001-12-30 12:30:60._")
|
||||
f.Add("+0001-12-30 12:30:60.1")
|
||||
f.Add("+0001-12-30 12:30:60.123456789_")
|
||||
f.Add("+0001-12-30 12:30:60.1234567890")
|
||||
f.Add("-12:30:60.1234567890")
|
||||
|
||||
c, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
s, _, err := c.Prepare(`SELECT julianday('00:00', ?)`)
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
// Default SQLite date.
|
||||
epoch := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
f.Fuzz(func(t *testing.T, str string) {
|
||||
years, months, days, duration, ok := sql3util.ParseTimeShift(str)
|
||||
|
||||
// Account for a full 400 year cycle.
|
||||
if years < -200 || years > +200 {
|
||||
t.Skip()
|
||||
}
|
||||
// SQLite only tracks milliseconds.
|
||||
if duration != duration.Truncate(time.Millisecond) {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
if ok {
|
||||
s.Reset()
|
||||
s.BindText(1, str)
|
||||
if !s.Step() {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
got := epoch.AddDate(years, months, days).Add(duration)
|
||||
|
||||
// Julian day introduces floating point inaccuracy.
|
||||
want := s.ColumnTime(0, sqlite3.TimeFormatJulianDay)
|
||||
want = want.Round(time.Millisecond)
|
||||
if !got.Equal(want) {
|
||||
t.Fatalf("with %q, got %v, want %v", str, got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -125,5 +125,5 @@ The VFS can be customized with a few build tags:
|
||||
wraps a VFS to offer encryption at rest.
|
||||
- [`github.com/ncruces/go-sqlite3/vfs/xts`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/xts)
|
||||
wraps a VFS to offer encryption at rest.
|
||||
- [`github.com/ncruces/go-sqlite3/litestream`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/litestream)
|
||||
- [`github.com/ncruces/litestream`](https://pkg.go.dev/github.com/ncruces/litestream)
|
||||
implements Litestream [lightweight read-replicas](https://fly.io/blog/litestream-revamped/#lightweight-read-replicas).
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock || sqlite3_dotlk
|
||||
|
||||
package adiantum_test
|
||||
|
||||
import (
|
||||
@@ -17,7 +15,7 @@ import (
|
||||
"github.com/ncruces/go-sqlite3/vfs/adiantum"
|
||||
)
|
||||
|
||||
func ExampleRegister_hpolyc() {
|
||||
func Example_hPolyC() {
|
||||
vfs.Register("hpolyc", adiantum.Wrap(vfs.Find(""), hpolycCreator{}))
|
||||
|
||||
db, err := sqlite3.Open("file:demo.db?vfs=hpolyc" +
|
||||
|
||||
@@ -34,9 +34,6 @@ var (
|
||||
// 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,
|
||||
@@ -63,14 +60,16 @@ func Create(name string, data []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
memoryMtx.Lock()
|
||||
memoryDBs[name] = db
|
||||
memoryMtx.Unlock()
|
||||
}
|
||||
|
||||
// Delete deletes a shared memory database.
|
||||
func Delete(name string) {
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
delete(memoryDBs, name)
|
||||
memoryMtx.Unlock()
|
||||
}
|
||||
|
||||
// TestDB creates an empty shared memory database for the test to use.
|
||||
|
||||
@@ -92,10 +92,10 @@ type memDB struct {
|
||||
|
||||
func (m *memDB) release() {
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
if m.refs--; m.refs == 0 && m == memoryDBs[m.name] {
|
||||
delete(memoryDBs, m.name)
|
||||
}
|
||||
memoryMtx.Unlock()
|
||||
}
|
||||
|
||||
type memFile struct {
|
||||
|
||||
@@ -35,13 +35,12 @@ var (
|
||||
// using a snapshot as its initial contents.
|
||||
func Create(name string, snapshot Snapshot) {
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
|
||||
memoryDBs[name] = &mvccDB{
|
||||
refs: 1,
|
||||
name: name,
|
||||
data: snapshot.Tree,
|
||||
}
|
||||
memoryMtx.Unlock()
|
||||
}
|
||||
|
||||
// Delete deletes a shared memory database.
|
||||
@@ -49,8 +48,8 @@ func Delete(name string) {
|
||||
name = getName(name)
|
||||
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
delete(memoryDBs, name)
|
||||
memoryMtx.Unlock()
|
||||
}
|
||||
|
||||
// Snapshot represents a database snapshot.
|
||||
@@ -83,8 +82,9 @@ func TakeSnapshot(name string) Snapshot {
|
||||
name = getName(name)
|
||||
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
db := memoryDBs[name]
|
||||
memoryMtx.Unlock()
|
||||
|
||||
if db == nil {
|
||||
return Snapshot{}
|
||||
}
|
||||
|
||||
@@ -79,10 +79,10 @@ type mvccDB struct {
|
||||
|
||||
func (m *mvccDB) release() {
|
||||
memoryMtx.Lock()
|
||||
defer memoryMtx.Unlock()
|
||||
if m.refs--; m.refs == 0 && m == memoryDBs[m.name] {
|
||||
delete(memoryDBs, m.name)
|
||||
}
|
||||
memoryMtx.Unlock()
|
||||
}
|
||||
|
||||
type mvccFile struct {
|
||||
@@ -105,10 +105,10 @@ func (m *mvccFile) Close() error {
|
||||
m.data = nil
|
||||
m.lock = vfs.LOCK_NONE
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
if m.owner == m {
|
||||
m.owner = nil
|
||||
}
|
||||
m.mtx.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -313,10 +313,10 @@ func (m *mvccFile) CommitPhaseTwo() error {
|
||||
// Modified without lock, commit changes.
|
||||
if m.lock > vfs.LOCK_EXCLUSIVE {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
m.mvccDB.data = m.data
|
||||
m.lock = vfs.LOCK_NONE
|
||||
m.data = nil
|
||||
m.mtx.Unlock()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ var (
|
||||
// otherwise SQLite might return incorrect query results and/or [sqlite3.CORRUPT] errors.
|
||||
func Create(name string, reader ioutil.SizeReaderAt) {
|
||||
readerMtx.Lock()
|
||||
defer readerMtx.Unlock()
|
||||
readerDBs[name] = reader
|
||||
readerMtx.Unlock()
|
||||
}
|
||||
|
||||
// Delete deletes a shared memory database.
|
||||
func Delete(name string) {
|
||||
readerMtx.Lock()
|
||||
defer readerMtx.Unlock()
|
||||
delete(readerDBs, name)
|
||||
readerMtx.Unlock()
|
||||
}
|
||||
|
||||
@@ -3,16 +3,15 @@ package readervfs
|
||||
import (
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/util/ioutil"
|
||||
"github.com/ncruces/go-sqlite3/util/vfsutil"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
)
|
||||
|
||||
type readerVFS struct{}
|
||||
|
||||
func (readerVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||
// Temp journals, as used by the sorter, use SliceFile.
|
||||
if flags&vfs.OPEN_TEMP_JOURNAL != 0 {
|
||||
return &vfsutil.SliceFile{}, flags | vfs.OPEN_MEMORY, nil
|
||||
// Temporary files use the default VFS.
|
||||
if name == "" || flags&vfs.OPEN_DELETEONCLOSE != 0 {
|
||||
return vfs.Find("").Open(name, flags)
|
||||
}
|
||||
// Refuse to open all other file types.
|
||||
if flags&vfs.OPEN_MAIN_DB == 0 {
|
||||
|
||||
@@ -10,13 +10,17 @@ var (
|
||||
|
||||
// Find returns a VFS given its name.
|
||||
// If there is no match, nil is returned.
|
||||
// If name is empty, the default VFS is returned.
|
||||
// If name is empty or "os", the default VFS is returned.
|
||||
//
|
||||
// https://sqlite.org/c3ref/vfs_find.html
|
||||
func Find(name string) VFS {
|
||||
if name == "" || name == "os" {
|
||||
return vfsOS{}
|
||||
}
|
||||
return find(name)
|
||||
}
|
||||
|
||||
func find(name string) VFS {
|
||||
vfsRegistryMtx.RLock()
|
||||
defer vfsRegistryMtx.RUnlock()
|
||||
return vfsRegistry[name]
|
||||
@@ -31,11 +35,11 @@ func Register(name string, vfs VFS) {
|
||||
return
|
||||
}
|
||||
vfsRegistryMtx.Lock()
|
||||
defer vfsRegistryMtx.Unlock()
|
||||
if vfsRegistry == nil {
|
||||
vfsRegistry = map[string]VFS{}
|
||||
}
|
||||
vfsRegistry[name] = vfs
|
||||
vfsRegistryMtx.Unlock()
|
||||
}
|
||||
|
||||
// Unregister unregisters a VFS.
|
||||
@@ -43,6 +47,6 @@ func Register(name string, vfs VFS) {
|
||||
// https://sqlite.org/c3ref/vfs_find.html
|
||||
func Unregister(name string) {
|
||||
vfsRegistryMtx.Lock()
|
||||
defer vfsRegistryMtx.Unlock()
|
||||
delete(vfsRegistry, name)
|
||||
vfsRegistryMtx.Unlock()
|
||||
}
|
||||
|
||||
@@ -50,8 +50,7 @@ func ExportHostFunctions(env wazero.HostModuleBuilder) wazero.HostModuleBuilder
|
||||
}
|
||||
|
||||
func vfsFind(ctx context.Context, mod api.Module, zVfsName ptr_t) uint32 {
|
||||
name := util.ReadString(mod, zVfsName, _MAX_NAME)
|
||||
if vfs := Find(name); vfs != nil && vfs != (vfsOS{}) {
|
||||
if find(util.ReadString(mod, zVfsName, _MAX_NAME)) != nil {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
@@ -329,9 +328,9 @@ func vfsFileControlImpl(ctx context.Context, mod api.Module, file File, op _Fcnt
|
||||
|
||||
case _FCNTL_PRAGMA:
|
||||
if file, ok := file.(FilePragma); ok {
|
||||
var value string
|
||||
ptr := util.Read32[ptr_t](mod, pArg+1*ptrlen)
|
||||
name := util.ReadString(mod, ptr, _MAX_SQL_LENGTH)
|
||||
var value string
|
||||
if ptr := util.Read32[ptr_t](mod, pArg+2*ptrlen); ptr != 0 {
|
||||
value = util.ReadString(mod, ptr, _MAX_SQL_LENGTH)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user