mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-21 16:55:38 +03:00
[experiment] add alternative wasm sqlite3 implementation available via build-tag (#2863)
This allows for building GoToSocial with [SQLite transpiled to WASM](https://github.com/ncruces/go-sqlite3) and accessed through [Wazero](https://wazero.io/).
This commit is contained in:
parent
cce21c11cb
commit
1e7b32490d
398 changed files with 86174 additions and 684 deletions
5
go.mod
5
go.mod
|
@ -44,6 +44,7 @@ require (
|
||||||
github.com/miekg/dns v1.1.59
|
github.com/miekg/dns v1.1.59
|
||||||
github.com/minio/minio-go/v7 v7.0.70
|
github.com/minio/minio-go/v7 v7.0.70
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
|
github.com/ncruces/go-sqlite3 v0.16.0
|
||||||
github.com/oklog/ulid v1.3.1
|
github.com/oklog/ulid v1.3.1
|
||||||
github.com/prometheus/client_golang v1.19.1
|
github.com/prometheus/client_golang v1.19.1
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
|
@ -78,7 +79,7 @@ require (
|
||||||
golang.org/x/text v0.15.0
|
golang.org/x/text v0.15.0
|
||||||
gopkg.in/mcuadros/go-syslog.v2 v2.3.0
|
gopkg.in/mcuadros/go-syslog.v2 v2.3.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
modernc.org/sqlite v1.29.8
|
modernc.org/sqlite v0.0.0-00010101000000-000000000000
|
||||||
mvdan.cc/xurls/v2 v2.5.0
|
mvdan.cc/xurls/v2 v2.5.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -173,6 +174,7 @@ require (
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
|
github.com/ncruces/julianday v1.0.0 // indirect
|
||||||
github.com/opencontainers/runtime-spec v1.0.2 // indirect
|
github.com/opencontainers/runtime-spec v1.0.2 // indirect
|
||||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
@ -197,6 +199,7 @@ require (
|
||||||
github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe // indirect
|
github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe // indirect
|
||||||
github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB // indirect
|
github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB // indirect
|
||||||
github.com/tdewolff/parse/v2 v2.7.14 // indirect
|
github.com/tdewolff/parse/v2 v2.7.14 // indirect
|
||||||
|
github.com/tetratelabs/wazero v1.7.2 // indirect
|
||||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||||
github.com/toqueteos/webbrowser v1.2.0 // indirect
|
github.com/toqueteos/webbrowser v1.2.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -445,8 +445,12 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
|
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
|
||||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||||
|
github.com/ncruces/go-sqlite3 v0.16.0 h1:O7eULuEjvSBnS1QCN+dDL/ixLQZoUGWr466A02Gx1xc=
|
||||||
|
github.com/ncruces/go-sqlite3 v0.16.0/go.mod h1:2TmAeD93ImsKXJRsUIKohfMvt17dZSbS6pzJ3k6YYFg=
|
||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
|
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||||
|
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
|
@ -558,6 +562,8 @@ github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03
|
||||||
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||||
github.com/technologize/otel-go-contrib v1.1.1 h1:wZH9aSPNWZWIkEh3vfaKfMb15AJ80jJ1aVj/4GZdqIw=
|
github.com/technologize/otel-go-contrib v1.1.1 h1:wZH9aSPNWZWIkEh3vfaKfMb15AJ80jJ1aVj/4GZdqIw=
|
||||||
github.com/technologize/otel-go-contrib v1.1.1/go.mod h1:dCN/wj2WyUO8aFZFdIN+6tfJHImjTML/8r2YVYAy3So=
|
github.com/technologize/otel-go-contrib v1.1.1/go.mod h1:dCN/wj2WyUO8aFZFdIN+6tfJHImjTML/8r2YVYAy3So=
|
||||||
|
github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc=
|
||||||
|
github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
|
||||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E=
|
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E=
|
||||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
||||||
github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo=
|
github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo=
|
||||||
|
|
|
@ -38,7 +38,6 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||||
|
@ -77,19 +76,22 @@ type MediaCreateTestSuite struct {
|
||||||
TEST INFRASTRUCTURE
|
TEST INFRASTRUCTURE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (suite *MediaCreateTestSuite) SetupSuite() {
|
func (suite *MediaCreateTestSuite) SetupTest() {
|
||||||
suite.state.Caches.Init()
|
|
||||||
testrig.StartNoopWorkers(&suite.state)
|
testrig.StartNoopWorkers(&suite.state)
|
||||||
|
|
||||||
// setup standard items
|
// setup standard items
|
||||||
testrig.InitTestConfig()
|
testrig.InitTestConfig()
|
||||||
testrig.InitTestLog()
|
testrig.InitTestLog()
|
||||||
|
|
||||||
suite.db = testrig.NewTestDB(&suite.state)
|
suite.state.Caches.Init()
|
||||||
suite.state.DB = suite.db
|
|
||||||
suite.storage = testrig.NewInMemoryStorage()
|
suite.storage = testrig.NewInMemoryStorage()
|
||||||
suite.state.Storage = suite.storage
|
suite.state.Storage = suite.storage
|
||||||
|
|
||||||
|
suite.db = testrig.NewTestDB(&suite.state)
|
||||||
|
testrig.StandardDBSetup(suite.db, nil)
|
||||||
|
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||||
|
|
||||||
suite.tc = typeutils.NewConverter(&suite.state)
|
suite.tc = typeutils.NewConverter(&suite.state)
|
||||||
|
|
||||||
testrig.StartTimelines(
|
testrig.StartTimelines(
|
||||||
|
@ -106,21 +108,8 @@ func (suite *MediaCreateTestSuite) SetupSuite() {
|
||||||
|
|
||||||
// setup module being tested
|
// setup module being tested
|
||||||
suite.mediaModule = mediamodule.New(suite.processor)
|
suite.mediaModule = mediamodule.New(suite.processor)
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *MediaCreateTestSuite) TearDownSuite() {
|
|
||||||
if err := suite.db.Close(); err != nil {
|
|
||||||
log.Panicf(nil, "error closing db connection: %s", err)
|
|
||||||
}
|
|
||||||
testrig.StopWorkers(&suite.state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *MediaCreateTestSuite) SetupTest() {
|
|
||||||
suite.state.Caches.Init()
|
|
||||||
|
|
||||||
testrig.StandardDBSetup(suite.db, nil)
|
|
||||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
|
||||||
|
|
||||||
|
// setup test data
|
||||||
suite.testTokens = testrig.NewTestTokens()
|
suite.testTokens = testrig.NewTestTokens()
|
||||||
suite.testClients = testrig.NewTestClients()
|
suite.testClients = testrig.NewTestClients()
|
||||||
suite.testApplications = testrig.NewTestApplications()
|
suite.testApplications = testrig.NewTestApplications()
|
||||||
|
@ -132,6 +121,7 @@ func (suite *MediaCreateTestSuite) SetupTest() {
|
||||||
func (suite *MediaCreateTestSuite) TearDownTest() {
|
func (suite *MediaCreateTestSuite) TearDownTest() {
|
||||||
testrig.StandardDBTeardown(suite.db)
|
testrig.StandardDBTeardown(suite.db)
|
||||||
testrig.StandardStorageTeardown(suite.storage)
|
testrig.StandardStorageTeardown(suite.storage)
|
||||||
|
testrig.StopWorkers(&suite.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -36,7 +36,6 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||||
|
@ -75,18 +74,22 @@ type MediaUpdateTestSuite struct {
|
||||||
TEST INFRASTRUCTURE
|
TEST INFRASTRUCTURE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (suite *MediaUpdateTestSuite) SetupSuite() {
|
func (suite *MediaUpdateTestSuite) SetupTest() {
|
||||||
testrig.StartNoopWorkers(&suite.state)
|
testrig.StartNoopWorkers(&suite.state)
|
||||||
|
|
||||||
// setup standard items
|
// setup standard items
|
||||||
testrig.InitTestConfig()
|
testrig.InitTestConfig()
|
||||||
testrig.InitTestLog()
|
testrig.InitTestLog()
|
||||||
|
|
||||||
suite.db = testrig.NewTestDB(&suite.state)
|
suite.state.Caches.Init()
|
||||||
suite.state.DB = suite.db
|
|
||||||
suite.storage = testrig.NewInMemoryStorage()
|
suite.storage = testrig.NewInMemoryStorage()
|
||||||
suite.state.Storage = suite.storage
|
suite.state.Storage = suite.storage
|
||||||
|
|
||||||
|
suite.db = testrig.NewTestDB(&suite.state)
|
||||||
|
testrig.StandardDBSetup(suite.db, nil)
|
||||||
|
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
||||||
|
|
||||||
suite.tc = typeutils.NewConverter(&suite.state)
|
suite.tc = typeutils.NewConverter(&suite.state)
|
||||||
|
|
||||||
testrig.StartTimelines(
|
testrig.StartTimelines(
|
||||||
|
@ -103,21 +106,8 @@ func (suite *MediaUpdateTestSuite) SetupSuite() {
|
||||||
|
|
||||||
// setup module being tested
|
// setup module being tested
|
||||||
suite.mediaModule = mediamodule.New(suite.processor)
|
suite.mediaModule = mediamodule.New(suite.processor)
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *MediaUpdateTestSuite) TearDownSuite() {
|
|
||||||
if err := suite.db.Close(); err != nil {
|
|
||||||
log.Panicf(nil, "error closing db connection: %s", err)
|
|
||||||
}
|
|
||||||
testrig.StopWorkers(&suite.state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *MediaUpdateTestSuite) SetupTest() {
|
|
||||||
suite.state.Caches.Init()
|
|
||||||
|
|
||||||
testrig.StandardDBSetup(suite.db, nil)
|
|
||||||
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
|
|
||||||
|
|
||||||
|
// setup test data
|
||||||
suite.testTokens = testrig.NewTestTokens()
|
suite.testTokens = testrig.NewTestTokens()
|
||||||
suite.testClients = testrig.NewTestClients()
|
suite.testClients = testrig.NewTestClients()
|
||||||
suite.testApplications = testrig.NewTestApplications()
|
suite.testApplications = testrig.NewTestApplications()
|
||||||
|
@ -129,6 +119,7 @@ func (suite *MediaUpdateTestSuite) SetupTest() {
|
||||||
func (suite *MediaUpdateTestSuite) TearDownTest() {
|
func (suite *MediaUpdateTestSuite) TearDownTest() {
|
||||||
testrig.StandardDBTeardown(suite.db)
|
testrig.StandardDBTeardown(suite.db)
|
||||||
testrig.StandardStorageTeardown(suite.storage)
|
testrig.StandardStorageTeardown(suite.storage)
|
||||||
|
testrig.StopWorkers(&suite.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -70,8 +70,6 @@ func (suite *FileserverTestSuite) SetupSuite() {
|
||||||
testrig.InitTestConfig()
|
testrig.InitTestConfig()
|
||||||
testrig.InitTestLog()
|
testrig.InitTestLog()
|
||||||
|
|
||||||
suite.db = testrig.NewTestDB(&suite.state)
|
|
||||||
suite.state.DB = suite.db
|
|
||||||
suite.storage = testrig.NewInMemoryStorage()
|
suite.storage = testrig.NewInMemoryStorage()
|
||||||
suite.state.Storage = suite.storage
|
suite.state.Storage = suite.storage
|
||||||
|
|
||||||
|
@ -98,8 +96,12 @@ func (suite *FileserverTestSuite) SetupTest() {
|
||||||
suite.state.Caches.Init()
|
suite.state.Caches.Init()
|
||||||
testrig.StartNoopWorkers(&suite.state)
|
testrig.StartNoopWorkers(&suite.state)
|
||||||
|
|
||||||
|
suite.db = testrig.NewTestDB(&suite.state)
|
||||||
|
suite.state.DB = suite.db
|
||||||
|
|
||||||
testrig.StandardDBSetup(suite.db, nil)
|
testrig.StandardDBSetup(suite.db, nil)
|
||||||
testrig.StandardStorageSetup(suite.storage, "../../../testrig/media")
|
testrig.StandardStorageSetup(suite.storage, "../../../testrig/media")
|
||||||
|
|
||||||
suite.testTokens = testrig.NewTestTokens()
|
suite.testTokens = testrig.NewTestTokens()
|
||||||
suite.testClients = testrig.NewTestClients()
|
suite.testClients = testrig.NewTestClients()
|
||||||
suite.testApplications = testrig.NewTestApplications()
|
suite.testApplications = testrig.NewTestApplications()
|
||||||
|
|
|
@ -52,3 +52,9 @@ func LoadEarlyFlags(cmd *cobra.Command) error {
|
||||||
func BindFlags(cmd *cobra.Command) error {
|
func BindFlags(cmd *cobra.Command) error {
|
||||||
return global.BindFlags(cmd)
|
return global.BindFlags(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset will totally clear global
|
||||||
|
// ConfigState{}, loading defaults.
|
||||||
|
func Reset() {
|
||||||
|
global.Reset()
|
||||||
|
}
|
||||||
|
|
|
@ -37,25 +37,9 @@ type ConfigState struct { //nolint
|
||||||
|
|
||||||
// NewState returns a new initialized ConfigState instance.
|
// NewState returns a new initialized ConfigState instance.
|
||||||
func NewState() *ConfigState {
|
func NewState() *ConfigState {
|
||||||
viper := viper.New()
|
st := new(ConfigState)
|
||||||
|
st.Reset()
|
||||||
// Flag 'some-flag-name' becomes env var 'GTS_SOME_FLAG_NAME'
|
return st
|
||||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
|
||||||
viper.SetEnvPrefix("gts")
|
|
||||||
|
|
||||||
// Load appropriate named vals from env
|
|
||||||
viper.AutomaticEnv()
|
|
||||||
|
|
||||||
// Create new ConfigState with defaults
|
|
||||||
state := &ConfigState{
|
|
||||||
viper: viper,
|
|
||||||
config: Defaults,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform initial load into viper
|
|
||||||
state.reloadToViper()
|
|
||||||
|
|
||||||
return state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config provides safe access to the ConfigState's contained Configuration,
|
// Config provides safe access to the ConfigState's contained Configuration,
|
||||||
|
@ -116,6 +100,32 @@ func (st *ConfigState) Reload() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset will totally clear
|
||||||
|
// ConfigState{}, loading defaults.
|
||||||
|
func (st *ConfigState) Reset() {
|
||||||
|
// Do within lock.
|
||||||
|
st.mutex.Lock()
|
||||||
|
defer st.mutex.Unlock()
|
||||||
|
|
||||||
|
// Create new viper.
|
||||||
|
viper := viper.New()
|
||||||
|
|
||||||
|
// Flag 'some-flag-name' becomes env var 'GTS_SOME_FLAG_NAME'
|
||||||
|
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||||
|
viper.SetEnvPrefix("gts")
|
||||||
|
|
||||||
|
// Load appropriate
|
||||||
|
// named vals from env.
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
|
||||||
|
// Reset variables.
|
||||||
|
st.viper = viper
|
||||||
|
st.config = Defaults
|
||||||
|
|
||||||
|
// Load into viper.
|
||||||
|
st.reloadToViper()
|
||||||
|
}
|
||||||
|
|
||||||
// reloadToViper will reload Configuration{} values into viper.
|
// reloadToViper will reload Configuration{} values into viper.
|
||||||
func (st *ConfigState) reloadToViper() {
|
func (st *ConfigState) reloadToViper() {
|
||||||
raw, err := st.config.MarshalMap()
|
raw, err := st.config.MarshalMap()
|
||||||
|
|
|
@ -74,6 +74,7 @@ func (suite *AdminTestSuite) TestCreateInstanceAccount() {
|
||||||
// we need to take an empty db for this...
|
// we need to take an empty db for this...
|
||||||
testrig.StandardDBTeardown(suite.db)
|
testrig.StandardDBTeardown(suite.db)
|
||||||
// ...with tables created but no data
|
// ...with tables created but no data
|
||||||
|
suite.db = testrig.NewTestDB(&suite.state)
|
||||||
testrig.CreateTestTables(suite.db)
|
testrig.CreateTestTables(suite.db)
|
||||||
|
|
||||||
// make sure there's no instance account in the db yet
|
// make sure there's no instance account in the db yet
|
||||||
|
|
|
@ -48,8 +48,6 @@ import (
|
||||||
"github.com/uptrace/bun/dialect/pgdialect"
|
"github.com/uptrace/bun/dialect/pgdialect"
|
||||||
"github.com/uptrace/bun/dialect/sqlitedialect"
|
"github.com/uptrace/bun/dialect/sqlitedialect"
|
||||||
"github.com/uptrace/bun/migrate"
|
"github.com/uptrace/bun/migrate"
|
||||||
|
|
||||||
"modernc.org/sqlite"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DBService satisfies the DB interface
|
// DBService satisfies the DB interface
|
||||||
|
@ -133,12 +131,12 @@ func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) {
|
||||||
|
|
||||||
switch t {
|
switch t {
|
||||||
case "postgres":
|
case "postgres":
|
||||||
db, err = pgConn(ctx, state)
|
db, err = pgConn(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case "sqlite":
|
case "sqlite":
|
||||||
db, err = sqliteConn(ctx, state)
|
db, err = sqliteConn(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -295,7 +293,7 @@ func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) {
|
||||||
return ps, nil
|
return ps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func pgConn(ctx context.Context, state *state.State) (*bun.DB, error) {
|
func pgConn(ctx context.Context) (*bun.DB, error) {
|
||||||
opts, err := deriveBunDBPGOptions() //nolint:contextcheck
|
opts, err := deriveBunDBPGOptions() //nolint:contextcheck
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not create bundb postgres options: %w", err)
|
return nil, fmt.Errorf("could not create bundb postgres options: %w", err)
|
||||||
|
@ -326,7 +324,7 @@ func pgConn(ctx context.Context, state *state.State) (*bun.DB, error) {
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sqliteConn(ctx context.Context, state *state.State) (*bun.DB, error) {
|
func sqliteConn(ctx context.Context) (*bun.DB, error) {
|
||||||
// validate db address has actually been set
|
// validate db address has actually been set
|
||||||
address := config.GetDbAddress()
|
address := config.GetDbAddress()
|
||||||
if address == "" {
|
if address == "" {
|
||||||
|
@ -339,9 +337,6 @@ func sqliteConn(ctx context.Context, state *state.State) (*bun.DB, error) {
|
||||||
// Open new DB instance
|
// Open new DB instance
|
||||||
sqldb, err := sql.Open("sqlite-gts", address)
|
sqldb, err := sql.Open("sqlite-gts", address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errWithCode, ok := err.(*sqlite.Error); ok {
|
|
||||||
err = errors.New(sqlite.ErrorCodeString[errWithCode.Code()])
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("could not open sqlite db with address %s: %w", address, err)
|
return nil, fmt.Errorf("could not open sqlite db with address %s: %w", address, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,11 +351,9 @@ func sqliteConn(ctx context.Context, state *state.State) (*bun.DB, error) {
|
||||||
|
|
||||||
// ping to check the db is there and listening
|
// ping to check the db is there and listening
|
||||||
if err := db.PingContext(ctx); err != nil {
|
if err := db.PingContext(ctx); err != nil {
|
||||||
if errWithCode, ok := err.(*sqlite.Error); ok {
|
|
||||||
err = errors.New(sqlite.ErrorCodeString[errWithCode.Code()])
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("sqlite ping: %w", err)
|
return nil, fmt.Errorf("sqlite ping: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof(ctx, "connected to SQLITE database with address %s", address)
|
log.Infof(ctx, "connected to SQLITE database with address %s", address)
|
||||||
|
|
||||||
return db, nil
|
return db, nil
|
||||||
|
@ -528,12 +521,8 @@ func buildSQLiteAddress(addr string) string {
|
||||||
|
|
||||||
// Use random name for in-memory instead of ':memory:', so
|
// Use random name for in-memory instead of ':memory:', so
|
||||||
// multiple in-mem databases can be created without conflict.
|
// multiple in-mem databases can be created without conflict.
|
||||||
addr = uuid.NewString()
|
addr = "/" + uuid.NewString()
|
||||||
|
prefs.Add("vfs", "memdb")
|
||||||
// in-mem-specific preferences
|
|
||||||
// (shared cache so that tests don't fail)
|
|
||||||
prefs.Add("mode", "memory")
|
|
||||||
prefs.Add("cache", "shared")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if dur := config.GetDbSqliteBusyTimeout(); dur > 0 {
|
if dur := config.GetDbSqliteBusyTimeout(); dur > 0 {
|
||||||
|
|
|
@ -18,350 +18,14 @@
|
||||||
package bundb
|
package bundb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"database/sql/driver"
|
|
||||||
"time"
|
|
||||||
_ "unsafe" // linkname shenanigans
|
|
||||||
|
|
||||||
pgx "github.com/jackc/pgx/v5/stdlib"
|
"github.com/superseriousbusiness/gotosocial/internal/db/postgres"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db/sqlite"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
|
||||||
"modernc.org/sqlite"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// global SQL driver instances.
|
|
||||||
postgresDriver = pgx.GetDefaultDriver()
|
|
||||||
sqliteDriver = getSQLiteDriver()
|
|
||||||
|
|
||||||
// check the postgres connection
|
|
||||||
// conforms to our conn{} interface.
|
|
||||||
// (note SQLite doesn't export their
|
|
||||||
// conn type, and gets checked in
|
|
||||||
// tests very regularly anywho).
|
|
||||||
_ conn = (*pgx.Conn)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:linkname getSQLiteDriver modernc.org/sqlite.newDriver
|
|
||||||
func getSQLiteDriver() *sqlite.Driver
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
sql.Register("pgx-gts", &PostgreSQLDriver{})
|
// register our SQL driver implementations.
|
||||||
sql.Register("sqlite-gts", &SQLiteDriver{})
|
sql.Register("pgx-gts", &postgres.Driver{})
|
||||||
}
|
sql.Register("sqlite-gts", &sqlite.Driver{})
|
||||||
|
|
||||||
// PostgreSQLDriver is our own wrapper around the
|
|
||||||
// pgx/stdlib.Driver{} type in order to wrap further
|
|
||||||
// SQL driver types with our own err processing.
|
|
||||||
type PostgreSQLDriver struct{}
|
|
||||||
|
|
||||||
func (d *PostgreSQLDriver) Open(name string) (driver.Conn, error) {
|
|
||||||
c, err := postgresDriver.Open(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &PostgreSQLConn{conn: c.(conn)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type PostgreSQLConn struct{ conn }
|
|
||||||
|
|
||||||
func (c *PostgreSQLConn) Begin() (driver.Tx, error) {
|
|
||||||
return c.BeginTx(context.Background(), driver.TxOptions{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PostgreSQLConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
|
||||||
tx, err := c.conn.BeginTx(ctx, opts)
|
|
||||||
err = processPostgresError(err)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &PostgreSQLTx{tx}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PostgreSQLConn) Prepare(query string) (driver.Stmt, error) {
|
|
||||||
return c.PrepareContext(context.Background(), query)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PostgreSQLConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
|
||||||
st, err := c.conn.PrepareContext(ctx, query)
|
|
||||||
err = processPostgresError(err)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &PostgreSQLStmt{stmt: st.(stmt)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PostgreSQLConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
|
||||||
return c.ExecContext(context.Background(), query, toNamedValues(args))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PostgreSQLConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
|
||||||
result, err := c.conn.ExecContext(ctx, query, args)
|
|
||||||
err = processPostgresError(err)
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PostgreSQLConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
|
||||||
return c.QueryContext(context.Background(), query, toNamedValues(args))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PostgreSQLConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
|
||||||
rows, err := c.conn.QueryContext(ctx, query, args)
|
|
||||||
err = processPostgresError(err)
|
|
||||||
return rows, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PostgreSQLConn) Close() error {
|
|
||||||
return c.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PostgreSQLTx struct{ driver.Tx }
|
|
||||||
|
|
||||||
func (tx *PostgreSQLTx) Commit() error {
|
|
||||||
err := tx.Tx.Commit()
|
|
||||||
return processPostgresError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *PostgreSQLTx) Rollback() error {
|
|
||||||
err := tx.Tx.Rollback()
|
|
||||||
return processPostgresError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
type PostgreSQLStmt struct{ stmt }
|
|
||||||
|
|
||||||
func (stmt *PostgreSQLStmt) Exec(args []driver.Value) (driver.Result, error) {
|
|
||||||
return stmt.ExecContext(context.Background(), toNamedValues(args))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (stmt *PostgreSQLStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
|
||||||
res, err := stmt.stmt.ExecContext(ctx, args)
|
|
||||||
err = processPostgresError(err)
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (stmt *PostgreSQLStmt) Query(args []driver.Value) (driver.Rows, error) {
|
|
||||||
return stmt.QueryContext(context.Background(), toNamedValues(args))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (stmt *PostgreSQLStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
|
||||||
rows, err := stmt.stmt.QueryContext(ctx, args)
|
|
||||||
err = processPostgresError(err)
|
|
||||||
return rows, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SQLiteDriver is our own wrapper around the
|
|
||||||
// sqlite.Driver{} type in order to wrap further
|
|
||||||
// SQL driver types with our own functionality,
|
|
||||||
// e.g. hooks, retries and err processing.
|
|
||||||
type SQLiteDriver struct{}
|
|
||||||
|
|
||||||
func (d *SQLiteDriver) Open(name string) (driver.Conn, error) {
|
|
||||||
c, err := sqliteDriver.Open(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &SQLiteConn{conn: c.(conn)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type SQLiteConn struct{ conn }
|
|
||||||
|
|
||||||
func (c *SQLiteConn) Begin() (driver.Tx, error) {
|
|
||||||
return c.BeginTx(context.Background(), driver.TxOptions{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SQLiteConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx driver.Tx, err error) {
|
|
||||||
err = retryOnBusy(ctx, func() error {
|
|
||||||
tx, err = c.conn.BeginTx(ctx, opts)
|
|
||||||
err = processSQLiteError(err)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &SQLiteTx{Context: ctx, Tx: tx}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SQLiteConn) Prepare(query string) (driver.Stmt, error) {
|
|
||||||
return c.PrepareContext(context.Background(), query)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SQLiteConn) PrepareContext(ctx context.Context, query string) (st driver.Stmt, err error) {
|
|
||||||
err = retryOnBusy(ctx, func() error {
|
|
||||||
st, err = c.conn.PrepareContext(ctx, query)
|
|
||||||
err = processSQLiteError(err)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &SQLiteStmt{st.(stmt)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SQLiteConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
|
||||||
return c.ExecContext(context.Background(), query, toNamedValues(args))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SQLiteConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (result driver.Result, err error) {
|
|
||||||
err = retryOnBusy(ctx, func() error {
|
|
||||||
result, err = c.conn.ExecContext(ctx, query, args)
|
|
||||||
err = processSQLiteError(err)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SQLiteConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
|
||||||
return c.QueryContext(context.Background(), query, toNamedValues(args))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SQLiteConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) {
|
|
||||||
err = retryOnBusy(ctx, func() error {
|
|
||||||
rows, err = c.conn.QueryContext(ctx, query, args)
|
|
||||||
err = processSQLiteError(err)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SQLiteConn) Close() error {
|
|
||||||
// see: https://www.sqlite.org/pragma.html#pragma_optimize
|
|
||||||
const onClose = "PRAGMA analysis_limit=1000; PRAGMA optimize;"
|
|
||||||
_, _ = c.conn.ExecContext(context.Background(), onClose, nil)
|
|
||||||
return c.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
type SQLiteTx struct {
|
|
||||||
context.Context
|
|
||||||
driver.Tx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *SQLiteTx) Commit() (err error) {
|
|
||||||
err = retryOnBusy(tx.Context, func() error {
|
|
||||||
err = tx.Tx.Commit()
|
|
||||||
err = processSQLiteError(err)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *SQLiteTx) Rollback() (err error) {
|
|
||||||
err = retryOnBusy(tx.Context, func() error {
|
|
||||||
err = tx.Tx.Rollback()
|
|
||||||
err = processSQLiteError(err)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type SQLiteStmt struct{ stmt }
|
|
||||||
|
|
||||||
func (stmt *SQLiteStmt) Exec(args []driver.Value) (driver.Result, error) {
|
|
||||||
return stmt.ExecContext(context.Background(), toNamedValues(args))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (stmt *SQLiteStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) {
|
|
||||||
err = retryOnBusy(ctx, func() error {
|
|
||||||
res, err = stmt.stmt.ExecContext(ctx, args)
|
|
||||||
err = processSQLiteError(err)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (stmt *SQLiteStmt) Query(args []driver.Value) (driver.Rows, error) {
|
|
||||||
return stmt.QueryContext(context.Background(), toNamedValues(args))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (stmt *SQLiteStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) {
|
|
||||||
err = retryOnBusy(ctx, func() error {
|
|
||||||
rows, err = stmt.stmt.QueryContext(ctx, args)
|
|
||||||
err = processSQLiteError(err)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type conn interface {
|
|
||||||
driver.Conn
|
|
||||||
driver.ConnPrepareContext
|
|
||||||
driver.ExecerContext
|
|
||||||
driver.QueryerContext
|
|
||||||
driver.ConnBeginTx
|
|
||||||
}
|
|
||||||
|
|
||||||
type stmt interface {
|
|
||||||
driver.Stmt
|
|
||||||
driver.StmtExecContext
|
|
||||||
driver.StmtQueryContext
|
|
||||||
}
|
|
||||||
|
|
||||||
// retryOnBusy will retry given function on returned 'errBusy'.
|
|
||||||
func retryOnBusy(ctx context.Context, fn func() error) error {
|
|
||||||
if err := fn(); err != errBusy {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return retryOnBusySlow(ctx, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// retryOnBusySlow is the outlined form of retryOnBusy, to allow the fast path (i.e. only
|
|
||||||
// 1 attempt) to be inlined, leaving the slow retry loop to be a separate function call.
|
|
||||||
func retryOnBusySlow(ctx context.Context, fn func() error) error {
|
|
||||||
var backoff time.Duration
|
|
||||||
|
|
||||||
for i := 0; ; i++ {
|
|
||||||
// backoff according to a multiplier of 2ms * 2^2n,
|
|
||||||
// up to a maximum possible backoff time of 5 minutes.
|
|
||||||
//
|
|
||||||
// this works out as the following:
|
|
||||||
// 4ms
|
|
||||||
// 16ms
|
|
||||||
// 64ms
|
|
||||||
// 256ms
|
|
||||||
// 1.024s
|
|
||||||
// 4.096s
|
|
||||||
// 16.384s
|
|
||||||
// 1m5.536s
|
|
||||||
// 4m22.144s
|
|
||||||
backoff = 2 * time.Millisecond * (1 << (2*i + 1))
|
|
||||||
if backoff >= 5*time.Minute {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
// Context cancelled.
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
|
|
||||||
// Backoff for some time.
|
|
||||||
case <-time.After(backoff):
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform func.
|
|
||||||
err := fn()
|
|
||||||
|
|
||||||
if err != errBusy {
|
|
||||||
// May be nil, or may be
|
|
||||||
// some other error, either
|
|
||||||
// way return here.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return gtserror.Newf("%w (waited > %s)", db.ErrBusyTimeout, backoff)
|
|
||||||
}
|
|
||||||
|
|
||||||
// toNamedValues converts older driver.Value types to driver.NamedValue types.
|
|
||||||
func toNamedValues(args []driver.Value) []driver.NamedValue {
|
|
||||||
if args == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
args2 := make([]driver.NamedValue, len(args))
|
|
||||||
for i := range args {
|
|
||||||
args2[i] = driver.NamedValue{
|
|
||||||
Ordinal: i + 1,
|
|
||||||
Value: args[i],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return args2
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
// GoToSocial
|
|
||||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package bundb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql/driver"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
|
||||||
"modernc.org/sqlite"
|
|
||||||
sqlite3 "modernc.org/sqlite/lib"
|
|
||||||
)
|
|
||||||
|
|
||||||
// errBusy is a sentinel error indicating
|
|
||||||
// busy database (e.g. retry needed).
|
|
||||||
var errBusy = errors.New("busy")
|
|
||||||
|
|
||||||
// processPostgresError processes an error, replacing any postgres specific errors with our own error type
|
|
||||||
func processPostgresError(err error) error {
|
|
||||||
// Catch nil errs.
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to cast as postgres
|
|
||||||
pgErr, ok := err.(*pgconn.PgError)
|
|
||||||
if !ok {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle supplied error code:
|
|
||||||
// (https://www.postgresql.org/docs/10/errcodes-appendix.html)
|
|
||||||
switch pgErr.Code { //nolint
|
|
||||||
case "23505" /* unique_violation */ :
|
|
||||||
return db.ErrAlreadyExists
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// processSQLiteError processes an error, replacing any sqlite specific errors with our own error type
|
|
||||||
func processSQLiteError(err error) error {
|
|
||||||
// Catch nil errs.
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to cast as sqlite
|
|
||||||
sqliteErr, ok := err.(*sqlite.Error)
|
|
||||||
if !ok {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle supplied error code:
|
|
||||||
switch sqliteErr.Code() {
|
|
||||||
case sqlite3.SQLITE_CONSTRAINT_UNIQUE,
|
|
||||||
sqlite3.SQLITE_CONSTRAINT_PRIMARYKEY:
|
|
||||||
return db.ErrAlreadyExists
|
|
||||||
case sqlite3.SQLITE_BUSY,
|
|
||||||
sqlite3.SQLITE_BUSY_SNAPSHOT,
|
|
||||||
sqlite3.SQLITE_BUSY_RECOVERY:
|
|
||||||
return errBusy
|
|
||||||
case sqlite3.SQLITE_BUSY_TIMEOUT:
|
|
||||||
return db.ErrBusyTimeout
|
|
||||||
|
|
||||||
// WORKAROUND:
|
|
||||||
// text copied from matrix dev chat:
|
|
||||||
//
|
|
||||||
// okay i've found a workaround for now. so between
|
|
||||||
// v1.29.0 and v1.29.2 (modernc.org/sqlite) is that
|
|
||||||
// slightly tweaked interruptOnDone() behaviour, which
|
|
||||||
// causes interrupt to (imo, correctly) get called when
|
|
||||||
// a context is cancelled to cancel the running query. the
|
|
||||||
// issue is that every single query after that point seems
|
|
||||||
// to still then return interrupted. so as you thought,
|
|
||||||
// maybe that query count isn't being decremented. i don't
|
|
||||||
// think it's our code, but i haven't ruled it out yet.
|
|
||||||
//
|
|
||||||
// the workaround for now is adding to our sqlite error
|
|
||||||
// processor to replace an SQLITE_INTERRUPTED code with
|
|
||||||
// driver.ErrBadConn, which hints to the golang sql package
|
|
||||||
// that the conn needs to be closed and a new one opened
|
|
||||||
//
|
|
||||||
case sqlite3.SQLITE_INTERRUPT:
|
|
||||||
return driver.ErrBadConn
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -19,6 +19,7 @@ package bundb_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
@ -82,10 +83,20 @@ func (suite *TagTestSuite) TestPutTag() {
|
||||||
|
|
||||||
// Subsequent inserts should fail
|
// Subsequent inserts should fail
|
||||||
// since all these tags are equivalent.
|
// since all these tags are equivalent.
|
||||||
suite.ErrorIs(err, db.ErrAlreadyExists)
|
if !suite.ErrorIs(err, db.ErrAlreadyExists) {
|
||||||
|
suite.T().Logf("%T(%v) %v", err, err, unwrap(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTagTestSuite(t *testing.T) {
|
func TestTagTestSuite(t *testing.T) {
|
||||||
suite.Run(t, new(TagTestSuite))
|
suite.Run(t, new(TagTestSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unwrap(err error) (errs []error) {
|
||||||
|
for err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
err = errors.Unwrap(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -29,8 +29,4 @@ var (
|
||||||
|
|
||||||
// ErrAlreadyExists is returned when a conflict was encountered in the db when doing an insert.
|
// ErrAlreadyExists is returned when a conflict was encountered in the db when doing an insert.
|
||||||
ErrAlreadyExists = errors.New("already exists")
|
ErrAlreadyExists = errors.New("already exists")
|
||||||
|
|
||||||
// ErrBusyTimeout is returned if the database connection indicates the connection is too busy
|
|
||||||
// to complete the supplied query. This is generally intended to be handled internally by the DB.
|
|
||||||
ErrBusyTimeout = errors.New("busy timeout")
|
|
||||||
)
|
)
|
||||||
|
|
209
internal/db/postgres/driver.go
Normal file
209
internal/db/postgres/driver.go
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
// GoToSocial
|
||||||
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
|
||||||
|
pgx "github.com/jackc/pgx/v5/stdlib"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// global PostgreSQL driver instances.
|
||||||
|
postgresDriver = pgx.GetDefaultDriver().(*pgx.Driver)
|
||||||
|
|
||||||
|
// check the postgres driver types
|
||||||
|
// conforms to our interface types.
|
||||||
|
// (note SQLite doesn't export their
|
||||||
|
// driver types, and gets checked in
|
||||||
|
// tests very regularly anywho).
|
||||||
|
_ connIface = (*pgx.Conn)(nil)
|
||||||
|
_ stmtIface = (*pgx.Stmt)(nil)
|
||||||
|
_ rowsIface = (*pgx.Rows)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Driver is our own wrapper around the
|
||||||
|
// pgx/stdlib.Driver{} type in order to wrap further
|
||||||
|
// SQL driver types with our own err processing.
|
||||||
|
type Driver struct{}
|
||||||
|
|
||||||
|
func (d *Driver) Open(name string) (driver.Conn, error) {
|
||||||
|
conn, err := postgresDriver.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
err = processPostgresError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &postgresConn{conn.(connIface)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) OpenConnector(name string) (driver.Connector, error) {
|
||||||
|
cc, err := postgresDriver.OpenConnector(name)
|
||||||
|
if err != nil {
|
||||||
|
err = processPostgresError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &postgresConnector{driver: d, Connector: cc}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type postgresConnector struct {
|
||||||
|
driver *Driver
|
||||||
|
driver.Connector
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *postgresConnector) Driver() driver.Driver { return c.driver }
|
||||||
|
|
||||||
|
func (c *postgresConnector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
|
conn, err := c.Connector.Connect(ctx)
|
||||||
|
if err != nil {
|
||||||
|
err = processPostgresError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &postgresConn{conn.(connIface)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type postgresConn struct{ connIface }
|
||||||
|
|
||||||
|
func (c *postgresConn) Begin() (driver.Tx, error) {
|
||||||
|
return c.BeginTx(context.Background(), driver.TxOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *postgresConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
||||||
|
tx, err := c.connIface.BeginTx(ctx, opts)
|
||||||
|
err = processPostgresError(err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &postgresTx{tx}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *postgresConn) Prepare(query string) (driver.Stmt, error) {
|
||||||
|
return c.PrepareContext(context.Background(), query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *postgresConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
||||||
|
st, err := c.connIface.PrepareContext(ctx, query)
|
||||||
|
err = processPostgresError(err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &postgresStmt{st.(stmtIface)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *postgresConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
||||||
|
return c.ExecContext(context.Background(), query, db.ToNamedValues(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *postgresConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
result, err := c.connIface.ExecContext(ctx, query, args)
|
||||||
|
err = processPostgresError(err)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *postgresConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
||||||
|
return c.QueryContext(context.Background(), query, db.ToNamedValues(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *postgresConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
rows, err := c.connIface.QueryContext(ctx, query, args)
|
||||||
|
err = processPostgresError(err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &postgresRows{rows.(rowsIface)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *postgresConn) Close() error {
|
||||||
|
err := c.connIface.Close()
|
||||||
|
return processPostgresError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type postgresTx struct{ driver.Tx }
|
||||||
|
|
||||||
|
func (tx *postgresTx) Commit() error {
|
||||||
|
err := tx.Tx.Commit()
|
||||||
|
return processPostgresError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *postgresTx) Rollback() error {
|
||||||
|
err := tx.Tx.Rollback()
|
||||||
|
return processPostgresError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type postgresStmt struct{ stmtIface }
|
||||||
|
|
||||||
|
func (stmt *postgresStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||||
|
return stmt.ExecContext(context.Background(), db.ToNamedValues(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *postgresStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
res, err := stmt.stmtIface.ExecContext(ctx, args)
|
||||||
|
err = processPostgresError(err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *postgresStmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||||
|
return stmt.QueryContext(context.Background(), db.ToNamedValues(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *postgresStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
rows, err := stmt.stmtIface.QueryContext(ctx, args)
|
||||||
|
err = processPostgresError(err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &postgresRows{rows.(rowsIface)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type postgresRows struct{ rowsIface }
|
||||||
|
|
||||||
|
func (r *postgresRows) Next(dest []driver.Value) error {
|
||||||
|
err := r.rowsIface.Next(dest)
|
||||||
|
err = processPostgresError(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *postgresRows) Close() error {
|
||||||
|
err := r.rowsIface.Close()
|
||||||
|
err = processPostgresError(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type connIface interface {
|
||||||
|
driver.Conn
|
||||||
|
driver.ConnPrepareContext
|
||||||
|
driver.ExecerContext
|
||||||
|
driver.QueryerContext
|
||||||
|
driver.ConnBeginTx
|
||||||
|
}
|
||||||
|
|
||||||
|
type stmtIface interface {
|
||||||
|
driver.Stmt
|
||||||
|
driver.StmtExecContext
|
||||||
|
driver.StmtQueryContext
|
||||||
|
}
|
||||||
|
|
||||||
|
type rowsIface interface {
|
||||||
|
driver.Rows
|
||||||
|
driver.RowsColumnTypeDatabaseTypeName
|
||||||
|
driver.RowsColumnTypeLength
|
||||||
|
driver.RowsColumnTypePrecisionScale
|
||||||
|
driver.RowsColumnTypeScanType
|
||||||
|
driver.RowsColumnTypeScanType
|
||||||
|
}
|
46
internal/db/postgres/errors.go
Normal file
46
internal/db/postgres/errors.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// GoToSocial
|
||||||
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// processPostgresError processes an error, replacing any
|
||||||
|
// postgres specific errors with our own error type
|
||||||
|
func processPostgresError(err error) error {
|
||||||
|
// Attempt to cast as postgres
|
||||||
|
pgErr, ok := err.(*pgconn.PgError)
|
||||||
|
if !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle supplied error code:
|
||||||
|
// (https://www.postgresql.org/docs/10/errcodes-appendix.html)
|
||||||
|
switch pgErr.Code { //nolint
|
||||||
|
case "23505" /* unique_violation */ :
|
||||||
|
return db.ErrAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the returned error with the code and
|
||||||
|
// extended code for easier debugging later.
|
||||||
|
return fmt.Errorf("%w (code=%s)", err, pgErr.Code)
|
||||||
|
}
|
197
internal/db/sqlite/driver.go
Normal file
197
internal/db/sqlite/driver.go
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
// GoToSocial
|
||||||
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//go:build !wasmsqlite3
|
||||||
|
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
|
||||||
|
"modernc.org/sqlite"
|
||||||
|
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Driver is our own wrapper around the
|
||||||
|
// sqlite.Driver{} type in order to wrap
|
||||||
|
// further SQL types with our own
|
||||||
|
// functionality, e.g. err processing.
|
||||||
|
type Driver struct{ sqlite.Driver }
|
||||||
|
|
||||||
|
func (d *Driver) Open(name string) (driver.Conn, error) {
|
||||||
|
conn, err := d.Driver.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sqliteConn{conn.(connIface)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteConn struct{ connIface }
|
||||||
|
|
||||||
|
func (c *sqliteConn) Begin() (driver.Tx, error) {
|
||||||
|
return c.BeginTx(context.Background(), driver.TxOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx driver.Tx, err error) {
|
||||||
|
tx, err = c.connIface.BeginTx(ctx, opts)
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sqliteTx{tx}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConn) Prepare(query string) (driver.Stmt, error) {
|
||||||
|
return c.PrepareContext(context.Background(), query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) {
|
||||||
|
stmt, err = c.connIface.PrepareContext(ctx, query)
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sqliteStmt{stmtIface: stmt.(stmtIface)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
||||||
|
return c.ExecContext(context.Background(), query, db.ToNamedValues(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (res driver.Result, err error) {
|
||||||
|
res, err = c.connIface.ExecContext(ctx, query, args)
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
||||||
|
return c.QueryContext(context.Background(), query, db.ToNamedValues(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) {
|
||||||
|
rows, err = c.connIface.QueryContext(ctx, query, args)
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sqliteRows{rows.(rowsIface)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConn) Close() (err error) {
|
||||||
|
// see: https://www.sqlite.org/pragma.html#pragma_optimize
|
||||||
|
const onClose = "PRAGMA analysis_limit=1000; PRAGMA optimize;"
|
||||||
|
_, _ = c.connIface.ExecContext(context.Background(), onClose, nil)
|
||||||
|
|
||||||
|
// Finally, close the conn.
|
||||||
|
err = c.connIface.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteTx struct{ driver.Tx }
|
||||||
|
|
||||||
|
func (tx *sqliteTx) Commit() (err error) {
|
||||||
|
err = tx.Tx.Commit()
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *sqliteTx) Rollback() (err error) {
|
||||||
|
err = tx.Tx.Rollback()
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteStmt struct{ stmtIface }
|
||||||
|
|
||||||
|
func (stmt *sqliteStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||||
|
return stmt.ExecContext(context.Background(), db.ToNamedValues(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *sqliteStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) {
|
||||||
|
res, err = stmt.stmtIface.ExecContext(ctx, args)
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *sqliteStmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||||
|
return stmt.QueryContext(context.Background(), db.ToNamedValues(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *sqliteStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) {
|
||||||
|
rows, err = stmt.stmtIface.QueryContext(ctx, args)
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sqliteRows{rows.(rowsIface)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *sqliteStmt) Close() (err error) {
|
||||||
|
err = stmt.stmtIface.Close()
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteRows struct{ rowsIface }
|
||||||
|
|
||||||
|
func (r *sqliteRows) Next(dest []driver.Value) (err error) {
|
||||||
|
err = r.rowsIface.Next(dest)
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteRows) Close() (err error) {
|
||||||
|
err = r.rowsIface.Close()
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// connIface is the driver.Conn interface
|
||||||
|
// types (and the like) that modernc.org/sqlite.conn
|
||||||
|
// conforms to. Useful so you don't need
|
||||||
|
// to repeatedly perform checks yourself.
|
||||||
|
type connIface interface {
|
||||||
|
driver.Conn
|
||||||
|
driver.ConnBeginTx
|
||||||
|
driver.ConnPrepareContext
|
||||||
|
driver.ExecerContext
|
||||||
|
driver.QueryerContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// StmtIface is the driver.Stmt interface
|
||||||
|
// types (and the like) that modernc.org/sqlite.stmt
|
||||||
|
// conforms to. Useful so you don't need
|
||||||
|
// to repeatedly perform checks yourself.
|
||||||
|
type stmtIface interface {
|
||||||
|
driver.Stmt
|
||||||
|
driver.StmtExecContext
|
||||||
|
driver.StmtQueryContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// RowsIface is the driver.Rows interface
|
||||||
|
// types (and the like) that modernc.org/sqlite.rows
|
||||||
|
// conforms to. Useful so you don't need
|
||||||
|
// to repeatedly perform checks yourself.
|
||||||
|
type rowsIface interface {
|
||||||
|
driver.Rows
|
||||||
|
driver.RowsColumnTypeDatabaseTypeName
|
||||||
|
driver.RowsColumnTypeLength
|
||||||
|
driver.RowsColumnTypeScanType
|
||||||
|
}
|
211
internal/db/sqlite/driver_wasmsqlite3.go
Normal file
211
internal/db/sqlite/driver_wasmsqlite3.go
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
// GoToSocial
|
||||||
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//go:build wasmsqlite3
|
||||||
|
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3"
|
||||||
|
sqlite3driver "github.com/ncruces/go-sqlite3/driver"
|
||||||
|
_ "github.com/ncruces/go-sqlite3/embed" // embed wasm binary
|
||||||
|
_ "github.com/ncruces/go-sqlite3/vfs/memdb" // include memdb vfs
|
||||||
|
)
|
||||||
|
|
||||||
|
// Driver is our own wrapper around the
|
||||||
|
// driver.SQLite{} type in order to wrap
|
||||||
|
// further SQL types with our own
|
||||||
|
// functionality, e.g. err processing.
|
||||||
|
type Driver struct{ sqlite3driver.SQLite }
|
||||||
|
|
||||||
|
func (d *Driver) Open(name string) (driver.Conn, error) {
|
||||||
|
conn, err := d.SQLite.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sqliteConn{conn.(connIface)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) OpenConnector(name string) (driver.Connector, error) {
|
||||||
|
cc, err := d.SQLite.OpenConnector(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sqliteConnector{driver: d, Connector: cc}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteConnector struct {
|
||||||
|
driver *Driver
|
||||||
|
driver.Connector
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConnector) Driver() driver.Driver { return c.driver }
|
||||||
|
|
||||||
|
func (c *sqliteConnector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
|
conn, err := c.Connector.Connect(ctx)
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sqliteConn{conn.(connIface)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteConn struct{ connIface }
|
||||||
|
|
||||||
|
func (c *sqliteConn) Begin() (driver.Tx, error) {
|
||||||
|
return c.BeginTx(context.Background(), driver.TxOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx driver.Tx, err error) {
|
||||||
|
tx, err = c.connIface.BeginTx(ctx, opts)
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sqliteTx{tx}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConn) Prepare(query string) (driver.Stmt, error) {
|
||||||
|
return c.PrepareContext(context.Background(), query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) {
|
||||||
|
stmt, err = c.connIface.PrepareContext(ctx, query)
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sqliteStmt{stmtIface: stmt.(stmtIface)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
||||||
|
return c.ExecContext(context.Background(), query, db.ToNamedValues(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (res driver.Result, err error) {
|
||||||
|
res, err = c.connIface.ExecContext(ctx, query, args)
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sqliteConn) Close() (err error) {
|
||||||
|
// Get acces the underlying raw sqlite3 conn.
|
||||||
|
raw := c.connIface.(sqlite3.DriverConn).Raw()
|
||||||
|
|
||||||
|
// see: https://www.sqlite.org/pragma.html#pragma_optimize
|
||||||
|
const onClose = "PRAGMA analysis_limit=1000; PRAGMA optimize;"
|
||||||
|
_ = raw.Exec(onClose)
|
||||||
|
|
||||||
|
// Finally, close.
|
||||||
|
err = raw.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteTx struct{ driver.Tx }
|
||||||
|
|
||||||
|
func (tx *sqliteTx) Commit() (err error) {
|
||||||
|
err = tx.Tx.Commit()
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *sqliteTx) Rollback() (err error) {
|
||||||
|
err = tx.Tx.Rollback()
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteStmt struct{ stmtIface }
|
||||||
|
|
||||||
|
func (stmt *sqliteStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||||
|
return stmt.ExecContext(context.Background(), db.ToNamedValues(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *sqliteStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) {
|
||||||
|
res, err = stmt.stmtIface.ExecContext(ctx, args)
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *sqliteStmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||||
|
return stmt.QueryContext(context.Background(), db.ToNamedValues(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *sqliteStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) {
|
||||||
|
rows, err = stmt.stmtIface.QueryContext(ctx, args)
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sqliteRows{rows.(rowsIface)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *sqliteStmt) Close() (err error) {
|
||||||
|
err = stmt.stmtIface.Close()
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteRows struct{ rowsIface }
|
||||||
|
|
||||||
|
func (r *sqliteRows) Next(dest []driver.Value) (err error) {
|
||||||
|
err = r.rowsIface.Next(dest)
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteRows) Close() (err error) {
|
||||||
|
err = r.rowsIface.Close()
|
||||||
|
err = processSQLiteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// connIface is the driver.Conn interface
|
||||||
|
// types (and the like) that go-sqlite3/driver.conn
|
||||||
|
// conforms to. Useful so you don't need
|
||||||
|
// to repeatedly perform checks yourself.
|
||||||
|
type connIface interface {
|
||||||
|
driver.Conn
|
||||||
|
driver.ConnBeginTx
|
||||||
|
driver.ConnPrepareContext
|
||||||
|
driver.ExecerContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// StmtIface is the driver.Stmt interface
|
||||||
|
// types (and the like) that go-sqlite3/driver.stmt
|
||||||
|
// conforms to. Useful so you don't need
|
||||||
|
// to repeatedly perform checks yourself.
|
||||||
|
type stmtIface interface {
|
||||||
|
driver.Stmt
|
||||||
|
driver.StmtExecContext
|
||||||
|
driver.StmtQueryContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// RowsIface is the driver.Rows interface
|
||||||
|
// types (and the like) that go-sqlite3/driver.rows
|
||||||
|
// conforms to. Useful so you don't need
|
||||||
|
// to repeatedly perform checks yourself.
|
||||||
|
type rowsIface interface {
|
||||||
|
driver.Rows
|
||||||
|
driver.RowsColumnTypeDatabaseTypeName
|
||||||
|
}
|
62
internal/db/sqlite/errors.go
Normal file
62
internal/db/sqlite/errors.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
// GoToSocial
|
||||||
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//go:build !wasmsqlite3
|
||||||
|
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"modernc.org/sqlite"
|
||||||
|
sqlite3 "modernc.org/sqlite/lib"
|
||||||
|
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// processSQLiteError processes an sqlite3.Error to
|
||||||
|
// handle conversion to any of our common db types.
|
||||||
|
func processSQLiteError(err error) error {
|
||||||
|
// Attempt to cast as sqlite error.
|
||||||
|
sqliteErr, ok := err.(*sqlite.Error)
|
||||||
|
if !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle supplied error code:
|
||||||
|
switch sqliteErr.Code() {
|
||||||
|
case sqlite3.SQLITE_CONSTRAINT_UNIQUE,
|
||||||
|
sqlite3.SQLITE_CONSTRAINT_PRIMARYKEY:
|
||||||
|
return db.ErrAlreadyExists
|
||||||
|
|
||||||
|
// Busy should be very rare, but
|
||||||
|
// on busy tell the database to close
|
||||||
|
// the connection, re-open and re-attempt
|
||||||
|
// which should give a necessary timeout.
|
||||||
|
case sqlite3.SQLITE_BUSY,
|
||||||
|
sqlite3.SQLITE_BUSY_RECOVERY,
|
||||||
|
sqlite3.SQLITE_BUSY_SNAPSHOT:
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the returned error with the code and
|
||||||
|
// extended code for easier debugging later.
|
||||||
|
return fmt.Errorf("%w (code=%d)", err,
|
||||||
|
sqliteErr.Code(),
|
||||||
|
)
|
||||||
|
}
|
60
internal/db/sqlite/errors_wasmsqlite3.go
Normal file
60
internal/db/sqlite/errors_wasmsqlite3.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// GoToSocial
|
||||||
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//go:build wasmsqlite3
|
||||||
|
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// processSQLiteError processes an sqlite3.Error to
|
||||||
|
// handle conversion to any of our common db types.
|
||||||
|
func processSQLiteError(err error) error {
|
||||||
|
// Attempt to cast as sqlite error.
|
||||||
|
sqliteErr, ok := err.(*sqlite3.Error)
|
||||||
|
if !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle supplied error code:
|
||||||
|
switch sqliteErr.ExtendedCode() {
|
||||||
|
case sqlite3.CONSTRAINT_UNIQUE,
|
||||||
|
sqlite3.CONSTRAINT_PRIMARYKEY:
|
||||||
|
return db.ErrAlreadyExists
|
||||||
|
|
||||||
|
// Busy should be very rare, but on
|
||||||
|
// busy tell the database to close the
|
||||||
|
// connection, re-open and re-attempt
|
||||||
|
// which should give necessary timeout.
|
||||||
|
case sqlite3.BUSY_RECOVERY,
|
||||||
|
sqlite3.BUSY_SNAPSHOT:
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the returned error with the code and
|
||||||
|
// extended code for easier debugging later.
|
||||||
|
return fmt.Errorf("%w (code=%d extended=%d)", err,
|
||||||
|
sqliteErr.Code(),
|
||||||
|
sqliteErr.ExtendedCode(),
|
||||||
|
)
|
||||||
|
}
|
35
internal/db/util.go
Normal file
35
internal/db/util.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// GoToSocial
|
||||||
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import "database/sql/driver"
|
||||||
|
|
||||||
|
// ToNamedValues converts older driver.Value types to driver.NamedValue types.
|
||||||
|
func ToNamedValues(args []driver.Value) []driver.NamedValue {
|
||||||
|
if args == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
args2 := make([]driver.NamedValue, len(args))
|
||||||
|
for i := range args {
|
||||||
|
args2[i] = driver.NamedValue{
|
||||||
|
Ordinal: i + 1,
|
||||||
|
Value: args[i],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args2
|
||||||
|
}
|
|
@ -43,10 +43,6 @@ func TestMultiError(t *testing.T) {
|
||||||
t.Error("should be db.ErrAlreadyExists")
|
t.Error("should be db.ErrAlreadyExists")
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors.Is(err, db.ErrBusyTimeout) {
|
|
||||||
t.Error("should not be db.ErrBusyTimeout")
|
|
||||||
}
|
|
||||||
|
|
||||||
errString := err.Error()
|
errString := err.Error()
|
||||||
expected := `sql: no rows in result set
|
expected := `sql: no rows in result set
|
||||||
oopsie woopsie we did a fucky wucky etc
|
oopsie woopsie we did a fucky wucky etc
|
||||||
|
|
|
@ -30,6 +30,7 @@ type PagingSuite struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *PagingSuite) TestPagingStandard() {
|
func (suite *PagingSuite) TestPagingStandard() {
|
||||||
|
config.SetProtocol("https")
|
||||||
config.SetHost("example.org")
|
config.SetHost("example.org")
|
||||||
|
|
||||||
params := util.PageableResponseParams{
|
params := util.PageableResponseParams{
|
||||||
|
@ -52,6 +53,7 @@ func (suite *PagingSuite) TestPagingStandard() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *PagingSuite) TestPagingNoLimit() {
|
func (suite *PagingSuite) TestPagingNoLimit() {
|
||||||
|
config.SetProtocol("https")
|
||||||
config.SetHost("example.org")
|
config.SetHost("example.org")
|
||||||
|
|
||||||
params := util.PageableResponseParams{
|
params := util.PageableResponseParams{
|
||||||
|
@ -73,6 +75,7 @@ func (suite *PagingSuite) TestPagingNoLimit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *PagingSuite) TestPagingNoNextID() {
|
func (suite *PagingSuite) TestPagingNoNextID() {
|
||||||
|
config.SetProtocol("https")
|
||||||
config.SetHost("example.org")
|
config.SetHost("example.org")
|
||||||
|
|
||||||
params := util.PageableResponseParams{
|
params := util.PageableResponseParams{
|
||||||
|
@ -94,6 +97,7 @@ func (suite *PagingSuite) TestPagingNoNextID() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *PagingSuite) TestPagingNoPrevID() {
|
func (suite *PagingSuite) TestPagingNoPrevID() {
|
||||||
|
config.SetProtocol("https")
|
||||||
config.SetHost("example.org")
|
config.SetHost("example.org")
|
||||||
|
|
||||||
params := util.PageableResponseParams{
|
params := util.PageableResponseParams{
|
||||||
|
@ -115,6 +119,7 @@ func (suite *PagingSuite) TestPagingNoPrevID() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *PagingSuite) TestPagingNoItems() {
|
func (suite *PagingSuite) TestPagingNoItems() {
|
||||||
|
config.SetProtocol("https")
|
||||||
config.SetHost("example.org")
|
config.SetHost("example.org")
|
||||||
|
|
||||||
params := util.PageableResponseParams{
|
params := util.PageableResponseParams{
|
||||||
|
|
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# Determine available docker binary
|
||||||
|
_docker=$(command -v 'podman') || \
|
||||||
|
_docker=$(command -v 'docker') || \
|
||||||
|
{ echo 'docker not found'; exit 1; }
|
||||||
|
|
||||||
# Ensure test args are set.
|
# Ensure test args are set.
|
||||||
ARGS=${@}; [ -z "$ARGS" ] && \
|
ARGS=${@}; [ -z "$ARGS" ] && \
|
||||||
ARGS='./...'
|
ARGS='./...'
|
||||||
|
@ -10,33 +15,32 @@ ARGS='./...'
|
||||||
DB_NAME='postgres'
|
DB_NAME='postgres'
|
||||||
DB_USER='postgres'
|
DB_USER='postgres'
|
||||||
DB_PASS='postgres'
|
DB_PASS='postgres'
|
||||||
|
DB_IP='127.0.0.1'
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
|
|
||||||
# Start postgres container
|
# Start postgres container
|
||||||
CID=$(docker run --detach \
|
CID=$($_docker run --detach \
|
||||||
|
--publish "${DB_IP}:${DB_PORT}:${DB_PORT}" \
|
||||||
--env "POSTGRES_DB=${DB_NAME}" \
|
--env "POSTGRES_DB=${DB_NAME}" \
|
||||||
--env "POSTGRES_USER=${DB_USER}" \
|
--env "POSTGRES_USER=${DB_USER}" \
|
||||||
--env "POSTGRES_PASSWORD=${DB_PASS}" \
|
--env "POSTGRES_PASSWORD=${DB_PASS}" \
|
||||||
--env "POSTGRES_HOST_AUTH_METHOD=trust" \
|
--env "POSTGRES_HOST_AUTH_METHOD=trust" \
|
||||||
--env "PGHOST=0.0.0.0" \
|
--env "PGHOST=0.0.0.0" \
|
||||||
--env "PGPORT=${DB_PORT}" \
|
--env "PGPORT=${DB_PORT}" \
|
||||||
'postgres:latest')
|
'docker.io/postgres:latest')
|
||||||
|
|
||||||
# On exit kill the container
|
# On exit kill the container
|
||||||
trap "docker kill ${CID}" exit
|
trap "$_docker kill ${CID}" exit
|
||||||
|
|
||||||
sleep 5
|
sleep 5
|
||||||
#docker exec "$CID" psql --user "$DB_USER" --password "$DB_PASS" -c "CREATE DATABASE \"${DB_NAME}\" WITH LOCALE \"C.UTF-8\" TEMPLATE \"template0\";"
|
#docker exec "$CID" psql --user "$DB_USER" --password "$DB_PASS" -c "CREATE DATABASE \"${DB_NAME}\" WITH LOCALE \"C.UTF-8\" TEMPLATE \"template0\";"
|
||||||
docker exec "$CID" psql --user "$DB_USER" --password "$DB_PASS" -c "GRANT ALL PRIVILEGES ON DATABASE \"${DB_NAME}\" TO \"${DB_USER}\";"
|
$_docker exec "$CID" psql --user "$DB_USER" --password "$DB_PASS" -c "GRANT ALL PRIVILEGES ON DATABASE \"${DB_NAME}\" TO \"${DB_USER}\";"
|
||||||
|
|
||||||
# Get running container IP
|
|
||||||
IP=$(docker container inspect "${CID}" \
|
|
||||||
--format '{{ .NetworkSettings.IPAddress }}')
|
|
||||||
|
|
||||||
|
env \
|
||||||
GTS_DB_TYPE=postgres \
|
GTS_DB_TYPE=postgres \
|
||||||
GTS_DB_ADDRESS=${IP} \
|
GTS_DB_ADDRESS=${DB_IP} \
|
||||||
GTS_DB_PORT=${DB_PORT} \
|
GTS_DB_PORT=${DB_PORT} \
|
||||||
GTS_DB_USER=${DB_USER} \
|
GTS_DB_USER=${DB_USER} \
|
||||||
GTS_DB_PASSWORD=${DB_PASS} \
|
GTS_DB_PASSWORD=${DB_PASS} \
|
||||||
GTS_DB_DATABASE=${DB_NAME} \
|
GTS_DB_DATABASE=${DB_NAME} \
|
||||||
go test ./... -p 1 ${ARGS}
|
go test -p 1 ${ARGS}
|
|
@ -18,8 +18,8 @@
|
||||||
package testrig
|
package testrig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"codeberg.org/gruf/go-bytesize"
|
"codeberg.org/gruf/go-bytesize"
|
||||||
|
@ -28,128 +28,149 @@ import (
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/language"
|
"github.com/superseriousbusiness/gotosocial/internal/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitTestConfig initializes viper configuration with test defaults.
|
// InitTestConfig initializes viper
|
||||||
|
// configuration with test defaults.
|
||||||
func InitTestConfig() {
|
func InitTestConfig() {
|
||||||
config.Config(func(cfg *config.Configuration) {
|
config.Defaults = testDefaults()
|
||||||
*cfg = testDefaults
|
config.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDefaults() config.Configuration {
|
||||||
|
return config.Configuration{
|
||||||
|
LogLevel: envStr("GTS_LOG_LEVEL", "error"),
|
||||||
|
LogTimestampFormat: "02/01/2006 15:04:05.000",
|
||||||
|
LogDbQueries: true,
|
||||||
|
ApplicationName: "gotosocial",
|
||||||
|
LandingPageUser: "",
|
||||||
|
ConfigPath: "",
|
||||||
|
Host: "localhost:8080",
|
||||||
|
AccountDomain: "localhost:8080",
|
||||||
|
Protocol: "http",
|
||||||
|
BindAddress: "127.0.0.1",
|
||||||
|
Port: 8080,
|
||||||
|
TrustedProxies: []string{"127.0.0.1/32", "::1"},
|
||||||
|
DbType: envStr("GTS_DB_TYPE", "sqlite"),
|
||||||
|
DbAddress: envStr("GTS_DB_ADDRESS", ":memory:"),
|
||||||
|
DbPort: envInt("GTS_DB_PORT", 0),
|
||||||
|
DbUser: envStr("GTS_DB_USER", ""),
|
||||||
|
DbPassword: envStr("GTS_DB_PASSWORD", ""),
|
||||||
|
DbDatabase: envStr("GTS_DB_DATABASE", ""),
|
||||||
|
DbTLSMode: envStr("GTS_DB_TLS_MODE", ""),
|
||||||
|
DbTLSCACert: envStr("GTS_DB_TLS_CA_CERT", ""),
|
||||||
|
DbMaxOpenConnsMultiplier: 8,
|
||||||
|
DbSqliteJournalMode: "WAL",
|
||||||
|
DbSqliteSynchronous: "NORMAL",
|
||||||
|
DbSqliteCacheSize: 8 * bytesize.MiB,
|
||||||
|
DbSqliteBusyTimeout: time.Minute * 5,
|
||||||
|
|
||||||
|
WebTemplateBaseDir: "./web/template/",
|
||||||
|
WebAssetBaseDir: "./web/assets/",
|
||||||
|
|
||||||
|
InstanceFederationMode: config.InstanceFederationModeDefault,
|
||||||
|
InstanceFederationSpamFilter: true,
|
||||||
|
InstanceExposePeers: true,
|
||||||
|
InstanceExposeSuspended: true,
|
||||||
|
InstanceExposeSuspendedWeb: true,
|
||||||
|
InstanceDeliverToSharedInboxes: true,
|
||||||
|
InstanceLanguages: language.Languages{
|
||||||
|
{
|
||||||
|
TagStr: "nl",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TagStr: "en-gb",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
AccountsRegistrationOpen: true,
|
||||||
|
AccountsReasonRequired: true,
|
||||||
|
AccountsAllowCustomCSS: true,
|
||||||
|
AccountsCustomCSSLength: 10000,
|
||||||
|
|
||||||
|
MediaImageMaxSize: 10485760, // 10MiB
|
||||||
|
MediaVideoMaxSize: 41943040, // 40MiB
|
||||||
|
MediaDescriptionMinChars: 0,
|
||||||
|
MediaDescriptionMaxChars: 500,
|
||||||
|
MediaRemoteCacheDays: 7,
|
||||||
|
MediaEmojiLocalMaxSize: 51200, // 50KiB
|
||||||
|
MediaEmojiRemoteMaxSize: 102400, // 100KiB
|
||||||
|
MediaCleanupFrom: "00:00", // midnight.
|
||||||
|
MediaCleanupEvery: 24 * time.Hour, // 1/day.
|
||||||
|
|
||||||
|
// the testrig only uses in-memory storage, so we can
|
||||||
|
// safely set this value to 'test' to avoid running storage
|
||||||
|
// migrations, and other silly things like that
|
||||||
|
StorageBackend: "test",
|
||||||
|
StorageLocalBasePath: "",
|
||||||
|
|
||||||
|
StatusesMaxChars: 5000,
|
||||||
|
StatusesPollMaxOptions: 6,
|
||||||
|
StatusesPollOptionMaxChars: 50,
|
||||||
|
StatusesMediaMaxFiles: 6,
|
||||||
|
|
||||||
|
LetsEncryptEnabled: false,
|
||||||
|
LetsEncryptPort: 0,
|
||||||
|
LetsEncryptCertDir: "",
|
||||||
|
LetsEncryptEmailAddress: "",
|
||||||
|
|
||||||
|
OIDCEnabled: false,
|
||||||
|
OIDCIdpName: "",
|
||||||
|
OIDCSkipVerification: false,
|
||||||
|
OIDCIssuer: "",
|
||||||
|
OIDCClientID: "",
|
||||||
|
OIDCClientSecret: "",
|
||||||
|
OIDCScopes: []string{oidc.ScopeOpenID, "profile", "email", "groups"},
|
||||||
|
OIDCLinkExisting: false,
|
||||||
|
OIDCAdminGroups: []string{"adminRole"},
|
||||||
|
OIDCAllowedGroups: []string{"allowedRole"},
|
||||||
|
|
||||||
|
SMTPHost: "",
|
||||||
|
SMTPPort: 0,
|
||||||
|
SMTPUsername: "",
|
||||||
|
SMTPPassword: "",
|
||||||
|
SMTPFrom: "GoToSocial",
|
||||||
|
SMTPDiscloseRecipients: false,
|
||||||
|
|
||||||
|
TracingEnabled: false,
|
||||||
|
TracingEndpoint: "localhost:4317",
|
||||||
|
TracingTransport: "grpc",
|
||||||
|
TracingInsecureTransport: true,
|
||||||
|
|
||||||
|
MetricsEnabled: false,
|
||||||
|
MetricsAuthEnabled: false,
|
||||||
|
|
||||||
|
SyslogEnabled: false,
|
||||||
|
SyslogProtocol: "udp",
|
||||||
|
SyslogAddress: "localhost:514",
|
||||||
|
|
||||||
|
AdvancedCookiesSamesite: "lax",
|
||||||
|
AdvancedRateLimitRequests: 0, // disabled
|
||||||
|
AdvancedThrottlingMultiplier: 0, // disabled
|
||||||
|
AdvancedSenderMultiplier: 0, // 1 sender only, regardless of CPU
|
||||||
|
|
||||||
|
SoftwareVersion: "0.0.0-testrig",
|
||||||
|
|
||||||
|
// simply use cache defaults.
|
||||||
|
Cache: config.Defaults.Cache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func envInt(key string, _default int) int {
|
||||||
|
return env(key, _default, func(value string) int {
|
||||||
|
i, _ := strconv.Atoi(value)
|
||||||
|
return i
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var testDefaults = config.Configuration{
|
func envStr(key string, _default string) string {
|
||||||
LogLevel: cmp.Or(os.Getenv("GTS_LOG_LEVEL"), "error"),
|
return env(key, _default, func(value string) string {
|
||||||
LogTimestampFormat: "02/01/2006 15:04:05.000",
|
return value
|
||||||
LogDbQueries: true,
|
})
|
||||||
ApplicationName: "gotosocial",
|
}
|
||||||
LandingPageUser: "",
|
|
||||||
ConfigPath: "",
|
func env[T any](key string, _default T, parse func(string) T) T {
|
||||||
Host: "localhost:8080",
|
value, ok := os.LookupEnv(key)
|
||||||
AccountDomain: "localhost:8080",
|
if ok {
|
||||||
Protocol: "http",
|
return parse(value)
|
||||||
BindAddress: "127.0.0.1",
|
}
|
||||||
Port: 8080,
|
return _default
|
||||||
TrustedProxies: []string{"127.0.0.1/32", "::1"},
|
|
||||||
|
|
||||||
DbType: "sqlite",
|
|
||||||
DbAddress: ":memory:",
|
|
||||||
DbPort: 5432,
|
|
||||||
DbUser: "postgres",
|
|
||||||
DbPassword: "postgres",
|
|
||||||
DbDatabase: "postgres",
|
|
||||||
DbTLSMode: "disable",
|
|
||||||
DbTLSCACert: "",
|
|
||||||
DbMaxOpenConnsMultiplier: 8,
|
|
||||||
DbSqliteJournalMode: "WAL",
|
|
||||||
DbSqliteSynchronous: "NORMAL",
|
|
||||||
DbSqliteCacheSize: 8 * bytesize.MiB,
|
|
||||||
DbSqliteBusyTimeout: time.Minute * 5,
|
|
||||||
|
|
||||||
WebTemplateBaseDir: "./web/template/",
|
|
||||||
WebAssetBaseDir: "./web/assets/",
|
|
||||||
|
|
||||||
InstanceFederationMode: config.InstanceFederationModeDefault,
|
|
||||||
InstanceFederationSpamFilter: true,
|
|
||||||
InstanceExposePeers: true,
|
|
||||||
InstanceExposeSuspended: true,
|
|
||||||
InstanceExposeSuspendedWeb: true,
|
|
||||||
InstanceDeliverToSharedInboxes: true,
|
|
||||||
InstanceLanguages: language.Languages{
|
|
||||||
{
|
|
||||||
TagStr: "nl",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
TagStr: "en-gb",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
AccountsRegistrationOpen: true,
|
|
||||||
AccountsReasonRequired: true,
|
|
||||||
AccountsAllowCustomCSS: true,
|
|
||||||
AccountsCustomCSSLength: 10000,
|
|
||||||
|
|
||||||
MediaImageMaxSize: 10485760, // 10MiB
|
|
||||||
MediaVideoMaxSize: 41943040, // 40MiB
|
|
||||||
MediaDescriptionMinChars: 0,
|
|
||||||
MediaDescriptionMaxChars: 500,
|
|
||||||
MediaRemoteCacheDays: 7,
|
|
||||||
MediaEmojiLocalMaxSize: 51200, // 50KiB
|
|
||||||
MediaEmojiRemoteMaxSize: 102400, // 100KiB
|
|
||||||
MediaCleanupFrom: "00:00", // midnight.
|
|
||||||
MediaCleanupEvery: 24 * time.Hour, // 1/day.
|
|
||||||
|
|
||||||
// the testrig only uses in-memory storage, so we can
|
|
||||||
// safely set this value to 'test' to avoid running storage
|
|
||||||
// migrations, and other silly things like that
|
|
||||||
StorageBackend: "test",
|
|
||||||
StorageLocalBasePath: "",
|
|
||||||
|
|
||||||
StatusesMaxChars: 5000,
|
|
||||||
StatusesPollMaxOptions: 6,
|
|
||||||
StatusesPollOptionMaxChars: 50,
|
|
||||||
StatusesMediaMaxFiles: 6,
|
|
||||||
|
|
||||||
LetsEncryptEnabled: false,
|
|
||||||
LetsEncryptPort: 0,
|
|
||||||
LetsEncryptCertDir: "",
|
|
||||||
LetsEncryptEmailAddress: "",
|
|
||||||
|
|
||||||
OIDCEnabled: false,
|
|
||||||
OIDCIdpName: "",
|
|
||||||
OIDCSkipVerification: false,
|
|
||||||
OIDCIssuer: "",
|
|
||||||
OIDCClientID: "",
|
|
||||||
OIDCClientSecret: "",
|
|
||||||
OIDCScopes: []string{oidc.ScopeOpenID, "profile", "email", "groups"},
|
|
||||||
OIDCLinkExisting: false,
|
|
||||||
OIDCAdminGroups: []string{"adminRole"},
|
|
||||||
OIDCAllowedGroups: []string{"allowedRole"},
|
|
||||||
|
|
||||||
SMTPHost: "",
|
|
||||||
SMTPPort: 0,
|
|
||||||
SMTPUsername: "",
|
|
||||||
SMTPPassword: "",
|
|
||||||
SMTPFrom: "GoToSocial",
|
|
||||||
SMTPDiscloseRecipients: false,
|
|
||||||
|
|
||||||
TracingEnabled: false,
|
|
||||||
TracingEndpoint: "localhost:4317",
|
|
||||||
TracingTransport: "grpc",
|
|
||||||
TracingInsecureTransport: true,
|
|
||||||
|
|
||||||
MetricsEnabled: false,
|
|
||||||
MetricsAuthEnabled: false,
|
|
||||||
|
|
||||||
SyslogEnabled: false,
|
|
||||||
SyslogProtocol: "udp",
|
|
||||||
SyslogAddress: "localhost:514",
|
|
||||||
|
|
||||||
AdvancedCookiesSamesite: "lax",
|
|
||||||
AdvancedRateLimitRequests: 0, // disabled
|
|
||||||
AdvancedThrottlingMultiplier: 0, // disabled
|
|
||||||
AdvancedSenderMultiplier: 0, // 1 sender only, regardless of CPU
|
|
||||||
AdvancedHeaderFilterMode: config.RequestHeaderFilterModeBlock,
|
|
||||||
|
|
||||||
SoftwareVersion: "0.0.0-testrig",
|
|
||||||
|
|
||||||
// simply use cache defaults.
|
|
||||||
Cache: config.Defaults.Cache,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,7 @@ package testrig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
|
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
@ -84,22 +81,6 @@ var testModels = []interface{}{
|
||||||
// If the environment variable GTS_DB_PORT is set, it will take that
|
// If the environment variable GTS_DB_PORT is set, it will take that
|
||||||
// value as the port instead.
|
// value as the port instead.
|
||||||
func NewTestDB(state *state.State) db.DB {
|
func NewTestDB(state *state.State) db.DB {
|
||||||
if alternateAddress := os.Getenv("GTS_DB_ADDRESS"); alternateAddress != "" {
|
|
||||||
config.SetDbAddress(alternateAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
if alternateDBType := os.Getenv("GTS_DB_TYPE"); alternateDBType != "" {
|
|
||||||
config.SetDbType(alternateDBType)
|
|
||||||
}
|
|
||||||
|
|
||||||
if alternateDBPort := os.Getenv("GTS_DB_PORT"); alternateDBPort != "" {
|
|
||||||
port, err := strconv.ParseUint(alternateDBPort, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
config.SetDbPort(int(port))
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Caches.Init()
|
state.Caches.Init()
|
||||||
|
|
||||||
testDB, err := bundb.NewBunDBService(context.Background(), state)
|
testDB, err := bundb.NewBunDBService(context.Background(), state)
|
||||||
|
@ -374,9 +355,10 @@ func StandardDBTeardown(db db.DB) {
|
||||||
if db == nil {
|
if db == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer db.Close()
|
||||||
for _, m := range testModels {
|
for _, m := range testModels {
|
||||||
if err := db.DropTable(ctx, m); err != nil {
|
if err := db.DropTable(ctx, m); err != nil {
|
||||||
log.Panic(nil, err)
|
log.Error(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
16
vendor/github.com/ncruces/go-sqlite3/.gitignore
generated
vendored
Normal file
16
vendor/github.com/ncruces/go-sqlite3/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
tools
|
21
vendor/github.com/ncruces/go-sqlite3/LICENSE
generated
vendored
Normal file
21
vendor/github.com/ncruces/go-sqlite3/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Nuno Cruces
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
113
vendor/github.com/ncruces/go-sqlite3/README.md
generated
vendored
Normal file
113
vendor/github.com/ncruces/go-sqlite3/README.md
generated
vendored
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
# Go bindings to SQLite using Wazero
|
||||||
|
|
||||||
|
[![Go Reference](https://pkg.go.dev/badge/image)](https://pkg.go.dev/github.com/ncruces/go-sqlite3)
|
||||||
|
[![Go Report](https://goreportcard.com/badge/github.com/ncruces/go-sqlite3)](https://goreportcard.com/report/github.com/ncruces/go-sqlite3)
|
||||||
|
[![Go Coverage](https://github.com/ncruces/go-sqlite3/wiki/coverage.svg)](https://github.com/ncruces/go-sqlite3/wiki/Test-coverage-report)
|
||||||
|
|
||||||
|
Go module `github.com/ncruces/go-sqlite3` is a `cgo`-free [SQLite](https://sqlite.org/) wrapper.\
|
||||||
|
It provides a [`database/sql`](https://pkg.go.dev/database/sql) compatible driver,
|
||||||
|
as well as direct access to most of the [C SQLite API](https://sqlite.org/cintro.html).
|
||||||
|
|
||||||
|
It wraps a [Wasm](https://webassembly.org/) [build](embed/) of SQLite,
|
||||||
|
and uses [wazero](https://wazero.io/) as the runtime.\
|
||||||
|
Go, wazero and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ runtime dependencies [^1].
|
||||||
|
|
||||||
|
### Packages
|
||||||
|
|
||||||
|
- [`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3)
|
||||||
|
wraps the [C SQLite API](https://sqlite.org/cintro.html)
|
||||||
|
([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3#example-package)).
|
||||||
|
- [`github.com/ncruces/go-sqlite3/driver`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver)
|
||||||
|
provides a [`database/sql`](https://pkg.go.dev/database/sql) driver
|
||||||
|
([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package)).
|
||||||
|
- [`github.com/ncruces/go-sqlite3/embed`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/embed)
|
||||||
|
embeds a build of SQLite into your application.
|
||||||
|
- [`github.com/ncruces/go-sqlite3/vfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs)
|
||||||
|
wraps the [C SQLite VFS API](https://sqlite.org/vfs.html) and provides a pure Go implementation.
|
||||||
|
- [`github.com/ncruces/go-sqlite3/gormlite`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/gormlite)
|
||||||
|
provides a [GORM](https://gorm.io) driver.
|
||||||
|
|
||||||
|
### Extensions
|
||||||
|
|
||||||
|
- [`github.com/ncruces/go-sqlite3/ext/array`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/array)
|
||||||
|
provides the [`array`](https://sqlite.org/carray.html) table-valued function.
|
||||||
|
- [`github.com/ncruces/go-sqlite3/ext/blobio`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/blobio)
|
||||||
|
simplifies [incremental BLOB I/O](https://sqlite.org/c3ref/blob_open.html).
|
||||||
|
- [`github.com/ncruces/go-sqlite3/ext/csv`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/csv)
|
||||||
|
reads [comma-separated values](https://sqlite.org/csv.html).
|
||||||
|
- [`github.com/ncruces/go-sqlite3/ext/fileio`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/fileio)
|
||||||
|
reads, writes and lists files.
|
||||||
|
- [`github.com/ncruces/go-sqlite3/ext/hash`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/hash)
|
||||||
|
provides cryptographic hash functions.
|
||||||
|
- [`github.com/ncruces/go-sqlite3/ext/lines`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/lines)
|
||||||
|
reads data [line-by-line](https://github.com/asg017/sqlite-lines).
|
||||||
|
- [`github.com/ncruces/go-sqlite3/ext/pivot`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/pivot)
|
||||||
|
creates [pivot tables](https://github.com/jakethaw/pivot_vtab).
|
||||||
|
- [`github.com/ncruces/go-sqlite3/ext/statement`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/statement)
|
||||||
|
creates [parameterized views](https://github.com/0x09/sqlite-statement-vtab).
|
||||||
|
- [`github.com/ncruces/go-sqlite3/ext/stats`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/stats)
|
||||||
|
provides [statistics](https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html) functions.
|
||||||
|
- [`github.com/ncruces/go-sqlite3/ext/unicode`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/unicode)
|
||||||
|
provides [Unicode aware](https://sqlite.org/src/dir/ext/icu) functions.
|
||||||
|
- [`github.com/ncruces/go-sqlite3/ext/zorder`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/zorder)
|
||||||
|
maps multidimensional data to one dimension.
|
||||||
|
- [`github.com/ncruces/go-sqlite3/vfs/memdb`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/memdb)
|
||||||
|
implements an in-memory VFS.
|
||||||
|
- [`github.com/ncruces/go-sqlite3/vfs/readervfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/readervfs)
|
||||||
|
implements a VFS for immutable databases.
|
||||||
|
- [`github.com/ncruces/go-sqlite3/vfs/adiantum`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/adiantum)
|
||||||
|
wraps a VFS to offer encryption at rest.
|
||||||
|
|
||||||
|
### Advanced features
|
||||||
|
|
||||||
|
- [incremental BLOB I/O](https://sqlite.org/c3ref/blob_open.html)
|
||||||
|
- [nested transactions](https://sqlite.org/lang_savepoint.html)
|
||||||
|
- [custom functions](https://sqlite.org/c3ref/create_function.html)
|
||||||
|
- [virtual tables](https://sqlite.org/vtab.html)
|
||||||
|
- [custom VFSes](https://sqlite.org/vfs.html)
|
||||||
|
- [online backup](https://sqlite.org/backup.html)
|
||||||
|
- [JSON support](https://sqlite.org/json1.html)
|
||||||
|
- [math functions](https://sqlite.org/lang_mathfunc.html)
|
||||||
|
- [full-text search](https://sqlite.org/fts5.html)
|
||||||
|
- [geospatial search](https://sqlite.org/geopoly.html)
|
||||||
|
- [encryption at rest](vfs/adiantum/README.md)
|
||||||
|
- [and more…](embed/README.md)
|
||||||
|
|
||||||
|
### Caveats
|
||||||
|
|
||||||
|
This module replaces the SQLite [OS Interface](https://sqlite.org/vfs.html)
|
||||||
|
(aka VFS) with a [pure Go](vfs/) implementation,
|
||||||
|
which has advantages and disadvantages.
|
||||||
|
|
||||||
|
Read more about the Go VFS design [here](vfs/README.md).
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
This project aims for [high test coverage](https://github.com/ncruces/go-sqlite3/wiki/Test-coverage-report).
|
||||||
|
It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
|
||||||
|
[wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach) thorough testing.
|
||||||
|
|
||||||
|
Every commit is [tested](.github/workflows/test.yml) on
|
||||||
|
Linux (amd64/arm64/386/riscv64/s390x), macOS (amd64/arm64),
|
||||||
|
Windows (amd64), FreeBSD (amd64), illumos (amd64), and Solaris (amd64).
|
||||||
|
|
||||||
|
The Go VFS is tested by running SQLite's
|
||||||
|
[mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c).
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
Perfomance of the [`database/sql`](https://pkg.go.dev/database/sql) driver is
|
||||||
|
[competitive](https://github.com/cvilsmeier/go-sqlite-bench) with alternatives.
|
||||||
|
|
||||||
|
The Wasm and VFS layers are also tested by running SQLite's
|
||||||
|
[speedtest1](https://github.com/sqlite/sqlite/blob/master/test/speedtest1.c).
|
||||||
|
|
||||||
|
### Alternatives
|
||||||
|
|
||||||
|
- [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite)
|
||||||
|
- [`crawshaw.io/sqlite`](https://pkg.go.dev/crawshaw.io/sqlite)
|
||||||
|
- [`github.com/mattn/go-sqlite3`](https://pkg.go.dev/github.com/mattn/go-sqlite3)
|
||||||
|
- [`github.com/zombiezen/go-sqlite`](https://pkg.go.dev/github.com/zombiezen/go-sqlite)
|
||||||
|
|
||||||
|
[^1]: anything else you find in `go.mod` is either a test dependency,
|
||||||
|
or needed by one of the extensions.
|
134
vendor/github.com/ncruces/go-sqlite3/backup.go
generated
vendored
Normal file
134
vendor/github.com/ncruces/go-sqlite3/backup.go
generated
vendored
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
// Backup is an handle to an ongoing online backup operation.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/backup.html
|
||||||
|
type Backup struct {
|
||||||
|
c *Conn
|
||||||
|
handle uint32
|
||||||
|
otherc uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup backs up srcDB on the src connection to the "main" database in dstURI.
|
||||||
|
//
|
||||||
|
// Backup opens the SQLite database file dstURI,
|
||||||
|
// and blocks until the entire backup is complete.
|
||||||
|
// Use [Conn.BackupInit] for incremental backup.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/backup.html
|
||||||
|
func (src *Conn) Backup(srcDB, dstURI string) error {
|
||||||
|
b, err := src.BackupInit(srcDB, dstURI)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
_, err = b.Step(-1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores dstDB on the dst connection from the "main" database in srcURI.
|
||||||
|
//
|
||||||
|
// Restore opens the SQLite database file srcURI,
|
||||||
|
// and blocks until the entire restore is complete.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/backup.html
|
||||||
|
func (dst *Conn) Restore(dstDB, srcURI string) error {
|
||||||
|
src, err := dst.openDB(srcURI, OPEN_READONLY|OPEN_URI)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b, err := dst.backupInit(dst.handle, dstDB, src, "main")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer b.Close()
|
||||||
|
_, err = b.Step(-1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupInit initializes a backup operation to copy the content of one database into another.
|
||||||
|
//
|
||||||
|
// BackupInit opens the SQLite database file dstURI,
|
||||||
|
// then initializes a backup that copies the contents of srcDB on the src connection
|
||||||
|
// to the "main" database in dstURI.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupinit
|
||||||
|
func (src *Conn) BackupInit(srcDB, dstURI string) (*Backup, error) {
|
||||||
|
dst, err := src.openDB(dstURI, OPEN_READWRITE|OPEN_CREATE|OPEN_URI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return src.backupInit(dst, "main", src.handle, srcDB)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) backupInit(dst uint32, dstName string, src uint32, srcName string) (*Backup, error) {
|
||||||
|
defer c.arena.mark()()
|
||||||
|
dstPtr := c.arena.string(dstName)
|
||||||
|
srcPtr := c.arena.string(srcName)
|
||||||
|
|
||||||
|
other := dst
|
||||||
|
if c.handle == dst {
|
||||||
|
other = src
|
||||||
|
}
|
||||||
|
|
||||||
|
r := c.call("sqlite3_backup_init",
|
||||||
|
uint64(dst), uint64(dstPtr),
|
||||||
|
uint64(src), uint64(srcPtr))
|
||||||
|
if r == 0 {
|
||||||
|
defer c.closeDB(other)
|
||||||
|
r = c.call("sqlite3_errcode", uint64(dst))
|
||||||
|
return nil, c.sqlite.error(r, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Backup{
|
||||||
|
c: c,
|
||||||
|
otherc: other,
|
||||||
|
handle: uint32(r),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close finishes a backup operation.
|
||||||
|
//
|
||||||
|
// It is safe to close a nil, zero or closed Backup.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish
|
||||||
|
func (b *Backup) Close() error {
|
||||||
|
if b == nil || b.handle == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r := b.c.call("sqlite3_backup_finish", uint64(b.handle))
|
||||||
|
b.c.closeDB(b.otherc)
|
||||||
|
b.handle = 0
|
||||||
|
return b.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step copies up to nPage pages between the source and destination databases.
|
||||||
|
// If nPage is negative, all remaining source pages are copied.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupstep
|
||||||
|
func (b *Backup) Step(nPage int) (done bool, err error) {
|
||||||
|
r := b.c.call("sqlite3_backup_step", uint64(b.handle), uint64(nPage))
|
||||||
|
if r == _DONE {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, b.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remaining returns the number of pages still to be backed up
|
||||||
|
// at the conclusion of the most recent [Backup.Step].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining
|
||||||
|
func (b *Backup) Remaining() int {
|
||||||
|
r := b.c.call("sqlite3_backup_remaining", uint64(b.handle))
|
||||||
|
return int(int32(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageCount returns the total number of pages in the source database
|
||||||
|
// at the conclusion of the most recent [Backup.Step].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backuppagecount
|
||||||
|
func (b *Backup) PageCount() int {
|
||||||
|
r := b.c.call("sqlite3_backup_pagecount", uint64(b.handle))
|
||||||
|
return int(int32(r))
|
||||||
|
}
|
250
vendor/github.com/ncruces/go-sqlite3/blob.go
generated
vendored
Normal file
250
vendor/github.com/ncruces/go-sqlite3/blob.go
generated
vendored
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ZeroBlob represents a zero-filled, length n BLOB
|
||||||
|
// that can be used as an argument to
|
||||||
|
// [database/sql.DB.Exec] and similar methods.
|
||||||
|
type ZeroBlob int64
|
||||||
|
|
||||||
|
// Blob is an handle to an open BLOB.
|
||||||
|
//
|
||||||
|
// It implements [io.ReadWriteSeeker] for incremental BLOB I/O.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/blob.html
|
||||||
|
type Blob struct {
|
||||||
|
c *Conn
|
||||||
|
bytes int64
|
||||||
|
offset int64
|
||||||
|
handle uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ io.ReadWriteSeeker = &Blob{}
|
||||||
|
|
||||||
|
// OpenBlob opens a BLOB for incremental I/O.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/blob_open.html
|
||||||
|
func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob, error) {
|
||||||
|
c.checkInterrupt()
|
||||||
|
defer c.arena.mark()()
|
||||||
|
blobPtr := c.arena.new(ptrlen)
|
||||||
|
dbPtr := c.arena.string(db)
|
||||||
|
tablePtr := c.arena.string(table)
|
||||||
|
columnPtr := c.arena.string(column)
|
||||||
|
|
||||||
|
var flags uint64
|
||||||
|
if write {
|
||||||
|
flags = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
r := c.call("sqlite3_blob_open", uint64(c.handle),
|
||||||
|
uint64(dbPtr), uint64(tablePtr), uint64(columnPtr),
|
||||||
|
uint64(row), flags, uint64(blobPtr))
|
||||||
|
|
||||||
|
if err := c.error(r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
blob := Blob{c: c}
|
||||||
|
blob.handle = util.ReadUint32(c.mod, blobPtr)
|
||||||
|
blob.bytes = int64(c.call("sqlite3_blob_bytes", uint64(blob.handle)))
|
||||||
|
return &blob, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes a BLOB handle.
|
||||||
|
//
|
||||||
|
// It is safe to close a nil, zero or closed Blob.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/blob_close.html
|
||||||
|
func (b *Blob) Close() error {
|
||||||
|
if b == nil || b.handle == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r := b.c.call("sqlite3_blob_close", uint64(b.handle))
|
||||||
|
|
||||||
|
b.handle = 0
|
||||||
|
return b.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the size of the BLOB in bytes.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/blob_bytes.html
|
||||||
|
func (b *Blob) Size() int64 {
|
||||||
|
return b.bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements the [io.Reader] interface.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/blob_read.html
|
||||||
|
func (b *Blob) Read(p []byte) (n int, err error) {
|
||||||
|
if b.offset >= b.bytes {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
avail := b.bytes - b.offset
|
||||||
|
want := int64(len(p))
|
||||||
|
if want > avail {
|
||||||
|
want = avail
|
||||||
|
}
|
||||||
|
|
||||||
|
defer b.c.arena.mark()()
|
||||||
|
ptr := b.c.arena.new(uint64(want))
|
||||||
|
|
||||||
|
r := b.c.call("sqlite3_blob_read", uint64(b.handle),
|
||||||
|
uint64(ptr), uint64(want), uint64(b.offset))
|
||||||
|
err = b.c.error(r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
b.offset += want
|
||||||
|
if b.offset >= b.bytes {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(p, util.View(b.c.mod, ptr, uint64(want)))
|
||||||
|
return int(want), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo implements the [io.WriterTo] interface.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/blob_read.html
|
||||||
|
func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
if b.offset >= b.bytes {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
want := int64(1024 * 1024)
|
||||||
|
avail := b.bytes - b.offset
|
||||||
|
if want > avail {
|
||||||
|
want = avail
|
||||||
|
}
|
||||||
|
|
||||||
|
defer b.c.arena.mark()()
|
||||||
|
ptr := b.c.arena.new(uint64(want))
|
||||||
|
|
||||||
|
for want > 0 {
|
||||||
|
r := b.c.call("sqlite3_blob_read", uint64(b.handle),
|
||||||
|
uint64(ptr), uint64(want), uint64(b.offset))
|
||||||
|
err = b.c.error(r)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mem := util.View(b.c.mod, ptr, uint64(want))
|
||||||
|
m, err := w.Write(mem[:want])
|
||||||
|
b.offset += int64(m)
|
||||||
|
n += int64(m)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if int64(m) != want {
|
||||||
|
return n, io.ErrShortWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
avail = b.bytes - b.offset
|
||||||
|
if want > avail {
|
||||||
|
want = avail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements the [io.Writer] interface.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/blob_write.html
|
||||||
|
func (b *Blob) Write(p []byte) (n int, err error) {
|
||||||
|
defer b.c.arena.mark()()
|
||||||
|
ptr := b.c.arena.bytes(p)
|
||||||
|
|
||||||
|
r := b.c.call("sqlite3_blob_write", uint64(b.handle),
|
||||||
|
uint64(ptr), uint64(len(p)), uint64(b.offset))
|
||||||
|
err = b.c.error(r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
b.offset += int64(len(p))
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFrom implements the [io.ReaderFrom] interface.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/blob_write.html
|
||||||
|
func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
want := int64(1024 * 1024)
|
||||||
|
avail := b.bytes - b.offset
|
||||||
|
if l, ok := r.(*io.LimitedReader); ok && want > l.N {
|
||||||
|
want = l.N
|
||||||
|
}
|
||||||
|
if want > avail {
|
||||||
|
want = avail
|
||||||
|
}
|
||||||
|
if want < 1 {
|
||||||
|
want = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
defer b.c.arena.mark()()
|
||||||
|
ptr := b.c.arena.new(uint64(want))
|
||||||
|
|
||||||
|
for {
|
||||||
|
mem := util.View(b.c.mod, ptr, uint64(want))
|
||||||
|
m, err := r.Read(mem[:want])
|
||||||
|
if m > 0 {
|
||||||
|
r := b.c.call("sqlite3_blob_write", uint64(b.handle),
|
||||||
|
uint64(ptr), uint64(m), uint64(b.offset))
|
||||||
|
err := b.c.error(r)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
b.offset += int64(m)
|
||||||
|
n += int64(m)
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
avail = b.bytes - b.offset
|
||||||
|
if want > avail {
|
||||||
|
want = avail
|
||||||
|
}
|
||||||
|
if want < 1 {
|
||||||
|
want = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek implements the [io.Seeker] interface.
|
||||||
|
func (b *Blob) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
switch whence {
|
||||||
|
default:
|
||||||
|
return 0, util.WhenceErr
|
||||||
|
case io.SeekStart:
|
||||||
|
break
|
||||||
|
case io.SeekCurrent:
|
||||||
|
offset += b.offset
|
||||||
|
case io.SeekEnd:
|
||||||
|
offset += b.bytes
|
||||||
|
}
|
||||||
|
if offset < 0 {
|
||||||
|
return 0, util.OffsetErr
|
||||||
|
}
|
||||||
|
b.offset = offset
|
||||||
|
return offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reopen moves a BLOB handle to a new row of the same database table.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/blob_reopen.html
|
||||||
|
func (b *Blob) Reopen(row int64) error {
|
||||||
|
err := b.c.error(b.c.call("sqlite3_blob_reopen", uint64(b.handle), uint64(row)))
|
||||||
|
b.bytes = int64(b.c.call("sqlite3_blob_bytes", uint64(b.handle)))
|
||||||
|
b.offset = 0
|
||||||
|
return err
|
||||||
|
}
|
164
vendor/github.com/ncruces/go-sqlite3/config.go
generated
vendored
Normal file
164
vendor/github.com/ncruces/go-sqlite3/config.go
generated
vendored
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config makes configuration changes to a database connection.
|
||||||
|
// Only boolean configuration options are supported.
|
||||||
|
// Called with no arg reads the current configuration value,
|
||||||
|
// called with one arg sets and returns the new value.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/db_config.html
|
||||||
|
func (c *Conn) Config(op DBConfig, arg ...bool) (bool, error) {
|
||||||
|
defer c.arena.mark()()
|
||||||
|
argsPtr := c.arena.new(2 * ptrlen)
|
||||||
|
|
||||||
|
var flag int
|
||||||
|
switch {
|
||||||
|
case len(arg) == 0:
|
||||||
|
flag = -1
|
||||||
|
case arg[0]:
|
||||||
|
flag = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
util.WriteUint32(c.mod, argsPtr+0*ptrlen, uint32(flag))
|
||||||
|
util.WriteUint32(c.mod, argsPtr+1*ptrlen, argsPtr)
|
||||||
|
|
||||||
|
r := c.call("sqlite3_db_config", uint64(c.handle),
|
||||||
|
uint64(op), uint64(argsPtr))
|
||||||
|
return util.ReadUint32(c.mod, argsPtr) != 0, c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigLog sets up the error logging callback for the connection.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/errlog.html
|
||||||
|
func (c *Conn) ConfigLog(cb func(code ExtendedErrorCode, msg string)) error {
|
||||||
|
var enable uint64
|
||||||
|
if cb != nil {
|
||||||
|
enable = 1
|
||||||
|
}
|
||||||
|
r := c.call("sqlite3_config_log_go", enable)
|
||||||
|
if err := c.error(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.log = cb
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func logCallback(ctx context.Context, mod api.Module, _, iCode, zMsg uint32) {
|
||||||
|
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.log != nil {
|
||||||
|
msg := util.ReadString(mod, zMsg, _MAX_LENGTH)
|
||||||
|
c.log(xErrorCode(iCode), msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit allows the size of various constructs to be
|
||||||
|
// limited on a connection by connection basis.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/limit.html
|
||||||
|
func (c *Conn) Limit(id LimitCategory, value int) int {
|
||||||
|
r := c.call("sqlite3_limit", uint64(c.handle), uint64(id), uint64(value))
|
||||||
|
return int(int32(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAuthorizer registers an authorizer callback with the database connection.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/set_authorizer.html
|
||||||
|
func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4th, schema, nameInner string) AuthorizerReturnCode) error {
|
||||||
|
var enable uint64
|
||||||
|
if cb != nil {
|
||||||
|
enable = 1
|
||||||
|
}
|
||||||
|
r := c.call("sqlite3_set_authorizer_go", uint64(c.handle), enable)
|
||||||
|
if err := c.error(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.authorizer = cb
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zName3rd, zName4th, zSchema, zNameInner uint32) (rc AuthorizerReturnCode) {
|
||||||
|
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.authorizer != nil {
|
||||||
|
var name3rd, name4th, schema, nameInner string
|
||||||
|
if zName3rd != 0 {
|
||||||
|
name3rd = util.ReadString(mod, zName3rd, _MAX_NAME)
|
||||||
|
}
|
||||||
|
if zName4th != 0 {
|
||||||
|
name4th = util.ReadString(mod, zName4th, _MAX_NAME)
|
||||||
|
}
|
||||||
|
if zSchema != 0 {
|
||||||
|
schema = util.ReadString(mod, zSchema, _MAX_NAME)
|
||||||
|
}
|
||||||
|
if zNameInner != 0 {
|
||||||
|
nameInner = util.ReadString(mod, zNameInner, _MAX_NAME)
|
||||||
|
}
|
||||||
|
rc = c.authorizer(action, name3rd, name4th, schema, nameInner)
|
||||||
|
}
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalCheckpoint checkpoints a WAL database.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/wal_checkpoint_v2.html
|
||||||
|
func (c *Conn) WalCheckpoint(schema string, mode CheckpointMode) (nLog, nCkpt int, err error) {
|
||||||
|
defer c.arena.mark()()
|
||||||
|
nLogPtr := c.arena.new(ptrlen)
|
||||||
|
nCkptPtr := c.arena.new(ptrlen)
|
||||||
|
schemaPtr := c.arena.string(schema)
|
||||||
|
r := c.call("sqlite3_wal_checkpoint_v2",
|
||||||
|
uint64(c.handle), uint64(schemaPtr), uint64(mode),
|
||||||
|
uint64(nLogPtr), uint64(nCkptPtr))
|
||||||
|
nLog = int(int32(util.ReadUint32(c.mod, nLogPtr)))
|
||||||
|
nCkpt = int(int32(util.ReadUint32(c.mod, nCkptPtr)))
|
||||||
|
return nLog, nCkpt, c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalAutoCheckpoint configures WAL auto-checkpoints.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/wal_autocheckpoint.html
|
||||||
|
func (c *Conn) WalAutoCheckpoint(pages int) error {
|
||||||
|
r := c.call("sqlite3_wal_autocheckpoint", uint64(c.handle), uint64(pages))
|
||||||
|
return c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalHook registers a callback function to be invoked
|
||||||
|
// each time data is committed to a database in WAL mode.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/wal_hook.html
|
||||||
|
func (c *Conn) WalHook(cb func(db *Conn, schema string, pages int) error) {
|
||||||
|
var enable uint64
|
||||||
|
if cb != nil {
|
||||||
|
enable = 1
|
||||||
|
}
|
||||||
|
c.call("sqlite3_wal_hook_go", uint64(c.handle), enable)
|
||||||
|
c.wal = cb
|
||||||
|
}
|
||||||
|
|
||||||
|
func walCallback(ctx context.Context, mod api.Module, _, pDB, zSchema uint32, pages int32) (rc uint32) {
|
||||||
|
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.wal != nil {
|
||||||
|
schema := util.ReadString(mod, zSchema, _MAX_NAME)
|
||||||
|
err := c.wal(c, schema, int(pages))
|
||||||
|
_, rc = errorCode(err, ERROR)
|
||||||
|
}
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutoVacuumPages registers a autovacuum compaction amount callback.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/autovacuum_pages.html
|
||||||
|
func (c *Conn) AutoVacuumPages(cb func(schema string, dbPages, freePages, bytesPerPage uint) uint) error {
|
||||||
|
funcPtr := util.AddHandle(c.ctx, cb)
|
||||||
|
r := c.call("sqlite3_autovacuum_pages_go", uint64(c.handle), uint64(funcPtr))
|
||||||
|
return c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoVacuumCallback(ctx context.Context, mod api.Module, pApp, zSchema, nDbPage, nFreePage, nBytePerPage uint32) uint32 {
|
||||||
|
fn := util.GetHandle(ctx, pApp).(func(schema string, dbPages, freePages, bytesPerPage uint) uint)
|
||||||
|
schema := util.ReadString(mod, zSchema, _MAX_NAME)
|
||||||
|
return uint32(fn(schema, uint(nDbPage), uint(nFreePage), uint(nBytePerPage)))
|
||||||
|
}
|
426
vendor/github.com/ncruces/go-sqlite3/conn.go
generated
vendored
Normal file
426
vendor/github.com/ncruces/go-sqlite3/conn.go
generated
vendored
Normal file
|
@ -0,0 +1,426 @@
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
"github.com/ncruces/go-sqlite3/vfs"
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Conn is a database connection handle.
|
||||||
|
// A Conn is not safe for concurrent use by multiple goroutines.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/sqlite3.html
|
||||||
|
type Conn struct {
|
||||||
|
*sqlite
|
||||||
|
|
||||||
|
interrupt context.Context
|
||||||
|
pending *Stmt
|
||||||
|
busy func(int) bool
|
||||||
|
log func(xErrorCode, string)
|
||||||
|
collation func(*Conn, string)
|
||||||
|
authorizer func(AuthorizerActionCode, string, string, string, string) AuthorizerReturnCode
|
||||||
|
update func(AuthorizerActionCode, string, string, int64)
|
||||||
|
commit func() bool
|
||||||
|
rollback func()
|
||||||
|
wal func(*Conn, string, int) error
|
||||||
|
arena arena
|
||||||
|
|
||||||
|
handle uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open calls [OpenFlags] with [OPEN_READWRITE], [OPEN_CREATE], [OPEN_URI] and [OPEN_NOFOLLOW].
|
||||||
|
func Open(filename string) (*Conn, error) {
|
||||||
|
return newConn(filename, OPEN_READWRITE|OPEN_CREATE|OPEN_URI|OPEN_NOFOLLOW)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenFlags opens an SQLite database file as specified by the filename argument.
|
||||||
|
//
|
||||||
|
// If none of the required flags is used, a combination of [OPEN_READWRITE] and [OPEN_CREATE] is used.
|
||||||
|
// If a URI filename is used, PRAGMA statements to execute can be specified using "_pragma":
|
||||||
|
//
|
||||||
|
// sqlite3.Open("file:demo.db?_pragma=busy_timeout(10000)")
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/open.html
|
||||||
|
func OpenFlags(filename string, flags OpenFlag) (*Conn, error) {
|
||||||
|
if flags&(OPEN_READONLY|OPEN_READWRITE|OPEN_CREATE) == 0 {
|
||||||
|
flags |= OPEN_READWRITE | OPEN_CREATE
|
||||||
|
}
|
||||||
|
return newConn(filename, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
type connKey struct{}
|
||||||
|
|
||||||
|
func newConn(filename string, flags OpenFlag) (conn *Conn, err error) {
|
||||||
|
sqlite, err := instantiateSQLite()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if conn == nil {
|
||||||
|
sqlite.close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c := &Conn{sqlite: sqlite}
|
||||||
|
c.arena = c.newArena(1024)
|
||||||
|
c.ctx = context.WithValue(c.ctx, connKey{}, c)
|
||||||
|
c.handle, err = c.openDB(filename, flags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) {
|
||||||
|
defer c.arena.mark()()
|
||||||
|
connPtr := c.arena.new(ptrlen)
|
||||||
|
namePtr := c.arena.string(filename)
|
||||||
|
|
||||||
|
flags |= OPEN_EXRESCODE
|
||||||
|
r := c.call("sqlite3_open_v2", uint64(namePtr), uint64(connPtr), uint64(flags), 0)
|
||||||
|
|
||||||
|
handle := util.ReadUint32(c.mod, connPtr)
|
||||||
|
if err := c.sqlite.error(r, handle); err != nil {
|
||||||
|
c.closeDB(handle)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags|OPEN_URI != 0 && strings.HasPrefix(filename, "file:") {
|
||||||
|
var pragmas strings.Builder
|
||||||
|
if _, after, ok := strings.Cut(filename, "?"); ok {
|
||||||
|
query, _ := url.ParseQuery(after)
|
||||||
|
for _, p := range query["_pragma"] {
|
||||||
|
pragmas.WriteString(`PRAGMA `)
|
||||||
|
pragmas.WriteString(p)
|
||||||
|
pragmas.WriteString(`;`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pragmas.Len() != 0 {
|
||||||
|
pragmaPtr := c.arena.string(pragmas.String())
|
||||||
|
r := c.call("sqlite3_exec", uint64(handle), uint64(pragmaPtr), 0, 0, 0)
|
||||||
|
if err := c.sqlite.error(r, handle, pragmas.String()); err != nil {
|
||||||
|
err = fmt.Errorf("sqlite3: invalid _pragma: %w", err)
|
||||||
|
c.closeDB(handle)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.call("sqlite3_progress_handler_go", uint64(handle), 100)
|
||||||
|
return handle, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) closeDB(handle uint32) {
|
||||||
|
r := c.call("sqlite3_close_v2", uint64(handle))
|
||||||
|
if err := c.sqlite.error(r, handle); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the database connection.
|
||||||
|
//
|
||||||
|
// If the database connection is associated with unfinalized prepared statements,
|
||||||
|
// open blob handles, and/or unfinished backup objects,
|
||||||
|
// Close will leave the database connection open and return [BUSY].
|
||||||
|
//
|
||||||
|
// It is safe to close a nil, zero or closed Conn.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/close.html
|
||||||
|
func (c *Conn) Close() error {
|
||||||
|
if c == nil || c.handle == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.pending.Close()
|
||||||
|
c.pending = nil
|
||||||
|
|
||||||
|
r := c.call("sqlite3_close", uint64(c.handle))
|
||||||
|
if err := c.error(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.handle = 0
|
||||||
|
return c.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec is a convenience function that allows an application to run
|
||||||
|
// multiple statements of SQL without having to use a lot of code.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/exec.html
|
||||||
|
func (c *Conn) Exec(sql string) error {
|
||||||
|
c.checkInterrupt()
|
||||||
|
defer c.arena.mark()()
|
||||||
|
sqlPtr := c.arena.string(sql)
|
||||||
|
|
||||||
|
r := c.call("sqlite3_exec", uint64(c.handle), uint64(sqlPtr), 0, 0, 0)
|
||||||
|
return c.error(r, sql)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare calls [Conn.PrepareFlags] with no flags.
|
||||||
|
func (c *Conn) Prepare(sql string) (stmt *Stmt, tail string, err error) {
|
||||||
|
return c.PrepareFlags(sql, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareFlags compiles the first SQL statement in sql;
|
||||||
|
// tail is left pointing to what remains uncompiled.
|
||||||
|
// If the input text contains no SQL (if the input is an empty string or a comment),
|
||||||
|
// both stmt and err will be nil.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/prepare.html
|
||||||
|
func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) {
|
||||||
|
if len(sql) > _MAX_SQL_LENGTH {
|
||||||
|
return nil, "", TOOBIG
|
||||||
|
}
|
||||||
|
|
||||||
|
defer c.arena.mark()()
|
||||||
|
stmtPtr := c.arena.new(ptrlen)
|
||||||
|
tailPtr := c.arena.new(ptrlen)
|
||||||
|
sqlPtr := c.arena.string(sql)
|
||||||
|
|
||||||
|
r := c.call("sqlite3_prepare_v3", uint64(c.handle),
|
||||||
|
uint64(sqlPtr), uint64(len(sql)+1), uint64(flags),
|
||||||
|
uint64(stmtPtr), uint64(tailPtr))
|
||||||
|
|
||||||
|
stmt = &Stmt{c: c}
|
||||||
|
stmt.handle = util.ReadUint32(c.mod, stmtPtr)
|
||||||
|
if sql := sql[util.ReadUint32(c.mod, tailPtr)-sqlPtr:]; sql != "" {
|
||||||
|
tail = sql
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.error(r, sql); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
if stmt.handle == 0 {
|
||||||
|
return nil, "", nil
|
||||||
|
}
|
||||||
|
return stmt, tail, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DBName returns the schema name for n-th database on the database connection.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/db_name.html
|
||||||
|
func (c *Conn) DBName(n int) string {
|
||||||
|
r := c.call("sqlite3_db_name", uint64(c.handle), uint64(n))
|
||||||
|
|
||||||
|
ptr := uint32(r)
|
||||||
|
if ptr == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return util.ReadString(c.mod, ptr, _MAX_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filename returns the filename for a database.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/db_filename.html
|
||||||
|
func (c *Conn) Filename(schema string) *vfs.Filename {
|
||||||
|
var ptr uint32
|
||||||
|
if schema != "" {
|
||||||
|
defer c.arena.mark()()
|
||||||
|
ptr = c.arena.string(schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := c.call("sqlite3_db_filename", uint64(c.handle), uint64(ptr))
|
||||||
|
return vfs.OpenFilename(c.ctx, c.mod, uint32(r), vfs.OPEN_MAIN_DB)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOnly determines if a database is read-only.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/db_readonly.html
|
||||||
|
func (c *Conn) ReadOnly(schema string) (ro bool, ok bool) {
|
||||||
|
var ptr uint32
|
||||||
|
if schema != "" {
|
||||||
|
defer c.arena.mark()()
|
||||||
|
ptr = c.arena.string(schema)
|
||||||
|
}
|
||||||
|
r := c.call("sqlite3_db_readonly", uint64(c.handle), uint64(ptr))
|
||||||
|
return int32(r) > 0, int32(r) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAutocommit tests the connection for auto-commit mode.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/get_autocommit.html
|
||||||
|
func (c *Conn) GetAutocommit() bool {
|
||||||
|
r := c.call("sqlite3_get_autocommit", uint64(c.handle))
|
||||||
|
return r != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastInsertRowID returns the rowid of the most recent successful INSERT
|
||||||
|
// on the database connection.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/last_insert_rowid.html
|
||||||
|
func (c *Conn) LastInsertRowID() int64 {
|
||||||
|
r := c.call("sqlite3_last_insert_rowid", uint64(c.handle))
|
||||||
|
return int64(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLastInsertRowID allows the application to set the value returned by
|
||||||
|
// [Conn.LastInsertRowID].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/set_last_insert_rowid.html
|
||||||
|
func (c *Conn) SetLastInsertRowID(id int64) {
|
||||||
|
c.call("sqlite3_set_last_insert_rowid", uint64(c.handle), uint64(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changes returns the number of rows modified, inserted or deleted
|
||||||
|
// by the most recently completed INSERT, UPDATE or DELETE statement
|
||||||
|
// on the database connection.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/changes.html
|
||||||
|
func (c *Conn) Changes() int64 {
|
||||||
|
r := c.call("sqlite3_changes64", uint64(c.handle))
|
||||||
|
return int64(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalChanges returns the number of rows modified, inserted or deleted
|
||||||
|
// by all INSERT, UPDATE or DELETE statements completed
|
||||||
|
// since the database connection was opened.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/total_changes.html
|
||||||
|
func (c *Conn) TotalChanges() int64 {
|
||||||
|
r := c.call("sqlite3_total_changes64", uint64(c.handle))
|
||||||
|
return int64(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseMemory frees memory used by a database connection.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/db_release_memory.html
|
||||||
|
func (c *Conn) ReleaseMemory() error {
|
||||||
|
r := c.call("sqlite3_db_release_memory", uint64(c.handle))
|
||||||
|
return c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInterrupt gets the context set with [Conn.SetInterrupt],
|
||||||
|
// or nil if none was set.
|
||||||
|
func (c *Conn) GetInterrupt() context.Context {
|
||||||
|
return c.interrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetInterrupt interrupts a long-running query when a context is done.
|
||||||
|
//
|
||||||
|
// Subsequent uses of the connection will return [INTERRUPT]
|
||||||
|
// until the context is reset by another call to SetInterrupt.
|
||||||
|
//
|
||||||
|
// To associate a timeout with a connection:
|
||||||
|
//
|
||||||
|
// ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond)
|
||||||
|
// conn.SetInterrupt(ctx)
|
||||||
|
// defer cancel()
|
||||||
|
//
|
||||||
|
// SetInterrupt returns the old context assigned to the connection.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/interrupt.html
|
||||||
|
func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) {
|
||||||
|
// Is it the same context?
|
||||||
|
if ctx == c.interrupt {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// A busy SQL statement prevents SQLite from ignoring an interrupt
|
||||||
|
// that comes before any other statements are started.
|
||||||
|
if c.pending == nil {
|
||||||
|
c.pending, _, _ = c.Prepare(`WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x FROM c) SELECT x FROM c`)
|
||||||
|
}
|
||||||
|
|
||||||
|
old = c.interrupt
|
||||||
|
c.interrupt = ctx
|
||||||
|
|
||||||
|
if old != nil && old.Done() != nil && (ctx == nil || ctx.Err() == nil) {
|
||||||
|
c.pending.Reset()
|
||||||
|
}
|
||||||
|
if ctx != nil && ctx.Done() != nil {
|
||||||
|
c.pending.Step()
|
||||||
|
}
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) checkInterrupt() {
|
||||||
|
if c.interrupt != nil && c.interrupt.Err() != nil {
|
||||||
|
c.call("sqlite3_interrupt", uint64(c.handle))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func progressCallback(ctx context.Context, mod api.Module, pDB uint32) (interrupt uint32) {
|
||||||
|
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB &&
|
||||||
|
c.interrupt != nil && c.interrupt.Err() != nil {
|
||||||
|
interrupt = 1
|
||||||
|
}
|
||||||
|
return interrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
// BusyTimeout sets a busy timeout.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/busy_timeout.html
|
||||||
|
func (c *Conn) BusyTimeout(timeout time.Duration) error {
|
||||||
|
ms := min((timeout+time.Millisecond-1)/time.Millisecond, math.MaxInt32)
|
||||||
|
r := c.call("sqlite3_busy_timeout", uint64(c.handle), uint64(ms))
|
||||||
|
return c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeoutCallback(ctx context.Context, mod api.Module, pDB uint32, count, tmout int32) (retry uint32) {
|
||||||
|
if c, ok := ctx.Value(connKey{}).(*Conn); ok &&
|
||||||
|
(c.interrupt == nil || c.interrupt.Err() == nil) {
|
||||||
|
const delays = "\x01\x02\x05\x0a\x0f\x14\x19\x19\x19\x32\x32\x64"
|
||||||
|
const totals = "\x00\x01\x03\x08\x12\x21\x35\x4e\x67\x80\xb2\xe4"
|
||||||
|
const ndelay = int32(len(delays) - 1)
|
||||||
|
|
||||||
|
var delay, prior int32
|
||||||
|
if count <= ndelay {
|
||||||
|
delay = int32(delays[count])
|
||||||
|
prior = int32(totals[count])
|
||||||
|
} else {
|
||||||
|
delay = int32(delays[ndelay])
|
||||||
|
prior = int32(totals[ndelay]) + delay*(count-ndelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
if delay = min(delay, tmout-prior); delay > 0 {
|
||||||
|
time.Sleep(time.Duration(delay) * time.Millisecond)
|
||||||
|
retry = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retry
|
||||||
|
}
|
||||||
|
|
||||||
|
// BusyHandler registers a callback to handle [BUSY] errors.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/busy_handler.html
|
||||||
|
func (c *Conn) BusyHandler(cb func(count int) (retry bool)) error {
|
||||||
|
var enable uint64
|
||||||
|
if cb != nil {
|
||||||
|
enable = 1
|
||||||
|
}
|
||||||
|
r := c.call("sqlite3_busy_handler_go", uint64(c.handle), enable)
|
||||||
|
if err := c.error(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.busy = cb
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func busyCallback(ctx context.Context, mod api.Module, pDB uint32, count int32) (retry uint32) {
|
||||||
|
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.busy != nil &&
|
||||||
|
(c.interrupt == nil || c.interrupt.Err() == nil) {
|
||||||
|
if c.busy(int(count)) {
|
||||||
|
retry = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) error(rc uint64, sql ...string) error {
|
||||||
|
return c.sqlite.error(rc, c.handle, sql...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DriverConn is implemented by the SQLite [database/sql] driver connection.
|
||||||
|
//
|
||||||
|
// It can be used to access SQLite features like [online backup].
|
||||||
|
//
|
||||||
|
// [online backup]: https://sqlite.org/backup.html
|
||||||
|
type DriverConn interface {
|
||||||
|
Raw() *Conn
|
||||||
|
}
|
360
vendor/github.com/ncruces/go-sqlite3/const.go
generated
vendored
Normal file
360
vendor/github.com/ncruces/go-sqlite3/const.go
generated
vendored
Normal file
|
@ -0,0 +1,360 @@
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
const (
|
||||||
|
_OK = 0 /* Successful result */
|
||||||
|
_ROW = 100 /* sqlite3_step() has another row ready */
|
||||||
|
_DONE = 101 /* sqlite3_step() has finished executing */
|
||||||
|
|
||||||
|
_UTF8 = 1
|
||||||
|
|
||||||
|
_MAX_NAME = 1e6 // Self-imposed limit for most NUL terminated strings.
|
||||||
|
_MAX_LENGTH = 1e9
|
||||||
|
_MAX_SQL_LENGTH = 1e9
|
||||||
|
_MAX_ALLOCATION_SIZE = 0x7ffffeff
|
||||||
|
_MAX_FUNCTION_ARG = 100
|
||||||
|
|
||||||
|
ptrlen = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorCode is a result code that [Error.Code] might return.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/rescode.html
|
||||||
|
type ErrorCode uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
ERROR ErrorCode = 1 /* Generic error */
|
||||||
|
INTERNAL ErrorCode = 2 /* Internal logic error in SQLite */
|
||||||
|
PERM ErrorCode = 3 /* Access permission denied */
|
||||||
|
ABORT ErrorCode = 4 /* Callback routine requested an abort */
|
||||||
|
BUSY ErrorCode = 5 /* The database file is locked */
|
||||||
|
LOCKED ErrorCode = 6 /* A table in the database is locked */
|
||||||
|
NOMEM ErrorCode = 7 /* A malloc() failed */
|
||||||
|
READONLY ErrorCode = 8 /* Attempt to write a readonly database */
|
||||||
|
INTERRUPT ErrorCode = 9 /* Operation terminated by sqlite3_interrupt() */
|
||||||
|
IOERR ErrorCode = 10 /* Some kind of disk I/O error occurred */
|
||||||
|
CORRUPT ErrorCode = 11 /* The database disk image is malformed */
|
||||||
|
NOTFOUND ErrorCode = 12 /* Unknown opcode in sqlite3_file_control() */
|
||||||
|
FULL ErrorCode = 13 /* Insertion failed because database is full */
|
||||||
|
CANTOPEN ErrorCode = 14 /* Unable to open the database file */
|
||||||
|
PROTOCOL ErrorCode = 15 /* Database lock protocol error */
|
||||||
|
EMPTY ErrorCode = 16 /* Internal use only */
|
||||||
|
SCHEMA ErrorCode = 17 /* The database schema changed */
|
||||||
|
TOOBIG ErrorCode = 18 /* String or BLOB exceeds size limit */
|
||||||
|
CONSTRAINT ErrorCode = 19 /* Abort due to constraint violation */
|
||||||
|
MISMATCH ErrorCode = 20 /* Data type mismatch */
|
||||||
|
MISUSE ErrorCode = 21 /* Library used incorrectly */
|
||||||
|
NOLFS ErrorCode = 22 /* Uses OS features not supported on host */
|
||||||
|
AUTH ErrorCode = 23 /* Authorization denied */
|
||||||
|
FORMAT ErrorCode = 24 /* Not used */
|
||||||
|
RANGE ErrorCode = 25 /* 2nd parameter to sqlite3_bind out of range */
|
||||||
|
NOTADB ErrorCode = 26 /* File opened that is not a database file */
|
||||||
|
NOTICE ErrorCode = 27 /* Notifications from sqlite3_log() */
|
||||||
|
WARNING ErrorCode = 28 /* Warnings from sqlite3_log() */
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExtendedErrorCode is a result code that [Error.ExtendedCode] might return.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/rescode.html
|
||||||
|
type (
|
||||||
|
ExtendedErrorCode uint16
|
||||||
|
xErrorCode = ExtendedErrorCode
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ERROR_MISSING_COLLSEQ ExtendedErrorCode = xErrorCode(ERROR) | (1 << 8)
|
||||||
|
ERROR_RETRY ExtendedErrorCode = xErrorCode(ERROR) | (2 << 8)
|
||||||
|
ERROR_SNAPSHOT ExtendedErrorCode = xErrorCode(ERROR) | (3 << 8)
|
||||||
|
IOERR_READ ExtendedErrorCode = xErrorCode(IOERR) | (1 << 8)
|
||||||
|
IOERR_SHORT_READ ExtendedErrorCode = xErrorCode(IOERR) | (2 << 8)
|
||||||
|
IOERR_WRITE ExtendedErrorCode = xErrorCode(IOERR) | (3 << 8)
|
||||||
|
IOERR_FSYNC ExtendedErrorCode = xErrorCode(IOERR) | (4 << 8)
|
||||||
|
IOERR_DIR_FSYNC ExtendedErrorCode = xErrorCode(IOERR) | (5 << 8)
|
||||||
|
IOERR_TRUNCATE ExtendedErrorCode = xErrorCode(IOERR) | (6 << 8)
|
||||||
|
IOERR_FSTAT ExtendedErrorCode = xErrorCode(IOERR) | (7 << 8)
|
||||||
|
IOERR_UNLOCK ExtendedErrorCode = xErrorCode(IOERR) | (8 << 8)
|
||||||
|
IOERR_RDLOCK ExtendedErrorCode = xErrorCode(IOERR) | (9 << 8)
|
||||||
|
IOERR_DELETE ExtendedErrorCode = xErrorCode(IOERR) | (10 << 8)
|
||||||
|
IOERR_BLOCKED ExtendedErrorCode = xErrorCode(IOERR) | (11 << 8)
|
||||||
|
IOERR_NOMEM ExtendedErrorCode = xErrorCode(IOERR) | (12 << 8)
|
||||||
|
IOERR_ACCESS ExtendedErrorCode = xErrorCode(IOERR) | (13 << 8)
|
||||||
|
IOERR_CHECKRESERVEDLOCK ExtendedErrorCode = xErrorCode(IOERR) | (14 << 8)
|
||||||
|
IOERR_LOCK ExtendedErrorCode = xErrorCode(IOERR) | (15 << 8)
|
||||||
|
IOERR_CLOSE ExtendedErrorCode = xErrorCode(IOERR) | (16 << 8)
|
||||||
|
IOERR_DIR_CLOSE ExtendedErrorCode = xErrorCode(IOERR) | (17 << 8)
|
||||||
|
IOERR_SHMOPEN ExtendedErrorCode = xErrorCode(IOERR) | (18 << 8)
|
||||||
|
IOERR_SHMSIZE ExtendedErrorCode = xErrorCode(IOERR) | (19 << 8)
|
||||||
|
IOERR_SHMLOCK ExtendedErrorCode = xErrorCode(IOERR) | (20 << 8)
|
||||||
|
IOERR_SHMMAP ExtendedErrorCode = xErrorCode(IOERR) | (21 << 8)
|
||||||
|
IOERR_SEEK ExtendedErrorCode = xErrorCode(IOERR) | (22 << 8)
|
||||||
|
IOERR_DELETE_NOENT ExtendedErrorCode = xErrorCode(IOERR) | (23 << 8)
|
||||||
|
IOERR_MMAP ExtendedErrorCode = xErrorCode(IOERR) | (24 << 8)
|
||||||
|
IOERR_GETTEMPPATH ExtendedErrorCode = xErrorCode(IOERR) | (25 << 8)
|
||||||
|
IOERR_CONVPATH ExtendedErrorCode = xErrorCode(IOERR) | (26 << 8)
|
||||||
|
IOERR_VNODE ExtendedErrorCode = xErrorCode(IOERR) | (27 << 8)
|
||||||
|
IOERR_AUTH ExtendedErrorCode = xErrorCode(IOERR) | (28 << 8)
|
||||||
|
IOERR_BEGIN_ATOMIC ExtendedErrorCode = xErrorCode(IOERR) | (29 << 8)
|
||||||
|
IOERR_COMMIT_ATOMIC ExtendedErrorCode = xErrorCode(IOERR) | (30 << 8)
|
||||||
|
IOERR_ROLLBACK_ATOMIC ExtendedErrorCode = xErrorCode(IOERR) | (31 << 8)
|
||||||
|
IOERR_DATA ExtendedErrorCode = xErrorCode(IOERR) | (32 << 8)
|
||||||
|
IOERR_CORRUPTFS ExtendedErrorCode = xErrorCode(IOERR) | (33 << 8)
|
||||||
|
IOERR_IN_PAGE ExtendedErrorCode = xErrorCode(IOERR) | (34 << 8)
|
||||||
|
LOCKED_SHAREDCACHE ExtendedErrorCode = xErrorCode(LOCKED) | (1 << 8)
|
||||||
|
LOCKED_VTAB ExtendedErrorCode = xErrorCode(LOCKED) | (2 << 8)
|
||||||
|
BUSY_RECOVERY ExtendedErrorCode = xErrorCode(BUSY) | (1 << 8)
|
||||||
|
BUSY_SNAPSHOT ExtendedErrorCode = xErrorCode(BUSY) | (2 << 8)
|
||||||
|
BUSY_TIMEOUT ExtendedErrorCode = xErrorCode(BUSY) | (3 << 8)
|
||||||
|
CANTOPEN_NOTEMPDIR ExtendedErrorCode = xErrorCode(CANTOPEN) | (1 << 8)
|
||||||
|
CANTOPEN_ISDIR ExtendedErrorCode = xErrorCode(CANTOPEN) | (2 << 8)
|
||||||
|
CANTOPEN_FULLPATH ExtendedErrorCode = xErrorCode(CANTOPEN) | (3 << 8)
|
||||||
|
CANTOPEN_CONVPATH ExtendedErrorCode = xErrorCode(CANTOPEN) | (4 << 8)
|
||||||
|
CANTOPEN_DIRTYWAL ExtendedErrorCode = xErrorCode(CANTOPEN) | (5 << 8) /* Not Used */
|
||||||
|
CANTOPEN_SYMLINK ExtendedErrorCode = xErrorCode(CANTOPEN) | (6 << 8)
|
||||||
|
CORRUPT_VTAB ExtendedErrorCode = xErrorCode(CORRUPT) | (1 << 8)
|
||||||
|
CORRUPT_SEQUENCE ExtendedErrorCode = xErrorCode(CORRUPT) | (2 << 8)
|
||||||
|
CORRUPT_INDEX ExtendedErrorCode = xErrorCode(CORRUPT) | (3 << 8)
|
||||||
|
READONLY_RECOVERY ExtendedErrorCode = xErrorCode(READONLY) | (1 << 8)
|
||||||
|
READONLY_CANTLOCK ExtendedErrorCode = xErrorCode(READONLY) | (2 << 8)
|
||||||
|
READONLY_ROLLBACK ExtendedErrorCode = xErrorCode(READONLY) | (3 << 8)
|
||||||
|
READONLY_DBMOVED ExtendedErrorCode = xErrorCode(READONLY) | (4 << 8)
|
||||||
|
READONLY_CANTINIT ExtendedErrorCode = xErrorCode(READONLY) | (5 << 8)
|
||||||
|
READONLY_DIRECTORY ExtendedErrorCode = xErrorCode(READONLY) | (6 << 8)
|
||||||
|
ABORT_ROLLBACK ExtendedErrorCode = xErrorCode(ABORT) | (2 << 8)
|
||||||
|
CONSTRAINT_CHECK ExtendedErrorCode = xErrorCode(CONSTRAINT) | (1 << 8)
|
||||||
|
CONSTRAINT_COMMITHOOK ExtendedErrorCode = xErrorCode(CONSTRAINT) | (2 << 8)
|
||||||
|
CONSTRAINT_FOREIGNKEY ExtendedErrorCode = xErrorCode(CONSTRAINT) | (3 << 8)
|
||||||
|
CONSTRAINT_FUNCTION ExtendedErrorCode = xErrorCode(CONSTRAINT) | (4 << 8)
|
||||||
|
CONSTRAINT_NOTNULL ExtendedErrorCode = xErrorCode(CONSTRAINT) | (5 << 8)
|
||||||
|
CONSTRAINT_PRIMARYKEY ExtendedErrorCode = xErrorCode(CONSTRAINT) | (6 << 8)
|
||||||
|
CONSTRAINT_TRIGGER ExtendedErrorCode = xErrorCode(CONSTRAINT) | (7 << 8)
|
||||||
|
CONSTRAINT_UNIQUE ExtendedErrorCode = xErrorCode(CONSTRAINT) | (8 << 8)
|
||||||
|
CONSTRAINT_VTAB ExtendedErrorCode = xErrorCode(CONSTRAINT) | (9 << 8)
|
||||||
|
CONSTRAINT_ROWID ExtendedErrorCode = xErrorCode(CONSTRAINT) | (10 << 8)
|
||||||
|
CONSTRAINT_PINNED ExtendedErrorCode = xErrorCode(CONSTRAINT) | (11 << 8)
|
||||||
|
CONSTRAINT_DATATYPE ExtendedErrorCode = xErrorCode(CONSTRAINT) | (12 << 8)
|
||||||
|
NOTICE_RECOVER_WAL ExtendedErrorCode = xErrorCode(NOTICE) | (1 << 8)
|
||||||
|
NOTICE_RECOVER_ROLLBACK ExtendedErrorCode = xErrorCode(NOTICE) | (2 << 8)
|
||||||
|
NOTICE_RBU ExtendedErrorCode = xErrorCode(NOTICE) | (3 << 8)
|
||||||
|
WARNING_AUTOINDEX ExtendedErrorCode = xErrorCode(WARNING) | (1 << 8)
|
||||||
|
AUTH_USER ExtendedErrorCode = xErrorCode(AUTH) | (1 << 8)
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenFlag is a flag for the [OpenFlags] function.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_open_autoproxy.html
|
||||||
|
type OpenFlag uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
OPEN_READONLY OpenFlag = 0x00000001 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_READWRITE OpenFlag = 0x00000002 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_CREATE OpenFlag = 0x00000004 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_URI OpenFlag = 0x00000040 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_MEMORY OpenFlag = 0x00000080 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_NOMUTEX OpenFlag = 0x00008000 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_FULLMUTEX OpenFlag = 0x00010000 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_SHAREDCACHE OpenFlag = 0x00020000 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_PRIVATECACHE OpenFlag = 0x00040000 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_NOFOLLOW OpenFlag = 0x01000000 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_EXRESCODE OpenFlag = 0x02000000 /* Extended result codes */
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrepareFlag is a flag that can be passed to [Conn.PrepareFlags].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_prepare_normalize.html
|
||||||
|
type PrepareFlag uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
PREPARE_PERSISTENT PrepareFlag = 0x01
|
||||||
|
PREPARE_NORMALIZE PrepareFlag = 0x02
|
||||||
|
PREPARE_NO_VTAB PrepareFlag = 0x04
|
||||||
|
)
|
||||||
|
|
||||||
|
// FunctionFlag is a flag that can be passed to
|
||||||
|
// [Conn.CreateFunction] and [Conn.CreateWindowFunction].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_deterministic.html
|
||||||
|
type FunctionFlag uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
DETERMINISTIC FunctionFlag = 0x000000800
|
||||||
|
DIRECTONLY FunctionFlag = 0x000080000
|
||||||
|
SUBTYPE FunctionFlag = 0x000100000
|
||||||
|
INNOCUOUS FunctionFlag = 0x000200000
|
||||||
|
RESULT_SUBTYPE FunctionFlag = 0x001000000
|
||||||
|
)
|
||||||
|
|
||||||
|
// StmtStatus name counter values associated with the [Stmt.Status] method.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_stmtstatus_counter.html
|
||||||
|
type StmtStatus uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
STMTSTATUS_FULLSCAN_STEP StmtStatus = 1
|
||||||
|
STMTSTATUS_SORT StmtStatus = 2
|
||||||
|
STMTSTATUS_AUTOINDEX StmtStatus = 3
|
||||||
|
STMTSTATUS_VM_STEP StmtStatus = 4
|
||||||
|
STMTSTATUS_REPREPARE StmtStatus = 5
|
||||||
|
STMTSTATUS_RUN StmtStatus = 6
|
||||||
|
STMTSTATUS_FILTER_MISS StmtStatus = 7
|
||||||
|
STMTSTATUS_FILTER_HIT StmtStatus = 8
|
||||||
|
STMTSTATUS_MEMUSED StmtStatus = 99
|
||||||
|
)
|
||||||
|
|
||||||
|
// DBConfig are the available database connection configuration options.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_dbconfig_defensive.html
|
||||||
|
type DBConfig uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DBCONFIG_MAINDBNAME DBConfig = 1000
|
||||||
|
// DBCONFIG_LOOKASIDE DBConfig = 1001
|
||||||
|
DBCONFIG_ENABLE_FKEY DBConfig = 1002
|
||||||
|
DBCONFIG_ENABLE_TRIGGER DBConfig = 1003
|
||||||
|
DBCONFIG_ENABLE_FTS3_TOKENIZER DBConfig = 1004
|
||||||
|
DBCONFIG_ENABLE_LOAD_EXTENSION DBConfig = 1005
|
||||||
|
DBCONFIG_NO_CKPT_ON_CLOSE DBConfig = 1006
|
||||||
|
DBCONFIG_ENABLE_QPSG DBConfig = 1007
|
||||||
|
DBCONFIG_TRIGGER_EQP DBConfig = 1008
|
||||||
|
DBCONFIG_RESET_DATABASE DBConfig = 1009
|
||||||
|
DBCONFIG_DEFENSIVE DBConfig = 1010
|
||||||
|
DBCONFIG_WRITABLE_SCHEMA DBConfig = 1011
|
||||||
|
DBCONFIG_LEGACY_ALTER_TABLE DBConfig = 1012
|
||||||
|
DBCONFIG_DQS_DML DBConfig = 1013
|
||||||
|
DBCONFIG_DQS_DDL DBConfig = 1014
|
||||||
|
DBCONFIG_ENABLE_VIEW DBConfig = 1015
|
||||||
|
DBCONFIG_LEGACY_FILE_FORMAT DBConfig = 1016
|
||||||
|
DBCONFIG_TRUSTED_SCHEMA DBConfig = 1017
|
||||||
|
DBCONFIG_STMT_SCANSTATUS DBConfig = 1018
|
||||||
|
DBCONFIG_REVERSE_SCANORDER DBConfig = 1019
|
||||||
|
)
|
||||||
|
|
||||||
|
// LimitCategory are the available run-time limit categories.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_limit_attached.html
|
||||||
|
type LimitCategory uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
LIMIT_LENGTH LimitCategory = 0
|
||||||
|
LIMIT_SQL_LENGTH LimitCategory = 1
|
||||||
|
LIMIT_COLUMN LimitCategory = 2
|
||||||
|
LIMIT_EXPR_DEPTH LimitCategory = 3
|
||||||
|
LIMIT_COMPOUND_SELECT LimitCategory = 4
|
||||||
|
LIMIT_VDBE_OP LimitCategory = 5
|
||||||
|
LIMIT_FUNCTION_ARG LimitCategory = 6
|
||||||
|
LIMIT_ATTACHED LimitCategory = 7
|
||||||
|
LIMIT_LIKE_PATTERN_LENGTH LimitCategory = 8
|
||||||
|
LIMIT_VARIABLE_NUMBER LimitCategory = 9
|
||||||
|
LIMIT_TRIGGER_DEPTH LimitCategory = 10
|
||||||
|
LIMIT_WORKER_THREADS LimitCategory = 11
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthorizerActionCode are the integer action codes
|
||||||
|
// that the authorizer callback may be passed.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_alter_table.html
|
||||||
|
type AuthorizerActionCode uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
/***************************************************** 3rd ************ 4th ***********/
|
||||||
|
AUTH_CREATE_INDEX AuthorizerActionCode = 1 /* Index Name Table Name */
|
||||||
|
AUTH_CREATE_TABLE AuthorizerActionCode = 2 /* Table Name NULL */
|
||||||
|
AUTH_CREATE_TEMP_INDEX AuthorizerActionCode = 3 /* Index Name Table Name */
|
||||||
|
AUTH_CREATE_TEMP_TABLE AuthorizerActionCode = 4 /* Table Name NULL */
|
||||||
|
AUTH_CREATE_TEMP_TRIGGER AuthorizerActionCode = 5 /* Trigger Name Table Name */
|
||||||
|
AUTH_CREATE_TEMP_VIEW AuthorizerActionCode = 6 /* View Name NULL */
|
||||||
|
AUTH_CREATE_TRIGGER AuthorizerActionCode = 7 /* Trigger Name Table Name */
|
||||||
|
AUTH_CREATE_VIEW AuthorizerActionCode = 8 /* View Name NULL */
|
||||||
|
AUTH_DELETE AuthorizerActionCode = 9 /* Table Name NULL */
|
||||||
|
AUTH_DROP_INDEX AuthorizerActionCode = 10 /* Index Name Table Name */
|
||||||
|
AUTH_DROP_TABLE AuthorizerActionCode = 11 /* Table Name NULL */
|
||||||
|
AUTH_DROP_TEMP_INDEX AuthorizerActionCode = 12 /* Index Name Table Name */
|
||||||
|
AUTH_DROP_TEMP_TABLE AuthorizerActionCode = 13 /* Table Name NULL */
|
||||||
|
AUTH_DROP_TEMP_TRIGGER AuthorizerActionCode = 14 /* Trigger Name Table Name */
|
||||||
|
AUTH_DROP_TEMP_VIEW AuthorizerActionCode = 15 /* View Name NULL */
|
||||||
|
AUTH_DROP_TRIGGER AuthorizerActionCode = 16 /* Trigger Name Table Name */
|
||||||
|
AUTH_DROP_VIEW AuthorizerActionCode = 17 /* View Name NULL */
|
||||||
|
AUTH_INSERT AuthorizerActionCode = 18 /* Table Name NULL */
|
||||||
|
AUTH_PRAGMA AuthorizerActionCode = 19 /* Pragma Name 1st arg or NULL */
|
||||||
|
AUTH_READ AuthorizerActionCode = 20 /* Table Name Column Name */
|
||||||
|
AUTH_SELECT AuthorizerActionCode = 21 /* NULL NULL */
|
||||||
|
AUTH_TRANSACTION AuthorizerActionCode = 22 /* Operation NULL */
|
||||||
|
AUTH_UPDATE AuthorizerActionCode = 23 /* Table Name Column Name */
|
||||||
|
AUTH_ATTACH AuthorizerActionCode = 24 /* Filename NULL */
|
||||||
|
AUTH_DETACH AuthorizerActionCode = 25 /* Database Name NULL */
|
||||||
|
AUTH_ALTER_TABLE AuthorizerActionCode = 26 /* Database Name Table Name */
|
||||||
|
AUTH_REINDEX AuthorizerActionCode = 27 /* Index Name NULL */
|
||||||
|
AUTH_ANALYZE AuthorizerActionCode = 28 /* Table Name NULL */
|
||||||
|
AUTH_CREATE_VTABLE AuthorizerActionCode = 29 /* Table Name Module Name */
|
||||||
|
AUTH_DROP_VTABLE AuthorizerActionCode = 30 /* Table Name Module Name */
|
||||||
|
AUTH_FUNCTION AuthorizerActionCode = 31 /* NULL Function Name */
|
||||||
|
AUTH_SAVEPOINT AuthorizerActionCode = 32 /* Operation Savepoint Name */
|
||||||
|
AUTH_COPY AuthorizerActionCode = 0 /* No longer used */
|
||||||
|
AUTH_RECURSIVE AuthorizerActionCode = 33 /* NULL NULL */
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthorizerReturnCode are the integer codes
|
||||||
|
// that the authorizer callback may return.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_deny.html
|
||||||
|
type AuthorizerReturnCode uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
AUTH_OK AuthorizerReturnCode = 0
|
||||||
|
AUTH_DENY AuthorizerReturnCode = 1 /* Abort the SQL statement with an error */
|
||||||
|
AUTH_IGNORE AuthorizerReturnCode = 2 /* Don't allow access, but don't generate an error */
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckpointMode are all the checkpoint mode values.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_checkpoint_full.html
|
||||||
|
type CheckpointMode uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
CHECKPOINT_PASSIVE CheckpointMode = 0 /* Do as much as possible w/o blocking */
|
||||||
|
CHECKPOINT_FULL CheckpointMode = 1 /* Wait for writers, then checkpoint */
|
||||||
|
CHECKPOINT_RESTART CheckpointMode = 2 /* Like FULL but wait for readers */
|
||||||
|
CHECKPOINT_TRUNCATE CheckpointMode = 3 /* Like RESTART but also truncate WAL */
|
||||||
|
)
|
||||||
|
|
||||||
|
// TxnState are the allowed return values from [Conn.TxnState].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_txn_none.html
|
||||||
|
type TxnState uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
TXN_NONE TxnState = 0
|
||||||
|
TXN_READ TxnState = 1
|
||||||
|
TXN_WRITE TxnState = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// Datatype is a fundamental datatype of SQLite.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_blob.html
|
||||||
|
type Datatype uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
INTEGER Datatype = 1
|
||||||
|
FLOAT Datatype = 2
|
||||||
|
TEXT Datatype = 3
|
||||||
|
BLOB Datatype = 4
|
||||||
|
NULL Datatype = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// String implements the [fmt.Stringer] interface.
|
||||||
|
func (t Datatype) String() string {
|
||||||
|
const name = "INTEGERFLOATEXTBLOBNULL"
|
||||||
|
switch t {
|
||||||
|
case INTEGER:
|
||||||
|
return name[0:7]
|
||||||
|
case FLOAT:
|
||||||
|
return name[7:12]
|
||||||
|
case TEXT:
|
||||||
|
return name[11:15]
|
||||||
|
case BLOB:
|
||||||
|
return name[15:19]
|
||||||
|
case NULL:
|
||||||
|
return name[19:23]
|
||||||
|
}
|
||||||
|
return strconv.FormatUint(uint64(t), 10)
|
||||||
|
}
|
229
vendor/github.com/ncruces/go-sqlite3/context.go
generated
vendored
Normal file
229
vendor/github.com/ncruces/go-sqlite3/context.go
generated
vendored
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Context is the context in which an SQL function executes.
|
||||||
|
// An SQLite [Context] is in no way related to a Go [context.Context].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/context.html
|
||||||
|
type Context struct {
|
||||||
|
c *Conn
|
||||||
|
handle uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conn returns the database connection of the
|
||||||
|
// [Conn.CreateFunction] or [Conn.CreateWindowFunction]
|
||||||
|
// routines that originally registered the application defined function.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/context_db_handle.html
|
||||||
|
func (ctx Context) Conn() *Conn {
|
||||||
|
return ctx.c
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAuxData saves metadata for argument n of the function.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/get_auxdata.html
|
||||||
|
func (ctx Context) SetAuxData(n int, data any) {
|
||||||
|
ptr := util.AddHandle(ctx.c.ctx, data)
|
||||||
|
ctx.c.call("sqlite3_set_auxdata_go", uint64(ctx.handle), uint64(n), uint64(ptr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuxData returns metadata for argument n of the function.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/get_auxdata.html
|
||||||
|
func (ctx Context) GetAuxData(n int) any {
|
||||||
|
ptr := uint32(ctx.c.call("sqlite3_get_auxdata", uint64(ctx.handle), uint64(n)))
|
||||||
|
return util.GetHandle(ctx.c.ctx, ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultBool sets the result of the function to a bool.
|
||||||
|
// SQLite does not have a separate boolean storage class.
|
||||||
|
// Instead, boolean values are stored as integers 0 (false) and 1 (true).
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
|
func (ctx Context) ResultBool(value bool) {
|
||||||
|
var i int64
|
||||||
|
if value {
|
||||||
|
i = 1
|
||||||
|
}
|
||||||
|
ctx.ResultInt64(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultInt sets the result of the function to an int.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
|
func (ctx Context) ResultInt(value int) {
|
||||||
|
ctx.ResultInt64(int64(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultInt64 sets the result of the function to an int64.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
|
func (ctx Context) ResultInt64(value int64) {
|
||||||
|
ctx.c.call("sqlite3_result_int64",
|
||||||
|
uint64(ctx.handle), uint64(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultFloat sets the result of the function to a float64.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
|
func (ctx Context) ResultFloat(value float64) {
|
||||||
|
ctx.c.call("sqlite3_result_double",
|
||||||
|
uint64(ctx.handle), math.Float64bits(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultText sets the result of the function to a string.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
|
func (ctx Context) ResultText(value string) {
|
||||||
|
ptr := ctx.c.newString(value)
|
||||||
|
ctx.c.call("sqlite3_result_text64",
|
||||||
|
uint64(ctx.handle), uint64(ptr), uint64(len(value)),
|
||||||
|
uint64(ctx.c.freer), _UTF8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultRawText sets the text result of the function to a []byte.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
|
func (ctx Context) ResultRawText(value []byte) {
|
||||||
|
ptr := ctx.c.newBytes(value)
|
||||||
|
ctx.c.call("sqlite3_result_text64",
|
||||||
|
uint64(ctx.handle), uint64(ptr), uint64(len(value)),
|
||||||
|
uint64(ctx.c.freer), _UTF8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultBlob sets the result of the function to a []byte.
|
||||||
|
// Returning a nil slice is the same as calling [Context.ResultNull].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
|
func (ctx Context) ResultBlob(value []byte) {
|
||||||
|
ptr := ctx.c.newBytes(value)
|
||||||
|
ctx.c.call("sqlite3_result_blob64",
|
||||||
|
uint64(ctx.handle), uint64(ptr), uint64(len(value)),
|
||||||
|
uint64(ctx.c.freer))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultZeroBlob sets the result of the function to a zero-filled, length n BLOB.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
|
func (ctx Context) ResultZeroBlob(n int64) {
|
||||||
|
ctx.c.call("sqlite3_result_zeroblob64",
|
||||||
|
uint64(ctx.handle), uint64(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultNull sets the result of the function to NULL.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
|
func (ctx Context) ResultNull() {
|
||||||
|
ctx.c.call("sqlite3_result_null",
|
||||||
|
uint64(ctx.handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultTime sets the result of the function to a [time.Time].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
|
func (ctx Context) ResultTime(value time.Time, format TimeFormat) {
|
||||||
|
if format == TimeFormatDefault {
|
||||||
|
ctx.resultRFC3339Nano(value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch v := format.Encode(value).(type) {
|
||||||
|
case string:
|
||||||
|
ctx.ResultText(v)
|
||||||
|
case int64:
|
||||||
|
ctx.ResultInt64(v)
|
||||||
|
case float64:
|
||||||
|
ctx.ResultFloat(v)
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx Context) resultRFC3339Nano(value time.Time) {
|
||||||
|
const maxlen = uint64(len(time.RFC3339Nano)) + 5
|
||||||
|
|
||||||
|
ptr := ctx.c.new(maxlen)
|
||||||
|
buf := util.View(ctx.c.mod, ptr, maxlen)
|
||||||
|
buf = value.AppendFormat(buf[:0], time.RFC3339Nano)
|
||||||
|
|
||||||
|
ctx.c.call("sqlite3_result_text64",
|
||||||
|
uint64(ctx.handle), uint64(ptr), uint64(len(buf)),
|
||||||
|
uint64(ctx.c.freer), _UTF8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultPointer sets the result of the function to NULL, just like [Context.ResultNull],
|
||||||
|
// except that it also associates ptr with that NULL value such that it can be retrieved
|
||||||
|
// within an application-defined SQL function using [Value.Pointer].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
|
func (ctx Context) ResultPointer(ptr any) {
|
||||||
|
valPtr := util.AddHandle(ctx.c.ctx, ptr)
|
||||||
|
ctx.c.call("sqlite3_result_pointer_go", uint64(valPtr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultJSON sets the result of the function to the JSON encoding of value.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
|
func (ctx Context) ResultJSON(value any) {
|
||||||
|
data, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ResultError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.ResultRawText(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultValue sets the result of the function to a copy of [Value].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
|
func (ctx Context) ResultValue(value Value) {
|
||||||
|
if value.c != ctx.c {
|
||||||
|
ctx.ResultError(MISUSE)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.c.call("sqlite3_result_value",
|
||||||
|
uint64(ctx.handle), uint64(value.handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultError sets the result of the function an error.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/result_blob.html
|
||||||
|
func (ctx Context) ResultError(err error) {
|
||||||
|
if errors.Is(err, NOMEM) {
|
||||||
|
ctx.c.call("sqlite3_result_error_nomem", uint64(ctx.handle))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, TOOBIG) {
|
||||||
|
ctx.c.call("sqlite3_result_error_toobig", uint64(ctx.handle))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, code := errorCode(err, _OK)
|
||||||
|
if msg != "" {
|
||||||
|
defer ctx.c.arena.mark()()
|
||||||
|
ptr := ctx.c.arena.string(msg)
|
||||||
|
ctx.c.call("sqlite3_result_error",
|
||||||
|
uint64(ctx.handle), uint64(ptr), uint64(len(msg)))
|
||||||
|
}
|
||||||
|
if code != _OK {
|
||||||
|
ctx.c.call("sqlite3_result_error_code",
|
||||||
|
uint64(ctx.handle), uint64(code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VTabNoChange may return true if a column is being fetched as part
|
||||||
|
// of an update during which the column value will not change.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/vtab_nochange.html
|
||||||
|
func (ctx Context) VTabNoChange() bool {
|
||||||
|
r := ctx.c.call("sqlite3_vtab_nochange", uint64(ctx.handle))
|
||||||
|
return r != 0
|
||||||
|
}
|
579
vendor/github.com/ncruces/go-sqlite3/driver/driver.go
generated
vendored
Normal file
579
vendor/github.com/ncruces/go-sqlite3/driver/driver.go
generated
vendored
Normal file
|
@ -0,0 +1,579 @@
|
||||||
|
// Package driver provides a database/sql driver for SQLite.
|
||||||
|
//
|
||||||
|
// Importing package driver registers a [database/sql] driver named "sqlite3".
|
||||||
|
// You may also need to import package embed.
|
||||||
|
//
|
||||||
|
// import _ "github.com/ncruces/go-sqlite3/driver"
|
||||||
|
// import _ "github.com/ncruces/go-sqlite3/embed"
|
||||||
|
//
|
||||||
|
// The data source name for "sqlite3" databases can be a filename or a "file:" [URI].
|
||||||
|
//
|
||||||
|
// The [TRANSACTION] mode can be specified using "_txlock":
|
||||||
|
//
|
||||||
|
// sql.Open("sqlite3", "file:demo.db?_txlock=immediate")
|
||||||
|
//
|
||||||
|
// Possible values are: "deferred", "immediate", "exclusive".
|
||||||
|
// A [read-only] transaction is always "deferred", regardless of "_txlock".
|
||||||
|
//
|
||||||
|
// The time encoding/decoding format can be specified using "_timefmt":
|
||||||
|
//
|
||||||
|
// sql.Open("sqlite3", "file:demo.db?_timefmt=sqlite")
|
||||||
|
//
|
||||||
|
// Possible values are: "auto" (the default), "sqlite", "rfc3339";
|
||||||
|
// "auto" encodes as RFC 3339 and decodes any [format] supported by SQLite;
|
||||||
|
// "sqlite" encodes as SQLite and decodes any [format] supported by SQLite;
|
||||||
|
// "rfc3339" encodes and decodes RFC 3339 only.
|
||||||
|
//
|
||||||
|
// [PRAGMA] statements can be specified using "_pragma":
|
||||||
|
//
|
||||||
|
// sql.Open("sqlite3", "file:demo.db?_pragma=busy_timeout(10000)")
|
||||||
|
//
|
||||||
|
// If no PRAGMAs are specified, a busy timeout of 1 minute is set.
|
||||||
|
//
|
||||||
|
// Order matters:
|
||||||
|
// busy timeout and locking mode should be the first PRAGMAs set, in that order.
|
||||||
|
//
|
||||||
|
// [URI]: https://sqlite.org/uri.html
|
||||||
|
// [PRAGMA]: https://sqlite.org/pragma.html
|
||||||
|
// [format]: https://sqlite.org/lang_datefunc.html#time_values
|
||||||
|
// [TRANSACTION]: https://sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions
|
||||||
|
// [read-only]: https://pkg.go.dev/database/sql#TxOptions
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3"
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This variable can be replaced with -ldflags:
|
||||||
|
//
|
||||||
|
// go build -ldflags="-X github.com/ncruces/go-sqlite3/driver.driverName=sqlite"
|
||||||
|
var driverName = "sqlite3"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if driverName != "" {
|
||||||
|
sql.Register(driverName, &SQLite{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open opens the SQLite database specified by dataSourceName as a [database/sql.DB].
|
||||||
|
//
|
||||||
|
// The init function is called by the driver on new connections.
|
||||||
|
// The [sqlite3.Conn] can be used to execute queries, register functions, etc.
|
||||||
|
// Any error returned closes the connection and is returned to [database/sql].
|
||||||
|
func Open(dataSourceName string, init func(*sqlite3.Conn) error) (*sql.DB, error) {
|
||||||
|
c, err := (&SQLite{Init: init}).OpenConnector(dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sql.OpenDB(c), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SQLite implements [database/sql/driver.Driver].
|
||||||
|
type SQLite struct {
|
||||||
|
// Init function is called by the driver on new connections.
|
||||||
|
// The [sqlite3.Conn] can be used to execute queries, register functions, etc.
|
||||||
|
// Any error returned closes the connection and is returned to [database/sql].
|
||||||
|
Init func(*sqlite3.Conn) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open implements [database/sql/driver.Driver].
|
||||||
|
func (d *SQLite) Open(name string) (driver.Conn, error) {
|
||||||
|
c, err := d.newConnector(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.Connect(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenConnector implements [database/sql/driver.DriverContext].
|
||||||
|
func (d *SQLite) OpenConnector(name string) (driver.Connector, error) {
|
||||||
|
return d.newConnector(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SQLite) newConnector(name string) (*connector, error) {
|
||||||
|
c := connector{driver: d, name: name}
|
||||||
|
|
||||||
|
var txlock, timefmt string
|
||||||
|
if strings.HasPrefix(name, "file:") {
|
||||||
|
if _, after, ok := strings.Cut(name, "?"); ok {
|
||||||
|
query, err := url.ParseQuery(after)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
txlock = query.Get("_txlock")
|
||||||
|
timefmt = query.Get("_timefmt")
|
||||||
|
c.pragmas = query.Has("_pragma")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch txlock {
|
||||||
|
case "":
|
||||||
|
c.txBegin = "BEGIN"
|
||||||
|
case "deferred", "immediate", "exclusive":
|
||||||
|
c.txBegin = "BEGIN " + txlock
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("sqlite3: invalid _txlock: %s", txlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch timefmt {
|
||||||
|
case "":
|
||||||
|
c.tmRead = sqlite3.TimeFormatAuto
|
||||||
|
c.tmWrite = sqlite3.TimeFormatDefault
|
||||||
|
case "sqlite":
|
||||||
|
c.tmRead = sqlite3.TimeFormatAuto
|
||||||
|
c.tmWrite = sqlite3.TimeFormat3
|
||||||
|
case "rfc3339":
|
||||||
|
c.tmRead = sqlite3.TimeFormatDefault
|
||||||
|
c.tmWrite = sqlite3.TimeFormatDefault
|
||||||
|
default:
|
||||||
|
c.tmRead = sqlite3.TimeFormat(timefmt)
|
||||||
|
c.tmWrite = sqlite3.TimeFormat(timefmt)
|
||||||
|
}
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type connector struct {
|
||||||
|
driver *SQLite
|
||||||
|
name string
|
||||||
|
txBegin string
|
||||||
|
tmRead sqlite3.TimeFormat
|
||||||
|
tmWrite sqlite3.TimeFormat
|
||||||
|
pragmas bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *connector) Driver() driver.Driver {
|
||||||
|
return n.driver
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
|
||||||
|
c := &conn{
|
||||||
|
txBegin: n.txBegin,
|
||||||
|
tmRead: n.tmRead,
|
||||||
|
tmWrite: n.tmWrite,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Conn, err = sqlite3.Open(n.name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
old := c.Conn.SetInterrupt(ctx)
|
||||||
|
defer c.Conn.SetInterrupt(old)
|
||||||
|
|
||||||
|
if !n.pragmas {
|
||||||
|
err = c.Conn.BusyTimeout(60 * time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n.driver.Init != nil {
|
||||||
|
err = n.driver.Init(c.Conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n.pragmas || n.driver.Init != nil {
|
||||||
|
s, _, err := c.Conn.Prepare(`PRAGMA query_only`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if s.Step() && s.ColumnBool(0) {
|
||||||
|
c.readOnly = '1'
|
||||||
|
} else {
|
||||||
|
c.readOnly = '0'
|
||||||
|
}
|
||||||
|
err = s.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type conn struct {
|
||||||
|
*sqlite3.Conn
|
||||||
|
txBegin string
|
||||||
|
txCommit string
|
||||||
|
txRollback string
|
||||||
|
tmRead sqlite3.TimeFormat
|
||||||
|
tmWrite sqlite3.TimeFormat
|
||||||
|
readOnly byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Ensure these interfaces are implemented:
|
||||||
|
_ driver.ConnPrepareContext = &conn{}
|
||||||
|
_ driver.ExecerContext = &conn{}
|
||||||
|
_ driver.ConnBeginTx = &conn{}
|
||||||
|
_ sqlite3.DriverConn = &conn{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *conn) Raw() *sqlite3.Conn {
|
||||||
|
return c.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) Begin() (driver.Tx, error) {
|
||||||
|
return c.BeginTx(context.Background(), driver.TxOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
||||||
|
txBegin := c.txBegin
|
||||||
|
c.txCommit = `COMMIT`
|
||||||
|
c.txRollback = `ROLLBACK`
|
||||||
|
|
||||||
|
if opts.ReadOnly {
|
||||||
|
txBegin = `
|
||||||
|
BEGIN deferred;
|
||||||
|
PRAGMA query_only=on`
|
||||||
|
c.txRollback = `
|
||||||
|
ROLLBACK;
|
||||||
|
PRAGMA query_only=` + string(c.readOnly)
|
||||||
|
c.txCommit = c.txRollback
|
||||||
|
}
|
||||||
|
|
||||||
|
switch opts.Isolation {
|
||||||
|
default:
|
||||||
|
return nil, util.IsolationErr
|
||||||
|
case
|
||||||
|
driver.IsolationLevel(sql.LevelDefault),
|
||||||
|
driver.IsolationLevel(sql.LevelSerializable):
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
old := c.Conn.SetInterrupt(ctx)
|
||||||
|
defer c.Conn.SetInterrupt(old)
|
||||||
|
|
||||||
|
err := c.Conn.Exec(txBegin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) Commit() error {
|
||||||
|
err := c.Conn.Exec(c.txCommit)
|
||||||
|
if err != nil && !c.Conn.GetAutocommit() {
|
||||||
|
c.Rollback()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) Rollback() error {
|
||||||
|
err := c.Conn.Exec(c.txRollback)
|
||||||
|
if errors.Is(err, sqlite3.INTERRUPT) {
|
||||||
|
old := c.Conn.SetInterrupt(context.Background())
|
||||||
|
defer c.Conn.SetInterrupt(old)
|
||||||
|
err = c.Conn.Exec(c.txRollback)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) Prepare(query string) (driver.Stmt, error) {
|
||||||
|
return c.PrepareContext(context.Background(), query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
||||||
|
old := c.Conn.SetInterrupt(ctx)
|
||||||
|
defer c.Conn.SetInterrupt(old)
|
||||||
|
|
||||||
|
s, tail, err := c.Conn.Prepare(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if tail != "" {
|
||||||
|
s.Close()
|
||||||
|
return nil, util.TailErr
|
||||||
|
}
|
||||||
|
return &stmt{Stmt: s, tmRead: c.tmRead, tmWrite: c.tmWrite}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
if len(args) != 0 {
|
||||||
|
// Slow path.
|
||||||
|
return nil, driver.ErrSkip
|
||||||
|
}
|
||||||
|
|
||||||
|
if savept, ok := ctx.(*saveptCtx); ok {
|
||||||
|
// Called from driver.Savepoint.
|
||||||
|
savept.Savepoint = c.Conn.Savepoint()
|
||||||
|
return resultRowsAffected(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
old := c.Conn.SetInterrupt(ctx)
|
||||||
|
defer c.Conn.SetInterrupt(old)
|
||||||
|
|
||||||
|
err := c.Conn.Exec(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newResult(c.Conn), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) CheckNamedValue(arg *driver.NamedValue) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type stmt struct {
|
||||||
|
*sqlite3.Stmt
|
||||||
|
tmWrite sqlite3.TimeFormat
|
||||||
|
tmRead sqlite3.TimeFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Ensure these interfaces are implemented:
|
||||||
|
_ driver.StmtExecContext = &stmt{}
|
||||||
|
_ driver.StmtQueryContext = &stmt{}
|
||||||
|
_ driver.NamedValueChecker = &stmt{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *stmt) NumInput() int {
|
||||||
|
n := s.Stmt.BindCount()
|
||||||
|
for i := 1; i <= n; i++ {
|
||||||
|
if s.Stmt.BindName(i) != "" {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use ExecContext instead.
|
||||||
|
func (s *stmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||||
|
return s.ExecContext(context.Background(), namedValues(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use QueryContext instead.
|
||||||
|
func (s *stmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||||
|
return s.QueryContext(context.Background(), namedValues(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
err := s.setupBindings(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
old := s.Stmt.Conn().SetInterrupt(ctx)
|
||||||
|
defer s.Stmt.Conn().SetInterrupt(old)
|
||||||
|
|
||||||
|
err = s.Stmt.Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newResult(s.Stmt.Conn()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
err := s.setupBindings(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &rows{ctx: ctx, stmt: s}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stmt) setupBindings(args []driver.NamedValue) error {
|
||||||
|
err := s.Stmt.ClearBindings()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ids [3]int
|
||||||
|
for _, arg := range args {
|
||||||
|
ids := ids[:0]
|
||||||
|
if arg.Name == "" {
|
||||||
|
ids = append(ids, arg.Ordinal)
|
||||||
|
} else {
|
||||||
|
for _, prefix := range []string{":", "@", "$"} {
|
||||||
|
if id := s.Stmt.BindIndex(prefix + arg.Name); id != 0 {
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range ids {
|
||||||
|
switch a := arg.Value.(type) {
|
||||||
|
case bool:
|
||||||
|
err = s.Stmt.BindBool(id, a)
|
||||||
|
case int:
|
||||||
|
err = s.Stmt.BindInt(id, a)
|
||||||
|
case int64:
|
||||||
|
err = s.Stmt.BindInt64(id, a)
|
||||||
|
case float64:
|
||||||
|
err = s.Stmt.BindFloat(id, a)
|
||||||
|
case string:
|
||||||
|
err = s.Stmt.BindText(id, a)
|
||||||
|
case []byte:
|
||||||
|
err = s.Stmt.BindBlob(id, a)
|
||||||
|
case sqlite3.ZeroBlob:
|
||||||
|
err = s.Stmt.BindZeroBlob(id, int64(a))
|
||||||
|
case time.Time:
|
||||||
|
err = s.Stmt.BindTime(id, a, s.tmWrite)
|
||||||
|
case util.JSON:
|
||||||
|
err = s.Stmt.BindJSON(id, a.Value)
|
||||||
|
case util.PointerUnwrap:
|
||||||
|
err = s.Stmt.BindPointer(id, util.UnwrapPointer(a))
|
||||||
|
case nil:
|
||||||
|
err = s.Stmt.BindNull(id)
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stmt) CheckNamedValue(arg *driver.NamedValue) error {
|
||||||
|
switch arg.Value.(type) {
|
||||||
|
case bool, int, int64, float64, string, []byte,
|
||||||
|
time.Time, sqlite3.ZeroBlob,
|
||||||
|
util.JSON, util.PointerUnwrap,
|
||||||
|
nil:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return driver.ErrSkip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newResult(c *sqlite3.Conn) driver.Result {
|
||||||
|
rows := c.Changes()
|
||||||
|
if rows != 0 {
|
||||||
|
id := c.LastInsertRowID()
|
||||||
|
if id != 0 {
|
||||||
|
return result{id, rows}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultRowsAffected(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
type result struct{ lastInsertId, rowsAffected int64 }
|
||||||
|
|
||||||
|
func (r result) LastInsertId() (int64, error) {
|
||||||
|
return r.lastInsertId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r result) RowsAffected() (int64, error) {
|
||||||
|
return r.rowsAffected, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type resultRowsAffected int64
|
||||||
|
|
||||||
|
func (r resultRowsAffected) LastInsertId() (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r resultRowsAffected) RowsAffected() (int64, error) {
|
||||||
|
return int64(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rows struct {
|
||||||
|
ctx context.Context
|
||||||
|
*stmt
|
||||||
|
names []string
|
||||||
|
types []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rows) Close() error {
|
||||||
|
r.Stmt.ClearBindings()
|
||||||
|
return r.Stmt.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rows) Columns() []string {
|
||||||
|
if r.names == nil {
|
||||||
|
count := r.Stmt.ColumnCount()
|
||||||
|
r.names = make([]string, count)
|
||||||
|
for i := range r.names {
|
||||||
|
r.names[i] = r.Stmt.ColumnName(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.names
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rows) declType(index int) string {
|
||||||
|
if r.types == nil {
|
||||||
|
count := r.Stmt.ColumnCount()
|
||||||
|
r.types = make([]string, count)
|
||||||
|
for i := range r.types {
|
||||||
|
r.types[i] = strings.ToUpper(r.Stmt.ColumnDeclType(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.types[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rows) ColumnTypeDatabaseTypeName(index int) string {
|
||||||
|
decltype := r.declType(index)
|
||||||
|
if len := len(decltype); len > 0 && decltype[len-1] == ')' {
|
||||||
|
if i := strings.LastIndexByte(decltype, '('); i >= 0 {
|
||||||
|
decltype = decltype[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(decltype)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rows) Next(dest []driver.Value) error {
|
||||||
|
old := r.Stmt.Conn().SetInterrupt(r.ctx)
|
||||||
|
defer r.Stmt.Conn().SetInterrupt(old)
|
||||||
|
|
||||||
|
if !r.Stmt.Step() {
|
||||||
|
if err := r.Stmt.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
data := unsafe.Slice((*any)(unsafe.SliceData(dest)), len(dest))
|
||||||
|
err := r.Stmt.Columns(data)
|
||||||
|
for i := range dest {
|
||||||
|
if t, ok := r.decodeTime(i, dest[i]); ok {
|
||||||
|
dest[i] = t
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s, ok := dest[i].(string); ok {
|
||||||
|
t, ok := maybeTime(s)
|
||||||
|
if ok {
|
||||||
|
dest[i] = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rows) decodeTime(i int, v any) (_ time.Time, _ bool) {
|
||||||
|
if r.tmRead == sqlite3.TimeFormatDefault {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch r.declType(i) {
|
||||||
|
case "DATE", "TIME", "DATETIME", "TIMESTAMP":
|
||||||
|
// maybe
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch v.(type) {
|
||||||
|
case int64, float64, string:
|
||||||
|
// maybe
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t, err := r.tmRead.Decode(v)
|
||||||
|
return t, err == nil
|
||||||
|
}
|
27
vendor/github.com/ncruces/go-sqlite3/driver/savepoint.go
generated
vendored
Normal file
27
vendor/github.com/ncruces/go-sqlite3/driver/savepoint.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Savepoint establishes a new transaction savepoint.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/lang_savepoint.html
|
||||||
|
func Savepoint(tx *sql.Tx) sqlite3.Savepoint {
|
||||||
|
var ctx saveptCtx
|
||||||
|
tx.ExecContext(&ctx, "")
|
||||||
|
return ctx.Savepoint
|
||||||
|
}
|
||||||
|
|
||||||
|
type saveptCtx struct{ sqlite3.Savepoint }
|
||||||
|
|
||||||
|
func (*saveptCtx) Deadline() (deadline time.Time, ok bool) { return }
|
||||||
|
|
||||||
|
func (*saveptCtx) Done() <-chan struct{} { return nil }
|
||||||
|
|
||||||
|
func (*saveptCtx) Err() error { return nil }
|
||||||
|
|
||||||
|
func (*saveptCtx) Value(key any) any { return nil }
|
31
vendor/github.com/ncruces/go-sqlite3/driver/time.go
generated
vendored
Normal file
31
vendor/github.com/ncruces/go-sqlite3/driver/time.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Convert a string in [time.RFC3339Nano] format into a [time.Time]
|
||||||
|
// if it roundtrips back to the same string.
|
||||||
|
// This way times can be persisted to, and recovered from, the database,
|
||||||
|
// but if a string is needed, [database/sql] will recover the same string.
|
||||||
|
func maybeTime(text string) (_ time.Time, _ bool) {
|
||||||
|
// Weed out (some) values that can't possibly be
|
||||||
|
// [time.RFC3339Nano] timestamps.
|
||||||
|
if len(text) < len("2006-01-02T15:04:05Z") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(text) > len(time.RFC3339Nano) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if text[4] != '-' || text[10] != 'T' || text[16] != ':' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slow path.
|
||||||
|
var buf [len(time.RFC3339Nano)]byte
|
||||||
|
date, err := time.Parse(time.RFC3339Nano, text)
|
||||||
|
if err == nil && text == string(date.AppendFormat(buf[:0], time.RFC3339Nano)) {
|
||||||
|
return date, true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
14
vendor/github.com/ncruces/go-sqlite3/driver/util.go
generated
vendored
Normal file
14
vendor/github.com/ncruces/go-sqlite3/driver/util.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import "database/sql/driver"
|
||||||
|
|
||||||
|
func namedValues(args []driver.Value) []driver.NamedValue {
|
||||||
|
named := make([]driver.NamedValue, len(args))
|
||||||
|
for i, v := range args {
|
||||||
|
named[i] = driver.NamedValue{
|
||||||
|
Ordinal: i + 1,
|
||||||
|
Value: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return named
|
||||||
|
}
|
27
vendor/github.com/ncruces/go-sqlite3/embed/README.md
generated
vendored
Normal file
27
vendor/github.com/ncruces/go-sqlite3/embed/README.md
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# Embeddable Wasm build of SQLite
|
||||||
|
|
||||||
|
This folder includes an embeddable Wasm build of SQLite 3.46.0 for use with
|
||||||
|
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
|
||||||
|
|
||||||
|
The following optional features are compiled in:
|
||||||
|
- [math functions](https://sqlite.org/lang_mathfunc.html)
|
||||||
|
- [FTS5](https://sqlite.org/fts5.html)
|
||||||
|
- [JSON](https://sqlite.org/json1.html)
|
||||||
|
- [R*Tree](https://sqlite.org/rtree.html)
|
||||||
|
- [GeoPoly](https://sqlite.org/geopoly.html)
|
||||||
|
- [soundex](https://sqlite.org/lang_corefunc.html#soundex)
|
||||||
|
- [stat4](https://sqlite.org/compile.html#enable_stat4)
|
||||||
|
- [base64](https://github.com/sqlite/sqlite/blob/master/ext/misc/base64.c)
|
||||||
|
- [decimal](https://github.com/sqlite/sqlite/blob/master/ext/misc/decimal.c)
|
||||||
|
- [ieee754](https://github.com/sqlite/sqlite/blob/master/ext/misc/ieee754.c)
|
||||||
|
- [regexp](https://github.com/sqlite/sqlite/blob/master/ext/misc/regexp.c)
|
||||||
|
- [series](https://github.com/sqlite/sqlite/blob/master/ext/misc/series.c)
|
||||||
|
- [uint](https://github.com/sqlite/sqlite/blob/master/ext/misc/uint.c)
|
||||||
|
- [uuid](https://github.com/sqlite/sqlite/blob/master/ext/misc/uuid.c)
|
||||||
|
- [time](../sqlite3/time.c)
|
||||||
|
|
||||||
|
See the [configuration options](../sqlite3/sqlite_cfg.h),
|
||||||
|
and [patches](../sqlite3) applied.
|
||||||
|
|
||||||
|
Built using [`wasi-sdk`](https://github.com/WebAssembly/wasi-sdk),
|
||||||
|
and [`binaryen`](https://github.com/WebAssembly/binaryen).
|
32
vendor/github.com/ncruces/go-sqlite3/embed/build.sh
generated
vendored
Normal file
32
vendor/github.com/ncruces/go-sqlite3/embed/build.sh
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd -P -- "$(dirname -- "$0")"
|
||||||
|
|
||||||
|
ROOT=../
|
||||||
|
BINARYEN="$ROOT/tools/binaryen-version_117/bin"
|
||||||
|
WASI_SDK="$ROOT/tools/wasi-sdk-22.0/bin"
|
||||||
|
|
||||||
|
"$WASI_SDK/clang" --target=wasm32-wasi -std=c17 -flto -g0 -O2 \
|
||||||
|
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
|
||||||
|
-o sqlite3.wasm "$ROOT/sqlite3/main.c" \
|
||||||
|
-I"$ROOT/sqlite3" \
|
||||||
|
-mexec-model=reactor \
|
||||||
|
-msimd128 -mmutable-globals \
|
||||||
|
-mbulk-memory -mreference-types \
|
||||||
|
-mnontrapping-fptoint -msign-ext \
|
||||||
|
-fno-stack-protector -fno-stack-clash-protection \
|
||||||
|
-Wl,--initial-memory=327680 \
|
||||||
|
-Wl,--stack-first \
|
||||||
|
-Wl,--import-undefined \
|
||||||
|
-D_HAVE_SQLITE_CONFIG_H \
|
||||||
|
-DSQLITE_CUSTOM_INCLUDE=sqlite_opt.h \
|
||||||
|
$(awk '{print "-Wl,--export="$0}' exports.txt)
|
||||||
|
|
||||||
|
trap 'rm -f sqlite3.tmp' EXIT
|
||||||
|
"$BINARYEN/wasm-ctor-eval" -g -c _initialize sqlite3.wasm -o sqlite3.tmp
|
||||||
|
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
|
||||||
|
sqlite3.tmp -o sqlite3.wasm \
|
||||||
|
--enable-simd --enable-mutable-globals --enable-multivalue \
|
||||||
|
--enable-bulk-memory --enable-reference-types \
|
||||||
|
--enable-nontrapping-float-to-int --enable-sign-ext
|
130
vendor/github.com/ncruces/go-sqlite3/embed/exports.txt
generated
vendored
Normal file
130
vendor/github.com/ncruces/go-sqlite3/embed/exports.txt
generated
vendored
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
aligned_alloc
|
||||||
|
free
|
||||||
|
malloc
|
||||||
|
malloc_destructor
|
||||||
|
sqlite3_anycollseq_init
|
||||||
|
sqlite3_autovacuum_pages_go
|
||||||
|
sqlite3_backup_finish
|
||||||
|
sqlite3_backup_init
|
||||||
|
sqlite3_backup_pagecount
|
||||||
|
sqlite3_backup_remaining
|
||||||
|
sqlite3_backup_step
|
||||||
|
sqlite3_bind_blob64
|
||||||
|
sqlite3_bind_double
|
||||||
|
sqlite3_bind_int64
|
||||||
|
sqlite3_bind_null
|
||||||
|
sqlite3_bind_parameter_count
|
||||||
|
sqlite3_bind_parameter_index
|
||||||
|
sqlite3_bind_parameter_name
|
||||||
|
sqlite3_bind_pointer_go
|
||||||
|
sqlite3_bind_text64
|
||||||
|
sqlite3_bind_value
|
||||||
|
sqlite3_bind_zeroblob64
|
||||||
|
sqlite3_blob_bytes
|
||||||
|
sqlite3_blob_close
|
||||||
|
sqlite3_blob_open
|
||||||
|
sqlite3_blob_read
|
||||||
|
sqlite3_blob_reopen
|
||||||
|
sqlite3_blob_write
|
||||||
|
sqlite3_busy_handler_go
|
||||||
|
sqlite3_busy_timeout
|
||||||
|
sqlite3_changes64
|
||||||
|
sqlite3_clear_bindings
|
||||||
|
sqlite3_close
|
||||||
|
sqlite3_close_v2
|
||||||
|
sqlite3_collation_needed_go
|
||||||
|
sqlite3_column_blob
|
||||||
|
sqlite3_column_bytes
|
||||||
|
sqlite3_column_count
|
||||||
|
sqlite3_column_database_name
|
||||||
|
sqlite3_column_decltype
|
||||||
|
sqlite3_column_double
|
||||||
|
sqlite3_column_int64
|
||||||
|
sqlite3_column_name
|
||||||
|
sqlite3_column_origin_name
|
||||||
|
sqlite3_column_table_name
|
||||||
|
sqlite3_column_text
|
||||||
|
sqlite3_column_type
|
||||||
|
sqlite3_column_value
|
||||||
|
sqlite3_columns_go
|
||||||
|
sqlite3_commit_hook_go
|
||||||
|
sqlite3_config_log_go
|
||||||
|
sqlite3_create_aggregate_function_go
|
||||||
|
sqlite3_create_collation_go
|
||||||
|
sqlite3_create_function_go
|
||||||
|
sqlite3_create_module_go
|
||||||
|
sqlite3_create_window_function_go
|
||||||
|
sqlite3_database_file_object
|
||||||
|
sqlite3_db_config
|
||||||
|
sqlite3_db_filename
|
||||||
|
sqlite3_db_name
|
||||||
|
sqlite3_db_readonly
|
||||||
|
sqlite3_db_release_memory
|
||||||
|
sqlite3_declare_vtab
|
||||||
|
sqlite3_errcode
|
||||||
|
sqlite3_errmsg
|
||||||
|
sqlite3_error_offset
|
||||||
|
sqlite3_errstr
|
||||||
|
sqlite3_exec
|
||||||
|
sqlite3_filename_database
|
||||||
|
sqlite3_filename_journal
|
||||||
|
sqlite3_filename_wal
|
||||||
|
sqlite3_finalize
|
||||||
|
sqlite3_get_autocommit
|
||||||
|
sqlite3_get_auxdata
|
||||||
|
sqlite3_interrupt
|
||||||
|
sqlite3_last_insert_rowid
|
||||||
|
sqlite3_limit
|
||||||
|
sqlite3_open_v2
|
||||||
|
sqlite3_overload_function
|
||||||
|
sqlite3_prepare_v3
|
||||||
|
sqlite3_progress_handler_go
|
||||||
|
sqlite3_reset
|
||||||
|
sqlite3_result_blob64
|
||||||
|
sqlite3_result_double
|
||||||
|
sqlite3_result_error
|
||||||
|
sqlite3_result_error_code
|
||||||
|
sqlite3_result_error_nomem
|
||||||
|
sqlite3_result_error_toobig
|
||||||
|
sqlite3_result_int64
|
||||||
|
sqlite3_result_null
|
||||||
|
sqlite3_result_pointer_go
|
||||||
|
sqlite3_result_text64
|
||||||
|
sqlite3_result_value
|
||||||
|
sqlite3_result_zeroblob64
|
||||||
|
sqlite3_rollback_hook_go
|
||||||
|
sqlite3_set_authorizer_go
|
||||||
|
sqlite3_set_auxdata_go
|
||||||
|
sqlite3_set_last_insert_rowid
|
||||||
|
sqlite3_step
|
||||||
|
sqlite3_stmt_busy
|
||||||
|
sqlite3_stmt_readonly
|
||||||
|
sqlite3_stmt_status
|
||||||
|
sqlite3_total_changes64
|
||||||
|
sqlite3_txn_state
|
||||||
|
sqlite3_update_hook_go
|
||||||
|
sqlite3_uri_key
|
||||||
|
sqlite3_uri_parameter
|
||||||
|
sqlite3_value_blob
|
||||||
|
sqlite3_value_bytes
|
||||||
|
sqlite3_value_double
|
||||||
|
sqlite3_value_dup
|
||||||
|
sqlite3_value_free
|
||||||
|
sqlite3_value_int64
|
||||||
|
sqlite3_value_nochange
|
||||||
|
sqlite3_value_numeric_type
|
||||||
|
sqlite3_value_pointer_go
|
||||||
|
sqlite3_value_text
|
||||||
|
sqlite3_value_type
|
||||||
|
sqlite3_vtab_collation
|
||||||
|
sqlite3_vtab_config_go
|
||||||
|
sqlite3_vtab_distinct
|
||||||
|
sqlite3_vtab_in
|
||||||
|
sqlite3_vtab_in_first
|
||||||
|
sqlite3_vtab_in_next
|
||||||
|
sqlite3_vtab_nochange
|
||||||
|
sqlite3_vtab_on_conflict
|
||||||
|
sqlite3_vtab_rhs_value
|
||||||
|
sqlite3_wal_autocheckpoint
|
||||||
|
sqlite3_wal_checkpoint_v2
|
||||||
|
sqlite3_wal_hook_go
|
20
vendor/github.com/ncruces/go-sqlite3/embed/init.go
generated
vendored
Normal file
20
vendor/github.com/ncruces/go-sqlite3/embed/init.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// Package embed embeds SQLite into your application.
|
||||||
|
//
|
||||||
|
// Importing package embed initializes the [sqlite3.Binary] variable
|
||||||
|
// with an appropriate build of SQLite:
|
||||||
|
//
|
||||||
|
// import _ "github.com/ncruces/go-sqlite3/embed"
|
||||||
|
package embed
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed sqlite3.wasm
|
||||||
|
var binary []byte
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sqlite3.Binary = binary
|
||||||
|
}
|
BIN
vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm
generated
vendored
Normal file
BIN
vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm
generated
vendored
Normal file
Binary file not shown.
162
vendor/github.com/ncruces/go-sqlite3/error.go
generated
vendored
Normal file
162
vendor/github.com/ncruces/go-sqlite3/error.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error wraps an SQLite Error Code.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/errcode.html
|
||||||
|
type Error struct {
|
||||||
|
str string
|
||||||
|
msg string
|
||||||
|
sql string
|
||||||
|
code uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code returns the primary error code for this error.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/rescode.html
|
||||||
|
func (e *Error) Code() ErrorCode {
|
||||||
|
return ErrorCode(e.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtendedCode returns the extended error code for this error.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/rescode.html
|
||||||
|
func (e *Error) ExtendedCode() ExtendedErrorCode {
|
||||||
|
return ExtendedErrorCode(e.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("sqlite3: ")
|
||||||
|
|
||||||
|
if e.str != "" {
|
||||||
|
b.WriteString(e.str)
|
||||||
|
} else {
|
||||||
|
b.WriteString(strconv.Itoa(int(e.code)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.msg != "" {
|
||||||
|
b.WriteString(": ")
|
||||||
|
b.WriteString(e.msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is tests whether this error matches a given [ErrorCode] or [ExtendedErrorCode].
|
||||||
|
//
|
||||||
|
// It makes it possible to do:
|
||||||
|
//
|
||||||
|
// if errors.Is(err, sqlite3.BUSY) {
|
||||||
|
// // ... handle BUSY
|
||||||
|
// }
|
||||||
|
func (e *Error) Is(err error) bool {
|
||||||
|
switch c := err.(type) {
|
||||||
|
case ErrorCode:
|
||||||
|
return c == e.Code()
|
||||||
|
case ExtendedErrorCode:
|
||||||
|
return c == e.ExtendedCode()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// As converts this error to an [ErrorCode] or [ExtendedErrorCode].
|
||||||
|
func (e *Error) As(err any) bool {
|
||||||
|
switch c := err.(type) {
|
||||||
|
case *ErrorCode:
|
||||||
|
*c = e.Code()
|
||||||
|
return true
|
||||||
|
case *ExtendedErrorCode:
|
||||||
|
*c = e.ExtendedCode()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporary returns true for [BUSY] errors.
|
||||||
|
func (e *Error) Temporary() bool {
|
||||||
|
return e.Code() == BUSY
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout returns true for [BUSY_TIMEOUT] errors.
|
||||||
|
func (e *Error) Timeout() bool {
|
||||||
|
return e.ExtendedCode() == BUSY_TIMEOUT
|
||||||
|
}
|
||||||
|
|
||||||
|
// SQL returns the SQL starting at the token that triggered a syntax error.
|
||||||
|
func (e *Error) SQL() string {
|
||||||
|
return e.sql
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (e ErrorCode) Error() string {
|
||||||
|
return util.ErrorCodeString(uint32(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporary returns true for [BUSY] errors.
|
||||||
|
func (e ErrorCode) Temporary() bool {
|
||||||
|
return e == BUSY
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (e ExtendedErrorCode) Error() string {
|
||||||
|
return util.ErrorCodeString(uint32(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is tests whether this error matches a given [ErrorCode].
|
||||||
|
func (e ExtendedErrorCode) Is(err error) bool {
|
||||||
|
c, ok := err.(ErrorCode)
|
||||||
|
return ok && c == ErrorCode(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// As converts this error to an [ErrorCode].
|
||||||
|
func (e ExtendedErrorCode) As(err any) bool {
|
||||||
|
c, ok := err.(*ErrorCode)
|
||||||
|
if ok {
|
||||||
|
*c = ErrorCode(e)
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporary returns true for [BUSY] errors.
|
||||||
|
func (e ExtendedErrorCode) Temporary() bool {
|
||||||
|
return ErrorCode(e) == BUSY
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout returns true for [BUSY_TIMEOUT] errors.
|
||||||
|
func (e ExtendedErrorCode) Timeout() bool {
|
||||||
|
return e == BUSY_TIMEOUT
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorCode(err error, def ErrorCode) (msg string, code uint32) {
|
||||||
|
switch code := err.(type) {
|
||||||
|
case nil:
|
||||||
|
return "", _OK
|
||||||
|
case ErrorCode:
|
||||||
|
return "", uint32(code)
|
||||||
|
case xErrorCode:
|
||||||
|
return "", uint32(code)
|
||||||
|
case *Error:
|
||||||
|
return code.msg, uint32(code.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ecode ErrorCode
|
||||||
|
var xcode xErrorCode
|
||||||
|
switch {
|
||||||
|
case errors.As(err, &xcode):
|
||||||
|
code = uint32(xcode)
|
||||||
|
case errors.As(err, &ecode):
|
||||||
|
code = uint32(ecode)
|
||||||
|
default:
|
||||||
|
code = uint32(def)
|
||||||
|
}
|
||||||
|
return err.Error(), code
|
||||||
|
}
|
214
vendor/github.com/ncruces/go-sqlite3/func.go
generated
vendored
Normal file
214
vendor/github.com/ncruces/go-sqlite3/func.go
generated
vendored
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CollationNeeded registers a callback to be invoked
|
||||||
|
// whenever an unknown collation sequence is required.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/collation_needed.html
|
||||||
|
func (c *Conn) CollationNeeded(cb func(db *Conn, name string)) error {
|
||||||
|
var enable uint64
|
||||||
|
if cb != nil {
|
||||||
|
enable = 1
|
||||||
|
}
|
||||||
|
r := c.call("sqlite3_collation_needed_go", uint64(c.handle), enable)
|
||||||
|
if err := c.error(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.collation = cb
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnyCollationNeeded uses [Conn.CollationNeeded] to register
|
||||||
|
// a fake collating function for any unknown collating sequence.
|
||||||
|
// The fake collating function works like BINARY.
|
||||||
|
//
|
||||||
|
// This can be used to load schemas that contain
|
||||||
|
// one or more unknown collating sequences.
|
||||||
|
func (c *Conn) AnyCollationNeeded() {
|
||||||
|
c.call("sqlite3_anycollseq_init", uint64(c.handle), 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCollation defines a new collating sequence.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/create_collation.html
|
||||||
|
func (c *Conn) CreateCollation(name string, fn func(a, b []byte) int) error {
|
||||||
|
defer c.arena.mark()()
|
||||||
|
namePtr := c.arena.string(name)
|
||||||
|
funcPtr := util.AddHandle(c.ctx, fn)
|
||||||
|
r := c.call("sqlite3_create_collation_go",
|
||||||
|
uint64(c.handle), uint64(namePtr), uint64(funcPtr))
|
||||||
|
return c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFunction defines a new scalar SQL function.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/create_function.html
|
||||||
|
func (c *Conn) CreateFunction(name string, nArg int, flag FunctionFlag, fn ScalarFunction) error {
|
||||||
|
defer c.arena.mark()()
|
||||||
|
namePtr := c.arena.string(name)
|
||||||
|
funcPtr := util.AddHandle(c.ctx, fn)
|
||||||
|
r := c.call("sqlite3_create_function_go",
|
||||||
|
uint64(c.handle), uint64(namePtr), uint64(nArg),
|
||||||
|
uint64(flag), uint64(funcPtr))
|
||||||
|
return c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScalarFunction is the type of a scalar SQL function.
|
||||||
|
// Implementations must not retain arg.
|
||||||
|
type ScalarFunction func(ctx Context, arg ...Value)
|
||||||
|
|
||||||
|
// CreateWindowFunction defines a new aggregate or aggregate window SQL function.
|
||||||
|
// If fn returns a [WindowFunction], then an aggregate window function is created.
|
||||||
|
// If fn returns an [io.Closer], it will be called to free resources.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/create_function.html
|
||||||
|
func (c *Conn) CreateWindowFunction(name string, nArg int, flag FunctionFlag, fn func() AggregateFunction) error {
|
||||||
|
defer c.arena.mark()()
|
||||||
|
call := "sqlite3_create_aggregate_function_go"
|
||||||
|
namePtr := c.arena.string(name)
|
||||||
|
funcPtr := util.AddHandle(c.ctx, fn)
|
||||||
|
if _, ok := fn().(WindowFunction); ok {
|
||||||
|
call = "sqlite3_create_window_function_go"
|
||||||
|
}
|
||||||
|
r := c.call(call,
|
||||||
|
uint64(c.handle), uint64(namePtr), uint64(nArg),
|
||||||
|
uint64(flag), uint64(funcPtr))
|
||||||
|
return c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AggregateFunction is the interface an aggregate function should implement.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/appfunc.html
|
||||||
|
type AggregateFunction interface {
|
||||||
|
// Step is invoked to add a row to the current window.
|
||||||
|
// The function arguments, if any, corresponding to the row being added, are passed to Step.
|
||||||
|
// Implementations must not retain arg.
|
||||||
|
Step(ctx Context, arg ...Value)
|
||||||
|
|
||||||
|
// Value is invoked to return the current (or final) value of the aggregate.
|
||||||
|
Value(ctx Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WindowFunction is the interface an aggregate window function should implement.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/windowfunctions.html
|
||||||
|
type WindowFunction interface {
|
||||||
|
AggregateFunction
|
||||||
|
|
||||||
|
// Inverse is invoked to remove the oldest presently aggregated result of Step from the current window.
|
||||||
|
// The function arguments, if any, are those passed to Step for the row being removed.
|
||||||
|
// Implementations must not retain arg.
|
||||||
|
Inverse(ctx Context, arg ...Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OverloadFunction overloads a function for a virtual table.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/overload_function.html
|
||||||
|
func (c *Conn) OverloadFunction(name string, nArg int) error {
|
||||||
|
defer c.arena.mark()()
|
||||||
|
namePtr := c.arena.string(name)
|
||||||
|
r := c.call("sqlite3_overload_function",
|
||||||
|
uint64(c.handle), uint64(namePtr), uint64(nArg))
|
||||||
|
return c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func destroyCallback(ctx context.Context, mod api.Module, pApp uint32) {
|
||||||
|
util.DelHandle(ctx, pApp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func collationCallback(ctx context.Context, mod api.Module, pArg, pDB, eTextRep, zName uint32) {
|
||||||
|
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.collation != nil {
|
||||||
|
name := util.ReadString(mod, zName, _MAX_NAME)
|
||||||
|
c.collation(c, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareCallback(ctx context.Context, mod api.Module, pApp, nKey1, pKey1, nKey2, pKey2 uint32) uint32 {
|
||||||
|
fn := util.GetHandle(ctx, pApp).(func(a, b []byte) int)
|
||||||
|
return uint32(fn(util.View(mod, pKey1, uint64(nKey1)), util.View(mod, pKey2, uint64(nKey2))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func funcCallback(ctx context.Context, mod api.Module, pCtx, pApp, nArg, pArg uint32) {
|
||||||
|
args := getFuncArgs()
|
||||||
|
defer putFuncArgs(args)
|
||||||
|
db := ctx.Value(connKey{}).(*Conn)
|
||||||
|
fn := util.GetHandle(db.ctx, pApp).(ScalarFunction)
|
||||||
|
callbackArgs(db, args[:nArg], pArg)
|
||||||
|
fn(Context{db, pCtx}, args[:nArg]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stepCallback(ctx context.Context, mod api.Module, pCtx, pAgg, pApp, nArg, pArg uint32) {
|
||||||
|
args := getFuncArgs()
|
||||||
|
defer putFuncArgs(args)
|
||||||
|
db := ctx.Value(connKey{}).(*Conn)
|
||||||
|
callbackArgs(db, args[:nArg], pArg)
|
||||||
|
fn, _ := callbackAggregate(db, pAgg, pApp)
|
||||||
|
fn.Step(Context{db, pCtx}, args[:nArg]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func finalCallback(ctx context.Context, mod api.Module, pCtx, pAgg, pApp uint32) {
|
||||||
|
db := ctx.Value(connKey{}).(*Conn)
|
||||||
|
fn, handle := callbackAggregate(db, pAgg, pApp)
|
||||||
|
fn.Value(Context{db, pCtx})
|
||||||
|
util.DelHandle(ctx, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func valueCallback(ctx context.Context, mod api.Module, pCtx, pAgg uint32) {
|
||||||
|
db := ctx.Value(connKey{}).(*Conn)
|
||||||
|
fn := util.GetHandle(db.ctx, pAgg).(AggregateFunction)
|
||||||
|
fn.Value(Context{db, pCtx})
|
||||||
|
}
|
||||||
|
|
||||||
|
func inverseCallback(ctx context.Context, mod api.Module, pCtx, pAgg, nArg, pArg uint32) {
|
||||||
|
args := getFuncArgs()
|
||||||
|
defer putFuncArgs(args)
|
||||||
|
db := ctx.Value(connKey{}).(*Conn)
|
||||||
|
callbackArgs(db, args[:nArg], pArg)
|
||||||
|
fn := util.GetHandle(db.ctx, pAgg).(WindowFunction)
|
||||||
|
fn.Inverse(Context{db, pCtx}, args[:nArg]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func callbackAggregate(db *Conn, pAgg, pApp uint32) (AggregateFunction, uint32) {
|
||||||
|
if pApp == 0 {
|
||||||
|
handle := util.ReadUint32(db.mod, pAgg)
|
||||||
|
return util.GetHandle(db.ctx, handle).(AggregateFunction), handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to create the aggregate.
|
||||||
|
fn := util.GetHandle(db.ctx, pApp).(func() AggregateFunction)()
|
||||||
|
handle := util.AddHandle(db.ctx, fn)
|
||||||
|
if pAgg != 0 {
|
||||||
|
util.WriteUint32(db.mod, pAgg, handle)
|
||||||
|
}
|
||||||
|
return fn, handle
|
||||||
|
}
|
||||||
|
|
||||||
|
func callbackArgs(db *Conn, arg []Value, pArg uint32) {
|
||||||
|
for i := range arg {
|
||||||
|
arg[i] = Value{
|
||||||
|
c: db,
|
||||||
|
handle: util.ReadUint32(db.mod, pArg+ptrlen*uint32(i)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var funcArgsPool sync.Pool
|
||||||
|
|
||||||
|
func putFuncArgs(p *[_MAX_FUNCTION_ARG]Value) {
|
||||||
|
funcArgsPool.Put(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFuncArgs() *[_MAX_FUNCTION_ARG]Value {
|
||||||
|
if p := funcArgsPool.Get(); p == nil {
|
||||||
|
return new([_MAX_FUNCTION_ARG]Value)
|
||||||
|
} else {
|
||||||
|
return p.(*[_MAX_FUNCTION_ARG]Value)
|
||||||
|
}
|
||||||
|
}
|
6
vendor/github.com/ncruces/go-sqlite3/go.work
generated
vendored
Normal file
6
vendor/github.com/ncruces/go-sqlite3/go.work
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
use (
|
||||||
|
.
|
||||||
|
./gormlite
|
||||||
|
)
|
9
vendor/github.com/ncruces/go-sqlite3/go.work.sum
generated
vendored
Normal file
9
vendor/github.com/ncruces/go-sqlite3/go.work.sum
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
|
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||||
|
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||||
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
|
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
9
vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_other.go
generated
vendored
Normal file
9
vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_other.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
//go:build !(unix || windows) || sqlite3_nosys
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import "github.com/tetratelabs/wazero/experimental"
|
||||||
|
|
||||||
|
func virtualAlloc(cap, max uint64) experimental.LinearMemory {
|
||||||
|
return sliceAlloc(cap, max)
|
||||||
|
}
|
25
vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_slice.go
generated
vendored
Normal file
25
vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_slice.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
//go:build !(darwin || linux) || !(amd64 || arm64 || riscv64) || sqlite3_noshm || sqlite3_nosys
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import "github.com/tetratelabs/wazero/experimental"
|
||||||
|
|
||||||
|
func sliceAlloc(cap, max uint64) experimental.LinearMemory {
|
||||||
|
return &sliceBuffer{make([]byte, cap), max}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sliceBuffer struct {
|
||||||
|
buf []byte
|
||||||
|
max uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *sliceBuffer) Free() {}
|
||||||
|
|
||||||
|
func (b *sliceBuffer) Reallocate(size uint64) []byte {
|
||||||
|
if cap := uint64(cap(b.buf)); size > cap {
|
||||||
|
b.buf = append(b.buf[:cap], make([]byte, size-cap)...)
|
||||||
|
} else {
|
||||||
|
b.buf = b.buf[:size]
|
||||||
|
}
|
||||||
|
return b.buf
|
||||||
|
}
|
67
vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_unix.go
generated
vendored
Normal file
67
vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
//go:build unix && !sqlite3_nosys
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero/experimental"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func virtualAlloc(cap, max uint64) experimental.LinearMemory {
|
||||||
|
// Round up to the page size.
|
||||||
|
rnd := uint64(unix.Getpagesize() - 1)
|
||||||
|
max = (max + rnd) &^ rnd
|
||||||
|
|
||||||
|
if max > math.MaxInt {
|
||||||
|
// This ensures int(max) overflows to a negative value,
|
||||||
|
// and unix.Mmap returns EINVAL.
|
||||||
|
max = math.MaxUint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve max bytes of address space, to ensure we won't need to move it.
|
||||||
|
// A protected, private, anonymous mapping should not commit memory.
|
||||||
|
b, err := unix.Mmap(-1, 0, int(max), unix.PROT_NONE, unix.MAP_PRIVATE|unix.MAP_ANON)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return &mmappedMemory{buf: b[:0]}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The slice covers the entire mmapped memory:
|
||||||
|
// - len(buf) is the already committed memory,
|
||||||
|
// - cap(buf) is the reserved address space.
|
||||||
|
type mmappedMemory struct {
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mmappedMemory) Reallocate(size uint64) []byte {
|
||||||
|
com := uint64(len(m.buf))
|
||||||
|
res := uint64(cap(m.buf))
|
||||||
|
if com < size && size < res {
|
||||||
|
// Round up to the page size.
|
||||||
|
rnd := uint64(unix.Getpagesize() - 1)
|
||||||
|
new := (size + rnd) &^ rnd
|
||||||
|
|
||||||
|
// Commit additional memory up to new bytes.
|
||||||
|
err := unix.Mprotect(m.buf[com:new], unix.PROT_READ|unix.PROT_WRITE)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update committed memory.
|
||||||
|
m.buf = m.buf[:new]
|
||||||
|
}
|
||||||
|
// Limit returned capacity because bytes beyond
|
||||||
|
// len(m.buf) have not yet been committed.
|
||||||
|
return m.buf[:size:len(m.buf)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mmappedMemory) Free() {
|
||||||
|
err := unix.Munmap(m.buf[:cap(m.buf)])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
m.buf = nil
|
||||||
|
}
|
76
vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_windows.go
generated
vendored
Normal file
76
vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
//go:build !sqlite3_nosys
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero/experimental"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func virtualAlloc(cap, max uint64) experimental.LinearMemory {
|
||||||
|
// Round up to the page size.
|
||||||
|
rnd := uint64(windows.Getpagesize() - 1)
|
||||||
|
max = (max + rnd) &^ rnd
|
||||||
|
|
||||||
|
if max > math.MaxInt {
|
||||||
|
// This ensures uintptr(max) overflows to a large value,
|
||||||
|
// and windows.VirtualAlloc returns an error.
|
||||||
|
max = math.MaxUint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve max bytes of address space, to ensure we won't need to move it.
|
||||||
|
// This does not commit memory.
|
||||||
|
r, err := windows.VirtualAlloc(0, uintptr(max), windows.MEM_RESERVE, windows.PAGE_READWRITE)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mem := virtualMemory{addr: r}
|
||||||
|
// SliceHeader, although deprecated, avoids a go vet warning.
|
||||||
|
sh := (*reflect.SliceHeader)(unsafe.Pointer(&mem.buf))
|
||||||
|
sh.Cap = int(max) // Not a bug.
|
||||||
|
sh.Data = r
|
||||||
|
return &mem
|
||||||
|
}
|
||||||
|
|
||||||
|
// The slice covers the entire mmapped memory:
|
||||||
|
// - len(buf) is the already committed memory,
|
||||||
|
// - cap(buf) is the reserved address space.
|
||||||
|
type virtualMemory struct {
|
||||||
|
buf []byte
|
||||||
|
addr uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *virtualMemory) Reallocate(size uint64) []byte {
|
||||||
|
com := uint64(len(m.buf))
|
||||||
|
res := uint64(cap(m.buf))
|
||||||
|
if com < size && size < res {
|
||||||
|
// Round up to the page size.
|
||||||
|
rnd := uint64(windows.Getpagesize() - 1)
|
||||||
|
new := (size + rnd) &^ rnd
|
||||||
|
|
||||||
|
// Commit additional memory up to new bytes.
|
||||||
|
_, err := windows.VirtualAlloc(m.addr, uintptr(new), windows.MEM_COMMIT, windows.PAGE_READWRITE)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update committed memory.
|
||||||
|
m.buf = m.buf[:new]
|
||||||
|
}
|
||||||
|
// Limit returned capacity because bytes beyond
|
||||||
|
// len(m.buf) have not yet been committed.
|
||||||
|
return m.buf[:size:len(m.buf)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *virtualMemory) Free() {
|
||||||
|
err := windows.VirtualFree(m.addr, 0, windows.MEM_RELEASE)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
m.addr = 0
|
||||||
|
}
|
22
vendor/github.com/ncruces/go-sqlite3/internal/util/bool.go
generated
vendored
Normal file
22
vendor/github.com/ncruces/go-sqlite3/internal/util/bool.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func ParseBool(s string) (b, ok bool) {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
if s[0] == '0' {
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
if '1' <= s[0] && s[0] <= '9' {
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
switch strings.ToLower(s) {
|
||||||
|
case "true", "yes", "on":
|
||||||
|
return true, true
|
||||||
|
case "false", "no", "off":
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
return false, false
|
||||||
|
}
|
117
vendor/github.com/ncruces/go-sqlite3/internal/util/const.go
generated
vendored
Normal file
117
vendor/github.com/ncruces/go-sqlite3/internal/util/const.go
generated
vendored
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
// https://sqlite.com/matrix/rescode.html
|
||||||
|
const (
|
||||||
|
OK = 0 /* Successful result */
|
||||||
|
|
||||||
|
ERROR = 1 /* Generic error */
|
||||||
|
INTERNAL = 2 /* Internal logic error in SQLite */
|
||||||
|
PERM = 3 /* Access permission denied */
|
||||||
|
ABORT = 4 /* Callback routine requested an abort */
|
||||||
|
BUSY = 5 /* The database file is locked */
|
||||||
|
LOCKED = 6 /* A table in the database is locked */
|
||||||
|
NOMEM = 7 /* A malloc() failed */
|
||||||
|
READONLY = 8 /* Attempt to write a readonly database */
|
||||||
|
INTERRUPT = 9 /* Operation terminated by sqlite3_interrupt() */
|
||||||
|
IOERR = 10 /* Some kind of disk I/O error occurred */
|
||||||
|
CORRUPT = 11 /* The database disk image is malformed */
|
||||||
|
NOTFOUND = 12 /* Unknown opcode in sqlite3_file_control() */
|
||||||
|
FULL = 13 /* Insertion failed because database is full */
|
||||||
|
CANTOPEN = 14 /* Unable to open the database file */
|
||||||
|
PROTOCOL = 15 /* Database lock protocol error */
|
||||||
|
EMPTY = 16 /* Internal use only */
|
||||||
|
SCHEMA = 17 /* The database schema changed */
|
||||||
|
TOOBIG = 18 /* String or BLOB exceeds size limit */
|
||||||
|
CONSTRAINT = 19 /* Abort due to constraint violation */
|
||||||
|
MISMATCH = 20 /* Data type mismatch */
|
||||||
|
MISUSE = 21 /* Library used incorrectly */
|
||||||
|
NOLFS = 22 /* Uses OS features not supported on host */
|
||||||
|
AUTH = 23 /* Authorization denied */
|
||||||
|
FORMAT = 24 /* Not used */
|
||||||
|
RANGE = 25 /* 2nd parameter to sqlite3_bind out of range */
|
||||||
|
NOTADB = 26 /* File opened that is not a database file */
|
||||||
|
NOTICE = 27 /* Notifications from sqlite3_log() */
|
||||||
|
WARNING = 28 /* Warnings from sqlite3_log() */
|
||||||
|
|
||||||
|
ROW = 100 /* sqlite3_step() has another row ready */
|
||||||
|
DONE = 101 /* sqlite3_step() has finished executing */
|
||||||
|
|
||||||
|
ERROR_MISSING_COLLSEQ = ERROR | (1 << 8)
|
||||||
|
ERROR_RETRY = ERROR | (2 << 8)
|
||||||
|
ERROR_SNAPSHOT = ERROR | (3 << 8)
|
||||||
|
IOERR_READ = IOERR | (1 << 8)
|
||||||
|
IOERR_SHORT_READ = IOERR | (2 << 8)
|
||||||
|
IOERR_WRITE = IOERR | (3 << 8)
|
||||||
|
IOERR_FSYNC = IOERR | (4 << 8)
|
||||||
|
IOERR_DIR_FSYNC = IOERR | (5 << 8)
|
||||||
|
IOERR_TRUNCATE = IOERR | (6 << 8)
|
||||||
|
IOERR_FSTAT = IOERR | (7 << 8)
|
||||||
|
IOERR_UNLOCK = IOERR | (8 << 8)
|
||||||
|
IOERR_RDLOCK = IOERR | (9 << 8)
|
||||||
|
IOERR_DELETE = IOERR | (10 << 8)
|
||||||
|
IOERR_BLOCKED = IOERR | (11 << 8)
|
||||||
|
IOERR_NOMEM = IOERR | (12 << 8)
|
||||||
|
IOERR_ACCESS = IOERR | (13 << 8)
|
||||||
|
IOERR_CHECKRESERVEDLOCK = IOERR | (14 << 8)
|
||||||
|
IOERR_LOCK = IOERR | (15 << 8)
|
||||||
|
IOERR_CLOSE = IOERR | (16 << 8)
|
||||||
|
IOERR_DIR_CLOSE = IOERR | (17 << 8)
|
||||||
|
IOERR_SHMOPEN = IOERR | (18 << 8)
|
||||||
|
IOERR_SHMSIZE = IOERR | (19 << 8)
|
||||||
|
IOERR_SHMLOCK = IOERR | (20 << 8)
|
||||||
|
IOERR_SHMMAP = IOERR | (21 << 8)
|
||||||
|
IOERR_SEEK = IOERR | (22 << 8)
|
||||||
|
IOERR_DELETE_NOENT = IOERR | (23 << 8)
|
||||||
|
IOERR_MMAP = IOERR | (24 << 8)
|
||||||
|
IOERR_GETTEMPPATH = IOERR | (25 << 8)
|
||||||
|
IOERR_CONVPATH = IOERR | (26 << 8)
|
||||||
|
IOERR_VNODE = IOERR | (27 << 8)
|
||||||
|
IOERR_AUTH = IOERR | (28 << 8)
|
||||||
|
IOERR_BEGIN_ATOMIC = IOERR | (29 << 8)
|
||||||
|
IOERR_COMMIT_ATOMIC = IOERR | (30 << 8)
|
||||||
|
IOERR_ROLLBACK_ATOMIC = IOERR | (31 << 8)
|
||||||
|
IOERR_DATA = IOERR | (32 << 8)
|
||||||
|
IOERR_CORRUPTFS = IOERR | (33 << 8)
|
||||||
|
IOERR_IN_PAGE = IOERR | (34 << 8)
|
||||||
|
LOCKED_SHAREDCACHE = LOCKED | (1 << 8)
|
||||||
|
LOCKED_VTAB = LOCKED | (2 << 8)
|
||||||
|
BUSY_RECOVERY = BUSY | (1 << 8)
|
||||||
|
BUSY_SNAPSHOT = BUSY | (2 << 8)
|
||||||
|
BUSY_TIMEOUT = BUSY | (3 << 8)
|
||||||
|
CANTOPEN_NOTEMPDIR = CANTOPEN | (1 << 8)
|
||||||
|
CANTOPEN_ISDIR = CANTOPEN | (2 << 8)
|
||||||
|
CANTOPEN_FULLPATH = CANTOPEN | (3 << 8)
|
||||||
|
CANTOPEN_CONVPATH = CANTOPEN | (4 << 8)
|
||||||
|
CANTOPEN_DIRTYWAL = CANTOPEN | (5 << 8) /* Not Used */
|
||||||
|
CANTOPEN_SYMLINK = CANTOPEN | (6 << 8)
|
||||||
|
CORRUPT_VTAB = CORRUPT | (1 << 8)
|
||||||
|
CORRUPT_SEQUENCE = CORRUPT | (2 << 8)
|
||||||
|
CORRUPT_INDEX = CORRUPT | (3 << 8)
|
||||||
|
READONLY_RECOVERY = READONLY | (1 << 8)
|
||||||
|
READONLY_CANTLOCK = READONLY | (2 << 8)
|
||||||
|
READONLY_ROLLBACK = READONLY | (3 << 8)
|
||||||
|
READONLY_DBMOVED = READONLY | (4 << 8)
|
||||||
|
READONLY_CANTINIT = READONLY | (5 << 8)
|
||||||
|
READONLY_DIRECTORY = READONLY | (6 << 8)
|
||||||
|
ABORT_ROLLBACK = ABORT | (2 << 8)
|
||||||
|
CONSTRAINT_CHECK = CONSTRAINT | (1 << 8)
|
||||||
|
CONSTRAINT_COMMITHOOK = CONSTRAINT | (2 << 8)
|
||||||
|
CONSTRAINT_FOREIGNKEY = CONSTRAINT | (3 << 8)
|
||||||
|
CONSTRAINT_FUNCTION = CONSTRAINT | (4 << 8)
|
||||||
|
CONSTRAINT_NOTNULL = CONSTRAINT | (5 << 8)
|
||||||
|
CONSTRAINT_PRIMARYKEY = CONSTRAINT | (6 << 8)
|
||||||
|
CONSTRAINT_TRIGGER = CONSTRAINT | (7 << 8)
|
||||||
|
CONSTRAINT_UNIQUE = CONSTRAINT | (8 << 8)
|
||||||
|
CONSTRAINT_VTAB = CONSTRAINT | (9 << 8)
|
||||||
|
CONSTRAINT_ROWID = CONSTRAINT | (10 << 8)
|
||||||
|
CONSTRAINT_PINNED = CONSTRAINT | (11 << 8)
|
||||||
|
CONSTRAINT_DATATYPE = CONSTRAINT | (12 << 8)
|
||||||
|
NOTICE_RECOVER_WAL = NOTICE | (1 << 8)
|
||||||
|
NOTICE_RECOVER_ROLLBACK = NOTICE | (2 << 8)
|
||||||
|
NOTICE_RBU = NOTICE | (3 << 8)
|
||||||
|
WARNING_AUTOINDEX = WARNING | (1 << 8)
|
||||||
|
AUTH_USER = AUTH | (1 << 8)
|
||||||
|
|
||||||
|
OK_LOAD_PERMANENTLY = OK | (1 << 8)
|
||||||
|
OK_SYMLINK = OK | (2 << 8) /* internal use only */
|
||||||
|
)
|
106
vendor/github.com/ncruces/go-sqlite3/internal/util/error.go
generated
vendored
Normal file
106
vendor/github.com/ncruces/go-sqlite3/internal/util/error.go
generated
vendored
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrorString string
|
||||||
|
|
||||||
|
func (e ErrorString) Error() string { return string(e) }
|
||||||
|
|
||||||
|
const (
|
||||||
|
NilErr = ErrorString("sqlite3: invalid memory address or null pointer dereference")
|
||||||
|
OOMErr = ErrorString("sqlite3: out of memory")
|
||||||
|
RangeErr = ErrorString("sqlite3: index out of range")
|
||||||
|
NoNulErr = ErrorString("sqlite3: missing NUL terminator")
|
||||||
|
NoBinaryErr = ErrorString("sqlite3: no SQLite binary embed/set/loaded")
|
||||||
|
BadBinaryErr = ErrorString("sqlite3: invalid SQLite binary embed/set/loaded")
|
||||||
|
TimeErr = ErrorString("sqlite3: invalid time value")
|
||||||
|
WhenceErr = ErrorString("sqlite3: invalid whence")
|
||||||
|
OffsetErr = ErrorString("sqlite3: invalid offset")
|
||||||
|
TailErr = ErrorString("sqlite3: multiple statements")
|
||||||
|
IsolationErr = ErrorString("sqlite3: unsupported isolation level")
|
||||||
|
ValueErr = ErrorString("sqlite3: unsupported value")
|
||||||
|
NoVFSErr = ErrorString("sqlite3: no such vfs: ")
|
||||||
|
)
|
||||||
|
|
||||||
|
func AssertErr() ErrorString {
|
||||||
|
msg := "sqlite3: assertion failed"
|
||||||
|
if _, file, line, ok := runtime.Caller(1); ok {
|
||||||
|
msg += " (" + file + ":" + strconv.Itoa(line) + ")"
|
||||||
|
}
|
||||||
|
return ErrorString(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorCodeString(rc uint32) string {
|
||||||
|
switch rc {
|
||||||
|
case ABORT_ROLLBACK:
|
||||||
|
return "sqlite3: abort due to ROLLBACK"
|
||||||
|
case ROW:
|
||||||
|
return "sqlite3: another row available"
|
||||||
|
case DONE:
|
||||||
|
return "sqlite3: no more rows available"
|
||||||
|
}
|
||||||
|
switch rc & 0xff {
|
||||||
|
case OK:
|
||||||
|
return "sqlite3: not an error"
|
||||||
|
case ERROR:
|
||||||
|
return "sqlite3: SQL logic error"
|
||||||
|
case INTERNAL:
|
||||||
|
break
|
||||||
|
case PERM:
|
||||||
|
return "sqlite3: access permission denied"
|
||||||
|
case ABORT:
|
||||||
|
return "sqlite3: query aborted"
|
||||||
|
case BUSY:
|
||||||
|
return "sqlite3: database is locked"
|
||||||
|
case LOCKED:
|
||||||
|
return "sqlite3: database table is locked"
|
||||||
|
case NOMEM:
|
||||||
|
return "sqlite3: out of memory"
|
||||||
|
case READONLY:
|
||||||
|
return "sqlite3: attempt to write a readonly database"
|
||||||
|
case INTERRUPT:
|
||||||
|
return "sqlite3: interrupted"
|
||||||
|
case IOERR:
|
||||||
|
return "sqlite3: disk I/O error"
|
||||||
|
case CORRUPT:
|
||||||
|
return "sqlite3: database disk image is malformed"
|
||||||
|
case NOTFOUND:
|
||||||
|
return "sqlite3: unknown operation"
|
||||||
|
case FULL:
|
||||||
|
return "sqlite3: database or disk is full"
|
||||||
|
case CANTOPEN:
|
||||||
|
return "sqlite3: unable to open database file"
|
||||||
|
case PROTOCOL:
|
||||||
|
return "sqlite3: locking protocol"
|
||||||
|
case FORMAT:
|
||||||
|
break
|
||||||
|
case SCHEMA:
|
||||||
|
return "sqlite3: database schema has changed"
|
||||||
|
case TOOBIG:
|
||||||
|
return "sqlite3: string or blob too big"
|
||||||
|
case CONSTRAINT:
|
||||||
|
return "sqlite3: constraint failed"
|
||||||
|
case MISMATCH:
|
||||||
|
return "sqlite3: datatype mismatch"
|
||||||
|
case MISUSE:
|
||||||
|
return "sqlite3: bad parameter or other API misuse"
|
||||||
|
case NOLFS:
|
||||||
|
break
|
||||||
|
case AUTH:
|
||||||
|
return "sqlite3: authorization denied"
|
||||||
|
case EMPTY:
|
||||||
|
break
|
||||||
|
case RANGE:
|
||||||
|
return "sqlite3: column index out of range"
|
||||||
|
case NOTADB:
|
||||||
|
return "sqlite3: file is not a database"
|
||||||
|
case NOTICE:
|
||||||
|
return "sqlite3: notification message"
|
||||||
|
case WARNING:
|
||||||
|
return "sqlite3: warning message"
|
||||||
|
}
|
||||||
|
return "sqlite3: unknown error"
|
||||||
|
}
|
193
vendor/github.com/ncruces/go-sqlite3/internal/util/func.go
generated
vendored
Normal file
193
vendor/github.com/ncruces/go-sqlite3/internal/util/func.go
generated
vendored
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero"
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type i32 interface{ ~int32 | ~uint32 }
|
||||||
|
type i64 interface{ ~int64 | ~uint64 }
|
||||||
|
|
||||||
|
type funcVI[T0 i32] func(context.Context, api.Module, T0)
|
||||||
|
|
||||||
|
func (fn funcVI[T0]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||||
|
fn(ctx, mod, T0(stack[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportFuncVI[T0 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0)) {
|
||||||
|
mod.NewFunctionBuilder().
|
||||||
|
WithGoModuleFunction(funcVI[T0](fn),
|
||||||
|
[]api.ValueType{api.ValueTypeI32}, nil).
|
||||||
|
Export(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcVII[T0, T1 i32] func(context.Context, api.Module, T0, T1)
|
||||||
|
|
||||||
|
func (fn funcVII[T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||||
|
fn(ctx, mod, T0(stack[0]), T1(stack[1]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportFuncVII[T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1)) {
|
||||||
|
mod.NewFunctionBuilder().
|
||||||
|
WithGoModuleFunction(funcVII[T0, T1](fn),
|
||||||
|
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, nil).
|
||||||
|
Export(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcVIII[T0, T1, T2 i32] func(context.Context, api.Module, T0, T1, T2)
|
||||||
|
|
||||||
|
func (fn funcVIII[T0, T1, T2]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||||
|
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportFuncVIII[T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2)) {
|
||||||
|
mod.NewFunctionBuilder().
|
||||||
|
WithGoModuleFunction(funcVIII[T0, T1, T2](fn),
|
||||||
|
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil).
|
||||||
|
Export(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcVIIII[T0, T1, T2, T3 i32] func(context.Context, api.Module, T0, T1, T2, T3)
|
||||||
|
|
||||||
|
func (fn funcVIIII[T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||||
|
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportFuncVIIII[T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3)) {
|
||||||
|
mod.NewFunctionBuilder().
|
||||||
|
WithGoModuleFunction(funcVIIII[T0, T1, T2, T3](fn),
|
||||||
|
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil).
|
||||||
|
Export(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcVIIIII[T0, T1, T2, T3, T4 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4)
|
||||||
|
|
||||||
|
func (fn funcVIIIII[T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||||
|
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportFuncVIIIII[T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4)) {
|
||||||
|
mod.NewFunctionBuilder().
|
||||||
|
WithGoModuleFunction(funcVIIIII[T0, T1, T2, T3, T4](fn),
|
||||||
|
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil).
|
||||||
|
Export(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcVIIIIJ[T0, T1, T2, T3 i32, T4 i64] func(context.Context, api.Module, T0, T1, T2, T3, T4)
|
||||||
|
|
||||||
|
func (fn funcVIIIIJ[T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||||
|
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportFuncVIIIIJ[T0, T1, T2, T3 i32, T4 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4)) {
|
||||||
|
mod.NewFunctionBuilder().
|
||||||
|
WithGoModuleFunction(funcVIIIIJ[T0, T1, T2, T3, T4](fn),
|
||||||
|
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI64}, nil).
|
||||||
|
Export(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcII[TR, T0 i32] func(context.Context, api.Module, T0) TR
|
||||||
|
|
||||||
|
func (fn funcII[TR, T0]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||||
|
stack[0] = uint64(fn(ctx, mod, T0(stack[0])))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportFuncII[TR, T0 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0) TR) {
|
||||||
|
mod.NewFunctionBuilder().
|
||||||
|
WithGoModuleFunction(funcII[TR, T0](fn),
|
||||||
|
[]api.ValueType{api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
|
||||||
|
Export(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcIII[TR, T0, T1 i32] func(context.Context, api.Module, T0, T1) TR
|
||||||
|
|
||||||
|
func (fn funcIII[TR, T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||||
|
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1])))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportFuncIII[TR, T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1) TR) {
|
||||||
|
mod.NewFunctionBuilder().
|
||||||
|
WithGoModuleFunction(funcIII[TR, T0, T1](fn),
|
||||||
|
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
|
||||||
|
Export(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcIIII[TR, T0, T1, T2 i32] func(context.Context, api.Module, T0, T1, T2) TR
|
||||||
|
|
||||||
|
func (fn funcIIII[TR, T0, T1, T2]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||||
|
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2])))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportFuncIIII[TR, T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2) TR) {
|
||||||
|
mod.NewFunctionBuilder().
|
||||||
|
WithGoModuleFunction(funcIIII[TR, T0, T1, T2](fn),
|
||||||
|
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
|
||||||
|
Export(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcIIIII[TR, T0, T1, T2, T3 i32] func(context.Context, api.Module, T0, T1, T2, T3) TR
|
||||||
|
|
||||||
|
func (fn funcIIIII[TR, T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||||
|
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3])))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportFuncIIIII[TR, T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3) TR) {
|
||||||
|
mod.NewFunctionBuilder().
|
||||||
|
WithGoModuleFunction(funcIIIII[TR, T0, T1, T2, T3](fn),
|
||||||
|
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
|
||||||
|
Export(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcIIIIII[TR, T0, T1, T2, T3, T4 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4) TR
|
||||||
|
|
||||||
|
func (fn funcIIIIII[TR, T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||||
|
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4])))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportFuncIIIIII[TR, T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4) TR) {
|
||||||
|
mod.NewFunctionBuilder().
|
||||||
|
WithGoModuleFunction(funcIIIIII[TR, T0, T1, T2, T3, T4](fn),
|
||||||
|
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
|
||||||
|
Export(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcIIIIIII[TR, T0, T1, T2, T3, T4, T5 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4, T5) TR
|
||||||
|
|
||||||
|
func (fn funcIIIIIII[TR, T0, T1, T2, T3, T4, T5]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||||
|
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]), T5(stack[5])))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportFuncIIIIIII[TR, T0, T1, T2, T3, T4, T5 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4, T5) TR) {
|
||||||
|
mod.NewFunctionBuilder().
|
||||||
|
WithGoModuleFunction(funcIIIIIII[TR, T0, T1, T2, T3, T4, T5](fn),
|
||||||
|
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
|
||||||
|
Export(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcIIIIJ[TR, T0, T1, T2 i32, T3 i64] func(context.Context, api.Module, T0, T1, T2, T3) TR
|
||||||
|
|
||||||
|
func (fn funcIIIIJ[TR, T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||||
|
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3])))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportFuncIIIIJ[TR, T0, T1, T2 i32, T3 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3) TR) {
|
||||||
|
mod.NewFunctionBuilder().
|
||||||
|
WithGoModuleFunction(funcIIIIJ[TR, T0, T1, T2, T3](fn),
|
||||||
|
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}).
|
||||||
|
Export(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcIIJ[TR, T0 i32, T1 i64] func(context.Context, api.Module, T0, T1) TR
|
||||||
|
|
||||||
|
func (fn funcIIJ[TR, T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||||
|
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1])))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportFuncIIJ[TR, T0 i32, T1 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1) TR) {
|
||||||
|
mod.NewFunctionBuilder().
|
||||||
|
WithGoModuleFunction(funcIIJ[TR, T0, T1](fn),
|
||||||
|
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}).
|
||||||
|
Export(name)
|
||||||
|
}
|
65
vendor/github.com/ncruces/go-sqlite3/internal/util/handle.go
generated
vendored
Normal file
65
vendor/github.com/ncruces/go-sqlite3/internal/util/handle.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type handleState struct {
|
||||||
|
handles []any
|
||||||
|
holes int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *handleState) CloseNotify(ctx context.Context, exitCode uint32) {
|
||||||
|
for _, h := range s.handles {
|
||||||
|
if c, ok := h.(io.Closer); ok {
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.handles = nil
|
||||||
|
s.holes = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHandle(ctx context.Context, id uint32) any {
|
||||||
|
if id == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s := ctx.Value(moduleKey{}).(*moduleState)
|
||||||
|
return s.handles[^id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func DelHandle(ctx context.Context, id uint32) error {
|
||||||
|
if id == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s := ctx.Value(moduleKey{}).(*moduleState)
|
||||||
|
a := s.handles[^id]
|
||||||
|
s.handles[^id] = nil
|
||||||
|
s.holes++
|
||||||
|
if c, ok := a.(io.Closer); ok {
|
||||||
|
return c.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddHandle(ctx context.Context, a any) (id uint32) {
|
||||||
|
if a == nil {
|
||||||
|
panic(NilErr)
|
||||||
|
}
|
||||||
|
s := ctx.Value(moduleKey{}).(*moduleState)
|
||||||
|
|
||||||
|
// Find an empty slot.
|
||||||
|
if s.holes > cap(s.handles)-len(s.handles) {
|
||||||
|
for id, h := range s.handles {
|
||||||
|
if h == nil {
|
||||||
|
s.holes--
|
||||||
|
s.handles[id] = a
|
||||||
|
return ^uint32(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new slot.
|
||||||
|
s.handles = append(s.handles, a)
|
||||||
|
return -uint32(len(s.handles))
|
||||||
|
}
|
35
vendor/github.com/ncruces/go-sqlite3/internal/util/json.go
generated
vendored
Normal file
35
vendor/github.com/ncruces/go-sqlite3/internal/util/json.go
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JSON struct{ Value any }
|
||||||
|
|
||||||
|
func (j JSON) Scan(value any) error {
|
||||||
|
var buf []byte
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []byte:
|
||||||
|
buf = v
|
||||||
|
case string:
|
||||||
|
buf = unsafe.Slice(unsafe.StringData(v), len(v))
|
||||||
|
case int64:
|
||||||
|
buf = strconv.AppendInt(nil, v, 10)
|
||||||
|
case float64:
|
||||||
|
buf = strconv.AppendFloat(nil, v, 'g', -1, 64)
|
||||||
|
case time.Time:
|
||||||
|
buf = append(buf, '"')
|
||||||
|
buf = v.AppendFormat(buf, time.RFC3339Nano)
|
||||||
|
buf = append(buf, '"')
|
||||||
|
case nil:
|
||||||
|
buf = append(buf, "null"...)
|
||||||
|
default:
|
||||||
|
panic(AssertErr())
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(buf, j.Value)
|
||||||
|
}
|
134
vendor/github.com/ncruces/go-sqlite3/internal/util/mem.go
generated
vendored
Normal file
134
vendor/github.com/ncruces/go-sqlite3/internal/util/mem.go
generated
vendored
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func View(mod api.Module, ptr uint32, size uint64) []byte {
|
||||||
|
if ptr == 0 {
|
||||||
|
panic(NilErr)
|
||||||
|
}
|
||||||
|
if size > math.MaxUint32 {
|
||||||
|
panic(RangeErr)
|
||||||
|
}
|
||||||
|
if size == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
buf, ok := mod.Memory().Read(ptr, uint32(size))
|
||||||
|
if !ok {
|
||||||
|
panic(RangeErr)
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadUint8(mod api.Module, ptr uint32) uint8 {
|
||||||
|
if ptr == 0 {
|
||||||
|
panic(NilErr)
|
||||||
|
}
|
||||||
|
v, ok := mod.Memory().ReadByte(ptr)
|
||||||
|
if !ok {
|
||||||
|
panic(RangeErr)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadUint32(mod api.Module, ptr uint32) uint32 {
|
||||||
|
if ptr == 0 {
|
||||||
|
panic(NilErr)
|
||||||
|
}
|
||||||
|
v, ok := mod.Memory().ReadUint32Le(ptr)
|
||||||
|
if !ok {
|
||||||
|
panic(RangeErr)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteUint8(mod api.Module, ptr uint32, v uint8) {
|
||||||
|
if ptr == 0 {
|
||||||
|
panic(NilErr)
|
||||||
|
}
|
||||||
|
ok := mod.Memory().WriteByte(ptr, v)
|
||||||
|
if !ok {
|
||||||
|
panic(RangeErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteUint32(mod api.Module, ptr uint32, v uint32) {
|
||||||
|
if ptr == 0 {
|
||||||
|
panic(NilErr)
|
||||||
|
}
|
||||||
|
ok := mod.Memory().WriteUint32Le(ptr, v)
|
||||||
|
if !ok {
|
||||||
|
panic(RangeErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadUint64(mod api.Module, ptr uint32) uint64 {
|
||||||
|
if ptr == 0 {
|
||||||
|
panic(NilErr)
|
||||||
|
}
|
||||||
|
v, ok := mod.Memory().ReadUint64Le(ptr)
|
||||||
|
if !ok {
|
||||||
|
panic(RangeErr)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteUint64(mod api.Module, ptr uint32, v uint64) {
|
||||||
|
if ptr == 0 {
|
||||||
|
panic(NilErr)
|
||||||
|
}
|
||||||
|
ok := mod.Memory().WriteUint64Le(ptr, v)
|
||||||
|
if !ok {
|
||||||
|
panic(RangeErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadFloat64(mod api.Module, ptr uint32) float64 {
|
||||||
|
return math.Float64frombits(ReadUint64(mod, ptr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteFloat64(mod api.Module, ptr uint32, v float64) {
|
||||||
|
WriteUint64(mod, ptr, math.Float64bits(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadString(mod api.Module, ptr, maxlen uint32) string {
|
||||||
|
if ptr == 0 {
|
||||||
|
panic(NilErr)
|
||||||
|
}
|
||||||
|
switch maxlen {
|
||||||
|
case 0:
|
||||||
|
return ""
|
||||||
|
case math.MaxUint32:
|
||||||
|
// avoid overflow
|
||||||
|
default:
|
||||||
|
maxlen = maxlen + 1
|
||||||
|
}
|
||||||
|
mem := mod.Memory()
|
||||||
|
buf, ok := mem.Read(ptr, maxlen)
|
||||||
|
if !ok {
|
||||||
|
buf, ok = mem.Read(ptr, mem.Size()-ptr)
|
||||||
|
if !ok {
|
||||||
|
panic(RangeErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i := bytes.IndexByte(buf, 0); i < 0 {
|
||||||
|
panic(NoNulErr)
|
||||||
|
} else {
|
||||||
|
return string(buf[:i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteBytes(mod api.Module, ptr uint32, b []byte) {
|
||||||
|
buf := View(mod, ptr, uint64(len(b)))
|
||||||
|
copy(buf, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteString(mod api.Module, ptr uint32, s string) {
|
||||||
|
buf := View(mod, ptr, uint64(len(s)+1))
|
||||||
|
buf[len(s)] = 0
|
||||||
|
copy(buf, s)
|
||||||
|
}
|
97
vendor/github.com/ncruces/go-sqlite3/internal/util/mmap.go
generated
vendored
Normal file
97
vendor/github.com/ncruces/go-sqlite3/internal/util/mmap.go
generated
vendored
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
//go:build (darwin || linux) && (amd64 || arm64 || riscv64) && !(sqlite3_noshm || sqlite3_nosys)
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
"github.com/tetratelabs/wazero/experimental"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func withAllocator(ctx context.Context) context.Context {
|
||||||
|
return experimental.WithMemoryAllocator(ctx,
|
||||||
|
experimental.MemoryAllocatorFunc(virtualAlloc))
|
||||||
|
}
|
||||||
|
|
||||||
|
type mmapState struct {
|
||||||
|
regions []*MappedRegion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mmapState) new(ctx context.Context, mod api.Module, size int32) *MappedRegion {
|
||||||
|
// Find unused region.
|
||||||
|
for _, r := range s.regions {
|
||||||
|
if !r.used && r.size == size {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate page aligned memmory.
|
||||||
|
alloc := mod.ExportedFunction("aligned_alloc")
|
||||||
|
stack := [2]uint64{
|
||||||
|
uint64(unix.Getpagesize()),
|
||||||
|
uint64(size),
|
||||||
|
}
|
||||||
|
if err := alloc.CallWithStack(ctx, stack[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if stack[0] == 0 {
|
||||||
|
panic(OOMErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the newly allocated region.
|
||||||
|
ptr := uint32(stack[0])
|
||||||
|
buf := View(mod, ptr, uint64(size))
|
||||||
|
addr := uintptr(unsafe.Pointer(&buf[0]))
|
||||||
|
s.regions = append(s.regions, &MappedRegion{
|
||||||
|
Ptr: ptr,
|
||||||
|
addr: addr,
|
||||||
|
size: size,
|
||||||
|
})
|
||||||
|
return s.regions[len(s.regions)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
type MappedRegion struct {
|
||||||
|
addr uintptr
|
||||||
|
Ptr uint32
|
||||||
|
size int32
|
||||||
|
used bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapRegion(ctx context.Context, mod api.Module, f *os.File, offset int64, size int32, prot int) (*MappedRegion, error) {
|
||||||
|
s := ctx.Value(moduleKey{}).(*moduleState)
|
||||||
|
r := s.new(ctx, mod, size)
|
||||||
|
err := r.mmap(f, offset, prot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MappedRegion) Unmap() error {
|
||||||
|
// We can't munmap the region, otherwise it could be remaped.
|
||||||
|
// Instead, convert it to a protected, private, anonymous mapping.
|
||||||
|
// If successful, it can be reused for a subsequent mmap.
|
||||||
|
_, err := mmap(r.addr, uintptr(r.size),
|
||||||
|
unix.PROT_NONE, unix.MAP_PRIVATE|unix.MAP_ANON|unix.MAP_FIXED,
|
||||||
|
-1, 0)
|
||||||
|
r.used = err != nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MappedRegion) mmap(f *os.File, offset int64, prot int) error {
|
||||||
|
_, err := mmap(r.addr, uintptr(r.size),
|
||||||
|
prot, unix.MAP_SHARED|unix.MAP_FIXED,
|
||||||
|
int(f.Fd()), offset)
|
||||||
|
r.used = err == nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need the low level mmap for MAP_FIXED to work.
|
||||||
|
// Bind the syscall version hoping that it is more stable.
|
||||||
|
|
||||||
|
//go:linkname mmap syscall.mmap
|
||||||
|
func mmap(addr, length uintptr, prot, flag, fd int, pos int64) (*byte, error)
|
21
vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_other.go
generated
vendored
Normal file
21
vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_other.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
//go:build !(darwin || linux) || !(amd64 || arm64 || riscv64) || sqlite3_noshm || sqlite3_nosys
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero/experimental"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mmapState struct{}
|
||||||
|
|
||||||
|
func withAllocator(ctx context.Context) context.Context {
|
||||||
|
return experimental.WithMemoryAllocator(ctx,
|
||||||
|
experimental.MemoryAllocatorFunc(func(cap, max uint64) experimental.LinearMemory {
|
||||||
|
if cap == max {
|
||||||
|
return virtualAlloc(cap, max)
|
||||||
|
}
|
||||||
|
return sliceAlloc(cap, max)
|
||||||
|
}))
|
||||||
|
}
|
21
vendor/github.com/ncruces/go-sqlite3/internal/util/module.go
generated
vendored
Normal file
21
vendor/github.com/ncruces/go-sqlite3/internal/util/module.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero/experimental"
|
||||||
|
)
|
||||||
|
|
||||||
|
type moduleKey struct{}
|
||||||
|
type moduleState struct {
|
||||||
|
mmapState
|
||||||
|
handleState
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContext(ctx context.Context) context.Context {
|
||||||
|
state := new(moduleState)
|
||||||
|
ctx = withAllocator(ctx)
|
||||||
|
ctx = experimental.WithCloseNotifier(ctx, state)
|
||||||
|
ctx = context.WithValue(ctx, moduleKey{}, state)
|
||||||
|
return ctx
|
||||||
|
}
|
11
vendor/github.com/ncruces/go-sqlite3/internal/util/pointer.go
generated
vendored
Normal file
11
vendor/github.com/ncruces/go-sqlite3/internal/util/pointer.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
type Pointer[T any] struct{ Value T }
|
||||||
|
|
||||||
|
func (p Pointer[T]) unwrap() any { return p.Value }
|
||||||
|
|
||||||
|
type PointerUnwrap interface{ unwrap() any }
|
||||||
|
|
||||||
|
func UnwrapPointer(p PointerUnwrap) any {
|
||||||
|
return p.unwrap()
|
||||||
|
}
|
10
vendor/github.com/ncruces/go-sqlite3/internal/util/reflect.go
generated
vendored
Normal file
10
vendor/github.com/ncruces/go-sqlite3/internal/util/reflect.go
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
func ReflectType(v reflect.Value) reflect.Type {
|
||||||
|
if v.Kind() != reflect.Invalid {
|
||||||
|
return v.Type()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
11
vendor/github.com/ncruces/go-sqlite3/json.go
generated
vendored
Normal file
11
vendor/github.com/ncruces/go-sqlite3/json.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import "github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
|
||||||
|
// JSON returns a value that can be used as an argument to
|
||||||
|
// [database/sql.DB.Exec], [database/sql.Row.Scan] and similar methods to
|
||||||
|
// store value as JSON, or decode JSON into value.
|
||||||
|
// JSON should NOT be used with [BindJSON] or [ResultJSON].
|
||||||
|
func JSON(value any) any {
|
||||||
|
return util.JSON{Value: value}
|
||||||
|
}
|
12
vendor/github.com/ncruces/go-sqlite3/pointer.go
generated
vendored
Normal file
12
vendor/github.com/ncruces/go-sqlite3/pointer.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import "github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
|
||||||
|
// Pointer returns a pointer to a value that can be used as an argument to
|
||||||
|
// [database/sql.DB.Exec] and similar methods.
|
||||||
|
// Pointer should NOT be used with [BindPointer] or [ResultPointer].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/bindptr.html
|
||||||
|
func Pointer[T any](value T) any {
|
||||||
|
return util.Pointer[T]{Value: value}
|
||||||
|
}
|
112
vendor/github.com/ncruces/go-sqlite3/quote.go
generated
vendored
Normal file
112
vendor/github.com/ncruces/go-sqlite3/quote.go
generated
vendored
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Quote escapes and quotes a value
|
||||||
|
// making it safe to embed in SQL text.
|
||||||
|
func Quote(value any) string {
|
||||||
|
switch v := value.(type) {
|
||||||
|
case nil:
|
||||||
|
return "NULL"
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
return "1"
|
||||||
|
} else {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
case int:
|
||||||
|
return strconv.Itoa(v)
|
||||||
|
case int64:
|
||||||
|
return strconv.FormatInt(v, 10)
|
||||||
|
case float64:
|
||||||
|
switch {
|
||||||
|
case math.IsNaN(v):
|
||||||
|
return "NULL"
|
||||||
|
case math.IsInf(v, 1):
|
||||||
|
return "9.0e999"
|
||||||
|
case math.IsInf(v, -1):
|
||||||
|
return "-9.0e999"
|
||||||
|
}
|
||||||
|
return strconv.FormatFloat(v, 'g', -1, 64)
|
||||||
|
case time.Time:
|
||||||
|
return "'" + v.Format(time.RFC3339Nano) + "'"
|
||||||
|
|
||||||
|
case string:
|
||||||
|
if strings.IndexByte(v, 0) >= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 2+len(v)+strings.Count(v, "'"))
|
||||||
|
buf[0] = '\''
|
||||||
|
i := 1
|
||||||
|
for _, b := range []byte(v) {
|
||||||
|
if b == '\'' {
|
||||||
|
buf[i] = b
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
buf[i] = b
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
buf[i] = '\''
|
||||||
|
return unsafe.String(&buf[0], len(buf))
|
||||||
|
|
||||||
|
case []byte:
|
||||||
|
buf := make([]byte, 3+2*len(v))
|
||||||
|
buf[0] = 'x'
|
||||||
|
buf[1] = '\''
|
||||||
|
i := 2
|
||||||
|
for _, b := range v {
|
||||||
|
const hex = "0123456789ABCDEF"
|
||||||
|
buf[i+0] = hex[b/16]
|
||||||
|
buf[i+1] = hex[b%16]
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
buf[i] = '\''
|
||||||
|
return unsafe.String(&buf[0], len(buf))
|
||||||
|
|
||||||
|
case ZeroBlob:
|
||||||
|
if v > ZeroBlob(1e9-3)/2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.Repeat([]byte("0"), int(3+2*int64(v)))
|
||||||
|
buf[0] = 'x'
|
||||||
|
buf[1] = '\''
|
||||||
|
buf[len(buf)-1] = '\''
|
||||||
|
return unsafe.String(&buf[0], len(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(util.ValueErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuoteIdentifier escapes and quotes an identifier
|
||||||
|
// making it safe to embed in SQL text.
|
||||||
|
func QuoteIdentifier(id string) string {
|
||||||
|
if strings.IndexByte(id, 0) >= 0 {
|
||||||
|
panic(util.ValueErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 2+len(id)+strings.Count(id, `"`))
|
||||||
|
buf[0] = '"'
|
||||||
|
i := 1
|
||||||
|
for _, b := range []byte(id) {
|
||||||
|
if b == '"' {
|
||||||
|
buf[i] = b
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
buf[i] = b
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
buf[i] = '"'
|
||||||
|
return unsafe.String(&buf[0], len(buf))
|
||||||
|
}
|
341
vendor/github.com/ncruces/go-sqlite3/sqlite.go
generated
vendored
Normal file
341
vendor/github.com/ncruces/go-sqlite3/sqlite.go
generated
vendored
Normal file
|
@ -0,0 +1,341 @@
|
||||||
|
// Package sqlite3 wraps the C SQLite API.
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math"
|
||||||
|
"math/bits"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
"github.com/ncruces/go-sqlite3/vfs"
|
||||||
|
"github.com/tetratelabs/wazero"
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Configure SQLite Wasm.
|
||||||
|
//
|
||||||
|
// Importing package embed initializes [Binary]
|
||||||
|
// with an appropriate build of SQLite:
|
||||||
|
//
|
||||||
|
// import _ "github.com/ncruces/go-sqlite3/embed"
|
||||||
|
var (
|
||||||
|
Binary []byte // Wasm binary to load.
|
||||||
|
Path string // Path to load the binary from.
|
||||||
|
|
||||||
|
RuntimeConfig wazero.RuntimeConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize decodes and compiles the SQLite Wasm binary.
|
||||||
|
// This is called implicitly when the first connection is openned,
|
||||||
|
// but is potentially slow, so you may want to call it at a more convenient time.
|
||||||
|
func Initialize() error {
|
||||||
|
instance.once.Do(compileSQLite)
|
||||||
|
return instance.err
|
||||||
|
}
|
||||||
|
|
||||||
|
var instance struct {
|
||||||
|
runtime wazero.Runtime
|
||||||
|
compiled wazero.CompiledModule
|
||||||
|
err error
|
||||||
|
once sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func compileSQLite() {
|
||||||
|
if RuntimeConfig == nil {
|
||||||
|
RuntimeConfig = wazero.NewRuntimeConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
instance.runtime = wazero.NewRuntimeWithConfig(ctx, RuntimeConfig)
|
||||||
|
|
||||||
|
env := instance.runtime.NewHostModuleBuilder("env")
|
||||||
|
env = vfs.ExportHostFunctions(env)
|
||||||
|
env = exportCallbacks(env)
|
||||||
|
_, instance.err = env.Instantiate(ctx)
|
||||||
|
if instance.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bin := Binary
|
||||||
|
if bin == nil && Path != "" {
|
||||||
|
bin, instance.err = os.ReadFile(Path)
|
||||||
|
if instance.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bin == nil {
|
||||||
|
instance.err = util.NoBinaryErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.compiled, instance.err = instance.runtime.CompileModule(ctx, bin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqlite struct {
|
||||||
|
ctx context.Context
|
||||||
|
mod api.Module
|
||||||
|
funcs struct {
|
||||||
|
fn [32]api.Function
|
||||||
|
id [32]*byte
|
||||||
|
mask uint32
|
||||||
|
}
|
||||||
|
stack [8]uint64
|
||||||
|
freer uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func instantiateSQLite() (sqlt *sqlite, err error) {
|
||||||
|
if err := Initialize(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlt = new(sqlite)
|
||||||
|
sqlt.ctx = util.NewContext(context.Background())
|
||||||
|
|
||||||
|
sqlt.mod, err = instance.runtime.InstantiateModule(sqlt.ctx,
|
||||||
|
instance.compiled, wazero.NewModuleConfig().WithName(""))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
global := sqlt.mod.ExportedGlobal("malloc_destructor")
|
||||||
|
if global == nil {
|
||||||
|
return nil, util.BadBinaryErr
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlt.freer = util.ReadUint32(sqlt.mod, uint32(global.Get()))
|
||||||
|
if sqlt.freer == 0 {
|
||||||
|
return nil, util.BadBinaryErr
|
||||||
|
}
|
||||||
|
return sqlt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sqlt *sqlite) close() error {
|
||||||
|
return sqlt.mod.Close(sqlt.ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sqlt *sqlite) error(rc uint64, handle uint32, sql ...string) error {
|
||||||
|
if rc == _OK {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := Error{code: rc}
|
||||||
|
|
||||||
|
if err.Code() == NOMEM || err.ExtendedCode() == IOERR_NOMEM {
|
||||||
|
panic(util.OOMErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r := sqlt.call("sqlite3_errstr", rc); r != 0 {
|
||||||
|
err.str = util.ReadString(sqlt.mod, uint32(r), _MAX_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle != 0 {
|
||||||
|
if r := sqlt.call("sqlite3_errmsg", uint64(handle)); r != 0 {
|
||||||
|
err.msg = util.ReadString(sqlt.mod, uint32(r), _MAX_LENGTH)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sql != nil {
|
||||||
|
if r := sqlt.call("sqlite3_error_offset", uint64(handle)); r != math.MaxUint32 {
|
||||||
|
err.sql = sql[0][r:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch err.msg {
|
||||||
|
case err.str, "not an error":
|
||||||
|
err.msg = ""
|
||||||
|
}
|
||||||
|
return &err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sqlt *sqlite) getfn(name string) api.Function {
|
||||||
|
c := &sqlt.funcs
|
||||||
|
p := unsafe.StringData(name)
|
||||||
|
for i := range c.id {
|
||||||
|
if c.id[i] == p {
|
||||||
|
c.id[i] = nil
|
||||||
|
c.mask &^= uint32(1) << i
|
||||||
|
return c.fn[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sqlt.mod.ExportedFunction(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sqlt *sqlite) putfn(name string, fn api.Function) {
|
||||||
|
c := &sqlt.funcs
|
||||||
|
p := unsafe.StringData(name)
|
||||||
|
i := bits.TrailingZeros32(^c.mask)
|
||||||
|
if i < 32 {
|
||||||
|
c.id[i] = p
|
||||||
|
c.fn[i] = fn
|
||||||
|
c.mask |= uint32(1) << i
|
||||||
|
} else {
|
||||||
|
c.id[0] = p
|
||||||
|
c.fn[0] = fn
|
||||||
|
c.mask = uint32(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sqlt *sqlite) call(name string, params ...uint64) uint64 {
|
||||||
|
copy(sqlt.stack[:], params)
|
||||||
|
fn := sqlt.getfn(name)
|
||||||
|
err := fn.CallWithStack(sqlt.ctx, sqlt.stack[:])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
sqlt.putfn(name, fn)
|
||||||
|
return sqlt.stack[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sqlt *sqlite) free(ptr uint32) {
|
||||||
|
if ptr == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sqlt.call("free", uint64(ptr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sqlt *sqlite) new(size uint64) uint32 {
|
||||||
|
if size > _MAX_ALLOCATION_SIZE {
|
||||||
|
panic(util.OOMErr)
|
||||||
|
}
|
||||||
|
ptr := uint32(sqlt.call("malloc", size))
|
||||||
|
if ptr == 0 && size != 0 {
|
||||||
|
panic(util.OOMErr)
|
||||||
|
}
|
||||||
|
return ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sqlt *sqlite) newBytes(b []byte) uint32 {
|
||||||
|
if (*[0]byte)(b) == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
ptr := sqlt.new(uint64(len(b)))
|
||||||
|
util.WriteBytes(sqlt.mod, ptr, b)
|
||||||
|
return ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sqlt *sqlite) newString(s string) uint32 {
|
||||||
|
ptr := sqlt.new(uint64(len(s) + 1))
|
||||||
|
util.WriteString(sqlt.mod, ptr, s)
|
||||||
|
return ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sqlt *sqlite) newArena(size uint64) arena {
|
||||||
|
// Ensure the arena's size is a multiple of 8.
|
||||||
|
size = (size + 7) &^ 7
|
||||||
|
return arena{
|
||||||
|
sqlt: sqlt,
|
||||||
|
size: uint32(size),
|
||||||
|
base: sqlt.new(size),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type arena struct {
|
||||||
|
sqlt *sqlite
|
||||||
|
ptrs []uint32
|
||||||
|
base uint32
|
||||||
|
next uint32
|
||||||
|
size uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *arena) free() {
|
||||||
|
if a.sqlt == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, ptr := range a.ptrs {
|
||||||
|
a.sqlt.free(ptr)
|
||||||
|
}
|
||||||
|
a.sqlt.free(a.base)
|
||||||
|
a.sqlt = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *arena) mark() (reset func()) {
|
||||||
|
ptrs := len(a.ptrs)
|
||||||
|
next := a.next
|
||||||
|
return func() {
|
||||||
|
for _, ptr := range a.ptrs[ptrs:] {
|
||||||
|
a.sqlt.free(ptr)
|
||||||
|
}
|
||||||
|
a.ptrs = a.ptrs[:ptrs]
|
||||||
|
a.next = next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *arena) new(size uint64) uint32 {
|
||||||
|
// Align the next address, to 4 or 8 bytes.
|
||||||
|
if size&7 != 0 {
|
||||||
|
a.next = (a.next + 3) &^ 3
|
||||||
|
} else {
|
||||||
|
a.next = (a.next + 7) &^ 7
|
||||||
|
}
|
||||||
|
if size <= uint64(a.size-a.next) {
|
||||||
|
ptr := a.base + a.next
|
||||||
|
a.next += uint32(size)
|
||||||
|
return ptr
|
||||||
|
}
|
||||||
|
ptr := a.sqlt.new(size)
|
||||||
|
a.ptrs = append(a.ptrs, ptr)
|
||||||
|
return ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *arena) bytes(b []byte) uint32 {
|
||||||
|
if (*[0]byte)(b) == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
ptr := a.new(uint64(len(b)))
|
||||||
|
util.WriteBytes(a.sqlt.mod, ptr, b)
|
||||||
|
return ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *arena) string(s string) uint32 {
|
||||||
|
ptr := a.new(uint64(len(s) + 1))
|
||||||
|
util.WriteString(a.sqlt.mod, ptr, s)
|
||||||
|
return ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
|
||||||
|
util.ExportFuncII(env, "go_progress_handler", progressCallback)
|
||||||
|
util.ExportFuncIIII(env, "go_busy_timeout", timeoutCallback)
|
||||||
|
util.ExportFuncIII(env, "go_busy_handler", busyCallback)
|
||||||
|
util.ExportFuncII(env, "go_commit_hook", commitCallback)
|
||||||
|
util.ExportFuncVI(env, "go_rollback_hook", rollbackCallback)
|
||||||
|
util.ExportFuncVIIIIJ(env, "go_update_hook", updateCallback)
|
||||||
|
util.ExportFuncIIIII(env, "go_wal_hook", walCallback)
|
||||||
|
util.ExportFuncIIIIII(env, "go_autovacuum_pages", autoVacuumCallback)
|
||||||
|
util.ExportFuncIIIIIII(env, "go_authorizer", authorizerCallback)
|
||||||
|
util.ExportFuncVIII(env, "go_log", logCallback)
|
||||||
|
util.ExportFuncVI(env, "go_destroy", destroyCallback)
|
||||||
|
util.ExportFuncVIIII(env, "go_func", funcCallback)
|
||||||
|
util.ExportFuncVIIIII(env, "go_step", stepCallback)
|
||||||
|
util.ExportFuncVIII(env, "go_final", finalCallback)
|
||||||
|
util.ExportFuncVII(env, "go_value", valueCallback)
|
||||||
|
util.ExportFuncVIIII(env, "go_inverse", inverseCallback)
|
||||||
|
util.ExportFuncVIIII(env, "go_collation_needed", collationCallback)
|
||||||
|
util.ExportFuncIIIIII(env, "go_compare", compareCallback)
|
||||||
|
util.ExportFuncIIIIII(env, "go_vtab_create", vtabModuleCallback(xCreate))
|
||||||
|
util.ExportFuncIIIIII(env, "go_vtab_connect", vtabModuleCallback(xConnect))
|
||||||
|
util.ExportFuncII(env, "go_vtab_disconnect", vtabDisconnectCallback)
|
||||||
|
util.ExportFuncII(env, "go_vtab_destroy", vtabDestroyCallback)
|
||||||
|
util.ExportFuncIII(env, "go_vtab_best_index", vtabBestIndexCallback)
|
||||||
|
util.ExportFuncIIIII(env, "go_vtab_update", vtabUpdateCallback)
|
||||||
|
util.ExportFuncIII(env, "go_vtab_rename", vtabRenameCallback)
|
||||||
|
util.ExportFuncIIIII(env, "go_vtab_find_function", vtabFindFuncCallback)
|
||||||
|
util.ExportFuncII(env, "go_vtab_begin", vtabBeginCallback)
|
||||||
|
util.ExportFuncII(env, "go_vtab_sync", vtabSyncCallback)
|
||||||
|
util.ExportFuncII(env, "go_vtab_commit", vtabCommitCallback)
|
||||||
|
util.ExportFuncII(env, "go_vtab_rollback", vtabRollbackCallback)
|
||||||
|
util.ExportFuncIII(env, "go_vtab_savepoint", vtabSavepointCallback)
|
||||||
|
util.ExportFuncIII(env, "go_vtab_release", vtabReleaseCallback)
|
||||||
|
util.ExportFuncIII(env, "go_vtab_rollback_to", vtabRollbackToCallback)
|
||||||
|
util.ExportFuncIIIIII(env, "go_vtab_integrity", vtabIntegrityCallback)
|
||||||
|
util.ExportFuncIII(env, "go_cur_open", cursorOpenCallback)
|
||||||
|
util.ExportFuncII(env, "go_cur_close", cursorCloseCallback)
|
||||||
|
util.ExportFuncIIIIII(env, "go_cur_filter", cursorFilterCallback)
|
||||||
|
util.ExportFuncII(env, "go_cur_next", cursorNextCallback)
|
||||||
|
util.ExportFuncII(env, "go_cur_eof", cursorEOFCallback)
|
||||||
|
util.ExportFuncIIII(env, "go_cur_column", cursorColumnCallback)
|
||||||
|
util.ExportFuncIII(env, "go_cur_rowid", cursorRowIDCallback)
|
||||||
|
return env
|
||||||
|
}
|
639
vendor/github.com/ncruces/go-sqlite3/stmt.go
generated
vendored
Normal file
639
vendor/github.com/ncruces/go-sqlite3/stmt.go
generated
vendored
Normal file
|
@ -0,0 +1,639 @@
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stmt is a prepared statement object.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/stmt.html
|
||||||
|
type Stmt struct {
|
||||||
|
c *Conn
|
||||||
|
err error
|
||||||
|
handle uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close destroys the prepared statement object.
|
||||||
|
//
|
||||||
|
// It is safe to close a nil, zero or closed Stmt.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/finalize.html
|
||||||
|
func (s *Stmt) Close() error {
|
||||||
|
if s == nil || s.handle == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r := s.c.call("sqlite3_finalize", uint64(s.handle))
|
||||||
|
|
||||||
|
s.handle = 0
|
||||||
|
return s.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conn returns the database connection to which the prepared statement belongs.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/db_handle.html
|
||||||
|
func (s *Stmt) Conn() *Conn {
|
||||||
|
return s.c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOnly returns true if and only if the statement
|
||||||
|
// makes no direct changes to the content of the database file.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/stmt_readonly.html
|
||||||
|
func (s *Stmt) ReadOnly() bool {
|
||||||
|
r := s.c.call("sqlite3_stmt_readonly", uint64(s.handle))
|
||||||
|
return r != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the prepared statement object.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/reset.html
|
||||||
|
func (s *Stmt) Reset() error {
|
||||||
|
r := s.c.call("sqlite3_reset", uint64(s.handle))
|
||||||
|
s.err = nil
|
||||||
|
return s.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Busy determines if a prepared statement has been reset.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/stmt_busy.html
|
||||||
|
func (s *Stmt) Busy() bool {
|
||||||
|
r := s.c.call("sqlite3_stmt_busy", uint64(s.handle))
|
||||||
|
return r != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step evaluates the SQL statement.
|
||||||
|
// If the SQL statement being executed returns any data,
|
||||||
|
// then true is returned each time a new row of data is ready for processing by the caller.
|
||||||
|
// The values may be accessed using the Column access functions.
|
||||||
|
// Step is called again to retrieve the next row of data.
|
||||||
|
// If an error has occurred, Step returns false;
|
||||||
|
// call [Stmt.Err] or [Stmt.Reset] to get the error.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/step.html
|
||||||
|
func (s *Stmt) Step() bool {
|
||||||
|
s.c.checkInterrupt()
|
||||||
|
r := s.c.call("sqlite3_step", uint64(s.handle))
|
||||||
|
switch r {
|
||||||
|
case _ROW:
|
||||||
|
s.err = nil
|
||||||
|
return true
|
||||||
|
case _DONE:
|
||||||
|
s.err = nil
|
||||||
|
default:
|
||||||
|
s.err = s.c.error(r)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err gets the last error occurred during [Stmt.Step].
|
||||||
|
// Err returns nil after [Stmt.Reset] is called.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/step.html
|
||||||
|
func (s *Stmt) Err() error {
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec is a convenience function that repeatedly calls [Stmt.Step] until it returns false,
|
||||||
|
// then calls [Stmt.Reset] to reset the statement and get any error that occurred.
|
||||||
|
func (s *Stmt) Exec() error {
|
||||||
|
for s.Step() {
|
||||||
|
}
|
||||||
|
return s.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status monitors the performance characteristics of prepared statements.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/stmt_status.html
|
||||||
|
func (s *Stmt) Status(op StmtStatus, reset bool) int {
|
||||||
|
if op > STMTSTATUS_FILTER_HIT && op != STMTSTATUS_MEMUSED {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var i uint64
|
||||||
|
if reset {
|
||||||
|
i = 1
|
||||||
|
}
|
||||||
|
r := s.c.call("sqlite3_stmt_status", uint64(s.handle),
|
||||||
|
uint64(op), i)
|
||||||
|
return int(int32(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBindings resets all bindings on the prepared statement.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/clear_bindings.html
|
||||||
|
func (s *Stmt) ClearBindings() error {
|
||||||
|
r := s.c.call("sqlite3_clear_bindings", uint64(s.handle))
|
||||||
|
return s.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindCount returns the number of SQL parameters in the prepared statement.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_parameter_count.html
|
||||||
|
func (s *Stmt) BindCount() int {
|
||||||
|
r := s.c.call("sqlite3_bind_parameter_count",
|
||||||
|
uint64(s.handle))
|
||||||
|
return int(int32(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindIndex returns the index of a parameter in the prepared statement
|
||||||
|
// given its name.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_parameter_index.html
|
||||||
|
func (s *Stmt) BindIndex(name string) int {
|
||||||
|
defer s.c.arena.mark()()
|
||||||
|
namePtr := s.c.arena.string(name)
|
||||||
|
r := s.c.call("sqlite3_bind_parameter_index",
|
||||||
|
uint64(s.handle), uint64(namePtr))
|
||||||
|
return int(int32(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindName returns the name of a parameter in the prepared statement.
|
||||||
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_parameter_name.html
|
||||||
|
func (s *Stmt) BindName(param int) string {
|
||||||
|
r := s.c.call("sqlite3_bind_parameter_name",
|
||||||
|
uint64(s.handle), uint64(param))
|
||||||
|
|
||||||
|
ptr := uint32(r)
|
||||||
|
if ptr == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return util.ReadString(s.c.mod, ptr, _MAX_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindBool binds a bool to the prepared statement.
|
||||||
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
// SQLite does not have a separate boolean storage class.
|
||||||
|
// Instead, boolean values are stored as integers 0 (false) and 1 (true).
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_blob.html
|
||||||
|
func (s *Stmt) BindBool(param int, value bool) error {
|
||||||
|
var i int64
|
||||||
|
if value {
|
||||||
|
i = 1
|
||||||
|
}
|
||||||
|
return s.BindInt64(param, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindInt binds an int to the prepared statement.
|
||||||
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_blob.html
|
||||||
|
func (s *Stmt) BindInt(param int, value int) error {
|
||||||
|
return s.BindInt64(param, int64(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindInt64 binds an int64 to the prepared statement.
|
||||||
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_blob.html
|
||||||
|
func (s *Stmt) BindInt64(param int, value int64) error {
|
||||||
|
r := s.c.call("sqlite3_bind_int64",
|
||||||
|
uint64(s.handle), uint64(param), uint64(value))
|
||||||
|
return s.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindFloat binds a float64 to the prepared statement.
|
||||||
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_blob.html
|
||||||
|
func (s *Stmt) BindFloat(param int, value float64) error {
|
||||||
|
r := s.c.call("sqlite3_bind_double",
|
||||||
|
uint64(s.handle), uint64(param), math.Float64bits(value))
|
||||||
|
return s.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindText binds a string to the prepared statement.
|
||||||
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_blob.html
|
||||||
|
func (s *Stmt) BindText(param int, value string) error {
|
||||||
|
if len(value) > _MAX_LENGTH {
|
||||||
|
return TOOBIG
|
||||||
|
}
|
||||||
|
ptr := s.c.newString(value)
|
||||||
|
r := s.c.call("sqlite3_bind_text64",
|
||||||
|
uint64(s.handle), uint64(param),
|
||||||
|
uint64(ptr), uint64(len(value)),
|
||||||
|
uint64(s.c.freer), _UTF8)
|
||||||
|
return s.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindRawText binds a []byte to the prepared statement as text.
|
||||||
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_blob.html
|
||||||
|
func (s *Stmt) BindRawText(param int, value []byte) error {
|
||||||
|
if len(value) > _MAX_LENGTH {
|
||||||
|
return TOOBIG
|
||||||
|
}
|
||||||
|
ptr := s.c.newBytes(value)
|
||||||
|
r := s.c.call("sqlite3_bind_text64",
|
||||||
|
uint64(s.handle), uint64(param),
|
||||||
|
uint64(ptr), uint64(len(value)),
|
||||||
|
uint64(s.c.freer), _UTF8)
|
||||||
|
return s.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindBlob binds a []byte to the prepared statement.
|
||||||
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
// Binding a nil slice is the same as calling [Stmt.BindNull].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_blob.html
|
||||||
|
func (s *Stmt) BindBlob(param int, value []byte) error {
|
||||||
|
if len(value) > _MAX_LENGTH {
|
||||||
|
return TOOBIG
|
||||||
|
}
|
||||||
|
ptr := s.c.newBytes(value)
|
||||||
|
r := s.c.call("sqlite3_bind_blob64",
|
||||||
|
uint64(s.handle), uint64(param),
|
||||||
|
uint64(ptr), uint64(len(value)),
|
||||||
|
uint64(s.c.freer))
|
||||||
|
return s.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindZeroBlob binds a zero-filled, length n BLOB to the prepared statement.
|
||||||
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_blob.html
|
||||||
|
func (s *Stmt) BindZeroBlob(param int, n int64) error {
|
||||||
|
r := s.c.call("sqlite3_bind_zeroblob64",
|
||||||
|
uint64(s.handle), uint64(param), uint64(n))
|
||||||
|
return s.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindNull binds a NULL to the prepared statement.
|
||||||
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_blob.html
|
||||||
|
func (s *Stmt) BindNull(param int) error {
|
||||||
|
r := s.c.call("sqlite3_bind_null",
|
||||||
|
uint64(s.handle), uint64(param))
|
||||||
|
return s.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindTime binds a [time.Time] to the prepared statement.
|
||||||
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_blob.html
|
||||||
|
func (s *Stmt) BindTime(param int, value time.Time, format TimeFormat) error {
|
||||||
|
if format == TimeFormatDefault {
|
||||||
|
return s.bindRFC3339Nano(param, value)
|
||||||
|
}
|
||||||
|
switch v := format.Encode(value).(type) {
|
||||||
|
case string:
|
||||||
|
s.BindText(param, v)
|
||||||
|
case int64:
|
||||||
|
s.BindInt64(param, v)
|
||||||
|
case float64:
|
||||||
|
s.BindFloat(param, v)
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stmt) bindRFC3339Nano(param int, value time.Time) error {
|
||||||
|
const maxlen = uint64(len(time.RFC3339Nano)) + 5
|
||||||
|
|
||||||
|
ptr := s.c.new(maxlen)
|
||||||
|
buf := util.View(s.c.mod, ptr, maxlen)
|
||||||
|
buf = value.AppendFormat(buf[:0], time.RFC3339Nano)
|
||||||
|
|
||||||
|
r := s.c.call("sqlite3_bind_text64",
|
||||||
|
uint64(s.handle), uint64(param),
|
||||||
|
uint64(ptr), uint64(len(buf)),
|
||||||
|
uint64(s.c.freer), _UTF8)
|
||||||
|
return s.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindPointer binds a NULL to the prepared statement, just like [Stmt.BindNull],
|
||||||
|
// but it also associates ptr with that NULL value such that it can be retrieved
|
||||||
|
// within an application-defined SQL function using [Value.Pointer].
|
||||||
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_blob.html
|
||||||
|
func (s *Stmt) BindPointer(param int, ptr any) error {
|
||||||
|
valPtr := util.AddHandle(s.c.ctx, ptr)
|
||||||
|
r := s.c.call("sqlite3_bind_pointer_go",
|
||||||
|
uint64(s.handle), uint64(param), uint64(valPtr))
|
||||||
|
return s.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindJSON binds the JSON encoding of value to the prepared statement.
|
||||||
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_blob.html
|
||||||
|
func (s *Stmt) BindJSON(param int, value any) error {
|
||||||
|
data, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.BindRawText(param, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindValue binds a copy of value to the prepared statement.
|
||||||
|
// The leftmost SQL parameter has an index of 1.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/bind_blob.html
|
||||||
|
func (s *Stmt) BindValue(param int, value Value) error {
|
||||||
|
if value.c != s.c {
|
||||||
|
return MISUSE
|
||||||
|
}
|
||||||
|
r := s.c.call("sqlite3_bind_value",
|
||||||
|
uint64(s.handle), uint64(param), uint64(value.handle))
|
||||||
|
return s.c.error(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnCount returns the number of columns in a result set.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_count.html
|
||||||
|
func (s *Stmt) ColumnCount() int {
|
||||||
|
r := s.c.call("sqlite3_column_count",
|
||||||
|
uint64(s.handle))
|
||||||
|
return int(int32(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnName returns the name of the result column.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_name.html
|
||||||
|
func (s *Stmt) ColumnName(col int) string {
|
||||||
|
r := s.c.call("sqlite3_column_name",
|
||||||
|
uint64(s.handle), uint64(col))
|
||||||
|
if r == 0 {
|
||||||
|
panic(util.OOMErr)
|
||||||
|
}
|
||||||
|
return util.ReadString(s.c.mod, uint32(r), _MAX_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnType returns the initial [Datatype] of the result column.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_blob.html
|
||||||
|
func (s *Stmt) ColumnType(col int) Datatype {
|
||||||
|
r := s.c.call("sqlite3_column_type",
|
||||||
|
uint64(s.handle), uint64(col))
|
||||||
|
return Datatype(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnDeclType returns the declared datatype of the result column.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_decltype.html
|
||||||
|
func (s *Stmt) ColumnDeclType(col int) string {
|
||||||
|
r := s.c.call("sqlite3_column_decltype",
|
||||||
|
uint64(s.handle), uint64(col))
|
||||||
|
if r == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return util.ReadString(s.c.mod, uint32(r), _MAX_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnDatabaseName returns the name of the database
|
||||||
|
// that is the origin of a particular result column.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_database_name.html
|
||||||
|
func (s *Stmt) ColumnDatabaseName(col int) string {
|
||||||
|
r := s.c.call("sqlite3_column_database_name",
|
||||||
|
uint64(s.handle), uint64(col))
|
||||||
|
if r == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return util.ReadString(s.c.mod, uint32(r), _MAX_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTableName returns the name of the table
|
||||||
|
// that is the origin of a particular result column.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_database_name.html
|
||||||
|
func (s *Stmt) ColumnTableName(col int) string {
|
||||||
|
r := s.c.call("sqlite3_column_table_name",
|
||||||
|
uint64(s.handle), uint64(col))
|
||||||
|
if r == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return util.ReadString(s.c.mod, uint32(r), _MAX_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnOriginName returns the name of the table column
|
||||||
|
// that is the origin of a particular result column.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_database_name.html
|
||||||
|
func (s *Stmt) ColumnOriginName(col int) string {
|
||||||
|
r := s.c.call("sqlite3_column_origin_name",
|
||||||
|
uint64(s.handle), uint64(col))
|
||||||
|
if r == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return util.ReadString(s.c.mod, uint32(r), _MAX_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnBool returns the value of the result column as a bool.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
// SQLite does not have a separate boolean storage class.
|
||||||
|
// Instead, boolean values are retrieved as integers,
|
||||||
|
// with 0 converted to false and any other value to true.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_blob.html
|
||||||
|
func (s *Stmt) ColumnBool(col int) bool {
|
||||||
|
return s.ColumnInt64(col) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnInt returns the value of the result column as an int.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_blob.html
|
||||||
|
func (s *Stmt) ColumnInt(col int) int {
|
||||||
|
return int(s.ColumnInt64(col))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnInt64 returns the value of the result column as an int64.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_blob.html
|
||||||
|
func (s *Stmt) ColumnInt64(col int) int64 {
|
||||||
|
r := s.c.call("sqlite3_column_int64",
|
||||||
|
uint64(s.handle), uint64(col))
|
||||||
|
return int64(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnFloat returns the value of the result column as a float64.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_blob.html
|
||||||
|
func (s *Stmt) ColumnFloat(col int) float64 {
|
||||||
|
r := s.c.call("sqlite3_column_double",
|
||||||
|
uint64(s.handle), uint64(col))
|
||||||
|
return math.Float64frombits(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnTime returns the value of the result column as a [time.Time].
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_blob.html
|
||||||
|
func (s *Stmt) ColumnTime(col int, format TimeFormat) time.Time {
|
||||||
|
var v any
|
||||||
|
switch s.ColumnType(col) {
|
||||||
|
case INTEGER:
|
||||||
|
v = s.ColumnInt64(col)
|
||||||
|
case FLOAT:
|
||||||
|
v = s.ColumnFloat(col)
|
||||||
|
case TEXT, BLOB:
|
||||||
|
v = s.ColumnText(col)
|
||||||
|
case NULL:
|
||||||
|
return time.Time{}
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
t, err := format.Decode(v)
|
||||||
|
if err != nil {
|
||||||
|
s.err = err
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnText returns the value of the result column as a string.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_blob.html
|
||||||
|
func (s *Stmt) ColumnText(col int) string {
|
||||||
|
return string(s.ColumnRawText(col))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnBlob appends to buf and returns
|
||||||
|
// the value of the result column as a []byte.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_blob.html
|
||||||
|
func (s *Stmt) ColumnBlob(col int, buf []byte) []byte {
|
||||||
|
return append(buf, s.ColumnRawBlob(col)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnRawText returns the value of the result column as a []byte.
|
||||||
|
// The []byte is owned by SQLite and may be invalidated by
|
||||||
|
// subsequent calls to [Stmt] methods.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_blob.html
|
||||||
|
func (s *Stmt) ColumnRawText(col int) []byte {
|
||||||
|
r := s.c.call("sqlite3_column_text",
|
||||||
|
uint64(s.handle), uint64(col))
|
||||||
|
return s.columnRawBytes(col, uint32(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnRawBlob returns the value of the result column as a []byte.
|
||||||
|
// The []byte is owned by SQLite and may be invalidated by
|
||||||
|
// subsequent calls to [Stmt] methods.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_blob.html
|
||||||
|
func (s *Stmt) ColumnRawBlob(col int) []byte {
|
||||||
|
r := s.c.call("sqlite3_column_blob",
|
||||||
|
uint64(s.handle), uint64(col))
|
||||||
|
return s.columnRawBytes(col, uint32(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stmt) columnRawBytes(col int, ptr uint32) []byte {
|
||||||
|
if ptr == 0 {
|
||||||
|
r := s.c.call("sqlite3_errcode", uint64(s.c.handle))
|
||||||
|
s.err = s.c.error(r)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r := s.c.call("sqlite3_column_bytes",
|
||||||
|
uint64(s.handle), uint64(col))
|
||||||
|
return util.View(s.c.mod, ptr, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnJSON parses the JSON-encoded value of the result column
|
||||||
|
// and stores it in the value pointed to by ptr.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_blob.html
|
||||||
|
func (s *Stmt) ColumnJSON(col int, ptr any) error {
|
||||||
|
var data []byte
|
||||||
|
switch s.ColumnType(col) {
|
||||||
|
case NULL:
|
||||||
|
data = append(data, "null"...)
|
||||||
|
case TEXT:
|
||||||
|
data = s.ColumnRawText(col)
|
||||||
|
case BLOB:
|
||||||
|
data = s.ColumnRawBlob(col)
|
||||||
|
case INTEGER:
|
||||||
|
data = strconv.AppendInt(nil, s.ColumnInt64(col), 10)
|
||||||
|
case FLOAT:
|
||||||
|
data = strconv.AppendFloat(nil, s.ColumnFloat(col), 'g', -1, 64)
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnValue returns the unprotected value of the result column.
|
||||||
|
// The leftmost column of the result set has the index 0.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/column_blob.html
|
||||||
|
func (s *Stmt) ColumnValue(col int) Value {
|
||||||
|
r := s.c.call("sqlite3_column_value",
|
||||||
|
uint64(s.handle), uint64(col))
|
||||||
|
return Value{
|
||||||
|
c: s.c,
|
||||||
|
unprot: true,
|
||||||
|
handle: uint32(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Columns populates result columns into the provided slice.
|
||||||
|
// The slice must have [Stmt.ColumnCount] length.
|
||||||
|
//
|
||||||
|
// [INTEGER] columns will be retrieved as int64 values,
|
||||||
|
// [FLOAT] as float64, [NULL] as nil,
|
||||||
|
// [TEXT] as string, and [BLOB] as []byte.
|
||||||
|
// Any []byte are owned by SQLite and may be invalidated by
|
||||||
|
// subsequent calls to [Stmt] methods.
|
||||||
|
func (s *Stmt) Columns(dest []any) error {
|
||||||
|
defer s.c.arena.mark()()
|
||||||
|
count := uint64(len(dest))
|
||||||
|
typePtr := s.c.arena.new(count)
|
||||||
|
dataPtr := s.c.arena.new(8 * count)
|
||||||
|
|
||||||
|
r := s.c.call("sqlite3_columns_go",
|
||||||
|
uint64(s.handle), count, uint64(typePtr), uint64(dataPtr))
|
||||||
|
if err := s.c.error(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
types := util.View(s.c.mod, typePtr, count)
|
||||||
|
for i := range dest {
|
||||||
|
switch types[i] {
|
||||||
|
case byte(INTEGER):
|
||||||
|
dest[i] = int64(util.ReadUint64(s.c.mod, dataPtr+8*uint32(i)))
|
||||||
|
continue
|
||||||
|
case byte(FLOAT):
|
||||||
|
dest[i] = util.ReadFloat64(s.c.mod, dataPtr+8*uint32(i))
|
||||||
|
continue
|
||||||
|
case byte(NULL):
|
||||||
|
dest[i] = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ptr := util.ReadUint32(s.c.mod, dataPtr+8*uint32(i)+0)
|
||||||
|
len := util.ReadUint32(s.c.mod, dataPtr+8*uint32(i)+4)
|
||||||
|
buf := util.View(s.c.mod, ptr, uint64(len))
|
||||||
|
if types[i] == byte(TEXT) {
|
||||||
|
dest[i] = string(buf)
|
||||||
|
} else {
|
||||||
|
dest[i] = buf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
354
vendor/github.com/ncruces/go-sqlite3/time.go
generated
vendored
Normal file
354
vendor/github.com/ncruces/go-sqlite3/time.go
generated
vendored
Normal file
|
@ -0,0 +1,354 @@
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
"github.com/ncruces/julianday"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TimeFormat specifies how to encode/decode time values.
|
||||||
|
//
|
||||||
|
// See the documentation for the [TimeFormatDefault] constant
|
||||||
|
// for formats recognized by SQLite.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/lang_datefunc.html
|
||||||
|
type TimeFormat string
|
||||||
|
|
||||||
|
// TimeFormats recognized by SQLite to encode/decode time values.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/lang_datefunc.html#time_values
|
||||||
|
const (
|
||||||
|
TimeFormatDefault TimeFormat = "" // time.RFC3339Nano
|
||||||
|
|
||||||
|
// Text formats
|
||||||
|
TimeFormat1 TimeFormat = "2006-01-02"
|
||||||
|
TimeFormat2 TimeFormat = "2006-01-02 15:04"
|
||||||
|
TimeFormat3 TimeFormat = "2006-01-02 15:04:05"
|
||||||
|
TimeFormat4 TimeFormat = "2006-01-02 15:04:05.000"
|
||||||
|
TimeFormat5 TimeFormat = "2006-01-02T15:04"
|
||||||
|
TimeFormat6 TimeFormat = "2006-01-02T15:04:05"
|
||||||
|
TimeFormat7 TimeFormat = "2006-01-02T15:04:05.000"
|
||||||
|
TimeFormat8 TimeFormat = "15:04"
|
||||||
|
TimeFormat9 TimeFormat = "15:04:05"
|
||||||
|
TimeFormat10 TimeFormat = "15:04:05.000"
|
||||||
|
|
||||||
|
TimeFormat2TZ = TimeFormat2 + "Z07:00"
|
||||||
|
TimeFormat3TZ = TimeFormat3 + "Z07:00"
|
||||||
|
TimeFormat4TZ = TimeFormat4 + "Z07:00"
|
||||||
|
TimeFormat5TZ = TimeFormat5 + "Z07:00"
|
||||||
|
TimeFormat6TZ = TimeFormat6 + "Z07:00"
|
||||||
|
TimeFormat7TZ = TimeFormat7 + "Z07:00"
|
||||||
|
TimeFormat8TZ = TimeFormat8 + "Z07:00"
|
||||||
|
TimeFormat9TZ = TimeFormat9 + "Z07:00"
|
||||||
|
TimeFormat10TZ = TimeFormat10 + "Z07:00"
|
||||||
|
|
||||||
|
// Numeric formats
|
||||||
|
TimeFormatJulianDay TimeFormat = "julianday"
|
||||||
|
TimeFormatUnix TimeFormat = "unixepoch"
|
||||||
|
TimeFormatUnixFrac TimeFormat = "unixepoch_frac"
|
||||||
|
TimeFormatUnixMilli TimeFormat = "unixepoch_milli" // not an SQLite format
|
||||||
|
TimeFormatUnixMicro TimeFormat = "unixepoch_micro" // not an SQLite format
|
||||||
|
TimeFormatUnixNano TimeFormat = "unixepoch_nano" // not an SQLite format
|
||||||
|
|
||||||
|
// Auto
|
||||||
|
TimeFormatAuto TimeFormat = "auto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encode encodes a time value using this format.
|
||||||
|
//
|
||||||
|
// [TimeFormatDefault] and [TimeFormatAuto] encode using [time.RFC3339Nano],
|
||||||
|
// with nanosecond accuracy, and preserving any timezone offset.
|
||||||
|
//
|
||||||
|
// This is the format used by the [database/sql] driver:
|
||||||
|
// [database/sql.Row.Scan] will decode as [time.Time]
|
||||||
|
// values encoded with [time.RFC3339Nano].
|
||||||
|
//
|
||||||
|
// Time values encoded with [time.RFC3339Nano] cannot be sorted as strings
|
||||||
|
// to produce a time-ordered sequence.
|
||||||
|
//
|
||||||
|
// Assuming that the time zones of the time values are the same (e.g., all in UTC),
|
||||||
|
// and expressed using the same string (e.g., all "Z" or all "+00:00"),
|
||||||
|
// use the TIME [collating sequence] to produce a time-ordered sequence.
|
||||||
|
//
|
||||||
|
// Otherwise, use [TimeFormat7] for time-ordered encoding.
|
||||||
|
//
|
||||||
|
// Formats [TimeFormat1] through [TimeFormat10]
|
||||||
|
// convert time values to UTC before encoding.
|
||||||
|
//
|
||||||
|
// Returns a string for the text formats,
|
||||||
|
// a float64 for [TimeFormatJulianDay] and [TimeFormatUnixFrac],
|
||||||
|
// or an int64 for the other numeric formats.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/lang_datefunc.html
|
||||||
|
//
|
||||||
|
// [collating sequence]: https://sqlite.org/datatype3.html#collating_sequences
|
||||||
|
func (f TimeFormat) Encode(t time.Time) any {
|
||||||
|
switch f {
|
||||||
|
// Numeric formats
|
||||||
|
case TimeFormatJulianDay:
|
||||||
|
return julianday.Float(t)
|
||||||
|
case TimeFormatUnix:
|
||||||
|
return t.Unix()
|
||||||
|
case TimeFormatUnixFrac:
|
||||||
|
return float64(t.Unix()) + float64(t.Nanosecond())*1e-9
|
||||||
|
case TimeFormatUnixMilli:
|
||||||
|
return t.UnixMilli()
|
||||||
|
case TimeFormatUnixMicro:
|
||||||
|
return t.UnixMicro()
|
||||||
|
case TimeFormatUnixNano:
|
||||||
|
return t.UnixNano()
|
||||||
|
// Special formats.
|
||||||
|
case TimeFormatDefault, TimeFormatAuto:
|
||||||
|
f = time.RFC3339Nano
|
||||||
|
// SQLite assumes UTC if unspecified.
|
||||||
|
case
|
||||||
|
TimeFormat1, TimeFormat2,
|
||||||
|
TimeFormat3, TimeFormat4,
|
||||||
|
TimeFormat5, TimeFormat6,
|
||||||
|
TimeFormat7, TimeFormat8,
|
||||||
|
TimeFormat9, TimeFormat10:
|
||||||
|
t = t.UTC()
|
||||||
|
}
|
||||||
|
return t.Format(string(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode decodes a time value using this format.
|
||||||
|
//
|
||||||
|
// The time value can be a string, an int64, or a float64.
|
||||||
|
//
|
||||||
|
// Formats [TimeFormat8] through [TimeFormat10]
|
||||||
|
// (and [TimeFormat8TZ] through [TimeFormat10TZ])
|
||||||
|
// assume a date of 2000-01-01.
|
||||||
|
//
|
||||||
|
// The timezone indicator and fractional seconds are always optional
|
||||||
|
// for formats [TimeFormat2] through [TimeFormat10]
|
||||||
|
// (and [TimeFormat2TZ] through [TimeFormat10TZ]).
|
||||||
|
//
|
||||||
|
// [TimeFormatAuto] implements (and extends) the SQLite auto modifier.
|
||||||
|
// Julian day numbers are safe to use for historical dates,
|
||||||
|
// from 4712BC through 9999AD.
|
||||||
|
// Unix timestamps (expressed in seconds, milliseconds, microseconds, or nanoseconds)
|
||||||
|
// are safe to use for current events, from at least 1980 through at least 2260.
|
||||||
|
// Unix timestamps before 1980 and after 9999 may be misinterpreted as julian day numbers,
|
||||||
|
// or have the wrong time unit.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/lang_datefunc.html
|
||||||
|
func (f TimeFormat) Decode(v any) (time.Time, error) {
|
||||||
|
switch f {
|
||||||
|
// Numeric formats.
|
||||||
|
case TimeFormatJulianDay:
|
||||||
|
switch v := v.(type) {
|
||||||
|
case string:
|
||||||
|
return julianday.Parse(v)
|
||||||
|
case float64:
|
||||||
|
return julianday.FloatTime(v), nil
|
||||||
|
case int64:
|
||||||
|
return julianday.Time(v, 0), nil
|
||||||
|
default:
|
||||||
|
return time.Time{}, util.TimeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
case TimeFormatUnix, TimeFormatUnixFrac:
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
f, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
v = f
|
||||||
|
}
|
||||||
|
switch v := v.(type) {
|
||||||
|
case float64:
|
||||||
|
sec, frac := math.Modf(v)
|
||||||
|
nsec := math.Floor(frac * 1e9)
|
||||||
|
return time.Unix(int64(sec), int64(nsec)).UTC(), nil
|
||||||
|
case int64:
|
||||||
|
return time.Unix(v, 0).UTC(), nil
|
||||||
|
default:
|
||||||
|
return time.Time{}, util.TimeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
case TimeFormatUnixMilli:
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
i, err := strconv.ParseInt(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
v = i
|
||||||
|
}
|
||||||
|
switch v := v.(type) {
|
||||||
|
case float64:
|
||||||
|
return time.UnixMilli(int64(math.Floor(v))).UTC(), nil
|
||||||
|
case int64:
|
||||||
|
return time.UnixMilli(v).UTC(), nil
|
||||||
|
default:
|
||||||
|
return time.Time{}, util.TimeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
case TimeFormatUnixMicro:
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
i, err := strconv.ParseInt(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
v = i
|
||||||
|
}
|
||||||
|
switch v := v.(type) {
|
||||||
|
case float64:
|
||||||
|
return time.UnixMicro(int64(math.Floor(v))).UTC(), nil
|
||||||
|
case int64:
|
||||||
|
return time.UnixMicro(v).UTC(), nil
|
||||||
|
default:
|
||||||
|
return time.Time{}, util.TimeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
case TimeFormatUnixNano:
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
i, err := strconv.ParseInt(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, util.TimeErr
|
||||||
|
}
|
||||||
|
v = i
|
||||||
|
}
|
||||||
|
switch v := v.(type) {
|
||||||
|
case float64:
|
||||||
|
return time.Unix(0, int64(math.Floor(v))).UTC(), nil
|
||||||
|
case int64:
|
||||||
|
return time.Unix(0, v).UTC(), nil
|
||||||
|
default:
|
||||||
|
return time.Time{}, util.TimeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special formats.
|
||||||
|
case TimeFormatAuto:
|
||||||
|
switch s := v.(type) {
|
||||||
|
case string:
|
||||||
|
i, err := strconv.ParseInt(s, 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
v = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
f, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err == nil {
|
||||||
|
v = f
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
dates := []TimeFormat{
|
||||||
|
TimeFormat9, TimeFormat8,
|
||||||
|
TimeFormat6, TimeFormat5,
|
||||||
|
TimeFormat3, TimeFormat2, TimeFormat1,
|
||||||
|
}
|
||||||
|
for _, f := range dates {
|
||||||
|
t, err := f.Decode(s)
|
||||||
|
if err == nil {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch v := v.(type) {
|
||||||
|
case float64:
|
||||||
|
if 0 <= v && v < 5373484.5 {
|
||||||
|
return TimeFormatJulianDay.Decode(v)
|
||||||
|
}
|
||||||
|
if v < 253402300800 {
|
||||||
|
return TimeFormatUnixFrac.Decode(v)
|
||||||
|
}
|
||||||
|
if v < 253402300800_000 {
|
||||||
|
return TimeFormatUnixMilli.Decode(v)
|
||||||
|
}
|
||||||
|
if v < 253402300800_000000 {
|
||||||
|
return TimeFormatUnixMicro.Decode(v)
|
||||||
|
}
|
||||||
|
return TimeFormatUnixNano.Decode(v)
|
||||||
|
case int64:
|
||||||
|
if 0 <= v && v < 5373485 {
|
||||||
|
return TimeFormatJulianDay.Decode(v)
|
||||||
|
}
|
||||||
|
if v < 253402300800 {
|
||||||
|
return TimeFormatUnixFrac.Decode(v)
|
||||||
|
}
|
||||||
|
if v < 253402300800_000 {
|
||||||
|
return TimeFormatUnixMilli.Decode(v)
|
||||||
|
}
|
||||||
|
if v < 253402300800_000000 {
|
||||||
|
return TimeFormatUnixMicro.Decode(v)
|
||||||
|
}
|
||||||
|
return TimeFormatUnixNano.Decode(v)
|
||||||
|
default:
|
||||||
|
return time.Time{}, util.TimeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
case
|
||||||
|
TimeFormat2, TimeFormat2TZ,
|
||||||
|
TimeFormat3, TimeFormat3TZ,
|
||||||
|
TimeFormat4, TimeFormat4TZ,
|
||||||
|
TimeFormat5, TimeFormat5TZ,
|
||||||
|
TimeFormat6, TimeFormat6TZ,
|
||||||
|
TimeFormat7, TimeFormat7TZ:
|
||||||
|
s, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return time.Time{}, util.TimeErr
|
||||||
|
}
|
||||||
|
return f.parseRelaxed(s)
|
||||||
|
|
||||||
|
case
|
||||||
|
TimeFormat8, TimeFormat8TZ,
|
||||||
|
TimeFormat9, TimeFormat9TZ,
|
||||||
|
TimeFormat10, TimeFormat10TZ:
|
||||||
|
s, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return time.Time{}, util.TimeErr
|
||||||
|
}
|
||||||
|
t, err := f.parseRelaxed(s)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
return t.AddDate(2000, 0, 0), nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
s, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return time.Time{}, util.TimeErr
|
||||||
|
}
|
||||||
|
if f == "" {
|
||||||
|
f = time.RFC3339Nano
|
||||||
|
}
|
||||||
|
return time.Parse(string(f), s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f TimeFormat) parseRelaxed(s string) (time.Time, error) {
|
||||||
|
fs := string(f)
|
||||||
|
fs = strings.TrimSuffix(fs, "Z07:00")
|
||||||
|
fs = strings.TrimSuffix(fs, ".000")
|
||||||
|
t, err := time.Parse(fs+"Z07:00", s)
|
||||||
|
if err != nil {
|
||||||
|
return time.Parse(fs, s)
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scanner returns a [database/sql.Scanner] that can be used as an argument to
|
||||||
|
// [database/sql.Row.Scan] and similar methods to
|
||||||
|
// decode a time value into dest using this format.
|
||||||
|
func (f TimeFormat) Scanner(dest *time.Time) interface{ Scan(any) error } {
|
||||||
|
return timeScanner{dest, f}
|
||||||
|
}
|
||||||
|
|
||||||
|
type timeScanner struct {
|
||||||
|
*time.Time
|
||||||
|
TimeFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s timeScanner) Scan(src any) error {
|
||||||
|
var ok bool
|
||||||
|
var err error
|
||||||
|
if *s.Time, ok = src.(time.Time); !ok {
|
||||||
|
*s.Time, err = s.Decode(src)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
294
vendor/github.com/ncruces/go-sqlite3/txn.go
generated
vendored
Normal file
294
vendor/github.com/ncruces/go-sqlite3/txn.go
generated
vendored
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Txn is an in-progress database transaction.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/lang_transaction.html
|
||||||
|
type Txn struct {
|
||||||
|
c *Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin starts a deferred transaction.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/lang_transaction.html
|
||||||
|
func (c *Conn) Begin() Txn {
|
||||||
|
// BEGIN even if interrupted.
|
||||||
|
err := c.txnExecInterrupted(`BEGIN DEFERRED`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return Txn{c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeginImmediate starts an immediate transaction.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/lang_transaction.html
|
||||||
|
func (c *Conn) BeginImmediate() (Txn, error) {
|
||||||
|
err := c.Exec(`BEGIN IMMEDIATE`)
|
||||||
|
if err != nil {
|
||||||
|
return Txn{}, err
|
||||||
|
}
|
||||||
|
return Txn{c}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeginExclusive starts an exclusive transaction.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/lang_transaction.html
|
||||||
|
func (c *Conn) BeginExclusive() (Txn, error) {
|
||||||
|
err := c.Exec(`BEGIN EXCLUSIVE`)
|
||||||
|
if err != nil {
|
||||||
|
return Txn{}, err
|
||||||
|
}
|
||||||
|
return Txn{c}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// End calls either [Txn.Commit] or [Txn.Rollback]
|
||||||
|
// depending on whether *error points to a nil or non-nil error.
|
||||||
|
//
|
||||||
|
// This is meant to be deferred:
|
||||||
|
//
|
||||||
|
// func doWork(db *sqlite3.Conn) (err error) {
|
||||||
|
// tx := db.Begin()
|
||||||
|
// defer tx.End(&err)
|
||||||
|
//
|
||||||
|
// // ... do work in the transaction
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// https://sqlite.org/lang_transaction.html
|
||||||
|
func (tx Txn) End(errp *error) {
|
||||||
|
recovered := recover()
|
||||||
|
if recovered != nil {
|
||||||
|
defer panic(recovered)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *errp == nil && recovered == nil {
|
||||||
|
// Success path.
|
||||||
|
if tx.c.GetAutocommit() { // There is nothing to commit.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*errp = tx.Commit()
|
||||||
|
if *errp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Fall through to the error path.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error path.
|
||||||
|
if tx.c.GetAutocommit() { // There is nothing to rollback.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := tx.Rollback()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit commits the transaction.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/lang_transaction.html
|
||||||
|
func (tx Txn) Commit() error {
|
||||||
|
return tx.c.Exec(`COMMIT`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rollback rolls back the transaction,
|
||||||
|
// even if the connection has been interrupted.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/lang_transaction.html
|
||||||
|
func (tx Txn) Rollback() error {
|
||||||
|
return tx.c.txnExecInterrupted(`ROLLBACK`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Savepoint is a marker within a transaction
|
||||||
|
// that allows for partial rollback.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/lang_savepoint.html
|
||||||
|
type Savepoint struct {
|
||||||
|
c *Conn
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Savepoint establishes a new transaction savepoint.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/lang_savepoint.html
|
||||||
|
func (c *Conn) Savepoint() Savepoint {
|
||||||
|
// Names can be reused; this makes catching bugs more likely.
|
||||||
|
name := saveptName() + "_" + strconv.Itoa(int(rand.Int31()))
|
||||||
|
|
||||||
|
err := c.txnExecInterrupted(fmt.Sprintf("SAVEPOINT %q;", name))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return Savepoint{c: c, name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveptName() (name string) {
|
||||||
|
defer func() {
|
||||||
|
if name == "" {
|
||||||
|
name = "sqlite3.Savepoint"
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var pc [8]uintptr
|
||||||
|
n := runtime.Callers(3, pc[:])
|
||||||
|
if n <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
frames := runtime.CallersFrames(pc[:n])
|
||||||
|
frame, more := frames.Next()
|
||||||
|
for more && (strings.HasPrefix(frame.Function, "database/sql.") ||
|
||||||
|
strings.HasPrefix(frame.Function, "github.com/ncruces/go-sqlite3/driver.")) {
|
||||||
|
frame, more = frames.Next()
|
||||||
|
}
|
||||||
|
return frame.Function
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release releases the savepoint rolling back any changes
|
||||||
|
// if *error points to a non-nil error.
|
||||||
|
//
|
||||||
|
// This is meant to be deferred:
|
||||||
|
//
|
||||||
|
// func doWork(db *sqlite3.Conn) (err error) {
|
||||||
|
// savept := db.Savepoint()
|
||||||
|
// defer savept.Release(&err)
|
||||||
|
//
|
||||||
|
// // ... do work in the transaction
|
||||||
|
// }
|
||||||
|
func (s Savepoint) Release(errp *error) {
|
||||||
|
recovered := recover()
|
||||||
|
if recovered != nil {
|
||||||
|
defer panic(recovered)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *errp == nil && recovered == nil {
|
||||||
|
// Success path.
|
||||||
|
if s.c.GetAutocommit() { // There is nothing to commit.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*errp = s.c.Exec(fmt.Sprintf("RELEASE %q;", s.name))
|
||||||
|
if *errp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Fall through to the error path.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error path.
|
||||||
|
if s.c.GetAutocommit() { // There is nothing to rollback.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// ROLLBACK and RELEASE even if interrupted.
|
||||||
|
err := s.c.txnExecInterrupted(fmt.Sprintf(`
|
||||||
|
ROLLBACK TO %[1]q;
|
||||||
|
RELEASE %[1]q;
|
||||||
|
`, s.name))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rollback rolls the transaction back to the savepoint,
|
||||||
|
// even if the connection has been interrupted.
|
||||||
|
// Rollback does not release the savepoint.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/lang_transaction.html
|
||||||
|
func (s Savepoint) Rollback() error {
|
||||||
|
// ROLLBACK even if interrupted.
|
||||||
|
return s.c.txnExecInterrupted(fmt.Sprintf("ROLLBACK TO %q;", s.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) txnExecInterrupted(sql string) error {
|
||||||
|
err := c.Exec(sql)
|
||||||
|
if errors.Is(err, INTERRUPT) {
|
||||||
|
old := c.SetInterrupt(context.Background())
|
||||||
|
defer c.SetInterrupt(old)
|
||||||
|
err = c.Exec(sql)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxnState starts a deferred transaction.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/txn_state.html
|
||||||
|
func (c *Conn) TxnState(schema string) TxnState {
|
||||||
|
var ptr uint32
|
||||||
|
if schema != "" {
|
||||||
|
defer c.arena.mark()()
|
||||||
|
ptr = c.arena.string(schema)
|
||||||
|
}
|
||||||
|
r := c.call("sqlite3_txn_state", uint64(c.handle), uint64(ptr))
|
||||||
|
return TxnState(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitHook registers a callback function to be invoked
|
||||||
|
// whenever a transaction is committed.
|
||||||
|
// Return true to allow the commit operation to continue normally.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/commit_hook.html
|
||||||
|
func (c *Conn) CommitHook(cb func() (ok bool)) {
|
||||||
|
var enable uint64
|
||||||
|
if cb != nil {
|
||||||
|
enable = 1
|
||||||
|
}
|
||||||
|
c.call("sqlite3_commit_hook_go", uint64(c.handle), enable)
|
||||||
|
c.commit = cb
|
||||||
|
}
|
||||||
|
|
||||||
|
// RollbackHook registers a callback function to be invoked
|
||||||
|
// whenever a transaction is rolled back.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/commit_hook.html
|
||||||
|
func (c *Conn) RollbackHook(cb func()) {
|
||||||
|
var enable uint64
|
||||||
|
if cb != nil {
|
||||||
|
enable = 1
|
||||||
|
}
|
||||||
|
c.call("sqlite3_rollback_hook_go", uint64(c.handle), enable)
|
||||||
|
c.rollback = cb
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateHook registers a callback function to be invoked
|
||||||
|
// whenever a row is updated, inserted or deleted in a rowid table.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/update_hook.html
|
||||||
|
func (c *Conn) UpdateHook(cb func(action AuthorizerActionCode, schema, table string, rowid int64)) {
|
||||||
|
var enable uint64
|
||||||
|
if cb != nil {
|
||||||
|
enable = 1
|
||||||
|
}
|
||||||
|
c.call("sqlite3_update_hook_go", uint64(c.handle), enable)
|
||||||
|
c.update = cb
|
||||||
|
}
|
||||||
|
|
||||||
|
func commitCallback(ctx context.Context, mod api.Module, pDB uint32) (rollback uint32) {
|
||||||
|
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.commit != nil {
|
||||||
|
if !c.commit() {
|
||||||
|
rollback = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rollback
|
||||||
|
}
|
||||||
|
|
||||||
|
func rollbackCallback(ctx context.Context, mod api.Module, pDB uint32) {
|
||||||
|
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.rollback != nil {
|
||||||
|
c.rollback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zSchema, zTabName uint32, rowid uint64) {
|
||||||
|
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.update != nil {
|
||||||
|
schema := util.ReadString(mod, zSchema, _MAX_NAME)
|
||||||
|
table := util.ReadString(mod, zTabName, _MAX_NAME)
|
||||||
|
c.update(action, schema, table, int64(rowid))
|
||||||
|
}
|
||||||
|
}
|
16
vendor/github.com/ncruces/go-sqlite3/util/osutil/open.go
generated
vendored
Normal file
16
vendor/github.com/ncruces/go-sqlite3/util/osutil/open.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package osutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenFile behaves the same as [os.OpenFile],
|
||||||
|
// except on Windows it sets [syscall.FILE_SHARE_DELETE].
|
||||||
|
//
|
||||||
|
// See: https://go.dev/issue/32088#issuecomment-502850674
|
||||||
|
func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
|
||||||
|
return os.OpenFile(name, flag, perm)
|
||||||
|
}
|
112
vendor/github.com/ncruces/go-sqlite3/util/osutil/open_windows.go
generated
vendored
Normal file
112
vendor/github.com/ncruces/go-sqlite3/util/osutil/open_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package osutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
. "syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenFile behaves the same as [os.OpenFile],
|
||||||
|
// except on Windows it sets [syscall.FILE_SHARE_DELETE].
|
||||||
|
//
|
||||||
|
// See: https://go.dev/issue/32088#issuecomment-502850674
|
||||||
|
func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
|
||||||
|
if name == "" {
|
||||||
|
return nil, &os.PathError{Op: "open", Path: name, Err: ENOENT}
|
||||||
|
}
|
||||||
|
r, e := syscallOpen(name, flag, uint32(perm.Perm()))
|
||||||
|
if e != nil {
|
||||||
|
return nil, &os.PathError{Op: "open", Path: name, Err: e}
|
||||||
|
}
|
||||||
|
return os.NewFile(uintptr(r), name), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// syscallOpen is a copy of [syscall.Open]
|
||||||
|
// that uses [syscall.FILE_SHARE_DELETE].
|
||||||
|
//
|
||||||
|
// https://go.dev/src/syscall/syscall_windows.go
|
||||||
|
func syscallOpen(path string, mode int, perm uint32) (fd Handle, err error) {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return InvalidHandle, ERROR_FILE_NOT_FOUND
|
||||||
|
}
|
||||||
|
pathp, err := UTF16PtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return InvalidHandle, err
|
||||||
|
}
|
||||||
|
var access uint32
|
||||||
|
switch mode & (O_RDONLY | O_WRONLY | O_RDWR) {
|
||||||
|
case O_RDONLY:
|
||||||
|
access = GENERIC_READ
|
||||||
|
case O_WRONLY:
|
||||||
|
access = GENERIC_WRITE
|
||||||
|
case O_RDWR:
|
||||||
|
access = GENERIC_READ | GENERIC_WRITE
|
||||||
|
}
|
||||||
|
if mode&O_CREAT != 0 {
|
||||||
|
access |= GENERIC_WRITE
|
||||||
|
}
|
||||||
|
if mode&O_APPEND != 0 {
|
||||||
|
access &^= GENERIC_WRITE
|
||||||
|
access |= FILE_APPEND_DATA
|
||||||
|
}
|
||||||
|
sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
|
||||||
|
var sa *SecurityAttributes
|
||||||
|
if mode&O_CLOEXEC == 0 {
|
||||||
|
sa = makeInheritSa()
|
||||||
|
}
|
||||||
|
var createmode uint32
|
||||||
|
switch {
|
||||||
|
case mode&(O_CREAT|O_EXCL) == (O_CREAT | O_EXCL):
|
||||||
|
createmode = CREATE_NEW
|
||||||
|
case mode&(O_CREAT|O_TRUNC) == (O_CREAT | O_TRUNC):
|
||||||
|
createmode = CREATE_ALWAYS
|
||||||
|
case mode&O_CREAT == O_CREAT:
|
||||||
|
createmode = OPEN_ALWAYS
|
||||||
|
case mode&O_TRUNC == O_TRUNC:
|
||||||
|
createmode = TRUNCATE_EXISTING
|
||||||
|
default:
|
||||||
|
createmode = OPEN_EXISTING
|
||||||
|
}
|
||||||
|
var attrs uint32 = FILE_ATTRIBUTE_NORMAL
|
||||||
|
if perm&S_IWRITE == 0 {
|
||||||
|
attrs = FILE_ATTRIBUTE_READONLY
|
||||||
|
if createmode == CREATE_ALWAYS {
|
||||||
|
const _ERROR_BAD_NETPATH = Errno(53)
|
||||||
|
// We have been asked to create a read-only file.
|
||||||
|
// If the file already exists, the semantics of
|
||||||
|
// the Unix open system call is to preserve the
|
||||||
|
// existing permissions. If we pass CREATE_ALWAYS
|
||||||
|
// and FILE_ATTRIBUTE_READONLY to CreateFile,
|
||||||
|
// and the file already exists, CreateFile will
|
||||||
|
// change the file permissions.
|
||||||
|
// Avoid that to preserve the Unix semantics.
|
||||||
|
h, e := CreateFile(pathp, access, sharemode, sa, TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
|
||||||
|
switch e {
|
||||||
|
case ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, ERROR_PATH_NOT_FOUND:
|
||||||
|
// File does not exist. These are the same
|
||||||
|
// errors as Errno.Is checks for ErrNotExist.
|
||||||
|
// Carry on to create the file.
|
||||||
|
default:
|
||||||
|
// Success or some different error.
|
||||||
|
return h, e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if createmode == OPEN_EXISTING && access == GENERIC_READ {
|
||||||
|
// Necessary for opening directory handles.
|
||||||
|
attrs |= FILE_FLAG_BACKUP_SEMANTICS
|
||||||
|
}
|
||||||
|
if mode&O_SYNC != 0 {
|
||||||
|
const _FILE_FLAG_WRITE_THROUGH = 0x80000000
|
||||||
|
attrs |= _FILE_FLAG_WRITE_THROUGH
|
||||||
|
}
|
||||||
|
return CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeInheritSa() *SecurityAttributes {
|
||||||
|
var sa SecurityAttributes
|
||||||
|
sa.Length = uint32(unsafe.Sizeof(sa))
|
||||||
|
sa.InheritHandle = 1
|
||||||
|
return &sa
|
||||||
|
}
|
33
vendor/github.com/ncruces/go-sqlite3/util/osutil/osfs.go
generated
vendored
Normal file
33
vendor/github.com/ncruces/go-sqlite3/util/osutil/osfs.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package osutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FS implements [fs.FS], [fs.StatFS], and [fs.ReadFileFS]
|
||||||
|
// using package [os].
|
||||||
|
//
|
||||||
|
// This filesystem does not respect [fs.ValidPath] rules,
|
||||||
|
// and fails [testing/fstest.TestFS]!
|
||||||
|
//
|
||||||
|
// Still, it can be a useful tool to unify implementations
|
||||||
|
// that can access either the [os] filesystem or an [fs.FS].
|
||||||
|
// It's OK to use this to open files, but you should avoid
|
||||||
|
// opening directories, resolving paths, or walking the file system.
|
||||||
|
type FS struct{}
|
||||||
|
|
||||||
|
// Open implements [fs.FS].
|
||||||
|
func (FS) Open(name string) (fs.File, error) {
|
||||||
|
return OpenFile(name, os.O_RDONLY, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFileFS implements [fs.StatFS].
|
||||||
|
func (FS) Stat(name string) (fs.FileInfo, error) {
|
||||||
|
return os.Stat(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFile implements [fs.ReadFileFS].
|
||||||
|
func (FS) ReadFile(name string) ([]byte, error) {
|
||||||
|
return os.ReadFile(name)
|
||||||
|
}
|
2
vendor/github.com/ncruces/go-sqlite3/util/osutil/osutil.go
generated
vendored
Normal file
2
vendor/github.com/ncruces/go-sqlite3/util/osutil/osutil.go
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Package osutil implements operating system utility functions.
|
||||||
|
package osutil
|
236
vendor/github.com/ncruces/go-sqlite3/value.go
generated
vendored
Normal file
236
vendor/github.com/ncruces/go-sqlite3/value.go
generated
vendored
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Value is any value that can be stored in a database table.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/value.html
|
||||||
|
type Value struct {
|
||||||
|
c *Conn
|
||||||
|
handle uint32
|
||||||
|
unprot bool
|
||||||
|
copied bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Value) protected() uint64 {
|
||||||
|
if v.unprot {
|
||||||
|
panic(util.ValueErr)
|
||||||
|
}
|
||||||
|
return uint64(v.handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dup makes a copy of the SQL value and returns a pointer to that copy.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/value_dup.html
|
||||||
|
func (v Value) Dup() *Value {
|
||||||
|
r := v.c.call("sqlite3_value_dup", uint64(v.handle))
|
||||||
|
return &Value{
|
||||||
|
c: v.c,
|
||||||
|
copied: true,
|
||||||
|
handle: uint32(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close frees an SQL value previously obtained by [Value.Dup].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/value_dup.html
|
||||||
|
func (dup *Value) Close() error {
|
||||||
|
if !dup.copied {
|
||||||
|
panic(util.ValueErr)
|
||||||
|
}
|
||||||
|
dup.c.call("sqlite3_value_free", uint64(dup.handle))
|
||||||
|
dup.handle = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the initial datatype of the value.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/value_blob.html
|
||||||
|
func (v Value) Type() Datatype {
|
||||||
|
r := v.c.call("sqlite3_value_type", v.protected())
|
||||||
|
return Datatype(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the numeric datatype of the value.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/value_blob.html
|
||||||
|
func (v Value) NumericType() Datatype {
|
||||||
|
r := v.c.call("sqlite3_value_numeric_type", v.protected())
|
||||||
|
return Datatype(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns the value as a bool.
|
||||||
|
// SQLite does not have a separate boolean storage class.
|
||||||
|
// Instead, boolean values are retrieved as integers,
|
||||||
|
// with 0 converted to false and any other value to true.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/value_blob.html
|
||||||
|
func (v Value) Bool() bool {
|
||||||
|
return v.Int64() != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns the value as an int.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/value_blob.html
|
||||||
|
func (v Value) Int() int {
|
||||||
|
return int(v.Int64())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 returns the value as an int64.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/value_blob.html
|
||||||
|
func (v Value) Int64() int64 {
|
||||||
|
r := v.c.call("sqlite3_value_int64", v.protected())
|
||||||
|
return int64(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float returns the value as a float64.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/value_blob.html
|
||||||
|
func (v Value) Float() float64 {
|
||||||
|
r := v.c.call("sqlite3_value_double", v.protected())
|
||||||
|
return math.Float64frombits(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns the value as a [time.Time].
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/value_blob.html
|
||||||
|
func (v Value) Time(format TimeFormat) time.Time {
|
||||||
|
var a any
|
||||||
|
switch v.Type() {
|
||||||
|
case INTEGER:
|
||||||
|
a = v.Int64()
|
||||||
|
case FLOAT:
|
||||||
|
a = v.Float()
|
||||||
|
case TEXT, BLOB:
|
||||||
|
a = v.Text()
|
||||||
|
case NULL:
|
||||||
|
return time.Time{}
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
t, _ := format.Decode(a)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text returns the value as a string.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/value_blob.html
|
||||||
|
func (v Value) Text() string {
|
||||||
|
return string(v.RawText())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blob appends to buf and returns
|
||||||
|
// the value as a []byte.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/value_blob.html
|
||||||
|
func (v Value) Blob(buf []byte) []byte {
|
||||||
|
return append(buf, v.RawBlob()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawText returns the value as a []byte.
|
||||||
|
// The []byte is owned by SQLite and may be invalidated by
|
||||||
|
// subsequent calls to [Value] methods.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/value_blob.html
|
||||||
|
func (v Value) RawText() []byte {
|
||||||
|
r := v.c.call("sqlite3_value_text", v.protected())
|
||||||
|
return v.rawBytes(uint32(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawBlob returns the value as a []byte.
|
||||||
|
// The []byte is owned by SQLite and may be invalidated by
|
||||||
|
// subsequent calls to [Value] methods.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/value_blob.html
|
||||||
|
func (v Value) RawBlob() []byte {
|
||||||
|
r := v.c.call("sqlite3_value_blob", v.protected())
|
||||||
|
return v.rawBytes(uint32(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Value) rawBytes(ptr uint32) []byte {
|
||||||
|
if ptr == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r := v.c.call("sqlite3_value_bytes", v.protected())
|
||||||
|
return util.View(v.c.mod, ptr, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer gets the pointer associated with this value,
|
||||||
|
// or nil if it has no associated pointer.
|
||||||
|
func (v Value) Pointer() any {
|
||||||
|
r := v.c.call("sqlite3_value_pointer_go", v.protected())
|
||||||
|
return util.GetHandle(v.c.ctx, uint32(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON parses a JSON-encoded value
|
||||||
|
// and stores the result in the value pointed to by ptr.
|
||||||
|
func (v Value) JSON(ptr any) error {
|
||||||
|
var data []byte
|
||||||
|
switch v.Type() {
|
||||||
|
case NULL:
|
||||||
|
data = append(data, "null"...)
|
||||||
|
case TEXT:
|
||||||
|
data = v.RawText()
|
||||||
|
case BLOB:
|
||||||
|
data = v.RawBlob()
|
||||||
|
case INTEGER:
|
||||||
|
data = strconv.AppendInt(nil, v.Int64(), 10)
|
||||||
|
case FLOAT:
|
||||||
|
data = strconv.AppendFloat(nil, v.Float(), 'g', -1, 64)
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoChange returns true if and only if the value is unchanged
|
||||||
|
// in a virtual table update operatiom.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/value_blob.html
|
||||||
|
func (v Value) NoChange() bool {
|
||||||
|
r := v.c.call("sqlite3_value_nochange", v.protected())
|
||||||
|
return r != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// InFirst returns the first element
|
||||||
|
// on the right-hand side of an IN constraint.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/vtab_in_first.html
|
||||||
|
func (v Value) InFirst() (Value, error) {
|
||||||
|
defer v.c.arena.mark()()
|
||||||
|
valPtr := v.c.arena.new(ptrlen)
|
||||||
|
r := v.c.call("sqlite3_vtab_in_first", uint64(v.handle), uint64(valPtr))
|
||||||
|
if err := v.c.error(r); err != nil {
|
||||||
|
return Value{}, err
|
||||||
|
}
|
||||||
|
return Value{
|
||||||
|
c: v.c,
|
||||||
|
handle: util.ReadUint32(v.c.mod, valPtr),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InNext returns the next element
|
||||||
|
// on the right-hand side of an IN constraint.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/vtab_in_first.html
|
||||||
|
func (v Value) InNext() (Value, error) {
|
||||||
|
defer v.c.arena.mark()()
|
||||||
|
valPtr := v.c.arena.new(ptrlen)
|
||||||
|
r := v.c.call("sqlite3_vtab_in_next", uint64(v.handle), uint64(valPtr))
|
||||||
|
if err := v.c.error(r); err != nil {
|
||||||
|
return Value{}, err
|
||||||
|
}
|
||||||
|
return Value{
|
||||||
|
c: v.c,
|
||||||
|
handle: util.ReadUint32(v.c.mod, valPtr),
|
||||||
|
}, nil
|
||||||
|
}
|
86
vendor/github.com/ncruces/go-sqlite3/vfs/README.md
generated
vendored
Normal file
86
vendor/github.com/ncruces/go-sqlite3/vfs/README.md
generated
vendored
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
# Go SQLite VFS API
|
||||||
|
|
||||||
|
This package implements the SQLite [OS Interface](https://sqlite.org/vfs.html) (aka VFS).
|
||||||
|
|
||||||
|
It replaces the default SQLite VFS with a **pure Go** implementation,
|
||||||
|
and exposes [interfaces](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#VFS)
|
||||||
|
that should allow you to implement your own custom VFSes.
|
||||||
|
|
||||||
|
Since it is a from scratch reimplementation,
|
||||||
|
there are naturally some ways it deviates from the original.
|
||||||
|
|
||||||
|
The main differences are [file locking](#file-locking) and [WAL mode](#write-ahead-logging) support.
|
||||||
|
|
||||||
|
### File Locking
|
||||||
|
|
||||||
|
POSIX advisory locks, which SQLite uses on Unix, are
|
||||||
|
[broken by design](https://github.com/sqlite/sqlite/blob/b74eb0/src/os_unix.c#L1073-L1161).
|
||||||
|
|
||||||
|
On Linux and macOS, this module uses
|
||||||
|
[OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
|
||||||
|
to synchronize access to database files.
|
||||||
|
OFD locks are fully compatible with POSIX advisory locks.
|
||||||
|
|
||||||
|
This module can also use
|
||||||
|
[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2),
|
||||||
|
albeit with reduced concurrency (`BEGIN IMMEDIATE` behaves like `BEGIN EXCLUSIVE`).
|
||||||
|
On BSD, macOS, and illumos, BSD locks are fully compatible with POSIX advisory locks;
|
||||||
|
on Linux and z/OS, they are fully functional, but incompatible;
|
||||||
|
elsewhere, they are very likely broken.
|
||||||
|
BSD locks are the default on BSD and illumos,
|
||||||
|
but you can opt into them with the `sqlite3_flock` build tag.
|
||||||
|
|
||||||
|
On Windows, this module uses `LockFileEx` and `UnlockFileEx`,
|
||||||
|
like SQLite.
|
||||||
|
|
||||||
|
Otherwise, file locking is not supported, and you must use
|
||||||
|
[`nolock=1`](https://sqlite.org/uri.html#urinolock)
|
||||||
|
(or [`immutable=1`](https://sqlite.org/uri.html#uriimmutable))
|
||||||
|
to open database files.
|
||||||
|
To use the [`database/sql`](https://pkg.go.dev/database/sql) driver
|
||||||
|
with `nolock=1` you must disable connection pooling by calling
|
||||||
|
[`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns).
|
||||||
|
|
||||||
|
You can use [`vfs.SupportsFileLocking`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#SupportsFileLocking)
|
||||||
|
to check if your build supports file locking.
|
||||||
|
|
||||||
|
### Write-Ahead Logging
|
||||||
|
|
||||||
|
On 64-bit Linux and macOS, this module uses `mmap` to implement
|
||||||
|
[shared-memory for the WAL-index](https://sqlite.org/wal.html#implementation_of_shared_memory_for_the_wal_index),
|
||||||
|
like SQLite.
|
||||||
|
|
||||||
|
To allow `mmap` to work, each connection needs to reserve up to 4GB of address space.
|
||||||
|
To limit the address space each connection reserves,
|
||||||
|
use [`WithMemoryLimitPages`](../tests/testcfg/testcfg.go).
|
||||||
|
|
||||||
|
Otherwise, [WAL support is limited](https://sqlite.org/wal.html#noshm),
|
||||||
|
and `EXCLUSIVE` locking mode must be set to create, read, and write WAL databases.
|
||||||
|
To use `EXCLUSIVE` locking mode with the
|
||||||
|
[`database/sql`](https://pkg.go.dev/database/sql) driver
|
||||||
|
you must disable connection pooling by calling
|
||||||
|
[`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns).
|
||||||
|
|
||||||
|
You can use [`vfs.SupportsSharedMemory`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#SupportsSharedMemory)
|
||||||
|
to check if your build supports shared memory.
|
||||||
|
|
||||||
|
### Batch-Atomic Write
|
||||||
|
|
||||||
|
On 64-bit Linux, this module supports [batch-atomic writes](https://sqlite.org/cgi/src/technote/714)
|
||||||
|
on the F2FS filesystem.
|
||||||
|
|
||||||
|
### Build Tags
|
||||||
|
|
||||||
|
The VFS can be customized with a few build tags:
|
||||||
|
- `sqlite3_flock` forces the use of BSD locks; it can be used on z/OS to enable locking,
|
||||||
|
and elsewhere to test BSD locks.
|
||||||
|
- `sqlite3_nosys` prevents importing [`x/sys`](https://pkg.go.dev/golang.org/x/sys);
|
||||||
|
disables locking _and_ shared memory on all platforms.
|
||||||
|
- `sqlite3_noshm` disables shared memory on all platforms.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> The default configuration of this package is compatible with
|
||||||
|
> the standard [Unix and Windows SQLite VFSes](https://sqlite.org/vfs.html#multiple_vfses);
|
||||||
|
> `sqlite3_flock` is compatible with the [`unix-flock` VFS](https://sqlite.org/compile.html#enable_locking_style).
|
||||||
|
> If incompatible file locking is used, accessing databases concurrently with _other_ SQLite libraries
|
||||||
|
> will eventually corrupt data.
|
175
vendor/github.com/ncruces/go-sqlite3/vfs/api.go
generated
vendored
Normal file
175
vendor/github.com/ncruces/go-sqlite3/vfs/api.go
generated
vendored
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
// Package vfs wraps the C SQLite VFS API.
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A VFS defines the interface between the SQLite core and the underlying operating system.
|
||||||
|
//
|
||||||
|
// Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/vfs.html
|
||||||
|
type VFS interface {
|
||||||
|
Open(name string, flags OpenFlag) (File, OpenFlag, error)
|
||||||
|
Delete(name string, syncDir bool) error
|
||||||
|
Access(name string, flags AccessFlag) (bool, error)
|
||||||
|
FullPathname(name string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VFSFilename extends VFS with the ability to use Filename
|
||||||
|
// objects for opening files.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/filename.html
|
||||||
|
type VFSFilename interface {
|
||||||
|
VFS
|
||||||
|
OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A File represents an open file in the OS interface layer.
|
||||||
|
//
|
||||||
|
// Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite.
|
||||||
|
// In particular, sqlite3.BUSY is necessary to correctly implement lock methods.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/io_methods.html
|
||||||
|
type File interface {
|
||||||
|
Close() error
|
||||||
|
ReadAt(p []byte, off int64) (n int, err error)
|
||||||
|
WriteAt(p []byte, off int64) (n int, err error)
|
||||||
|
Truncate(size int64) error
|
||||||
|
Sync(flags SyncFlag) error
|
||||||
|
Size() (int64, error)
|
||||||
|
Lock(lock LockLevel) error
|
||||||
|
Unlock(lock LockLevel) error
|
||||||
|
CheckReservedLock() (bool, error)
|
||||||
|
SectorSize() int
|
||||||
|
DeviceCharacteristics() DeviceCharacteristic
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileLockState extends File to implement the
|
||||||
|
// SQLITE_FCNTL_LOCKSTATE file control opcode.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntllockstate
|
||||||
|
type FileLockState interface {
|
||||||
|
File
|
||||||
|
LockState() LockLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileChunkSize extends File to implement the
|
||||||
|
// SQLITE_FCNTL_CHUNK_SIZE file control opcode.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlchunksize
|
||||||
|
type FileChunkSize interface {
|
||||||
|
File
|
||||||
|
ChunkSize(size int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileSizeHint extends File to implement the
|
||||||
|
// SQLITE_FCNTL_SIZE_HINT file control opcode.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlsizehint
|
||||||
|
type FileSizeHint interface {
|
||||||
|
File
|
||||||
|
SizeHint(size int64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileHasMoved extends File to implement the
|
||||||
|
// SQLITE_FCNTL_HAS_MOVED file control opcode.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlhasmoved
|
||||||
|
type FileHasMoved interface {
|
||||||
|
File
|
||||||
|
HasMoved() (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileOverwrite extends File to implement the
|
||||||
|
// SQLITE_FCNTL_OVERWRITE file control opcode.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntloverwrite
|
||||||
|
type FileOverwrite interface {
|
||||||
|
File
|
||||||
|
Overwrite() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilePersistentWAL extends File to implement the
|
||||||
|
// SQLITE_FCNTL_PERSIST_WAL file control opcode.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpersistwal
|
||||||
|
type FilePersistentWAL interface {
|
||||||
|
File
|
||||||
|
PersistentWAL() bool
|
||||||
|
SetPersistentWAL(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilePowersafeOverwrite extends File to implement the
|
||||||
|
// SQLITE_FCNTL_POWERSAFE_OVERWRITE file control opcode.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpowersafeoverwrite
|
||||||
|
type FilePowersafeOverwrite interface {
|
||||||
|
File
|
||||||
|
PowersafeOverwrite() bool
|
||||||
|
SetPowersafeOverwrite(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileCommitPhaseTwo extends File to implement the
|
||||||
|
// SQLITE_FCNTL_COMMIT_PHASETWO file control opcode.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlcommitphasetwo
|
||||||
|
type FileCommitPhaseTwo interface {
|
||||||
|
File
|
||||||
|
CommitPhaseTwo() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileBatchAtomicWrite extends File to implement the
|
||||||
|
// SQLITE_FCNTL_BEGIN_ATOMIC_WRITE, SQLITE_FCNTL_COMMIT_ATOMIC_WRITE
|
||||||
|
// and SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE file control opcodes.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlbeginatomicwrite
|
||||||
|
type FileBatchAtomicWrite interface {
|
||||||
|
File
|
||||||
|
BeginAtomicWrite() error
|
||||||
|
CommitAtomicWrite() error
|
||||||
|
RollbackAtomicWrite() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilePragma extends File to implement the
|
||||||
|
// SQLITE_FCNTL_PRAGMA file control opcode.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpragma
|
||||||
|
type FilePragma interface {
|
||||||
|
File
|
||||||
|
Pragma(name, value string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileCheckpoint extends File to implement the
|
||||||
|
// SQLITE_FCNTL_CKPT_START and SQLITE_FCNTL_CKPT_DONE
|
||||||
|
// file control opcodes.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlckptstart
|
||||||
|
type FileCheckpoint interface {
|
||||||
|
File
|
||||||
|
CheckpointDone() error
|
||||||
|
CheckpointStart() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileSharedMemory extends File to possibly implement
|
||||||
|
// shared-memory for the WAL-index.
|
||||||
|
// The same shared-memory instance must be returned
|
||||||
|
// for the entire life of the file.
|
||||||
|
// It's OK for SharedMemory to return nil.
|
||||||
|
type FileSharedMemory interface {
|
||||||
|
File
|
||||||
|
SharedMemory() SharedMemory
|
||||||
|
}
|
||||||
|
|
||||||
|
// SharedMemory is a shared-memory WAL-index implementation.
|
||||||
|
// Use [NewSharedMemory] to create a shared-memory.
|
||||||
|
type SharedMemory interface {
|
||||||
|
shmMap(context.Context, api.Module, int32, int32, bool) (uint32, error)
|
||||||
|
shmLock(int32, int32, _ShmFlag) error
|
||||||
|
shmUnmap(bool)
|
||||||
|
io.Closer
|
||||||
|
}
|
234
vendor/github.com/ncruces/go-sqlite3/vfs/const.go
generated
vendored
Normal file
234
vendor/github.com/ncruces/go-sqlite3/vfs/const.go
generated
vendored
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import "github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
|
||||||
|
const (
|
||||||
|
_MAX_NAME = 1e6 // Self-imposed limit for most NUL terminated strings.
|
||||||
|
_MAX_SQL_LENGTH = 1e9
|
||||||
|
_MAX_PATHNAME = 1024
|
||||||
|
_DEFAULT_SECTOR_SIZE = 4096
|
||||||
|
|
||||||
|
ptrlen = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://sqlite.org/rescode.html
|
||||||
|
type _ErrorCode uint32
|
||||||
|
|
||||||
|
func (e _ErrorCode) Error() string {
|
||||||
|
return util.ErrorCodeString(uint32(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
_OK _ErrorCode = util.OK
|
||||||
|
_ERROR _ErrorCode = util.ERROR
|
||||||
|
_PERM _ErrorCode = util.PERM
|
||||||
|
_BUSY _ErrorCode = util.BUSY
|
||||||
|
_READONLY _ErrorCode = util.READONLY
|
||||||
|
_IOERR _ErrorCode = util.IOERR
|
||||||
|
_NOTFOUND _ErrorCode = util.NOTFOUND
|
||||||
|
_CANTOPEN _ErrorCode = util.CANTOPEN
|
||||||
|
_IOERR_READ _ErrorCode = util.IOERR_READ
|
||||||
|
_IOERR_SHORT_READ _ErrorCode = util.IOERR_SHORT_READ
|
||||||
|
_IOERR_WRITE _ErrorCode = util.IOERR_WRITE
|
||||||
|
_IOERR_FSYNC _ErrorCode = util.IOERR_FSYNC
|
||||||
|
_IOERR_DIR_FSYNC _ErrorCode = util.IOERR_DIR_FSYNC
|
||||||
|
_IOERR_TRUNCATE _ErrorCode = util.IOERR_TRUNCATE
|
||||||
|
_IOERR_FSTAT _ErrorCode = util.IOERR_FSTAT
|
||||||
|
_IOERR_UNLOCK _ErrorCode = util.IOERR_UNLOCK
|
||||||
|
_IOERR_RDLOCK _ErrorCode = util.IOERR_RDLOCK
|
||||||
|
_IOERR_DELETE _ErrorCode = util.IOERR_DELETE
|
||||||
|
_IOERR_ACCESS _ErrorCode = util.IOERR_ACCESS
|
||||||
|
_IOERR_CHECKRESERVEDLOCK _ErrorCode = util.IOERR_CHECKRESERVEDLOCK
|
||||||
|
_IOERR_LOCK _ErrorCode = util.IOERR_LOCK
|
||||||
|
_IOERR_CLOSE _ErrorCode = util.IOERR_CLOSE
|
||||||
|
_IOERR_SHMOPEN _ErrorCode = util.IOERR_SHMOPEN
|
||||||
|
_IOERR_SHMSIZE _ErrorCode = util.IOERR_SHMSIZE
|
||||||
|
_IOERR_SHMLOCK _ErrorCode = util.IOERR_SHMLOCK
|
||||||
|
_IOERR_SHMMAP _ErrorCode = util.IOERR_SHMMAP
|
||||||
|
_IOERR_SEEK _ErrorCode = util.IOERR_SEEK
|
||||||
|
_IOERR_DELETE_NOENT _ErrorCode = util.IOERR_DELETE_NOENT
|
||||||
|
_IOERR_BEGIN_ATOMIC _ErrorCode = util.IOERR_BEGIN_ATOMIC
|
||||||
|
_IOERR_COMMIT_ATOMIC _ErrorCode = util.IOERR_COMMIT_ATOMIC
|
||||||
|
_IOERR_ROLLBACK_ATOMIC _ErrorCode = util.IOERR_ROLLBACK_ATOMIC
|
||||||
|
_CANTOPEN_FULLPATH _ErrorCode = util.CANTOPEN_FULLPATH
|
||||||
|
_CANTOPEN_ISDIR _ErrorCode = util.CANTOPEN_ISDIR
|
||||||
|
_READONLY_CANTINIT _ErrorCode = util.READONLY_CANTINIT
|
||||||
|
_OK_SYMLINK _ErrorCode = util.OK_SYMLINK
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenFlag is a flag for the [VFS] Open method.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_open_autoproxy.html
|
||||||
|
type OpenFlag uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
OPEN_READONLY OpenFlag = 0x00000001 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_READWRITE OpenFlag = 0x00000002 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_CREATE OpenFlag = 0x00000004 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_DELETEONCLOSE OpenFlag = 0x00000008 /* VFS only */
|
||||||
|
OPEN_EXCLUSIVE OpenFlag = 0x00000010 /* VFS only */
|
||||||
|
OPEN_AUTOPROXY OpenFlag = 0x00000020 /* VFS only */
|
||||||
|
OPEN_URI OpenFlag = 0x00000040 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_MEMORY OpenFlag = 0x00000080 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_MAIN_DB OpenFlag = 0x00000100 /* VFS only */
|
||||||
|
OPEN_TEMP_DB OpenFlag = 0x00000200 /* VFS only */
|
||||||
|
OPEN_TRANSIENT_DB OpenFlag = 0x00000400 /* VFS only */
|
||||||
|
OPEN_MAIN_JOURNAL OpenFlag = 0x00000800 /* VFS only */
|
||||||
|
OPEN_TEMP_JOURNAL OpenFlag = 0x00001000 /* VFS only */
|
||||||
|
OPEN_SUBJOURNAL OpenFlag = 0x00002000 /* VFS only */
|
||||||
|
OPEN_SUPER_JOURNAL OpenFlag = 0x00004000 /* VFS only */
|
||||||
|
OPEN_NOMUTEX OpenFlag = 0x00008000 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_FULLMUTEX OpenFlag = 0x00010000 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_SHAREDCACHE OpenFlag = 0x00020000 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_PRIVATECACHE OpenFlag = 0x00040000 /* Ok for sqlite3_open_v2() */
|
||||||
|
OPEN_WAL OpenFlag = 0x00080000 /* VFS only */
|
||||||
|
OPEN_NOFOLLOW OpenFlag = 0x01000000 /* Ok for sqlite3_open_v2() */
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessFlag is a flag for the [VFS] Access method.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_access_exists.html
|
||||||
|
type AccessFlag uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
ACCESS_EXISTS AccessFlag = 0
|
||||||
|
ACCESS_READWRITE AccessFlag = 1 /* Used by PRAGMA temp_store_directory */
|
||||||
|
ACCESS_READ AccessFlag = 2 /* Unused */
|
||||||
|
)
|
||||||
|
|
||||||
|
// SyncFlag is a flag for the [File] Sync method.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_sync_dataonly.html
|
||||||
|
type SyncFlag uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
SYNC_NORMAL SyncFlag = 0x00002
|
||||||
|
SYNC_FULL SyncFlag = 0x00003
|
||||||
|
SYNC_DATAONLY SyncFlag = 0x00010
|
||||||
|
)
|
||||||
|
|
||||||
|
// LockLevel is a value used with [File] Lock and Unlock methods.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_lock_exclusive.html
|
||||||
|
type LockLevel uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
// No locks are held on the database.
|
||||||
|
// The database may be neither read nor written.
|
||||||
|
// Any internally cached data is considered suspect and subject to
|
||||||
|
// verification against the database file before being used.
|
||||||
|
// Other processes can read or write the database as their own locking
|
||||||
|
// states permit.
|
||||||
|
// This is the default state.
|
||||||
|
LOCK_NONE LockLevel = 0 /* xUnlock() only */
|
||||||
|
|
||||||
|
// The database may be read but not written.
|
||||||
|
// Any number of processes can hold SHARED locks at the same time,
|
||||||
|
// hence there can be many simultaneous readers.
|
||||||
|
// But no other thread or process is allowed to write to the database file
|
||||||
|
// while one or more SHARED locks are active.
|
||||||
|
LOCK_SHARED LockLevel = 1 /* xLock() or xUnlock() */
|
||||||
|
|
||||||
|
// A RESERVED lock means that the process is planning on writing to the
|
||||||
|
// database file at some point in the future but that it is currently just
|
||||||
|
// reading from the file.
|
||||||
|
// Only a single RESERVED lock may be active at one time,
|
||||||
|
// though multiple SHARED locks can coexist with a single RESERVED lock.
|
||||||
|
// RESERVED differs from PENDING in that new SHARED locks can be acquired
|
||||||
|
// while there is a RESERVED lock.
|
||||||
|
LOCK_RESERVED LockLevel = 2 /* xLock() only */
|
||||||
|
|
||||||
|
// A PENDING lock means that the process holding the lock wants to write to
|
||||||
|
// the database as soon as possible and is just waiting on all current
|
||||||
|
// SHARED locks to clear so that it can get an EXCLUSIVE lock.
|
||||||
|
// No new SHARED locks are permitted against the database if a PENDING lock
|
||||||
|
// is active, though existing SHARED locks are allowed to continue.
|
||||||
|
LOCK_PENDING LockLevel = 3 /* internal use only */
|
||||||
|
|
||||||
|
// An EXCLUSIVE lock is needed in order to write to the database file.
|
||||||
|
// Only one EXCLUSIVE lock is allowed on the file and no other locks of any
|
||||||
|
// kind are allowed to coexist with an EXCLUSIVE lock.
|
||||||
|
// In order to maximize concurrency, SQLite works to minimize the amount of
|
||||||
|
// time that EXCLUSIVE locks are held.
|
||||||
|
LOCK_EXCLUSIVE LockLevel = 4 /* xLock() only */
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeviceCharacteristic is a flag retuned by the [File] DeviceCharacteristics method.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/c_iocap_atomic.html
|
||||||
|
type DeviceCharacteristic uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
IOCAP_ATOMIC DeviceCharacteristic = 0x00000001
|
||||||
|
IOCAP_ATOMIC512 DeviceCharacteristic = 0x00000002
|
||||||
|
IOCAP_ATOMIC1K DeviceCharacteristic = 0x00000004
|
||||||
|
IOCAP_ATOMIC2K DeviceCharacteristic = 0x00000008
|
||||||
|
IOCAP_ATOMIC4K DeviceCharacteristic = 0x00000010
|
||||||
|
IOCAP_ATOMIC8K DeviceCharacteristic = 0x00000020
|
||||||
|
IOCAP_ATOMIC16K DeviceCharacteristic = 0x00000040
|
||||||
|
IOCAP_ATOMIC32K DeviceCharacteristic = 0x00000080
|
||||||
|
IOCAP_ATOMIC64K DeviceCharacteristic = 0x00000100
|
||||||
|
IOCAP_SAFE_APPEND DeviceCharacteristic = 0x00000200
|
||||||
|
IOCAP_SEQUENTIAL DeviceCharacteristic = 0x00000400
|
||||||
|
IOCAP_UNDELETABLE_WHEN_OPEN DeviceCharacteristic = 0x00000800
|
||||||
|
IOCAP_POWERSAFE_OVERWRITE DeviceCharacteristic = 0x00001000
|
||||||
|
IOCAP_IMMUTABLE DeviceCharacteristic = 0x00002000
|
||||||
|
IOCAP_BATCH_ATOMIC DeviceCharacteristic = 0x00004000
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
|
||||||
|
type _FcntlOpcode uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
_FCNTL_LOCKSTATE _FcntlOpcode = 1
|
||||||
|
_FCNTL_GET_LOCKPROXYFILE _FcntlOpcode = 2
|
||||||
|
_FCNTL_SET_LOCKPROXYFILE _FcntlOpcode = 3
|
||||||
|
_FCNTL_LAST_ERRNO _FcntlOpcode = 4
|
||||||
|
_FCNTL_SIZE_HINT _FcntlOpcode = 5
|
||||||
|
_FCNTL_CHUNK_SIZE _FcntlOpcode = 6
|
||||||
|
_FCNTL_FILE_POINTER _FcntlOpcode = 7
|
||||||
|
_FCNTL_SYNC_OMITTED _FcntlOpcode = 8
|
||||||
|
_FCNTL_WIN32_AV_RETRY _FcntlOpcode = 9
|
||||||
|
_FCNTL_PERSIST_WAL _FcntlOpcode = 10
|
||||||
|
_FCNTL_OVERWRITE _FcntlOpcode = 11
|
||||||
|
_FCNTL_VFSNAME _FcntlOpcode = 12
|
||||||
|
_FCNTL_POWERSAFE_OVERWRITE _FcntlOpcode = 13
|
||||||
|
_FCNTL_PRAGMA _FcntlOpcode = 14
|
||||||
|
_FCNTL_BUSYHANDLER _FcntlOpcode = 15
|
||||||
|
_FCNTL_TEMPFILENAME _FcntlOpcode = 16
|
||||||
|
_FCNTL_MMAP_SIZE _FcntlOpcode = 18
|
||||||
|
_FCNTL_TRACE _FcntlOpcode = 19
|
||||||
|
_FCNTL_HAS_MOVED _FcntlOpcode = 20
|
||||||
|
_FCNTL_SYNC _FcntlOpcode = 21
|
||||||
|
_FCNTL_COMMIT_PHASETWO _FcntlOpcode = 22
|
||||||
|
_FCNTL_WIN32_SET_HANDLE _FcntlOpcode = 23
|
||||||
|
_FCNTL_WAL_BLOCK _FcntlOpcode = 24
|
||||||
|
_FCNTL_ZIPVFS _FcntlOpcode = 25
|
||||||
|
_FCNTL_RBU _FcntlOpcode = 26
|
||||||
|
_FCNTL_VFS_POINTER _FcntlOpcode = 27
|
||||||
|
_FCNTL_JOURNAL_POINTER _FcntlOpcode = 28
|
||||||
|
_FCNTL_WIN32_GET_HANDLE _FcntlOpcode = 29
|
||||||
|
_FCNTL_PDB _FcntlOpcode = 30
|
||||||
|
_FCNTL_BEGIN_ATOMIC_WRITE _FcntlOpcode = 31
|
||||||
|
_FCNTL_COMMIT_ATOMIC_WRITE _FcntlOpcode = 32
|
||||||
|
_FCNTL_ROLLBACK_ATOMIC_WRITE _FcntlOpcode = 33
|
||||||
|
_FCNTL_LOCK_TIMEOUT _FcntlOpcode = 34
|
||||||
|
_FCNTL_DATA_VERSION _FcntlOpcode = 35
|
||||||
|
_FCNTL_SIZE_LIMIT _FcntlOpcode = 36
|
||||||
|
_FCNTL_CKPT_DONE _FcntlOpcode = 37
|
||||||
|
_FCNTL_RESERVE_BYTES _FcntlOpcode = 38
|
||||||
|
_FCNTL_CKPT_START _FcntlOpcode = 39
|
||||||
|
_FCNTL_EXTERNAL_READER _FcntlOpcode = 40
|
||||||
|
_FCNTL_CKSM_FILE _FcntlOpcode = 41
|
||||||
|
_FCNTL_RESET_CACHE _FcntlOpcode = 42
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://sqlite.org/c3ref/c_shm_exclusive.html
|
||||||
|
type _ShmFlag uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
_SHM_UNLOCK _ShmFlag = 1
|
||||||
|
_SHM_LOCK _ShmFlag = 2
|
||||||
|
_SHM_SHARED _ShmFlag = 4
|
||||||
|
_SHM_EXCLUSIVE _ShmFlag = 8
|
||||||
|
)
|
217
vendor/github.com/ncruces/go-sqlite3/vfs/file.go
generated
vendored
Normal file
217
vendor/github.com/ncruces/go-sqlite3/vfs/file.go
generated
vendored
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/util/osutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type vfsOS struct{}
|
||||||
|
|
||||||
|
func (vfsOS) FullPathname(path string) (string, error) {
|
||||||
|
path, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
fi, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if fi.Mode()&fs.ModeSymlink != 0 {
|
||||||
|
err = _OK_SYMLINK
|
||||||
|
}
|
||||||
|
return path, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vfsOS) Delete(path string, syncDir bool) error {
|
||||||
|
err := os.Remove(path)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return _IOERR_DELETE_NOENT
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if runtime.GOOS != "windows" && syncDir {
|
||||||
|
f, err := os.Open(filepath.Dir(path))
|
||||||
|
if err != nil {
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
err = osSync(f, false, false)
|
||||||
|
if err != nil {
|
||||||
|
return _IOERR_DIR_FSYNC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vfsOS) Access(name string, flags AccessFlag) (bool, error) {
|
||||||
|
err := osAccess(name, flags)
|
||||||
|
if flags == ACCESS_EXISTS {
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if errors.Is(err, fs.ErrPermission) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err == nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vfsOS) Open(name string, flags OpenFlag) (File, OpenFlag, error) {
|
||||||
|
return nil, 0, _CANTOPEN
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vfsOS) OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error) {
|
||||||
|
var oflags int
|
||||||
|
if flags&OPEN_EXCLUSIVE != 0 {
|
||||||
|
oflags |= os.O_EXCL
|
||||||
|
}
|
||||||
|
if flags&OPEN_CREATE != 0 {
|
||||||
|
oflags |= os.O_CREATE
|
||||||
|
}
|
||||||
|
if flags&OPEN_READONLY != 0 {
|
||||||
|
oflags |= os.O_RDONLY
|
||||||
|
}
|
||||||
|
if flags&OPEN_READWRITE != 0 {
|
||||||
|
oflags |= os.O_RDWR
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var f *os.File
|
||||||
|
if name == nil {
|
||||||
|
f, err = os.CreateTemp("", "*.db")
|
||||||
|
} else {
|
||||||
|
f, err = osutil.OpenFile(name.String(), oflags, 0666)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, syscall.EISDIR) {
|
||||||
|
return nil, flags, _CANTOPEN_ISDIR
|
||||||
|
}
|
||||||
|
return nil, flags, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if modeof := name.URIParameter("modeof"); modeof != "" {
|
||||||
|
if err = osSetMode(f, modeof); err != nil {
|
||||||
|
f.Close()
|
||||||
|
return nil, flags, _IOERR_FSTAT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if flags&OPEN_DELETEONCLOSE != 0 {
|
||||||
|
os.Remove(f.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
file := vfsFile{
|
||||||
|
File: f,
|
||||||
|
psow: true,
|
||||||
|
readOnly: flags&OPEN_READONLY != 0,
|
||||||
|
syncDir: runtime.GOOS != "windows" &&
|
||||||
|
flags&(OPEN_CREATE) != 0 &&
|
||||||
|
flags&(OPEN_MAIN_JOURNAL|OPEN_SUPER_JOURNAL|OPEN_WAL) != 0,
|
||||||
|
shm: NewSharedMemory(name.String()+"-shm", flags),
|
||||||
|
}
|
||||||
|
return &file, flags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type vfsFile struct {
|
||||||
|
*os.File
|
||||||
|
shm SharedMemory
|
||||||
|
lock LockLevel
|
||||||
|
readOnly bool
|
||||||
|
keepWAL bool
|
||||||
|
syncDir bool
|
||||||
|
psow bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Ensure these interfaces are implemented:
|
||||||
|
_ FileLockState = &vfsFile{}
|
||||||
|
_ FileHasMoved = &vfsFile{}
|
||||||
|
_ FileSizeHint = &vfsFile{}
|
||||||
|
_ FilePersistentWAL = &vfsFile{}
|
||||||
|
_ FilePowersafeOverwrite = &vfsFile{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f *vfsFile) Close() error {
|
||||||
|
if f.shm != nil {
|
||||||
|
f.shm.Close()
|
||||||
|
}
|
||||||
|
return f.File.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *vfsFile) Sync(flags SyncFlag) error {
|
||||||
|
dataonly := (flags & SYNC_DATAONLY) != 0
|
||||||
|
fullsync := (flags & 0x0f) == SYNC_FULL
|
||||||
|
|
||||||
|
err := osSync(f.File, fullsync, dataonly)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if runtime.GOOS != "windows" && f.syncDir {
|
||||||
|
f.syncDir = false
|
||||||
|
d, err := os.Open(filepath.Dir(f.File.Name()))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer d.Close()
|
||||||
|
err = osSync(d, false, false)
|
||||||
|
if err != nil {
|
||||||
|
return _IOERR_DIR_FSYNC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *vfsFile) Size() (int64, error) {
|
||||||
|
return f.Seek(0, io.SeekEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *vfsFile) SectorSize() int {
|
||||||
|
return _DEFAULT_SECTOR_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *vfsFile) DeviceCharacteristics() DeviceCharacteristic {
|
||||||
|
var res DeviceCharacteristic
|
||||||
|
if osBatchAtomic(f.File) {
|
||||||
|
res |= IOCAP_BATCH_ATOMIC
|
||||||
|
}
|
||||||
|
if f.psow {
|
||||||
|
res |= IOCAP_POWERSAFE_OVERWRITE
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *vfsFile) SizeHint(size int64) error {
|
||||||
|
return osAllocate(f.File, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *vfsFile) HasMoved() (bool, error) {
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
pi, err := os.Stat(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return !os.SameFile(fi, pi), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *vfsFile) LockState() LockLevel { return f.lock }
|
||||||
|
func (f *vfsFile) PowersafeOverwrite() bool { return f.psow }
|
||||||
|
func (f *vfsFile) PersistentWAL() bool { return f.keepWAL }
|
||||||
|
func (f *vfsFile) SetPowersafeOverwrite(psow bool) { f.psow = psow }
|
||||||
|
func (f *vfsFile) SetPersistentWAL(keepWAL bool) { f.keepWAL = keepWAL }
|
174
vendor/github.com/ncruces/go-sqlite3/vfs/filename.go
generated
vendored
Normal file
174
vendor/github.com/ncruces/go-sqlite3/vfs/filename.go
generated
vendored
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filename is used by SQLite to pass filenames
|
||||||
|
// to the Open method of a VFS.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/filename.html
|
||||||
|
type Filename struct {
|
||||||
|
ctx context.Context
|
||||||
|
mod api.Module
|
||||||
|
zPath uint32
|
||||||
|
flags OpenFlag
|
||||||
|
stack [2]uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenFilename is an internal API users should not call directly.
|
||||||
|
func OpenFilename(ctx context.Context, mod api.Module, id uint32, flags OpenFlag) *Filename {
|
||||||
|
if id == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Filename{
|
||||||
|
ctx: ctx,
|
||||||
|
mod: mod,
|
||||||
|
zPath: id,
|
||||||
|
flags: flags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns this filename as a string.
|
||||||
|
func (n *Filename) String() string {
|
||||||
|
if n == nil || n.zPath == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return util.ReadString(n.mod, n.zPath, _MAX_PATHNAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database returns the name of the corresponding database file.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/filename_database.html
|
||||||
|
func (n *Filename) Database() string {
|
||||||
|
return n.path("sqlite3_filename_database")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Journal returns the name of the corresponding rollback journal file.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/filename_database.html
|
||||||
|
func (n *Filename) Journal() string {
|
||||||
|
return n.path("sqlite3_filename_journal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Journal returns the name of the corresponding WAL file.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/filename_database.html
|
||||||
|
func (n *Filename) WAL() string {
|
||||||
|
return n.path("sqlite3_filename_wal")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Filename) path(method string) string {
|
||||||
|
if n == nil || n.zPath == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
n.stack[0] = uint64(n.zPath)
|
||||||
|
fn := n.mod.ExportedFunction(method)
|
||||||
|
if err := fn.CallWithStack(n.ctx, n.stack[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return util.ReadString(n.mod, uint32(n.stack[0]), _MAX_PATHNAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabaseFile returns the main database [File] corresponding to a journal.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/database_file_object.html
|
||||||
|
func (n *Filename) DatabaseFile() File {
|
||||||
|
if n == nil || n.zPath == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if n.flags&(OPEN_MAIN_DB|OPEN_MAIN_JOURNAL|OPEN_WAL) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n.stack[0] = uint64(n.zPath)
|
||||||
|
fn := n.mod.ExportedFunction("sqlite3_database_file_object")
|
||||||
|
if err := fn.CallWithStack(n.ctx, n.stack[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
file, _ := vfsFileGet(n.ctx, n.mod, uint32(n.stack[0])).(File)
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
// URIParameter returns the value of a URI parameter.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/uri_boolean.html
|
||||||
|
func (n *Filename) URIParameter(key string) string {
|
||||||
|
if n == nil || n.zPath == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
uriKey := n.mod.ExportedFunction("sqlite3_uri_key")
|
||||||
|
n.stack[0] = uint64(n.zPath)
|
||||||
|
n.stack[1] = uint64(0)
|
||||||
|
if err := uriKey.CallWithStack(n.ctx, n.stack[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr := uint32(n.stack[0])
|
||||||
|
if ptr == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the format from:
|
||||||
|
// https://github.com/sqlite/sqlite/blob/b74eb0/src/pager.c#L4797-L4840
|
||||||
|
// This avoids having to alloc/free the key just to find a value.
|
||||||
|
for {
|
||||||
|
k := util.ReadString(n.mod, ptr, _MAX_NAME)
|
||||||
|
if k == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
ptr += uint32(len(k)) + 1
|
||||||
|
|
||||||
|
v := util.ReadString(n.mod, ptr, _MAX_NAME)
|
||||||
|
if k == key {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
ptr += uint32(len(v)) + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// URIParameters obtains values for URI parameters.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/uri_boolean.html
|
||||||
|
func (n *Filename) URIParameters() url.Values {
|
||||||
|
if n == nil || n.zPath == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uriKey := n.mod.ExportedFunction("sqlite3_uri_key")
|
||||||
|
n.stack[0] = uint64(n.zPath)
|
||||||
|
n.stack[1] = uint64(0)
|
||||||
|
if err := uriKey.CallWithStack(n.ctx, n.stack[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr := uint32(n.stack[0])
|
||||||
|
if ptr == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var params url.Values
|
||||||
|
|
||||||
|
// Parse the format from:
|
||||||
|
// https://github.com/sqlite/sqlite/blob/b74eb0/src/pager.c#L4797-L4840
|
||||||
|
// This is the only way to support multiple valued keys.
|
||||||
|
for {
|
||||||
|
k := util.ReadString(n.mod, ptr, _MAX_NAME)
|
||||||
|
if k == "" {
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
ptr += uint32(len(k)) + 1
|
||||||
|
|
||||||
|
v := util.ReadString(n.mod, ptr, _MAX_NAME)
|
||||||
|
if params == nil {
|
||||||
|
params = url.Values{}
|
||||||
|
}
|
||||||
|
params.Add(k, v)
|
||||||
|
ptr += uint32(len(v)) + 1
|
||||||
|
}
|
||||||
|
}
|
144
vendor/github.com/ncruces/go-sqlite3/vfs/lock.go
generated
vendored
Normal file
144
vendor/github.com/ncruces/go-sqlite3/vfs/lock.go
generated
vendored
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
//go:build (linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import "github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
|
||||||
|
// SupportsFileLocking is false on platforms that do not support file locking.
|
||||||
|
// To open a database file on those platforms,
|
||||||
|
// you need to use the [nolock] or [immutable] URI parameters.
|
||||||
|
//
|
||||||
|
// [nolock]: https://sqlite.org/uri.html#urinolock
|
||||||
|
// [immutable]: https://sqlite.org/uri.html#uriimmutable
|
||||||
|
const SupportsFileLocking = true
|
||||||
|
|
||||||
|
const (
|
||||||
|
_PENDING_BYTE = 0x40000000
|
||||||
|
_RESERVED_BYTE = (_PENDING_BYTE + 1)
|
||||||
|
_SHARED_FIRST = (_PENDING_BYTE + 2)
|
||||||
|
_SHARED_SIZE = 510
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f *vfsFile) Lock(lock LockLevel) error {
|
||||||
|
// Argument check. SQLite never explicitly requests a pending lock.
|
||||||
|
if lock != LOCK_SHARED && lock != LOCK_RESERVED && lock != LOCK_EXCLUSIVE {
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE:
|
||||||
|
// Connection state check.
|
||||||
|
panic(util.AssertErr())
|
||||||
|
case f.lock == LOCK_NONE && lock > LOCK_SHARED:
|
||||||
|
// We never move from unlocked to anything higher than a shared lock.
|
||||||
|
panic(util.AssertErr())
|
||||||
|
case f.lock != LOCK_SHARED && lock == LOCK_RESERVED:
|
||||||
|
// A shared lock is always held when a reserved lock is requested.
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we already have an equal or more restrictive lock, do nothing.
|
||||||
|
if f.lock >= lock {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not allow any kind of write-lock on a read-only database.
|
||||||
|
if f.readOnly && lock >= LOCK_RESERVED {
|
||||||
|
return _IOERR_LOCK
|
||||||
|
}
|
||||||
|
|
||||||
|
switch lock {
|
||||||
|
case LOCK_SHARED:
|
||||||
|
// Must be unlocked to get SHARED.
|
||||||
|
if f.lock != LOCK_NONE {
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
if rc := osGetSharedLock(f.File); rc != _OK {
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
f.lock = LOCK_SHARED
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case LOCK_RESERVED:
|
||||||
|
// Must be SHARED to get RESERVED.
|
||||||
|
if f.lock != LOCK_SHARED {
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
if rc := osGetReservedLock(f.File); rc != _OK {
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
f.lock = LOCK_RESERVED
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case LOCK_EXCLUSIVE:
|
||||||
|
// Must be SHARED, RESERVED or PENDING to get EXCLUSIVE.
|
||||||
|
if f.lock <= LOCK_NONE || f.lock >= LOCK_EXCLUSIVE {
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
reserved := f.lock == LOCK_RESERVED
|
||||||
|
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
|
||||||
|
if f.lock < LOCK_PENDING {
|
||||||
|
// If we're already RESERVED, we can block indefinitely,
|
||||||
|
// since only new readers may briefly hold the PENDING lock.
|
||||||
|
if rc := osGetPendingLock(f.File, reserved /* block */); rc != _OK {
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
f.lock = LOCK_PENDING
|
||||||
|
}
|
||||||
|
// We already have PENDING, so we're just waiting for readers to leave.
|
||||||
|
// If we were RESERVED, we can wait for a little while, before invoking
|
||||||
|
// the busy handler; we will only do this once.
|
||||||
|
if rc := osGetExclusiveLock(f.File, reserved /* wait */); rc != _OK {
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
f.lock = LOCK_EXCLUSIVE
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *vfsFile) Unlock(lock LockLevel) error {
|
||||||
|
// Argument check.
|
||||||
|
if lock != LOCK_NONE && lock != LOCK_SHARED {
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection state check.
|
||||||
|
if f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE {
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't have a more restrictive lock, do nothing.
|
||||||
|
if f.lock <= lock {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch lock {
|
||||||
|
case LOCK_SHARED:
|
||||||
|
rc := osDowngradeLock(f.File, f.lock)
|
||||||
|
f.lock = LOCK_SHARED
|
||||||
|
return rc
|
||||||
|
|
||||||
|
case LOCK_NONE:
|
||||||
|
rc := osReleaseLock(f.File, f.lock)
|
||||||
|
f.lock = LOCK_NONE
|
||||||
|
return rc
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *vfsFile) CheckReservedLock() (bool, error) {
|
||||||
|
// Connection state check.
|
||||||
|
if f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE {
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.lock >= LOCK_RESERVED {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return osCheckReservedLock(f.File)
|
||||||
|
}
|
23
vendor/github.com/ncruces/go-sqlite3/vfs/lock_other.go
generated
vendored
Normal file
23
vendor/github.com/ncruces/go-sqlite3/vfs/lock_other.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
//go:build !(linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) || sqlite3_nosys
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
// SupportsFileLocking is false on platforms that do not support file locking.
|
||||||
|
// To open a database file on those platforms,
|
||||||
|
// you need to use the [nolock] or [immutable] URI parameters.
|
||||||
|
//
|
||||||
|
// [nolock]: https://sqlite.org/uri.html#urinolock
|
||||||
|
// [immutable]: https://sqlite.org/uri.html#uriimmutable
|
||||||
|
const SupportsFileLocking = false
|
||||||
|
|
||||||
|
func (f *vfsFile) Lock(LockLevel) error {
|
||||||
|
return _IOERR_LOCK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *vfsFile) Unlock(LockLevel) error {
|
||||||
|
return _IOERR_UNLOCK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *vfsFile) CheckReservedLock() (bool, error) {
|
||||||
|
return false, _IOERR_CHECKRESERVEDLOCK
|
||||||
|
}
|
9
vendor/github.com/ncruces/go-sqlite3/vfs/memdb/README.md
generated
vendored
Normal file
9
vendor/github.com/ncruces/go-sqlite3/vfs/memdb/README.md
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Go `"memdb"` SQLite VFS
|
||||||
|
|
||||||
|
This package implements the [`"memdb"`](https://sqlite.org/src/doc/tip/src/memdb.c)
|
||||||
|
SQLite VFS in pure Go.
|
||||||
|
|
||||||
|
It has some benefits over the C version:
|
||||||
|
- the memory backing the database needs not be contiguous,
|
||||||
|
- the database can grow/shrink incrementally without copying,
|
||||||
|
- reader-writer concurrency is slightly improved.
|
68
vendor/github.com/ncruces/go-sqlite3/vfs/memdb/api.go
generated
vendored
Normal file
68
vendor/github.com/ncruces/go-sqlite3/vfs/memdb/api.go
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// Package memdb implements the "memdb" SQLite VFS.
|
||||||
|
//
|
||||||
|
// The "memdb" [vfs.VFS] allows the same in-memory database to be shared
|
||||||
|
// among multiple database connections in the same process,
|
||||||
|
// as long as the database name begins with "/".
|
||||||
|
//
|
||||||
|
// Importing package memdb registers the VFS:
|
||||||
|
//
|
||||||
|
// import _ "github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||||
|
package memdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/vfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
vfs.Register("memdb", memVFS{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
memoryMtx sync.Mutex
|
||||||
|
// +checklocks:memoryMtx
|
||||||
|
memoryDBs = map[string]*memDB{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create creates a shared memory database,
|
||||||
|
// using data as its initial contents.
|
||||||
|
// The new database takes ownership of data,
|
||||||
|
// and the caller should not use data after this call.
|
||||||
|
func Create(name string, data []byte) {
|
||||||
|
memoryMtx.Lock()
|
||||||
|
defer memoryMtx.Unlock()
|
||||||
|
|
||||||
|
db := &memDB{
|
||||||
|
refs: 1,
|
||||||
|
name: name,
|
||||||
|
size: int64(len(data)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert data from WAL to rollback journal.
|
||||||
|
if len(data) >= 20 && data[18] == 2 && data[19] == 2 {
|
||||||
|
data[18] = 1
|
||||||
|
data[19] = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
sectors := divRoundUp(db.size, sectorSize)
|
||||||
|
db.data = make([]*[sectorSize]byte, sectors)
|
||||||
|
for i := range db.data {
|
||||||
|
sector := data[i*sectorSize:]
|
||||||
|
if len(sector) >= sectorSize {
|
||||||
|
db.data[i] = (*[sectorSize]byte)(sector)
|
||||||
|
} else {
|
||||||
|
db.data[i] = new([sectorSize]byte)
|
||||||
|
copy((*db.data[i])[:], sector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memoryDBs[name] = db
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes a shared memory database.
|
||||||
|
func Delete(name string) {
|
||||||
|
memoryMtx.Lock()
|
||||||
|
defer memoryMtx.Unlock()
|
||||||
|
delete(memoryDBs, name)
|
||||||
|
}
|
311
vendor/github.com/ncruces/go-sqlite3/vfs/memdb/memdb.go
generated
vendored
Normal file
311
vendor/github.com/ncruces/go-sqlite3/vfs/memdb/memdb.go
generated
vendored
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
package memdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3"
|
||||||
|
"github.com/ncruces/go-sqlite3/vfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Must be a multiple of 64K (the largest page size).
|
||||||
|
const sectorSize = 65536
|
||||||
|
|
||||||
|
type memVFS struct{}
|
||||||
|
|
||||||
|
func (memVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
|
||||||
|
// For simplicity, we do not support reading or writing data
|
||||||
|
// across "sector" boundaries.
|
||||||
|
//
|
||||||
|
// This is not a problem for most SQLite file types:
|
||||||
|
// - databases, which only do page aligned reads/writes;
|
||||||
|
// - temp journals, as used by the sorter, which does the same:
|
||||||
|
// https://github.com/sqlite/sqlite/blob/b74eb0/src/vdbesort.c#L409-L412
|
||||||
|
//
|
||||||
|
// We refuse to open all other file types,
|
||||||
|
// but returning OPEN_MEMORY means SQLite won't ask us to.
|
||||||
|
const types = vfs.OPEN_MAIN_DB |
|
||||||
|
vfs.OPEN_TEMP_DB |
|
||||||
|
vfs.OPEN_TEMP_JOURNAL
|
||||||
|
if flags&types == 0 {
|
||||||
|
return nil, flags, sqlite3.CANTOPEN
|
||||||
|
}
|
||||||
|
|
||||||
|
// A shared database has a name that begins with "/".
|
||||||
|
shared := len(name) > 1 && name[0] == '/'
|
||||||
|
|
||||||
|
var db *memDB
|
||||||
|
if shared {
|
||||||
|
name = name[1:]
|
||||||
|
memoryMtx.Lock()
|
||||||
|
defer memoryMtx.Unlock()
|
||||||
|
db = memoryDBs[name]
|
||||||
|
}
|
||||||
|
if db == nil {
|
||||||
|
if flags&vfs.OPEN_CREATE == 0 {
|
||||||
|
return nil, flags, sqlite3.CANTOPEN
|
||||||
|
}
|
||||||
|
db = &memDB{name: name}
|
||||||
|
}
|
||||||
|
if shared {
|
||||||
|
db.refs++ // +checklocksforce: memoryMtx is held
|
||||||
|
memoryDBs[name] = db
|
||||||
|
}
|
||||||
|
|
||||||
|
return &memFile{
|
||||||
|
memDB: db,
|
||||||
|
readOnly: flags&vfs.OPEN_READONLY != 0,
|
||||||
|
}, flags | vfs.OPEN_MEMORY, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (memVFS) Delete(name string, dirSync bool) error {
|
||||||
|
return sqlite3.IOERR_DELETE
|
||||||
|
}
|
||||||
|
|
||||||
|
func (memVFS) Access(name string, flag vfs.AccessFlag) (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (memVFS) FullPathname(name string) (string, error) {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type memDB struct {
|
||||||
|
name string
|
||||||
|
|
||||||
|
// +checklocks:lockMtx
|
||||||
|
pending *memFile
|
||||||
|
// +checklocks:lockMtx
|
||||||
|
reserved *memFile
|
||||||
|
|
||||||
|
// +checklocks:dataMtx
|
||||||
|
data []*[sectorSize]byte
|
||||||
|
|
||||||
|
// +checklocks:dataMtx
|
||||||
|
size int64
|
||||||
|
|
||||||
|
// +checklocks:lockMtx
|
||||||
|
shared int
|
||||||
|
|
||||||
|
// +checklocks:memoryMtx
|
||||||
|
refs int
|
||||||
|
|
||||||
|
lockMtx sync.Mutex
|
||||||
|
dataMtx sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memDB) release() {
|
||||||
|
memoryMtx.Lock()
|
||||||
|
defer memoryMtx.Unlock()
|
||||||
|
if m.refs--; m.refs == 0 && m == memoryDBs[m.name] {
|
||||||
|
delete(memoryDBs, m.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type memFile struct {
|
||||||
|
*memDB
|
||||||
|
lock vfs.LockLevel
|
||||||
|
readOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Ensure these interfaces are implemented:
|
||||||
|
_ vfs.FileLockState = &memFile{}
|
||||||
|
_ vfs.FileSizeHint = &memFile{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *memFile) Close() error {
|
||||||
|
m.release()
|
||||||
|
return m.Unlock(vfs.LOCK_NONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memFile) ReadAt(b []byte, off int64) (n int, err error) {
|
||||||
|
m.dataMtx.RLock()
|
||||||
|
defer m.dataMtx.RUnlock()
|
||||||
|
|
||||||
|
if off >= m.size {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
base := off / sectorSize
|
||||||
|
rest := off % sectorSize
|
||||||
|
have := int64(sectorSize)
|
||||||
|
if base == int64(len(m.data))-1 {
|
||||||
|
have = modRoundUp(m.size, sectorSize)
|
||||||
|
}
|
||||||
|
n = copy(b, (*m.data[base])[rest:have])
|
||||||
|
if n < len(b) {
|
||||||
|
// Assume reads are page aligned.
|
||||||
|
return 0, io.ErrNoProgress
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memFile) WriteAt(b []byte, off int64) (n int, err error) {
|
||||||
|
m.dataMtx.Lock()
|
||||||
|
defer m.dataMtx.Unlock()
|
||||||
|
|
||||||
|
base := off / sectorSize
|
||||||
|
rest := off % sectorSize
|
||||||
|
for base >= int64(len(m.data)) {
|
||||||
|
m.data = append(m.data, new([sectorSize]byte))
|
||||||
|
}
|
||||||
|
n = copy((*m.data[base])[rest:], b)
|
||||||
|
if n < len(b) {
|
||||||
|
// Assume writes are page aligned.
|
||||||
|
return n, io.ErrShortWrite
|
||||||
|
}
|
||||||
|
if size := off + int64(len(b)); size > m.size {
|
||||||
|
m.size = size
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memFile) Truncate(size int64) error {
|
||||||
|
m.dataMtx.Lock()
|
||||||
|
defer m.dataMtx.Unlock()
|
||||||
|
return m.truncate(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// +checklocks:m.dataMtx
|
||||||
|
func (m *memFile) truncate(size int64) error {
|
||||||
|
if size < m.size {
|
||||||
|
base := size / sectorSize
|
||||||
|
rest := size % sectorSize
|
||||||
|
if rest != 0 {
|
||||||
|
clear((*m.data[base])[rest:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sectors := divRoundUp(size, sectorSize)
|
||||||
|
for sectors > int64(len(m.data)) {
|
||||||
|
m.data = append(m.data, new([sectorSize]byte))
|
||||||
|
}
|
||||||
|
clear(m.data[sectors:])
|
||||||
|
m.data = m.data[:sectors]
|
||||||
|
m.size = size
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memFile) Sync(flag vfs.SyncFlag) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memFile) Size() (int64, error) {
|
||||||
|
m.dataMtx.RLock()
|
||||||
|
defer m.dataMtx.RUnlock()
|
||||||
|
return m.size, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const spinWait = 25 * time.Microsecond
|
||||||
|
|
||||||
|
func (m *memFile) Lock(lock vfs.LockLevel) error {
|
||||||
|
if m.lock >= lock {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.readOnly && lock >= vfs.LOCK_RESERVED {
|
||||||
|
return sqlite3.IOERR_LOCK
|
||||||
|
}
|
||||||
|
|
||||||
|
m.lockMtx.Lock()
|
||||||
|
defer m.lockMtx.Unlock()
|
||||||
|
|
||||||
|
switch lock {
|
||||||
|
case vfs.LOCK_SHARED:
|
||||||
|
if m.pending != nil {
|
||||||
|
return sqlite3.BUSY
|
||||||
|
}
|
||||||
|
m.shared++
|
||||||
|
|
||||||
|
case vfs.LOCK_RESERVED:
|
||||||
|
if m.reserved != nil {
|
||||||
|
return sqlite3.BUSY
|
||||||
|
}
|
||||||
|
m.reserved = m
|
||||||
|
|
||||||
|
case vfs.LOCK_EXCLUSIVE:
|
||||||
|
if m.lock < vfs.LOCK_PENDING {
|
||||||
|
if m.pending != nil {
|
||||||
|
return sqlite3.BUSY
|
||||||
|
}
|
||||||
|
m.lock = vfs.LOCK_PENDING
|
||||||
|
m.pending = m
|
||||||
|
}
|
||||||
|
|
||||||
|
for before := time.Now(); m.shared > 1; {
|
||||||
|
if time.Since(before) > spinWait {
|
||||||
|
return sqlite3.BUSY
|
||||||
|
}
|
||||||
|
m.lockMtx.Unlock()
|
||||||
|
runtime.Gosched()
|
||||||
|
m.lockMtx.Lock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.lock = lock
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memFile) Unlock(lock vfs.LockLevel) error {
|
||||||
|
if m.lock <= lock {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m.lockMtx.Lock()
|
||||||
|
defer m.lockMtx.Unlock()
|
||||||
|
|
||||||
|
if m.pending == m {
|
||||||
|
m.pending = nil
|
||||||
|
}
|
||||||
|
if m.reserved == m {
|
||||||
|
m.reserved = nil
|
||||||
|
}
|
||||||
|
if lock < vfs.LOCK_SHARED {
|
||||||
|
m.shared--
|
||||||
|
}
|
||||||
|
m.lock = lock
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memFile) CheckReservedLock() (bool, error) {
|
||||||
|
if m.lock >= vfs.LOCK_RESERVED {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
m.lockMtx.Lock()
|
||||||
|
defer m.lockMtx.Unlock()
|
||||||
|
return m.reserved != nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memFile) SectorSize() int {
|
||||||
|
return sectorSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
|
||||||
|
return vfs.IOCAP_ATOMIC |
|
||||||
|
vfs.IOCAP_SEQUENTIAL |
|
||||||
|
vfs.IOCAP_SAFE_APPEND |
|
||||||
|
vfs.IOCAP_POWERSAFE_OVERWRITE
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memFile) SizeHint(size int64) error {
|
||||||
|
m.dataMtx.Lock()
|
||||||
|
defer m.dataMtx.Unlock()
|
||||||
|
if size > m.size {
|
||||||
|
return m.truncate(size)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memFile) LockState() vfs.LockLevel {
|
||||||
|
return m.lock
|
||||||
|
}
|
||||||
|
|
||||||
|
func divRoundUp(a, b int64) int64 {
|
||||||
|
return (a + b - 1) / b
|
||||||
|
}
|
||||||
|
|
||||||
|
func modRoundUp(a, b int64) int64 {
|
||||||
|
return b - (b-a%b)%b
|
||||||
|
}
|
33
vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go
generated
vendored
Normal file
33
vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
//go:build (freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func osUnlock(file *os.File, start, len int64) _ErrorCode {
|
||||||
|
if start == 0 && len == 0 {
|
||||||
|
err := unix.Flock(int(file.Fd()), unix.LOCK_UN)
|
||||||
|
if err != nil {
|
||||||
|
return _IOERR_UNLOCK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func osLock(file *os.File, how int, def _ErrorCode) _ErrorCode {
|
||||||
|
err := unix.Flock(int(file.Fd()), how)
|
||||||
|
return osLockErrorCode(err, def)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osReadLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.Duration) _ErrorCode {
|
||||||
|
return osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osWriteLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.Duration) _ErrorCode {
|
||||||
|
return osLock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK)
|
||||||
|
}
|
95
vendor/github.com/ncruces/go-sqlite3/vfs/os_darwin.go
generated
vendored
Normal file
95
vendor/github.com/ncruces/go-sqlite3/vfs/os_darwin.go
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
//go:build !(sqlite3_flock || sqlite3_nosys)
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
|
||||||
|
_F_OFD_SETLK = 90
|
||||||
|
_F_OFD_SETLKW = 91
|
||||||
|
_F_OFD_SETLKWTIMEOUT = 93
|
||||||
|
)
|
||||||
|
|
||||||
|
type flocktimeout_t struct {
|
||||||
|
fl unix.Flock_t
|
||||||
|
timeout unix.Timespec
|
||||||
|
}
|
||||||
|
|
||||||
|
func osSync(file *os.File, fullsync, _ /*dataonly*/ bool) error {
|
||||||
|
if fullsync {
|
||||||
|
return file.Sync()
|
||||||
|
}
|
||||||
|
return unix.Fsync(int(file.Fd()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func osAllocate(file *os.File, size int64) error {
|
||||||
|
off, err := file.Seek(0, io.SeekEnd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if size <= off {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
store := unix.Fstore_t{
|
||||||
|
Flags: unix.F_ALLOCATEALL | unix.F_ALLOCATECONTIG,
|
||||||
|
Posmode: unix.F_PEOFPOSMODE,
|
||||||
|
Offset: 0,
|
||||||
|
Length: size - off,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get a continuous chunk of disk space.
|
||||||
|
err = unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store)
|
||||||
|
if err != nil {
|
||||||
|
// OK, perhaps we are too fragmented, allocate non-continuous.
|
||||||
|
store.Flags = unix.F_ALLOCATEALL
|
||||||
|
unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store)
|
||||||
|
}
|
||||||
|
return file.Truncate(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osUnlock(file *os.File, start, len int64) _ErrorCode {
|
||||||
|
err := unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &unix.Flock_t{
|
||||||
|
Type: unix.F_UNLCK,
|
||||||
|
Start: start,
|
||||||
|
Len: len,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return _IOERR_UNLOCK
|
||||||
|
}
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode {
|
||||||
|
lock := flocktimeout_t{fl: unix.Flock_t{
|
||||||
|
Type: typ,
|
||||||
|
Start: start,
|
||||||
|
Len: len,
|
||||||
|
}}
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case timeout == 0:
|
||||||
|
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &lock.fl)
|
||||||
|
case timeout < 0:
|
||||||
|
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKW, &lock.fl)
|
||||||
|
default:
|
||||||
|
lock.timeout = unix.NsecToTimespec(int64(timeout / time.Nanosecond))
|
||||||
|
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKWTIMEOUT, &lock.fl)
|
||||||
|
}
|
||||||
|
return osLockErrorCode(err, def)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||||
|
return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||||
|
return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK)
|
||||||
|
}
|
34
vendor/github.com/ncruces/go-sqlite3/vfs/os_f2fs_linux.go
generated
vendored
Normal file
34
vendor/github.com/ncruces/go-sqlite3/vfs/os_f2fs_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
//go:build (amd64 || arm64 || riscv64) && !sqlite3_nosys
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_F2FS_IOC_START_ATOMIC_WRITE = 62721
|
||||||
|
_F2FS_IOC_COMMIT_ATOMIC_WRITE = 62722
|
||||||
|
_F2FS_IOC_ABORT_ATOMIC_WRITE = 62725
|
||||||
|
_F2FS_IOC_GET_FEATURES = 2147808524
|
||||||
|
_F2FS_FEATURE_ATOMIC_WRITE = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
func osBatchAtomic(file *os.File) bool {
|
||||||
|
flags, err := unix.IoctlGetInt(int(file.Fd()), _F2FS_IOC_GET_FEATURES)
|
||||||
|
return err == nil && flags&_F2FS_FEATURE_ATOMIC_WRITE != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *vfsFile) BeginAtomicWrite() error {
|
||||||
|
return unix.IoctlSetInt(int(f.Fd()), _F2FS_IOC_START_ATOMIC_WRITE, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *vfsFile) CommitAtomicWrite() error {
|
||||||
|
return unix.IoctlSetInt(int(f.Fd()), _F2FS_IOC_COMMIT_ATOMIC_WRITE, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *vfsFile) RollbackAtomicWrite() error {
|
||||||
|
return unix.IoctlSetInt(int(f.Fd()), _F2FS_IOC_ABORT_ATOMIC_WRITE, 0)
|
||||||
|
}
|
71
vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go
generated
vendored
Normal file
71
vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
//go:build !(sqlite3_flock || sqlite3_nosys)
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func osSync(file *os.File, _ /*fullsync*/, _ /*dataonly*/ bool) error {
|
||||||
|
// SQLite trusts Linux's fdatasync for all fsync's.
|
||||||
|
return unix.Fdatasync(int(file.Fd()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func osAllocate(file *os.File, size int64) error {
|
||||||
|
if size == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return unix.Fallocate(int(file.Fd()), 0, 0, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osUnlock(file *os.File, start, len int64) _ErrorCode {
|
||||||
|
err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &unix.Flock_t{
|
||||||
|
Type: unix.F_UNLCK,
|
||||||
|
Start: start,
|
||||||
|
Len: len,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return _IOERR_UNLOCK
|
||||||
|
}
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode {
|
||||||
|
lock := unix.Flock_t{
|
||||||
|
Type: typ,
|
||||||
|
Start: start,
|
||||||
|
Len: len,
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case timeout == 0:
|
||||||
|
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
|
||||||
|
case timeout < 0:
|
||||||
|
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLKW, &lock)
|
||||||
|
default:
|
||||||
|
before := time.Now()
|
||||||
|
for {
|
||||||
|
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
|
||||||
|
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if timeout < time.Since(before) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
osSleep(time.Duration(rand.Int63n(int64(time.Millisecond))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return osLockErrorCode(err, def)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||||
|
return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
|
||||||
|
return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK)
|
||||||
|
}
|
36
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_access.go
generated
vendored
Normal file
36
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_access.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
//go:build !unix || sqlite3_nosys
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func osAccess(path string, flags AccessFlag) error {
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if flags == ACCESS_EXISTS {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
S_IREAD = 0400
|
||||||
|
S_IWRITE = 0200
|
||||||
|
S_IEXEC = 0100
|
||||||
|
)
|
||||||
|
|
||||||
|
var want fs.FileMode = S_IREAD
|
||||||
|
if flags == ACCESS_READWRITE {
|
||||||
|
want |= S_IWRITE
|
||||||
|
}
|
||||||
|
if fi.IsDir() {
|
||||||
|
want |= S_IEXEC
|
||||||
|
}
|
||||||
|
if fi.Mode()&want != want {
|
||||||
|
return fs.ErrPermission
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
19
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_alloc.go
generated
vendored
Normal file
19
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_alloc.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
//go:build !(linux || darwin) || sqlite3_flock || sqlite3_nosys
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func osAllocate(file *os.File, size int64) error {
|
||||||
|
off, err := file.Seek(0, io.SeekEnd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if size <= off {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return file.Truncate(size)
|
||||||
|
}
|
9
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_atomic.go
generated
vendored
Normal file
9
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_atomic.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
//go:build !linux || !(amd64 || arm64 || riscv64) || sqlite3_nosys
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func osBatchAtomic(*os.File) bool {
|
||||||
|
return false
|
||||||
|
}
|
14
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_mode.go
generated
vendored
Normal file
14
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_mode.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
//go:build !unix || sqlite3_nosys
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func osSetMode(file *os.File, modeof string) error {
|
||||||
|
fi, err := os.Stat(modeof)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
file.Chmod(fi.Mode())
|
||||||
|
return nil
|
||||||
|
}
|
9
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_sleep.go
generated
vendored
Normal file
9
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_sleep.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
//go:build !windows || sqlite3_nosys
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
func osSleep(d time.Duration) {
|
||||||
|
time.Sleep(d)
|
||||||
|
}
|
9
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_sync.go
generated
vendored
Normal file
9
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_sync.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
//go:build !(linux || darwin) || sqlite3_flock || sqlite3_nosys
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func osSync(file *os.File, _ /*fullsync*/, _ /*dataonly*/ bool) error {
|
||||||
|
return file.Sync()
|
||||||
|
}
|
33
vendor/github.com/ncruces/go-sqlite3/vfs/os_unix.go
generated
vendored
Normal file
33
vendor/github.com/ncruces/go-sqlite3/vfs/os_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
//go:build unix && !sqlite3_nosys
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func osAccess(path string, flags AccessFlag) error {
|
||||||
|
var access uint32 // unix.F_OK
|
||||||
|
switch flags {
|
||||||
|
case ACCESS_READWRITE:
|
||||||
|
access = unix.R_OK | unix.W_OK
|
||||||
|
case ACCESS_READ:
|
||||||
|
access = unix.R_OK
|
||||||
|
}
|
||||||
|
return unix.Access(path, access)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osSetMode(file *os.File, modeof string) error {
|
||||||
|
fi, err := os.Stat(modeof)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
file.Chmod(fi.Mode())
|
||||||
|
if sys, ok := fi.Sys().(*syscall.Stat_t); ok {
|
||||||
|
file.Chown(int(sys.Uid), int(sys.Gid))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
106
vendor/github.com/ncruces/go-sqlite3/vfs/os_unix_lock.go
generated
vendored
Normal file
106
vendor/github.com/ncruces/go-sqlite3/vfs/os_unix_lock.go
generated
vendored
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
//go:build (linux || darwin || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func osGetSharedLock(file *os.File) _ErrorCode {
|
||||||
|
// Test the PENDING lock before acquiring a new SHARED lock.
|
||||||
|
if lock, _ := osGetLock(file, _PENDING_BYTE, 1); lock == unix.F_WRLCK {
|
||||||
|
return _BUSY
|
||||||
|
}
|
||||||
|
// Acquire the SHARED lock.
|
||||||
|
return osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osGetReservedLock(file *os.File) _ErrorCode {
|
||||||
|
// Acquire the RESERVED lock.
|
||||||
|
return osWriteLock(file, _RESERVED_BYTE, 1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osGetPendingLock(file *os.File, block bool) _ErrorCode {
|
||||||
|
var timeout time.Duration
|
||||||
|
if block {
|
||||||
|
timeout = -1
|
||||||
|
}
|
||||||
|
// Acquire the PENDING lock.
|
||||||
|
return osWriteLock(file, _PENDING_BYTE, 1, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode {
|
||||||
|
var timeout time.Duration
|
||||||
|
if wait {
|
||||||
|
timeout = time.Millisecond
|
||||||
|
}
|
||||||
|
// Acquire the EXCLUSIVE lock.
|
||||||
|
return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
|
||||||
|
if state >= LOCK_EXCLUSIVE {
|
||||||
|
// Downgrade to a SHARED lock.
|
||||||
|
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
|
||||||
|
// In theory, the downgrade to a SHARED cannot fail because another
|
||||||
|
// process is holding an incompatible lock. If it does, this
|
||||||
|
// indicates that the other process is not following the locking
|
||||||
|
// protocol. If this happens, return _IOERR_RDLOCK. Returning
|
||||||
|
// BUSY would confuse the upper layer.
|
||||||
|
return _IOERR_RDLOCK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Release the PENDING and RESERVED locks.
|
||||||
|
return osUnlock(file, _PENDING_BYTE, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode {
|
||||||
|
// Release all locks.
|
||||||
|
return osUnlock(file, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
|
||||||
|
// Test the RESERVED lock.
|
||||||
|
lock, rc := osGetLock(file, _RESERVED_BYTE, 1)
|
||||||
|
return lock == unix.F_WRLCK, rc
|
||||||
|
}
|
||||||
|
|
||||||
|
func osGetLock(file *os.File, start, len int64) (int16, _ErrorCode) {
|
||||||
|
lock := unix.Flock_t{
|
||||||
|
Type: unix.F_WRLCK,
|
||||||
|
Start: start,
|
||||||
|
Len: len,
|
||||||
|
}
|
||||||
|
if unix.FcntlFlock(file.Fd(), unix.F_GETLK, &lock) != nil {
|
||||||
|
return 0, _IOERR_CHECKRESERVEDLOCK
|
||||||
|
}
|
||||||
|
return lock.Type, _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
|
||||||
|
if err == nil {
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
if errno, ok := err.(unix.Errno); ok {
|
||||||
|
switch errno {
|
||||||
|
case
|
||||||
|
unix.EACCES,
|
||||||
|
unix.EAGAIN,
|
||||||
|
unix.EBUSY,
|
||||||
|
unix.EINTR,
|
||||||
|
unix.ENOLCK,
|
||||||
|
unix.EDEADLK,
|
||||||
|
unix.ETIMEDOUT:
|
||||||
|
return _BUSY
|
||||||
|
case unix.EPERM:
|
||||||
|
return _PERM
|
||||||
|
}
|
||||||
|
if errno == unix.EWOULDBLOCK && unix.EWOULDBLOCK != unix.EAGAIN {
|
||||||
|
return _BUSY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
186
vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go
generated
vendored
Normal file
186
vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
//go:build !sqlite3_nosys
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func osGetSharedLock(file *os.File) _ErrorCode {
|
||||||
|
// Acquire the PENDING lock temporarily before acquiring a new SHARED lock.
|
||||||
|
rc := osReadLock(file, _PENDING_BYTE, 1, 0)
|
||||||
|
if rc == _OK {
|
||||||
|
// Acquire the SHARED lock.
|
||||||
|
rc = osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
|
||||||
|
|
||||||
|
// Release the PENDING lock.
|
||||||
|
osUnlock(file, _PENDING_BYTE, 1)
|
||||||
|
}
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
func osGetReservedLock(file *os.File) _ErrorCode {
|
||||||
|
// Acquire the RESERVED lock.
|
||||||
|
return osWriteLock(file, _RESERVED_BYTE, 1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osGetPendingLock(file *os.File, block bool) _ErrorCode {
|
||||||
|
var timeout time.Duration
|
||||||
|
if block {
|
||||||
|
timeout = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire the PENDING lock.
|
||||||
|
return osWriteLock(file, _PENDING_BYTE, 1, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode {
|
||||||
|
var timeout time.Duration
|
||||||
|
if wait {
|
||||||
|
timeout = time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release the SHARED lock.
|
||||||
|
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
|
||||||
|
|
||||||
|
// Acquire the EXCLUSIVE lock.
|
||||||
|
rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
|
||||||
|
|
||||||
|
if rc != _OK {
|
||||||
|
// Reacquire the SHARED lock.
|
||||||
|
osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
|
||||||
|
}
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
|
||||||
|
if state >= LOCK_EXCLUSIVE {
|
||||||
|
// Release the EXCLUSIVE lock.
|
||||||
|
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
|
||||||
|
|
||||||
|
// Reacquire the SHARED lock.
|
||||||
|
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
|
||||||
|
// This should never happen.
|
||||||
|
// We should always be able to reacquire the read lock.
|
||||||
|
return _IOERR_RDLOCK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release the PENDING and RESERVED locks.
|
||||||
|
if state >= LOCK_RESERVED {
|
||||||
|
osUnlock(file, _RESERVED_BYTE, 1)
|
||||||
|
}
|
||||||
|
if state >= LOCK_PENDING {
|
||||||
|
osUnlock(file, _PENDING_BYTE, 1)
|
||||||
|
}
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
|
||||||
|
// Release all locks.
|
||||||
|
if state >= LOCK_RESERVED {
|
||||||
|
osUnlock(file, _RESERVED_BYTE, 1)
|
||||||
|
}
|
||||||
|
if state >= LOCK_SHARED {
|
||||||
|
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
|
||||||
|
}
|
||||||
|
if state >= LOCK_PENDING {
|
||||||
|
osUnlock(file, _PENDING_BYTE, 1)
|
||||||
|
}
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
|
||||||
|
// Test the RESERVED lock.
|
||||||
|
rc := osLock(file, 0, _RESERVED_BYTE, 1, 0, _IOERR_CHECKRESERVEDLOCK)
|
||||||
|
if rc == _BUSY {
|
||||||
|
return true, _OK
|
||||||
|
}
|
||||||
|
if rc == _OK {
|
||||||
|
// Release the RESERVED lock.
|
||||||
|
osUnlock(file, _RESERVED_BYTE, 1)
|
||||||
|
}
|
||||||
|
return false, rc
|
||||||
|
}
|
||||||
|
|
||||||
|
func osUnlock(file *os.File, start, len uint32) _ErrorCode {
|
||||||
|
err := windows.UnlockFileEx(windows.Handle(file.Fd()),
|
||||||
|
0, len, 0, &windows.Overlapped{Offset: start})
|
||||||
|
if err == windows.ERROR_NOT_LOCKED {
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return _IOERR_UNLOCK
|
||||||
|
}
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode {
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case timeout == 0:
|
||||||
|
err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len)
|
||||||
|
case timeout < 0:
|
||||||
|
err = osLockEx(file, flags, start, len)
|
||||||
|
default:
|
||||||
|
before := time.Now()
|
||||||
|
for {
|
||||||
|
err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len)
|
||||||
|
if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if timeout < time.Since(before) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
osSleep(time.Duration(rand.Int63n(int64(time.Millisecond))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return osLockErrorCode(err, def)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osLockEx(file *os.File, flags, start, len uint32) error {
|
||||||
|
return windows.LockFileEx(windows.Handle(file.Fd()), flags,
|
||||||
|
0, len, 0, &windows.Overlapped{Offset: start})
|
||||||
|
}
|
||||||
|
|
||||||
|
func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
|
||||||
|
return osLock(file, 0, start, len, timeout, _IOERR_RDLOCK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
|
||||||
|
return osLock(file, windows.LOCKFILE_EXCLUSIVE_LOCK, start, len, timeout, _IOERR_LOCK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
|
||||||
|
if err == nil {
|
||||||
|
return _OK
|
||||||
|
}
|
||||||
|
if errno, ok := err.(windows.Errno); ok {
|
||||||
|
// https://devblogs.microsoft.com/oldnewthing/20140905-00/?p=63
|
||||||
|
switch errno {
|
||||||
|
case
|
||||||
|
windows.ERROR_LOCK_VIOLATION,
|
||||||
|
windows.ERROR_IO_PENDING,
|
||||||
|
windows.ERROR_OPERATION_ABORTED:
|
||||||
|
return _BUSY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
func osSleep(d time.Duration) {
|
||||||
|
if d > 0 {
|
||||||
|
period := max(1, d/(5*time.Millisecond))
|
||||||
|
if period < 16 {
|
||||||
|
windows.TimeBeginPeriod(uint32(period))
|
||||||
|
}
|
||||||
|
time.Sleep(d)
|
||||||
|
if period < 16 {
|
||||||
|
windows.TimeEndPeriod(uint32(period))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
vendor/github.com/ncruces/go-sqlite3/vfs/registry.go
generated
vendored
Normal file
48
vendor/github.com/ncruces/go-sqlite3/vfs/registry.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// +checklocks:vfsRegistryMtx
|
||||||
|
vfsRegistry map[string]VFS
|
||||||
|
vfsRegistryMtx sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find returns a VFS given its name.
|
||||||
|
// If there is no match, nil is returned.
|
||||||
|
// If name is empty, the default VFS is returned.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/vfs_find.html
|
||||||
|
func Find(name string) VFS {
|
||||||
|
if name == "" || name == "os" {
|
||||||
|
return vfsOS{}
|
||||||
|
}
|
||||||
|
vfsRegistryMtx.RLock()
|
||||||
|
defer vfsRegistryMtx.RUnlock()
|
||||||
|
return vfsRegistry[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register registers a VFS.
|
||||||
|
// Empty and "os" are reserved names.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/vfs_find.html
|
||||||
|
func Register(name string, vfs VFS) {
|
||||||
|
if name == "" || name == "os" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vfsRegistryMtx.Lock()
|
||||||
|
defer vfsRegistryMtx.Unlock()
|
||||||
|
if vfsRegistry == nil {
|
||||||
|
vfsRegistry = map[string]VFS{}
|
||||||
|
}
|
||||||
|
vfsRegistry[name] = vfs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unregister unregisters a VFS.
|
||||||
|
//
|
||||||
|
// https://sqlite.org/c3ref/vfs_find.html
|
||||||
|
func Unregister(name string) {
|
||||||
|
vfsRegistryMtx.Lock()
|
||||||
|
defer vfsRegistryMtx.Unlock()
|
||||||
|
delete(vfsRegistry, name)
|
||||||
|
}
|
173
vendor/github.com/ncruces/go-sqlite3/vfs/shm.go
generated
vendored
Normal file
173
vendor/github.com/ncruces/go-sqlite3/vfs/shm.go
generated
vendored
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
//go:build (darwin || linux) && (amd64 || arm64 || riscv64) && !(sqlite3_flock || sqlite3_noshm || sqlite3_nosys)
|
||||||
|
|
||||||
|
package vfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/internal/util"
|
||||||
|
"github.com/tetratelabs/wazero/api"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SupportsSharedMemory is false on platforms that do not support shared memory.
|
||||||
|
// To use [WAL without shared-memory], you need to set [EXCLUSIVE locking mode].
|
||||||
|
//
|
||||||
|
// [WAL without shared-memory]: https://sqlite.org/wal.html#noshm
|
||||||
|
// [EXCLUSIVE locking mode]: https://sqlite.org/pragma.html#pragma_locking_mode
|
||||||
|
const SupportsSharedMemory = true
|
||||||
|
|
||||||
|
const (
|
||||||
|
_SHM_NLOCK = 8
|
||||||
|
_SHM_BASE = 120
|
||||||
|
_SHM_DMS = _SHM_BASE + _SHM_NLOCK
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f *vfsFile) SharedMemory() SharedMemory { return f.shm }
|
||||||
|
|
||||||
|
// NewSharedMemory returns a shared-memory WAL-index
|
||||||
|
// backed by a file with the given path.
|
||||||
|
// It will return nil if shared-memory is not supported,
|
||||||
|
// or not appropriate for the given flags.
|
||||||
|
// Only [OPEN_MAIN_DB] databases may need a WAL-index.
|
||||||
|
// You must ensure all concurrent accesses to a database
|
||||||
|
// use shared-memory instances created with the same path.
|
||||||
|
func NewSharedMemory(path string, flags OpenFlag) SharedMemory {
|
||||||
|
if flags&OPEN_MAIN_DB == 0 || flags&(OPEN_DELETEONCLOSE|OPEN_MEMORY) != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &vfsShm{
|
||||||
|
path: path,
|
||||||
|
readOnly: flags&OPEN_READONLY != 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type vfsShm struct {
|
||||||
|
*os.File
|
||||||
|
path string
|
||||||
|
regions []*util.MappedRegion
|
||||||
|
readOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, error) {
|
||||||
|
// Ensure size is a multiple of the OS page size.
|
||||||
|
if int(size)&(unix.Getpagesize()-1) != 0 {
|
||||||
|
return 0, _IOERR_SHMMAP
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.File == nil {
|
||||||
|
var flag int
|
||||||
|
if s.readOnly {
|
||||||
|
flag = unix.O_RDONLY
|
||||||
|
} else {
|
||||||
|
flag = unix.O_RDWR
|
||||||
|
}
|
||||||
|
f, err := os.OpenFile(s.path,
|
||||||
|
flag|unix.O_CREAT|unix.O_NOFOLLOW, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return 0, _CANTOPEN
|
||||||
|
}
|
||||||
|
s.File = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dead man's switch.
|
||||||
|
if lock, rc := osGetLock(s.File, _SHM_DMS, 1); rc != _OK {
|
||||||
|
return 0, _IOERR_LOCK
|
||||||
|
} else if lock == unix.F_WRLCK {
|
||||||
|
return 0, _BUSY
|
||||||
|
} else if lock == unix.F_UNLCK {
|
||||||
|
if s.readOnly {
|
||||||
|
return 0, _READONLY_CANTINIT
|
||||||
|
}
|
||||||
|
if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc != _OK {
|
||||||
|
return 0, rc
|
||||||
|
}
|
||||||
|
if err := s.Truncate(0); err != nil {
|
||||||
|
return 0, _IOERR_SHMOPEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rc := osReadLock(s.File, _SHM_DMS, 1, 0); rc != _OK {
|
||||||
|
return 0, rc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if file is big enough.
|
||||||
|
o, err := s.Seek(0, io.SeekEnd)
|
||||||
|
if err != nil {
|
||||||
|
return 0, _IOERR_SHMSIZE
|
||||||
|
}
|
||||||
|
if n := (int64(id) + 1) * int64(size); n > o {
|
||||||
|
if !extend {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
err := osAllocate(s.File, n)
|
||||||
|
if err != nil {
|
||||||
|
return 0, _IOERR_SHMSIZE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var prot int
|
||||||
|
if s.readOnly {
|
||||||
|
prot = unix.PROT_READ
|
||||||
|
} else {
|
||||||
|
prot = unix.PROT_READ | unix.PROT_WRITE
|
||||||
|
}
|
||||||
|
r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, prot)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
s.regions = append(s.regions, r)
|
||||||
|
return r.Ptr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) error {
|
||||||
|
// Argument check.
|
||||||
|
if n <= 0 || offset < 0 || offset+n > _SHM_NLOCK {
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
switch flags {
|
||||||
|
case
|
||||||
|
_SHM_LOCK | _SHM_SHARED,
|
||||||
|
_SHM_LOCK | _SHM_EXCLUSIVE,
|
||||||
|
_SHM_UNLOCK | _SHM_SHARED,
|
||||||
|
_SHM_UNLOCK | _SHM_EXCLUSIVE:
|
||||||
|
//
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
if n != 1 && flags&_SHM_EXCLUSIVE == 0 {
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case flags&_SHM_UNLOCK != 0:
|
||||||
|
return osUnlock(s.File, _SHM_BASE+int64(offset), int64(n))
|
||||||
|
case flags&_SHM_SHARED != 0:
|
||||||
|
return osReadLock(s.File, _SHM_BASE+int64(offset), int64(n), 0)
|
||||||
|
case flags&_SHM_EXCLUSIVE != 0:
|
||||||
|
return osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n), 0)
|
||||||
|
default:
|
||||||
|
panic(util.AssertErr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *vfsShm) shmUnmap(delete bool) {
|
||||||
|
if s.File == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmap regions.
|
||||||
|
for _, r := range s.regions {
|
||||||
|
r.Unmap()
|
||||||
|
}
|
||||||
|
clear(s.regions)
|
||||||
|
s.regions = s.regions[:0]
|
||||||
|
|
||||||
|
// Close the file.
|
||||||
|
defer s.Close()
|
||||||
|
if delete {
|
||||||
|
os.Remove(s.Name())
|
||||||
|
}
|
||||||
|
s.File = nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue