Compare commits

...

23 Commits

Author SHA1 Message Date
dependabot[bot]
d4764fb2fa Bump golang.org/x/sys from 0.39.0 to 0.40.0 (#346)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.39.0 to 0.40.0.
- [Commits](https://github.com/golang/sys/compare/v0.39.0...v0.40.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.40.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-08 23:12:26 +00:00
dependabot[bot]
42df71f3ff Bump github.com/ncruces/wbt from 0.2.0 to 1.0.0 (#345)
Bumps [github.com/ncruces/wbt](https://github.com/ncruces/wbt) from 0.2.0 to 1.0.0.
- [Release notes](https://github.com/ncruces/wbt/releases)
- [Commits](https://github.com/ncruces/wbt/compare/v0.2.0...v1.0.0)

---
updated-dependencies:
- dependency-name: github.com/ncruces/wbt
  dependency-version: 1.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-06 23:55:44 +00:00
Nuno Cruces
91e969c06b Example. 2026-01-05 12:49:21 +00:00
Nuno Cruces
8ab0ddf53e More temp files. 2025-12-29 20:45:44 +00:00
Nuno Cruces
74d22ded0a Move Litestream. 2025-12-29 12:20:05 +00:00
Nuno Cruces
d962611796 Bump cross-platform-actions/action from 0.31.0 to 0.32.0 2025-12-21 13:39:41 +00:00
Nuno Cruces
7df3814c34 Improved driver metadata. 2025-12-20 22:23:05 +00:00
Nuno Cruces
c5f49b835a Reduce mutex scope, use temp files. 2025-12-19 16:37:47 +00:00
Nuno Cruces
0e55451a0b Deps. 2025-12-19 12:28:57 +00:00
Nuno Cruces
ea9a58ab19 Remove singleflight. 2025-12-17 13:17:50 +00:00
Nuno Cruces
0b46e74ea6 Bump cross-platform-actions/action from 0.30.0 to 0.31.0 2025-12-16 12:34:55 +00:00
Nuno Cruces
8dca850bee Stricter floats. 2025-12-12 17:21:33 +00:00
Nuno Cruces
5b78823416 ScanColumn dropped. 2025-12-12 13:06:37 +00:00
Nuno Cruces
1764a571da Litestream v0.5.3. 2025-12-12 12:44:57 +00:00
Nuno Cruces
9837310af7 Fuzz time shift. 2025-12-10 16:15:44 +00:00
dependabot[bot]
ec8961a621 Bump golang.org/x/crypto from 0.45.0 to 0.46.0 (#341)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.45.0 to 0.46.0.
- [Commits](https://github.com/golang/crypto/compare/v0.45.0...v0.46.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-09 00:49:00 +00:00
Nuno Cruces
8c37aa2d97 Remove inprocess primary. 2025-12-04 16:31:41 +00:00
Nuno Cruces
ca93c498e7 Relative time, fixes. 2025-12-04 15:55:39 +00:00
Nuno Cruces
15e9087fa8 Time travel pragma. 2025-12-03 15:01:04 +00:00
Nuno Cruces
7028e3a5b9 SQLite 3.51.1. 2025-11-30 10:24:34 +00:00
Nuno Cruces
03bb20de6e Lock tweaks. 2025-11-29 16:12:29 +00:00
Nuno Cruces
20a51a344e Remove MinLevel. 2025-11-27 17:29:47 +00:00
Nuno Cruces
2dbcc480f7 Initial pragma support. 2025-11-27 16:28:16 +00:00
50 changed files with 621 additions and 1352 deletions

View File

@@ -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'

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -1,6 +1,6 @@
# Embeddable Wasm build of SQLite
This folder includes an embeddable Wasm build of SQLite 3.50.4 for use with
This folder includes an embeddable Wasm build of SQLite 3.51.1 for use with
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
The following optional features are compiled in:

Binary file not shown.

View File

@@ -15,14 +15,15 @@ cp "$ROOT"/sqlite3/*.[ch] build/
cp "$ROOT"/sqlite3/*.patch build/
cd sqlite/
# https://sqlite.org/src/info/352b363a5d727047
curl -#L https://github.com/sqlite/sqlite/archive/dbd613c.tar.gz | tar xz --strip-components=1
# curl -#L https://sqlite.org/src/tarball/sqlite.tar.gz?r=352b363a5d | tar xz --strip-components=1
# https://sqlite.org/src/info/f273f6b8245c5dca
curl -#L https://github.com/sqlite/sqlite/archive/7c126d7.tar.gz | tar xz --strip-components=1
# curl -#L https://sqlite.org/src/tarball/sqlite.tar.gz?r=f273f6b824 | tar xz --strip-components=1
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 ~-

View File

@@ -2,11 +2,11 @@ module github.com/ncruces/go-sqlite3/embed/bcw2
go 1.24.0
require github.com/ncruces/go-sqlite3 v0.30.1
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
)

View File

@@ -1,12 +1,12 @@
github.com/ncruces/go-sqlite3 v0.30.1 h1:pHC3YsyRdJv4pCMB4MO1Q2BXw/CAa+Hoj7GSaKtVk+g=
github.com/ncruces/go-sqlite3 v0.30.1/go.mod h1:UVsWrQaq1qkcal5/vT5lOJnZCVlR5rsThKdwidjFsKc=
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=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
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=

View File

@@ -23,6 +23,7 @@ trap 'rm -f sqlite3.tmp' EXIT
-Wl,--import-undefined \
-Wl,--initial-memory=327680 \
-D_HAVE_SQLITE_CONFIG_H \
-DSQLITE_EXPERIMENTAL_PRAGMA_20251114 \
-DSQLITE_CUSTOM_INCLUDE=sqlite_opt.h \
$(awk '{print "-Wl,--export="$0}' exports.txt)

View File

@@ -19,7 +19,7 @@ func Test_init(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if version != "3.51.0" {
if version != "3.51.1" {
t.Error(version)
}
}

Binary file not shown.

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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:

View File

@@ -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)
}

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -3,7 +3,7 @@ module github.com/ncruces/go-sqlite3/gormlite
go 1.24.0
require (
github.com/ncruces/go-sqlite3 v0.30.1
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
)

View File

@@ -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.1 h1:pHC3YsyRdJv4pCMB4MO1Q2BXw/CAa+Hoj7GSaKtVk+g=
github.com/ncruces/go-sqlite3 v0.30.1/go.mod h1:UVsWrQaq1qkcal5/vT5lOJnZCVlR5rsThKdwidjFsKc=
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=

View File

@@ -1,27 +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` (and `MinLevel`) 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.

View File

@@ -1,99 +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
// Minimum compaction level to track.
MinLevel int
// 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 litestream.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 a client.
// If restore is not nil, the database is first restored.
func NewPrimary(ctx context.Context, path string, client litestream.ReplicaClient, restore *litestream.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
}

View File

@@ -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
}
}
}

View File

@@ -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()
}
}

View File

@@ -1,64 +0,0 @@
module github.com/ncruces/go-sqlite3/litestream
go 1.24.4
require (
github.com/benbjohnson/litestream v0.5.2
github.com/ncruces/go-sqlite3 v0.30.1
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-20240315230605-24e1d98cf361 // 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

View File

@@ -1,197 +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.2 h1:uD9I17n6RgUgyCwPM/Sw2YXNmMGixecUB5kmJ4FL08o=
github.com/benbjohnson/litestream v0.5.2/go.mod h1:jSW6AGqbxmJnEXGjMHchlZclGphzbJ6jGrGo5fYIDhU=
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.1 h1:pHC3YsyRdJv4pCMB4MO1Q2BXw/CAa+Hoj7GSaKtVk+g=
github.com/ncruces/go-sqlite3 v0.30.1/go.mod h1:UVsWrQaq1qkcal5/vT5lOJnZCVlR5rsThKdwidjFsKc=
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-20240315230605-24e1d98cf361 h1:vAKifIJuYY306ZJSrwDgKonWcJGELijdaenABqbV03E=
github.com/psanford/sqlite3vfs v0.0.0-20240315230605-24e1d98cf361/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/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=

View File

@@ -1,11 +0,0 @@
module modernc.org/sqlite
go 1.24.0
require github.com/ncruces/go-sqlite3 v0.30.1
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
)

View File

@@ -1,10 +0,0 @@
github.com/ncruces/go-sqlite3 v0.30.1 h1:pHC3YsyRdJv4pCMB4MO1Q2BXw/CAa+Hoj7GSaKtVk+g=
github.com/ncruces/go-sqlite3 v0.30.1/go.mod h1:UVsWrQaq1qkcal5/vT5lOJnZCVlR5rsThKdwidjFsKc=
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.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=

View File

@@ -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)
}

View File

@@ -1,309 +0,0 @@
package litestream
import (
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"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) 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 pollLevels(f.opts.MinLevel) {
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)
return nil
}
func pollLevels(minLevel int) (r []int) {
// Updating from lower to upper levels is non-racy,
// since LTX files are compacted into higher levels
// before the lower level LTX files are deleted.
// Also, only level 0 compactions and snapshots delete files,
// so the intermediate levels never need to be updated.
if minLevel <= 0 {
return append(r, 0, 1, litestream.SnapshotLevel)
}
if minLevel >= litestream.SnapshotLevel {
return append(r, litestream.SnapshotLevel)
}
return append(r, minLevel, litestream.SnapshotLevel)
}
// Type aliases; these are a mouthful.
type pageIndex = wbt.Tree[uint32, ltx.PageIndexElem]
type levelTXIDs = [litestream.SnapshotLevel + 1]ltx.TXID

View File

@@ -1,34 +0,0 @@
package litestream
import (
"slices"
"strconv"
"testing"
"github.com/benbjohnson/litestream"
_ "github.com/ncruces/go-sqlite3/embed"
)
func Test_pollLevels(t *testing.T) {
tests := []struct {
minLevel int
want []int
}{
{minLevel: -1, want: []int{0, 1, litestream.SnapshotLevel}},
{minLevel: 0, want: []int{0, 1, litestream.SnapshotLevel}},
{minLevel: 1, want: []int{1, litestream.SnapshotLevel}},
{minLevel: 2, want: []int{2, litestream.SnapshotLevel}},
{minLevel: 3, want: []int{3, litestream.SnapshotLevel}},
{minLevel: litestream.SnapshotLevel, want: []int{litestream.SnapshotLevel}},
{minLevel: litestream.SnapshotLevel + 1, want: []int{litestream.SnapshotLevel}},
}
for _, tt := range tests {
t.Run(strconv.Itoa(tt.minLevel), func(t *testing.T) {
got := pollLevels(tt.minLevel)
if !slices.Equal(got, tt.want) {
t.Errorf("pollLevels() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -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 {

View File

@@ -3,11 +3,11 @@ set -euo pipefail
cd -P -- "$(dirname -- "$0")"
curl -#OL "https://sqlite.org/2025/sqlite-autoconf-3510000.tar.gz"
curl -#OL "https://sqlite.org/2025/sqlite-autoconf-3510100.tar.gz"
# Verify download.
if hash=$(openssl dgst -sha3-256 sqlite-autoconf-*.tar.gz); then
if ! [[ $hash =~ fa52f9cc74dbca004aa650ae698036a3350611f672649e165078f4eae21d6a2e ]]; then
if ! [[ $hash =~ 9b2b1e73f577def1d5b75c5541555a7f42e6e073ad19f7a9118478389c9bbd9b ]]; then
echo $hash
exit 1
fi
@@ -23,7 +23,7 @@ mv sqlite-*/sqlite3.h .
mv sqlite-*/sqlite3ext.h .
rm -r sqlite-*
GITHUB_TAG="https://github.com/sqlite/sqlite/raw/version-3.51.0"
GITHUB_TAG="https://github.com/sqlite/sqlite/raw/version-3.51.1"
mkdir -p ext/
cd ext/

Binary file not shown.

View File

@@ -4,7 +4,8 @@
(type $2 (func (param i32 i32 i32 i32)))
(type $3 (func (param i32) (result i32)))
(memory $0 256)
(data $0 (i32.const 4096) "\01")
(data $.data (i32.const 4097) "\10\00\00\01\00\00\00\00\00\00\00\0c\10\00\00\0c\10\00\00\0c\10")
(data $.data.1 (i32.const 4157) "\10\00\00\00\10")
(table $0 1 1 funcref)
(export "memory" (memory $0))
(export "qsort" (func $qsort))
@@ -1493,33 +1494,243 @@
(local $2 i32)
(local $3 i32)
(local $4 i32)
(local $5 i32)
(local $5 v128)
(local $6 v128)
(local $7 v128)
(local $8 v128)
(local $9 v128)
(block $block
(if
(local.tee $3
(i32.load8_u
(local.get $1)
(block $block2
(block $block
(br_if $block
(i32.eqz
(local.tee $3
(i32.load8_u
(local.get $1)
)
)
)
)
(then
(br_if $block
(br_if $block
(i32.eqz
(i32.load8_u offset=1
(local.get $1)
)
)
)
(loop $label
(v128.store
(i32.const 4080)
(local.get $6)
)
(i32.store8
(i32.or
(local.tee $3
(i32.and
(local.tee $2
(i32.load8_u
(local.get $1)
)
)
(i32.const 15)
)
)
(i32.const 4080)
)
(i32.or
(i32.load8_u
(i32.or
(local.get $3)
(i32.const 4080)
)
)
(i32.shl
(i32.const 1)
(i32.sub
(local.tee $4
(i32.shr_u
(local.get $2)
(i32.const 4)
)
)
(i32.const 8)
)
)
)
)
(v128.store
(i32.const 4064)
(local.get $5)
)
(i32.store8
(local.tee $3
(i32.or
(local.get $3)
(i32.const 4064)
)
)
(i32.or
(i32.load8_u
(local.get $3)
)
(i32.shl
(i32.const 1)
(local.get $4)
)
)
)
(local.set $1
(i32.add
(local.get $1)
(i32.const 1)
)
)
(local.set $6
(v128.load
(i32.const 4080)
)
)
(local.set $5
(v128.load
(i32.const 4064)
)
)
(br_if $label
(local.get $2)
)
)
(block $block1
(if
(v128.any_true
(local.tee $7
(v128.and
(v128.or
(i8x16.swizzle
(local.get $6)
(v128.xor
(local.tee $8
(v128.and
(local.tee $7
(v128.load
(local.tee $2
(i32.and
(local.get $0)
(i32.const -16)
)
)
)
)
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
)
)
(v128.const i32x4 0x80808080 0x80808080 0x80808080 0x80808080)
)
)
(i8x16.swizzle
(local.get $5)
(local.get $8)
)
)
(i8x16.swizzle
(v128.const i32x4 0x08040201 0x80402010 0x08040201 0x80402010)
(i8x16.shr_u
(local.get $7)
(i32.const 4)
)
)
)
)
)
(then
(br_if $block1
(local.tee $1
(i32.and
(i32.xor
(i8x16.bitmask
(i8x16.eq
(local.get $7)
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
)
)
(i32.const 65535)
)
(i32.shl
(i32.const -1)
(i32.and
(local.get $0)
(i32.const 15)
)
)
)
)
)
)
)
(loop $label1
(br_if $label1
(i32.eqz
(v128.any_true
(local.tee $7
(v128.and
(v128.or
(i8x16.swizzle
(local.get $6)
(v128.xor
(local.tee $8
(v128.and
(local.tee $7
(v128.load
(local.tee $2
(i32.add
(local.get $2)
(i32.const 16)
)
)
)
)
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
)
)
(v128.const i32x4 0x80808080 0x80808080 0x80808080 0x80808080)
)
)
(i8x16.swizzle
(local.get $5)
(local.get $8)
)
)
(i8x16.swizzle
(v128.const i32x4 0x08040201 0x80402010 0x08040201 0x80402010)
(i8x16.shr_u
(local.get $7)
(i32.const 4)
)
)
)
)
)
)
)
)
(local.set $1
(i32.xor
(i8x16.bitmask
(i8x16.eq
(local.get $7)
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
)
)
(i32.const 65535)
)
)
)
(br $block2)
)
(block $block1
(block $block3
(if
(v128.any_true
(local.tee $6
(local.tee $5
(v128.or
(i8x16.eq
(local.tee $7
(local.tee $6
(v128.load
(local.tee $2
(i32.and
@@ -1532,8 +1743,8 @@
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
)
(i8x16.eq
(local.get $7)
(local.tee $7
(local.get $6)
(local.tee $6
(i8x16.splat
(local.get $3)
)
@@ -1543,11 +1754,11 @@
)
)
(then
(br_if $block1
(br_if $block3
(local.tee $1
(i32.and
(i8x16.bitmask
(local.get $6)
(local.get $5)
)
(i32.shl
(i32.const -1)
@@ -1561,14 +1772,14 @@
)
)
)
(loop $label
(br_if $label
(loop $label2
(br_if $label2
(i32.eqz
(v128.any_true
(local.tee $6
(local.tee $5
(v128.or
(i8x16.eq
(local.tee $6
(local.tee $5
(v128.load
(local.tee $2
(i32.add
@@ -1581,8 +1792,8 @@
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
)
(i8x16.eq
(local.get $5)
(local.get $6)
(local.get $7)
)
)
)
@@ -1592,230 +1803,10 @@
)
(local.set $1
(i8x16.bitmask
(local.get $6)
)
)
)
(return
(i32.add
(i32.ctz
(local.get $1)
)
(i32.sub
(local.get $2)
(local.get $0)
)
)
)
)
(local.set $4
(i32.and
(local.get $0)
(i32.const 15)
)
)
(loop $label1
(v128.store
(i32.const 4080)
(local.get $7)
)
(i32.store8
(i32.or
(local.tee $3
(i32.and
(local.tee $2
(i32.load8_u
(local.get $1)
)
)
(i32.const 15)
)
)
(i32.const 4080)
)
(i32.or
(i32.load8_u
(i32.or
(local.get $3)
(i32.const 4080)
)
)
(i32.shl
(i32.const 1)
(i32.sub
(local.tee $5
(i32.shr_u
(local.get $2)
(i32.const 4)
)
)
(i32.const 8)
)
)
)
)
(v128.store
(i32.const 4064)
(local.get $6)
)
(i32.store8
(local.tee $3
(i32.or
(local.get $3)
(i32.const 4064)
)
)
(i32.or
(i32.load8_u
(local.get $3)
)
(i32.shl
(i32.const 1)
(local.get $5)
)
)
)
(local.set $1
(i32.add
(local.get $1)
(i32.const 1)
)
)
(local.set $7
(v128.load
(i32.const 4080)
)
)
(local.set $6
(v128.load
(i32.const 4064)
)
)
(br_if $label1
(local.get $2)
)
)
(block $block2
(if
(v128.any_true
(local.tee $8
(v128.and
(v128.or
(i8x16.swizzle
(local.get $7)
(v128.xor
(local.tee $9
(v128.and
(local.tee $8
(v128.load
(local.tee $2
(i32.and
(local.get $0)
(i32.const -16)
)
)
)
)
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
)
)
(v128.const i32x4 0x80808080 0x80808080 0x80808080 0x80808080)
)
)
(i8x16.swizzle
(local.get $6)
(local.get $9)
)
)
(i8x16.swizzle
(v128.const i32x4 0x08040201 0x80402010 0x08040201 0x80402010)
(i8x16.shr_u
(local.get $8)
(i32.const 4)
)
)
)
)
)
(then
(br_if $block2
(local.tee $1
(i32.and
(i32.xor
(i8x16.bitmask
(i8x16.eq
(local.get $8)
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
)
)
(i32.const 65535)
)
(i32.shl
(i32.const -1)
(local.get $4)
)
)
)
)
)
)
(loop $label2
(br_if $label2
(i32.eqz
(v128.any_true
(local.tee $8
(v128.and
(v128.or
(i8x16.swizzle
(local.get $7)
(v128.xor
(local.tee $9
(v128.and
(local.tee $8
(v128.load
(local.tee $2
(i32.add
(local.get $2)
(i32.const 16)
)
)
)
)
(v128.const i32x4 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f 0x8f8f8f8f)
)
)
(v128.const i32x4 0x80808080 0x80808080 0x80808080 0x80808080)
)
)
(i8x16.swizzle
(local.get $6)
(local.get $9)
)
)
(i8x16.swizzle
(v128.const i32x4 0x08040201 0x80402010 0x08040201 0x80402010)
(i8x16.shr_u
(local.get $8)
(i32.const 4)
)
)
)
)
)
)
)
)
(local.set $1
(i32.xor
(i8x16.bitmask
(i8x16.eq
(local.get $8)
(v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000)
)
)
(i32.const 65535)
)
)
)
(i32.add
(i32.ctz

15
time.go
View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
}
})
}

View File

@@ -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).

View File

@@ -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" +

View File

@@ -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.

View File

@@ -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 {
@@ -223,7 +223,7 @@ func (m *memFile) Lock(lock vfs.LockLevel) error {
m.reserved = true
case vfs.LOCK_EXCLUSIVE:
if m.lock < vfs.LOCK_PENDING {
if m.lock == vfs.LOCK_RESERVED {
m.lock = vfs.LOCK_PENDING
m.pending = true
}

View File

@@ -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{}
}

View File

@@ -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
}
@@ -252,9 +252,8 @@ func (m *mvccFile) Lock(lock vfs.LockLevel) error {
}
defer time.AfterFunc(time.Millisecond, m.waiter.Broadcast).Stop()
for m.owner != nil {
// Our snapshot is invalid.
if m.data != nil && m.data != m.mvccDB.data {
return sqlite3.BUSY_SNAPSHOT
return sqlite3.BUSY_SNAPSHOT // Our snapshot is invalid.
}
if time.Since(before) > time.Millisecond {
return sqlite3.BUSY
@@ -266,17 +265,16 @@ func (m *mvccFile) Lock(lock vfs.LockLevel) error {
case m.data == nil:
m.data = m.mvccDB.data
case m.data != m.mvccDB.data:
// Our snapshot is invalid.
return sqlite3.BUSY_SNAPSHOT
return sqlite3.BUSY_SNAPSHOT // Our snapshot is invalid.
}
// Take ownership.
m.wrflag = false
m.lock = lock
m.owner = m
return nil
}
func (m *mvccFile) Unlock(lock vfs.LockLevel) error {
m.wrflag = false // SQLite calls unlock even if locking is unsuccessful.
if m.lock <= lock {
return nil
}
@@ -287,7 +285,9 @@ func (m *mvccFile) Unlock(lock vfs.LockLevel) error {
// Relase ownership, commit changes.
if m.owner == m {
m.owner = nil
m.mvccDB.data = m.data
if m.lock == vfs.LOCK_EXCLUSIVE {
m.mvccDB.data = m.data
}
if m.waiter != nil {
m.waiter.Broadcast()
}
@@ -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
}

View File

@@ -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()
}

View File

@@ -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 {

View File

@@ -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()
}

Binary file not shown.

View File

@@ -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)
}