mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-06-02 21:39:25 +00:00
try out using wasm sqlite instead of modernc.org/sqlite
This commit is contained in:
parent
3554991444
commit
d04d4f2378
15
go.mod
15
go.mod
|
@ -45,6 +45,7 @@ require (
|
|||
github.com/miekg/dns v1.1.59
|
||||
github.com/minio/minio-go/v7 v7.0.70
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/ncruces/go-sqlite3 v0.14.0
|
||||
github.com/oklog/ulid v1.3.1
|
||||
github.com/prometheus/client_golang v1.18.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
|
@ -79,7 +80,6 @@ require (
|
|||
golang.org/x/text v0.15.0
|
||||
gopkg.in/mcuadros/go-syslog.v2 v2.3.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.29.8
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
)
|
||||
|
||||
|
@ -147,7 +147,6 @@ require (
|
|||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.2.2 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
|
@ -173,7 +172,7 @@ require (
|
|||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // 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/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
|
@ -183,7 +182,6 @@ require (
|
|||
github.com/prometheus/common v0.45.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
|
@ -198,6 +196,7 @@ require (
|
|||
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/tdewolff/parse/v2 v2.7.13 // indirect
|
||||
github.com/tetratelabs/wazero v1.7.1 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/toqueteos/webbrowser v1.2.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
|
@ -212,7 +211,7 @@ require (
|
|||
golang.org/x/arch v0.7.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
|
@ -221,10 +220,4 @@ require (
|
|||
google.golang.org/protobuf v1.34.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
modernc.org/libc v1.49.3 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
modernc.org/token v1.1.0 // indirect
|
||||
)
|
||||
|
|
46
go.sum
46
go.sum
|
@ -318,8 +318,6 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
|||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
|
@ -350,8 +348,6 @@ github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
|||
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
|
@ -416,8 +412,6 @@ github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
|
|||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||
|
@ -445,8 +439,10 @@ 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/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
|
||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||
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-sqlite3 v0.14.0 h1:R1Jmnc7o5ECQeZPNzbLHfE8vz1DLewV+bJypHSad354=
|
||||
github.com/ncruces/go-sqlite3 v0.14.0/go.mod h1:NRmOFatwnQaZq8niw0f3k/j3a0yUVt250qcVeDuyANY=
|
||||
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/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
|
@ -482,8 +478,6 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
|
|||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
|
@ -558,6 +552,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/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/tetratelabs/wazero v1.7.1 h1:QtSfd6KLc41DIMpDYlJdoMc6k7QTN246DM2+n2Y/Dx8=
|
||||
github.com/tetratelabs/wazero v1.7.1/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/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
||||
github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo=
|
||||
|
@ -631,8 +627,6 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
|
||||
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.8-concurrency-workaround h1:ESobxED9bfE0nOQP/WPv9+tMR8oZoDIWRKlNK2Vs4Ms=
|
||||
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.29.8-concurrency-workaround/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk=
|
||||
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
|
||||
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
|
@ -767,8 +761,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -991,30 +985,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk=
|
||||
modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA=
|
||||
modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg=
|
||||
modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8=
|
||||
mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
|
|
@ -48,8 +48,6 @@ import (
|
|||
"github.com/uptrace/bun/dialect/pgdialect"
|
||||
"github.com/uptrace/bun/dialect/sqlitedialect"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
|
||||
"modernc.org/sqlite"
|
||||
)
|
||||
|
||||
// DBService satisfies the DB interface
|
||||
|
@ -339,9 +337,7 @@ func sqliteConn(ctx context.Context, state *state.State) (*bun.DB, error) {
|
|||
// Open new DB instance
|
||||
sqldb, err := sql.Open("sqlite-gts", address)
|
||||
if err != nil {
|
||||
if errWithCode, ok := err.(*sqlite.Error); ok {
|
||||
err = errors.New(sqlite.ErrorCodeString[errWithCode.Code()])
|
||||
}
|
||||
err = processSQLiteError(err) // this adds error code information
|
||||
return nil, fmt.Errorf("could not open sqlite db with address %s: %w", address, err)
|
||||
}
|
||||
|
||||
|
@ -356,9 +352,7 @@ func sqliteConn(ctx context.Context, state *state.State) (*bun.DB, error) {
|
|||
|
||||
// ping to check the db is there and listening
|
||||
if err := db.PingContext(ctx); err != nil {
|
||||
if errWithCode, ok := err.(*sqlite.Error); ok {
|
||||
err = errors.New(sqlite.ErrorCodeString[errWithCode.Code()])
|
||||
}
|
||||
err = processSQLiteError(err) // this adds error code information
|
||||
return nil, fmt.Errorf("sqlite ping: %w", err)
|
||||
}
|
||||
log.Infof(ctx, "connected to SQLITE database with address %s", address)
|
||||
|
|
|
@ -25,15 +25,37 @@ import (
|
|||
_ "unsafe" // linkname shenanigans
|
||||
|
||||
pgx "github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"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.
|
||||
// global wrapped gts driver instances.
|
||||
gtsPostgresDriver = &PostgreSQLDriver{}
|
||||
gtsSQLiteDriver = &SQLiteDriver{}
|
||||
|
||||
// global PostgreSQL driver instances.
|
||||
postgresDriver = pgx.GetDefaultDriver()
|
||||
sqliteDriver = getSQLiteDriver()
|
||||
|
||||
// global SQLite3 driver instance.
|
||||
sqliteDriver = &sqlite.Driver{
|
||||
Init: func(c *sqlite3.Conn) error {
|
||||
return c.BusyHandler(func(ctx context.Context, i int) (retry bool) {
|
||||
backoff := 2 * time.Millisecond * (1 << (2*i + 1))
|
||||
if backoff > 5*time.Minute {
|
||||
return false
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false
|
||||
case <-time.After(backoff):
|
||||
return true
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
// check the postgres connection
|
||||
// conforms to our conn{} interface.
|
||||
|
@ -43,12 +65,9 @@ var (
|
|||
_ conn = (*pgx.Conn)(nil)
|
||||
)
|
||||
|
||||
//go:linkname getSQLiteDriver modernc.org/sqlite.newDriver
|
||||
func getSQLiteDriver() *sqlite.Driver
|
||||
|
||||
func init() {
|
||||
sql.Register("pgx-gts", &PostgreSQLDriver{})
|
||||
sql.Register("sqlite-gts", &SQLiteDriver{})
|
||||
sql.Register("pgx-gts", gtsPostgresDriver)
|
||||
sql.Register("sqlite-gts", gtsSQLiteDriver)
|
||||
}
|
||||
|
||||
// PostgreSQLDriver is our own wrapper around the
|
||||
|
@ -157,14 +176,35 @@ func (stmt *PostgreSQLStmt) QueryContext(ctx context.Context, args []driver.Name
|
|||
type SQLiteDriver struct{}
|
||||
|
||||
func (d *SQLiteDriver) Open(name string) (driver.Conn, error) {
|
||||
c, err := sqliteDriver.Open(name)
|
||||
cc, err := d.OpenConnector(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SQLiteConn{conn: c.(conn)}, nil
|
||||
return cc.Connect(context.Background())
|
||||
}
|
||||
|
||||
type SQLiteConn struct{ conn }
|
||||
func (d *SQLiteDriver) OpenConnector(name string) (driver.Connector, error) {
|
||||
cc, err := sqliteDriver.OpenConnector(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SQLiteConnector{cc}, nil
|
||||
}
|
||||
|
||||
type SQLiteConnector struct{ driver.Connector }
|
||||
|
||||
func (c *SQLiteConnector) Driver() driver.Driver { return gtsSQLiteDriver }
|
||||
|
||||
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.(sqlite.ConnIface)}, nil
|
||||
}
|
||||
|
||||
type SQLiteConn struct{ sqlite.ConnIface }
|
||||
|
||||
func (c *SQLiteConn) Begin() (driver.Tx, error) {
|
||||
return c.BeginTx(context.Background(), driver.TxOptions{})
|
||||
|
@ -172,42 +212,43 @@ func (c *SQLiteConn) Begin() (driver.Tx, error) {
|
|||
|
||||
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)
|
||||
tx, err = c.ConnIface.BeginTx(ctx, opts)
|
||||
err = processSQLiteError(err)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SQLiteTx{Context: ctx, Tx: tx}, nil
|
||||
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) (st driver.Stmt, err error) {
|
||||
func (c *SQLiteConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) {
|
||||
err = retryOnBusy(ctx, func() error {
|
||||
st, err = c.conn.PrepareContext(ctx, query)
|
||||
stmt, err = c.ConnIface.PrepareContext(ctx, query)
|
||||
err = processSQLiteError(err)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SQLiteStmt{st.(stmt)}, nil
|
||||
return &SQLiteStmt{StmtIface: stmt.(sqlite.StmtIface)}, 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
|
||||
})
|
||||
func (c *SQLiteConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (res driver.Result, err error) {
|
||||
st, err := c.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stmt := st.(*SQLiteStmt)
|
||||
res, err = stmt.ExecContext(ctx, args)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -216,45 +257,60 @@ func (c *SQLiteConn) Query(query string, args []driver.Value) (driver.Rows, erro
|
|||
}
|
||||
|
||||
func (c *SQLiteConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) {
|
||||
st, err := c.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stmt := st.(*SQLiteStmt)
|
||||
err = retryOnBusy(ctx, func() error {
|
||||
rows, err = c.conn.QueryContext(ctx, query, args)
|
||||
rows, err = stmt.StmtIface.QueryContext(ctx, args)
|
||||
err = processSQLiteError(err)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
if rows != nil {
|
||||
_ = rows.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &SQLiteTmpStmtRows{
|
||||
SQLiteRows: SQLiteRows{
|
||||
Ctx: ctx,
|
||||
RowsIface: rows.(sqlite.RowsIface),
|
||||
},
|
||||
SQLiteStmt: stmt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *SQLiteConn) Close() (err error) {
|
||||
// see: https://www.sqlite.org/pragma.html#pragma_optimize
|
||||
const onClose = "PRAGMA analysis_limit=1000; PRAGMA optimize;"
|
||||
if r, ok := c.ConnIface.(sqlite3.DriverConn); ok {
|
||||
_ = r.Raw().Exec(onClose) // perform ASAP
|
||||
_ = r.Raw().Close()
|
||||
}
|
||||
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
|
||||
}
|
||||
type SQLiteTx struct{ driver.Tx }
|
||||
|
||||
func (tx *SQLiteTx) Commit() (err error) {
|
||||
err = retryOnBusy(tx.Context, func() error {
|
||||
return retryOnBusy(context.Background(), func() error {
|
||||
err = tx.Tx.Commit()
|
||||
err = processSQLiteError(err)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (tx *SQLiteTx) Rollback() (err error) {
|
||||
err = retryOnBusy(tx.Context, func() error {
|
||||
return retryOnBusy(context.Background(), func() error {
|
||||
err = tx.Tx.Rollback()
|
||||
err = processSQLiteError(err)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
type SQLiteStmt struct{ stmt }
|
||||
type SQLiteStmt struct{ sqlite.StmtIface }
|
||||
|
||||
func (stmt *SQLiteStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||
return stmt.ExecContext(context.Background(), toNamedValues(args))
|
||||
|
@ -262,7 +318,7 @@ func (stmt *SQLiteStmt) Exec(args []driver.Value) (driver.Result, error) {
|
|||
|
||||
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)
|
||||
res, err = stmt.StmtIface.ExecContext(ctx, args)
|
||||
err = processSQLiteError(err)
|
||||
return err
|
||||
})
|
||||
|
@ -275,13 +331,62 @@ func (stmt *SQLiteStmt) Query(args []driver.Value) (driver.Rows, error) {
|
|||
|
||||
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)
|
||||
rows, err = stmt.StmtIface.QueryContext(ctx, args)
|
||||
err = processSQLiteError(err)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SQLiteRows{
|
||||
Ctx: ctx,
|
||||
RowsIface: rows.(sqlite.RowsIface),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (stmt *SQLiteStmt) Close() (err error) {
|
||||
err = retryOnBusy(context.Background(), func() error {
|
||||
err = stmt.StmtIface.Close()
|
||||
err = processSQLiteError(err)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
type SQLiteRows struct {
|
||||
Ctx context.Context
|
||||
sqlite.RowsIface
|
||||
}
|
||||
|
||||
func (r *SQLiteRows) Next(dest []driver.Value) (err error) {
|
||||
err = retryOnBusy(r.Ctx, func() error {
|
||||
err = r.RowsIface.Next(dest)
|
||||
err = processSQLiteError(err)
|
||||
return err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *SQLiteRows) Close() (err error) {
|
||||
err = retryOnBusy(context.Background(), func() error {
|
||||
err = r.RowsIface.Close()
|
||||
err = processSQLiteError(err)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
type SQLiteTmpStmtRows struct {
|
||||
SQLiteRows
|
||||
*SQLiteStmt
|
||||
}
|
||||
|
||||
func (r *SQLiteTmpStmtRows) Close() (err error) {
|
||||
err = r.SQLiteRows.Close()
|
||||
_ = r.SQLiteStmt.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
type conn interface {
|
||||
driver.Conn
|
||||
driver.ConnPrepareContext
|
||||
|
@ -296,6 +401,10 @@ type stmt interface {
|
|||
driver.StmtQueryContext
|
||||
}
|
||||
|
||||
type rows interface {
|
||||
driver.Rows
|
||||
}
|
||||
|
||||
// retryOnBusy will retry given function on returned 'errBusy'.
|
||||
func retryOnBusy(ctx context.Context, fn func() error) error {
|
||||
if err := fn(); err != errBusy {
|
||||
|
|
361
internal/db/bundb/drivers.go.old
Normal file
361
internal/db/bundb/drivers.go.old
Normal file
|
@ -0,0 +1,361 @@
|
|||
// 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 (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"time"
|
||||
_ "unsafe" // linkname shenanigans
|
||||
|
||||
pgx "github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db/sqlite"
|
||||
)
|
||||
|
||||
var (
|
||||
// global wrapped gts driver instances.
|
||||
gtsPostgresDriver = &PostgreSQLDriver{}
|
||||
gtsSQLiteDriver = &SQLiteDriver{}
|
||||
|
||||
// global PostgreSQL driver instances.
|
||||
postgresDriver = pgx.GetDefaultDriver()
|
||||
|
||||
// global SQLite3 driver instance.
|
||||
sqliteDriver = &sqlite.Driver{
|
||||
Init: func(c *sqlite3.Conn) error {
|
||||
return c.BusyHandler(busyHandler)
|
||||
},
|
||||
}
|
||||
|
||||
// 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)
|
||||
)
|
||||
|
||||
func init() {
|
||||
sql.Register("pgx-gts", gtsPostgresDriver)
|
||||
sql.Register("sqlite-gts", gtsSQLiteDriver)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
cc, err := d.OpenConnector(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cc.Connect(context.Background())
|
||||
}
|
||||
|
||||
func (d *SQLiteDriver) OpenConnector(name string) (driver.Connector, error) {
|
||||
cc, err := sqliteDriver.OpenConnector(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SQLiteConnector{cc}, nil
|
||||
}
|
||||
|
||||
type SQLiteConnector struct{ driver.Connector }
|
||||
|
||||
func (c *SQLiteConnector) Driver() driver.Driver { return gtsSQLiteDriver }
|
||||
|
||||
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.(sqlite.ConnIface)}, nil
|
||||
}
|
||||
|
||||
type SQLiteConn struct{ sqlite.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) (driver.Tx, 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) (driver.Stmt, error) {
|
||||
st, err := c.ConnIface.PrepareContext(ctx, query)
|
||||
err = processSQLiteError(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) (driver.Result, error) {
|
||||
st, err := c.PrepareContext(ctx, query)
|
||||
err = processSQLiteError(err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stmt := st.(*SQLiteStmt)
|
||||
result, err := stmt.ExecContext(ctx, args)
|
||||
err = processSQLiteError(err)
|
||||
return result, err
|
||||
}
|
||||
|
||||
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) (driver.Rows, error) {
|
||||
st, err := c.PrepareContext(ctx, query)
|
||||
err = processSQLiteError(err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stmt := st.(*SQLiteStmt)
|
||||
rows, err := stmt.QueryContext(ctx, args)
|
||||
err = processSQLiteError(err)
|
||||
return rows, err
|
||||
}
|
||||
|
||||
func (c *SQLiteConn) Close() error {
|
||||
// see: https://www.sqlite.org/pragma.html#pragma_optimize
|
||||
const onClose = "PRAGMA analysis_limit=1000; PRAGMA optimize;"
|
||||
_, _ = c.ExecContext(context.Background(), onClose, nil)
|
||||
err := c.ConnIface.Close()
|
||||
err = processSQLiteError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
type SQLiteTx struct{ driver.Tx }
|
||||
|
||||
func (tx *SQLiteTx) Commit() error {
|
||||
err := tx.Tx.Commit()
|
||||
err = processSQLiteError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (tx *SQLiteTx) Rollback() error {
|
||||
err := tx.Tx.Rollback()
|
||||
err = processSQLiteError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
type SQLiteStmt struct{ sqlite.StmtIface }
|
||||
|
||||
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) (driver.Result, error) {
|
||||
res, err := stmt.StmtIface.ExecContext(ctx, args)
|
||||
err = processSQLiteError(err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
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) (driver.Rows, error) {
|
||||
rows, err := stmt.StmtIface.QueryContext(ctx, args)
|
||||
err = processSQLiteError(err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SQLiteRows{rows.(sqlite.RowsIface)}, nil
|
||||
}
|
||||
|
||||
type SQLiteRows struct{ sqlite.RowsIface }
|
||||
|
||||
func (r *SQLiteRows) Next(dest []driver.Value) error {
|
||||
err := r.RowsIface.Next(dest)
|
||||
err = processSQLiteError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
type conn interface {
|
||||
driver.Conn
|
||||
driver.ConnPrepareContext
|
||||
driver.ExecerContext
|
||||
driver.QueryerContext
|
||||
driver.ConnBeginTx
|
||||
}
|
||||
|
||||
type stmt interface {
|
||||
driver.Stmt
|
||||
driver.StmtExecContext
|
||||
driver.StmtQueryContext
|
||||
}
|
||||
|
||||
type rows interface {
|
||||
driver.Rows
|
||||
}
|
||||
|
||||
// busyHandler performs a busy backoff against given context,
|
||||
// returning false if the backoff is too long or ctx is done.
|
||||
func busyHandler(ctx context.Context, i int) (retry bool) {
|
||||
backoff := 2 * time.Millisecond * (1 << (2*i + 1))
|
||||
if backoff > 5*time.Minute {
|
||||
return false
|
||||
}
|
||||
donech := ctx.Done()
|
||||
if donech == nil {
|
||||
time.Sleep(backoff)
|
||||
return true
|
||||
}
|
||||
select {
|
||||
case <-donech:
|
||||
return false
|
||||
case <-time.After(backoff):
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
|
@ -18,13 +18,12 @@
|
|||
package bundb
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"modernc.org/sqlite"
|
||||
sqlite3 "modernc.org/sqlite/lib"
|
||||
)
|
||||
|
||||
// errBusy is a sentinel error indicating
|
||||
|
@ -54,52 +53,31 @@ func processPostgresError(err error) error {
|
|||
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)
|
||||
// Attempt to cast as sqlite error.
|
||||
sqliteErr, ok := err.(*sqlite3.Error)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle supplied error code:
|
||||
switch sqliteErr.Code() {
|
||||
case sqlite3.SQLITE_CONSTRAINT_UNIQUE,
|
||||
sqlite3.SQLITE_CONSTRAINT_PRIMARYKEY:
|
||||
switch sqliteErr.ExtendedCode() {
|
||||
case sqlite3.BUSY_TIMEOUT:
|
||||
return err // return busy timeouts.
|
||||
case sqlite3.CONSTRAINT_UNIQUE,
|
||||
sqlite3.CONSTRAINT_PRIMARYKEY:
|
||||
return db.ErrAlreadyExists
|
||||
case sqlite3.SQLITE_BUSY,
|
||||
sqlite3.SQLITE_BUSY_SNAPSHOT,
|
||||
sqlite3.SQLITE_BUSY_RECOVERY:
|
||||
}
|
||||
switch sqliteErr.Code() {
|
||||
case sqlite3.BUSY,
|
||||
sqlite3.LOCKED:
|
||||
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
|
||||
// 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(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package bundb_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
@ -82,10 +83,20 @@ func (suite *TagTestSuite) TestPutTag() {
|
|||
|
||||
// Subsequent inserts should fail
|
||||
// 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) {
|
||||
suite.Run(t, new(TagTestSuite))
|
||||
}
|
||||
|
||||
func unwrap(err error) (errs []error) {
|
||||
for err != nil {
|
||||
errs = append(errs, err)
|
||||
err = errors.Unwrap(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -30,6 +30,9 @@ var (
|
|||
// ErrAlreadyExists is returned when a conflict was encountered in the db when doing an insert.
|
||||
ErrAlreadyExists = errors.New("already exists")
|
||||
|
||||
// ErrTemporarilyBusy should be returned when the database wants to indicate you should retry.
|
||||
ErrTemporarilyBusy = errors.New("temporarily busy")
|
||||
|
||||
// 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")
|
||||
|
|
102
internal/db/sqlite/sqlite.go
Normal file
102
internal/db/sqlite/sqlite.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
// 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 sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// we hide ExecerContex
|
||||
// as it follows a weird
|
||||
// logic path in sql.DB,
|
||||
// being only a short-path
|
||||
// for stmts with no args.
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
type Driver struct {
|
||||
Init func(*sqlite3.Conn) error
|
||||
}
|
||||
|
||||
// Open: implements database/sql/driver.Driver.
|
||||
func (d Driver) Open(name string) (driver.Conn, error) {
|
||||
c, err := d.OpenConnector(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Connect(context.Background())
|
||||
}
|
||||
|
||||
// OpenConnector: implements database/sql/driver.DriverContext.
|
||||
func (d Driver) OpenConnector(name string) (driver.Connector, error) {
|
||||
return newConnector(name, d.Init)
|
||||
}
|
||||
|
||||
type connector struct {
|
||||
init func(*sqlite3.Conn) error
|
||||
name string
|
||||
txBegin string
|
||||
tmRead sqlite3.TimeFormat
|
||||
tmWrite sqlite3.TimeFormat
|
||||
pragmas bool
|
||||
}
|
||||
|
||||
func (c *connector) Driver() driver.Driver { return Driver{Init: c.init} }
|
||||
|
||||
func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||
return connect(c, ctx)
|
||||
}
|
||||
|
||||
//go:linkname newConnector github.com/ncruces/go-sqlite3/driver.newConnector
|
||||
func newConnector(name string, init func(*sqlite3.Conn) error) (*connector, error)
|
||||
|
||||
//go:linkname connect github.com/ncruces/go-sqlite3/driver.(*connector).Connect
|
||||
func connect(n *connector, ctx context.Context) (driver.Conn, error) //nolint
|
23
vendor/github.com/hashicorp/golang-lru/v2/.gitignore
generated
vendored
23
vendor/github.com/hashicorp/golang-lru/v2/.gitignore
generated
vendored
|
@ -1,23 +0,0 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
46
vendor/github.com/hashicorp/golang-lru/v2/.golangci.yml
generated
vendored
46
vendor/github.com/hashicorp/golang-lru/v2/.golangci.yml
generated
vendored
|
@ -1,46 +0,0 @@
|
|||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
linters:
|
||||
fast: false
|
||||
disable-all: true
|
||||
enable:
|
||||
- revive
|
||||
- megacheck
|
||||
- govet
|
||||
- unconvert
|
||||
- gas
|
||||
- gocyclo
|
||||
- dupl
|
||||
- misspell
|
||||
- unparam
|
||||
- unused
|
||||
- typecheck
|
||||
- ineffassign
|
||||
# - stylecheck
|
||||
- exportloopref
|
||||
- gocritic
|
||||
- nakedret
|
||||
- gosimple
|
||||
- prealloc
|
||||
|
||||
# golangci-lint configuration file
|
||||
linters-settings:
|
||||
revive:
|
||||
ignore-generated-header: true
|
||||
severity: warning
|
||||
rules:
|
||||
- name: package-comments
|
||||
severity: warning
|
||||
disabled: true
|
||||
- name: exported
|
||||
severity: warning
|
||||
disabled: false
|
||||
arguments: ["checkPrivateReceivers", "disableStutteringCheck"]
|
||||
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- dupl
|
267
vendor/github.com/hashicorp/golang-lru/v2/2q.go
generated
vendored
267
vendor/github.com/hashicorp/golang-lru/v2/2q.go
generated
vendored
|
@ -1,267 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package lru
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/golang-lru/v2/simplelru"
|
||||
)
|
||||
|
||||
const (
|
||||
// Default2QRecentRatio is the ratio of the 2Q cache dedicated
|
||||
// to recently added entries that have only been accessed once.
|
||||
Default2QRecentRatio = 0.25
|
||||
|
||||
// Default2QGhostEntries is the default ratio of ghost
|
||||
// entries kept to track entries recently evicted
|
||||
Default2QGhostEntries = 0.50
|
||||
)
|
||||
|
||||
// TwoQueueCache is a thread-safe fixed size 2Q cache.
|
||||
// 2Q is an enhancement over the standard LRU cache
|
||||
// in that it tracks both frequently and recently used
|
||||
// entries separately. This avoids a burst in access to new
|
||||
// entries from evicting frequently used entries. It adds some
|
||||
// additional tracking overhead to the standard LRU cache, and is
|
||||
// computationally about 2x the cost, and adds some metadata over
|
||||
// head. The ARCCache is similar, but does not require setting any
|
||||
// parameters.
|
||||
type TwoQueueCache[K comparable, V any] struct {
|
||||
size int
|
||||
recentSize int
|
||||
recentRatio float64
|
||||
ghostRatio float64
|
||||
|
||||
recent simplelru.LRUCache[K, V]
|
||||
frequent simplelru.LRUCache[K, V]
|
||||
recentEvict simplelru.LRUCache[K, struct{}]
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// New2Q creates a new TwoQueueCache using the default
|
||||
// values for the parameters.
|
||||
func New2Q[K comparable, V any](size int) (*TwoQueueCache[K, V], error) {
|
||||
return New2QParams[K, V](size, Default2QRecentRatio, Default2QGhostEntries)
|
||||
}
|
||||
|
||||
// New2QParams creates a new TwoQueueCache using the provided
|
||||
// parameter values.
|
||||
func New2QParams[K comparable, V any](size int, recentRatio, ghostRatio float64) (*TwoQueueCache[K, V], error) {
|
||||
if size <= 0 {
|
||||
return nil, errors.New("invalid size")
|
||||
}
|
||||
if recentRatio < 0.0 || recentRatio > 1.0 {
|
||||
return nil, errors.New("invalid recent ratio")
|
||||
}
|
||||
if ghostRatio < 0.0 || ghostRatio > 1.0 {
|
||||
return nil, errors.New("invalid ghost ratio")
|
||||
}
|
||||
|
||||
// Determine the sub-sizes
|
||||
recentSize := int(float64(size) * recentRatio)
|
||||
evictSize := int(float64(size) * ghostRatio)
|
||||
|
||||
// Allocate the LRUs
|
||||
recent, err := simplelru.NewLRU[K, V](size, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
frequent, err := simplelru.NewLRU[K, V](size, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recentEvict, err := simplelru.NewLRU[K, struct{}](evictSize, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize the cache
|
||||
c := &TwoQueueCache[K, V]{
|
||||
size: size,
|
||||
recentSize: recentSize,
|
||||
recentRatio: recentRatio,
|
||||
ghostRatio: ghostRatio,
|
||||
recent: recent,
|
||||
frequent: frequent,
|
||||
recentEvict: recentEvict,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Get looks up a key's value from the cache.
|
||||
func (c *TwoQueueCache[K, V]) Get(key K) (value V, ok bool) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
// Check if this is a frequent value
|
||||
if val, ok := c.frequent.Get(key); ok {
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// If the value is contained in recent, then we
|
||||
// promote it to frequent
|
||||
if val, ok := c.recent.Peek(key); ok {
|
||||
c.recent.Remove(key)
|
||||
c.frequent.Add(key, val)
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// No hit
|
||||
return
|
||||
}
|
||||
|
||||
// Add adds a value to the cache.
|
||||
func (c *TwoQueueCache[K, V]) Add(key K, value V) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
// Check if the value is frequently used already,
|
||||
// and just update the value
|
||||
if c.frequent.Contains(key) {
|
||||
c.frequent.Add(key, value)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the value is recently used, and promote
|
||||
// the value into the frequent list
|
||||
if c.recent.Contains(key) {
|
||||
c.recent.Remove(key)
|
||||
c.frequent.Add(key, value)
|
||||
return
|
||||
}
|
||||
|
||||
// If the value was recently evicted, add it to the
|
||||
// frequently used list
|
||||
if c.recentEvict.Contains(key) {
|
||||
c.ensureSpace(true)
|
||||
c.recentEvict.Remove(key)
|
||||
c.frequent.Add(key, value)
|
||||
return
|
||||
}
|
||||
|
||||
// Add to the recently seen list
|
||||
c.ensureSpace(false)
|
||||
c.recent.Add(key, value)
|
||||
}
|
||||
|
||||
// ensureSpace is used to ensure we have space in the cache
|
||||
func (c *TwoQueueCache[K, V]) ensureSpace(recentEvict bool) {
|
||||
// If we have space, nothing to do
|
||||
recentLen := c.recent.Len()
|
||||
freqLen := c.frequent.Len()
|
||||
if recentLen+freqLen < c.size {
|
||||
return
|
||||
}
|
||||
|
||||
// If the recent buffer is larger than
|
||||
// the target, evict from there
|
||||
if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) {
|
||||
k, _, _ := c.recent.RemoveOldest()
|
||||
c.recentEvict.Add(k, struct{}{})
|
||||
return
|
||||
}
|
||||
|
||||
// Remove from the frequent list otherwise
|
||||
c.frequent.RemoveOldest()
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (c *TwoQueueCache[K, V]) Len() int {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
return c.recent.Len() + c.frequent.Len()
|
||||
}
|
||||
|
||||
// Resize changes the cache size.
|
||||
func (c *TwoQueueCache[K, V]) Resize(size int) (evicted int) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
// Recalculate the sub-sizes
|
||||
recentSize := int(float64(size) * c.recentRatio)
|
||||
evictSize := int(float64(size) * c.ghostRatio)
|
||||
c.size = size
|
||||
c.recentSize = recentSize
|
||||
|
||||
// ensureSpace
|
||||
diff := c.recent.Len() + c.frequent.Len() - size
|
||||
if diff < 0 {
|
||||
diff = 0
|
||||
}
|
||||
for i := 0; i < diff; i++ {
|
||||
c.ensureSpace(true)
|
||||
}
|
||||
|
||||
// Reallocate the LRUs
|
||||
c.recent.Resize(size)
|
||||
c.frequent.Resize(size)
|
||||
c.recentEvict.Resize(evictSize)
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
// Keys returns a slice of the keys in the cache.
|
||||
// The frequently used keys are first in the returned slice.
|
||||
func (c *TwoQueueCache[K, V]) Keys() []K {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
k1 := c.frequent.Keys()
|
||||
k2 := c.recent.Keys()
|
||||
return append(k1, k2...)
|
||||
}
|
||||
|
||||
// Values returns a slice of the values in the cache.
|
||||
// The frequently used values are first in the returned slice.
|
||||
func (c *TwoQueueCache[K, V]) Values() []V {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
v1 := c.frequent.Values()
|
||||
v2 := c.recent.Values()
|
||||
return append(v1, v2...)
|
||||
}
|
||||
|
||||
// Remove removes the provided key from the cache.
|
||||
func (c *TwoQueueCache[K, V]) Remove(key K) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if c.frequent.Remove(key) {
|
||||
return
|
||||
}
|
||||
if c.recent.Remove(key) {
|
||||
return
|
||||
}
|
||||
if c.recentEvict.Remove(key) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Purge is used to completely clear the cache.
|
||||
func (c *TwoQueueCache[K, V]) Purge() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.recent.Purge()
|
||||
c.frequent.Purge()
|
||||
c.recentEvict.Purge()
|
||||
}
|
||||
|
||||
// Contains is used to check if the cache contains a key
|
||||
// without updating recency or frequency.
|
||||
func (c *TwoQueueCache[K, V]) Contains(key K) bool {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
return c.frequent.Contains(key) || c.recent.Contains(key)
|
||||
}
|
||||
|
||||
// Peek is used to inspect the cache value of a key
|
||||
// without updating recency or frequency.
|
||||
func (c *TwoQueueCache[K, V]) Peek(key K) (value V, ok bool) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
if val, ok := c.frequent.Peek(key); ok {
|
||||
return val, ok
|
||||
}
|
||||
return c.recent.Peek(key)
|
||||
}
|
364
vendor/github.com/hashicorp/golang-lru/v2/LICENSE
generated
vendored
364
vendor/github.com/hashicorp/golang-lru/v2/LICENSE
generated
vendored
|
@ -1,364 +0,0 @@
|
|||
Copyright (c) 2014 HashiCorp, Inc.
|
||||
|
||||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. "Contributor"
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the terms of
|
||||
a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
|
||||
means a work that combines Covered Software with other material, in a
|
||||
separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether
|
||||
at the time of the initial grant or subsequently, any and all of the
|
||||
rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the License,
|
||||
by the making, using, selling, offering for sale, having made, import,
|
||||
or transfer of either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, "control" means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights to
|
||||
grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter the
|
||||
recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty, or
|
||||
limitations of liability) contained within the Source Code Form of the
|
||||
Covered Software, except that You may alter any license notices to the
|
||||
extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute,
|
||||
judicial order, or regulation then You must: (a) comply with the terms of
|
||||
this License to the maximum extent possible; and (b) describe the
|
||||
limitations and the code they affect. Such description must be placed in a
|
||||
text file included with all distributions of the Covered Software under
|
||||
this License. Except to the extent prohibited by statute or regulation,
|
||||
such description must be sufficiently detailed for a recipient of ordinary
|
||||
skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing
|
||||
basis, if such Contributor fails to notify You of the non-compliance by
|
||||
some reasonable means prior to 60 days after You have come back into
|
||||
compliance. Moreover, Your grants from a particular Contributor are
|
||||
reinstated on an ongoing basis if such Contributor notifies You of the
|
||||
non-compliance by some reasonable means, this is the first time You have
|
||||
received notice of non-compliance with this License from such
|
||||
Contributor, and You become compliant prior to 30 days after Your receipt
|
||||
of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an "as is" basis,
|
||||
without warranty of any kind, either expressed, implied, or statutory,
|
||||
including, without limitation, warranties that the Covered Software is free
|
||||
of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||
The entire risk as to the quality and performance of the Covered Software
|
||||
is with You. Should any Covered Software prove defective in any respect,
|
||||
You (not any Contributor) assume the cost of any necessary servicing,
|
||||
repair, or correction. This disclaimer of warranty constitutes an essential
|
||||
part of this License. No use of any Covered Software is authorized under
|
||||
this License except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from
|
||||
such party's negligence to the extent applicable law prohibits such
|
||||
limitation. Some jurisdictions do not allow the exclusion or limitation of
|
||||
incidental or consequential damages, so this exclusion and limitation may
|
||||
not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts
|
||||
of a jurisdiction where the defendant maintains its principal place of
|
||||
business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions. Nothing
|
||||
in this Section shall prevent a party's ability to bring cross-claims or
|
||||
counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides that
|
||||
the language of a contract shall be construed against the drafter shall not
|
||||
be used to construe this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses If You choose to distribute Source Code Form that is
|
||||
Incompatible With Secondary Licenses under the terms of this version of
|
||||
the License, the notice described in Exhibit B of this License must be
|
||||
attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file,
|
||||
then You may include the notice in a location (such as a LICENSE file in a
|
||||
relevant directory) where a recipient would be likely to look for such a
|
||||
notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
This Source Code Form is "Incompatible
|
||||
With Secondary Licenses", as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
79
vendor/github.com/hashicorp/golang-lru/v2/README.md
generated
vendored
79
vendor/github.com/hashicorp/golang-lru/v2/README.md
generated
vendored
|
@ -1,79 +0,0 @@
|
|||
golang-lru
|
||||
==========
|
||||
|
||||
This provides the `lru` package which implements a fixed-size
|
||||
thread safe LRU cache. It is based on the cache in Groupcache.
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
Full docs are available on [Go Packages](https://pkg.go.dev/github.com/hashicorp/golang-lru/v2)
|
||||
|
||||
LRU cache example
|
||||
=================
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/golang-lru/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
l, _ := lru.New[int, any](128)
|
||||
for i := 0; i < 256; i++ {
|
||||
l.Add(i, nil)
|
||||
}
|
||||
if l.Len() != 128 {
|
||||
panic(fmt.Sprintf("bad len: %v", l.Len()))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Expirable LRU cache example
|
||||
===========================
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/golang-lru/v2/expirable"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// make cache with 10ms TTL and 5 max keys
|
||||
cache := expirable.NewLRU[string, string](5, nil, time.Millisecond*10)
|
||||
|
||||
|
||||
// set value under key1.
|
||||
cache.Add("key1", "val1")
|
||||
|
||||
// get value under key1
|
||||
r, ok := cache.Get("key1")
|
||||
|
||||
// check for OK value
|
||||
if ok {
|
||||
fmt.Printf("value before expiration is found: %v, value: %q\n", ok, r)
|
||||
}
|
||||
|
||||
// wait for cache to expire
|
||||
time.Sleep(time.Millisecond * 12)
|
||||
|
||||
// get value under key1 after key expiration
|
||||
r, ok = cache.Get("key1")
|
||||
fmt.Printf("value after expiration is found: %v, value: %q\n", ok, r)
|
||||
|
||||
// set value under key2, would evict old entry because it is already expired.
|
||||
cache.Add("key2", "val2")
|
||||
|
||||
fmt.Printf("Cache len: %d\n", cache.Len())
|
||||
// Output:
|
||||
// value before expiration is found: true, value: "val1"
|
||||
// value after expiration is found: false, value: ""
|
||||
// Cache len: 1
|
||||
}
|
||||
```
|
24
vendor/github.com/hashicorp/golang-lru/v2/doc.go
generated
vendored
24
vendor/github.com/hashicorp/golang-lru/v2/doc.go
generated
vendored
|
@ -1,24 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Package lru provides three different LRU caches of varying sophistication.
|
||||
//
|
||||
// Cache is a simple LRU cache. It is based on the LRU implementation in
|
||||
// groupcache: https://github.com/golang/groupcache/tree/master/lru
|
||||
//
|
||||
// TwoQueueCache tracks frequently used and recently used entries separately.
|
||||
// This avoids a burst of accesses from taking out frequently used entries, at
|
||||
// the cost of about 2x computational overhead and some extra bookkeeping.
|
||||
//
|
||||
// ARCCache is an adaptive replacement cache. It tracks recent evictions as well
|
||||
// as recent usage in both the frequent and recent caches. Its computational
|
||||
// overhead is comparable to TwoQueueCache, but the memory overhead is linear
|
||||
// with the size of the cache.
|
||||
//
|
||||
// ARC has been patented by IBM, so do not use it if that is problematic for
|
||||
// your program. For this reason, it is in a separate go module contained within
|
||||
// this repository.
|
||||
//
|
||||
// All caches in this package take locks while operating, and are therefore
|
||||
// thread-safe for consumers.
|
||||
package lru
|
142
vendor/github.com/hashicorp/golang-lru/v2/internal/list.go
generated
vendored
142
vendor/github.com/hashicorp/golang-lru/v2/internal/list.go
generated
vendored
|
@ -1,142 +0,0 @@
|
|||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE_list file.
|
||||
|
||||
package internal
|
||||
|
||||
import "time"
|
||||
|
||||
// Entry is an LRU Entry
|
||||
type Entry[K comparable, V any] struct {
|
||||
// Next and previous pointers in the doubly-linked list of elements.
|
||||
// To simplify the implementation, internally a list l is implemented
|
||||
// as a ring, such that &l.root is both the next element of the last
|
||||
// list element (l.Back()) and the previous element of the first list
|
||||
// element (l.Front()).
|
||||
next, prev *Entry[K, V]
|
||||
|
||||
// The list to which this element belongs.
|
||||
list *LruList[K, V]
|
||||
|
||||
// The LRU Key of this element.
|
||||
Key K
|
||||
|
||||
// The Value stored with this element.
|
||||
Value V
|
||||
|
||||
// The time this element would be cleaned up, optional
|
||||
ExpiresAt time.Time
|
||||
|
||||
// The expiry bucket item was put in, optional
|
||||
ExpireBucket uint8
|
||||
}
|
||||
|
||||
// PrevEntry returns the previous list element or nil.
|
||||
func (e *Entry[K, V]) PrevEntry() *Entry[K, V] {
|
||||
if p := e.prev; e.list != nil && p != &e.list.root {
|
||||
return p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LruList represents a doubly linked list.
|
||||
// The zero Value for LruList is an empty list ready to use.
|
||||
type LruList[K comparable, V any] struct {
|
||||
root Entry[K, V] // sentinel list element, only &root, root.prev, and root.next are used
|
||||
len int // current list Length excluding (this) sentinel element
|
||||
}
|
||||
|
||||
// Init initializes or clears list l.
|
||||
func (l *LruList[K, V]) Init() *LruList[K, V] {
|
||||
l.root.next = &l.root
|
||||
l.root.prev = &l.root
|
||||
l.len = 0
|
||||
return l
|
||||
}
|
||||
|
||||
// NewList returns an initialized list.
|
||||
func NewList[K comparable, V any]() *LruList[K, V] { return new(LruList[K, V]).Init() }
|
||||
|
||||
// Length returns the number of elements of list l.
|
||||
// The complexity is O(1).
|
||||
func (l *LruList[K, V]) Length() int { return l.len }
|
||||
|
||||
// Back returns the last element of list l or nil if the list is empty.
|
||||
func (l *LruList[K, V]) Back() *Entry[K, V] {
|
||||
if l.len == 0 {
|
||||
return nil
|
||||
}
|
||||
return l.root.prev
|
||||
}
|
||||
|
||||
// lazyInit lazily initializes a zero List Value.
|
||||
func (l *LruList[K, V]) lazyInit() {
|
||||
if l.root.next == nil {
|
||||
l.Init()
|
||||
}
|
||||
}
|
||||
|
||||
// insert inserts e after at, increments l.len, and returns e.
|
||||
func (l *LruList[K, V]) insert(e, at *Entry[K, V]) *Entry[K, V] {
|
||||
e.prev = at
|
||||
e.next = at.next
|
||||
e.prev.next = e
|
||||
e.next.prev = e
|
||||
e.list = l
|
||||
l.len++
|
||||
return e
|
||||
}
|
||||
|
||||
// insertValue is a convenience wrapper for insert(&Entry{Value: v, ExpiresAt: ExpiresAt}, at).
|
||||
func (l *LruList[K, V]) insertValue(k K, v V, expiresAt time.Time, at *Entry[K, V]) *Entry[K, V] {
|
||||
return l.insert(&Entry[K, V]{Value: v, Key: k, ExpiresAt: expiresAt}, at)
|
||||
}
|
||||
|
||||
// Remove removes e from its list, decrements l.len
|
||||
func (l *LruList[K, V]) Remove(e *Entry[K, V]) V {
|
||||
e.prev.next = e.next
|
||||
e.next.prev = e.prev
|
||||
e.next = nil // avoid memory leaks
|
||||
e.prev = nil // avoid memory leaks
|
||||
e.list = nil
|
||||
l.len--
|
||||
|
||||
return e.Value
|
||||
}
|
||||
|
||||
// move moves e to next to at.
|
||||
func (l *LruList[K, V]) move(e, at *Entry[K, V]) {
|
||||
if e == at {
|
||||
return
|
||||
}
|
||||
e.prev.next = e.next
|
||||
e.next.prev = e.prev
|
||||
|
||||
e.prev = at
|
||||
e.next = at.next
|
||||
e.prev.next = e
|
||||
e.next.prev = e
|
||||
}
|
||||
|
||||
// PushFront inserts a new element e with value v at the front of list l and returns e.
|
||||
func (l *LruList[K, V]) PushFront(k K, v V) *Entry[K, V] {
|
||||
l.lazyInit()
|
||||
return l.insertValue(k, v, time.Time{}, &l.root)
|
||||
}
|
||||
|
||||
// PushFrontExpirable inserts a new expirable element e with Value v at the front of list l and returns e.
|
||||
func (l *LruList[K, V]) PushFrontExpirable(k K, v V, expiresAt time.Time) *Entry[K, V] {
|
||||
l.lazyInit()
|
||||
return l.insertValue(k, v, expiresAt, &l.root)
|
||||
}
|
||||
|
||||
// MoveToFront moves element e to the front of list l.
|
||||
// If e is not an element of l, the list is not modified.
|
||||
// The element must not be nil.
|
||||
func (l *LruList[K, V]) MoveToFront(e *Entry[K, V]) {
|
||||
if e.list != l || l.root.next == e {
|
||||
return
|
||||
}
|
||||
// see comment in List.Remove about initialization of l
|
||||
l.move(e, &l.root)
|
||||
}
|
250
vendor/github.com/hashicorp/golang-lru/v2/lru.go
generated
vendored
250
vendor/github.com/hashicorp/golang-lru/v2/lru.go
generated
vendored
|
@ -1,250 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package lru
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/golang-lru/v2/simplelru"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultEvictedBufferSize defines the default buffer size to store evicted key/val
|
||||
DefaultEvictedBufferSize = 16
|
||||
)
|
||||
|
||||
// Cache is a thread-safe fixed size LRU cache.
|
||||
type Cache[K comparable, V any] struct {
|
||||
lru *simplelru.LRU[K, V]
|
||||
evictedKeys []K
|
||||
evictedVals []V
|
||||
onEvictedCB func(k K, v V)
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// New creates an LRU of the given size.
|
||||
func New[K comparable, V any](size int) (*Cache[K, V], error) {
|
||||
return NewWithEvict[K, V](size, nil)
|
||||
}
|
||||
|
||||
// NewWithEvict constructs a fixed size cache with the given eviction
|
||||
// callback.
|
||||
func NewWithEvict[K comparable, V any](size int, onEvicted func(key K, value V)) (c *Cache[K, V], err error) {
|
||||
// create a cache with default settings
|
||||
c = &Cache[K, V]{
|
||||
onEvictedCB: onEvicted,
|
||||
}
|
||||
if onEvicted != nil {
|
||||
c.initEvictBuffers()
|
||||
onEvicted = c.onEvicted
|
||||
}
|
||||
c.lru, err = simplelru.NewLRU(size, onEvicted)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Cache[K, V]) initEvictBuffers() {
|
||||
c.evictedKeys = make([]K, 0, DefaultEvictedBufferSize)
|
||||
c.evictedVals = make([]V, 0, DefaultEvictedBufferSize)
|
||||
}
|
||||
|
||||
// onEvicted save evicted key/val and sent in externally registered callback
|
||||
// outside of critical section
|
||||
func (c *Cache[K, V]) onEvicted(k K, v V) {
|
||||
c.evictedKeys = append(c.evictedKeys, k)
|
||||
c.evictedVals = append(c.evictedVals, v)
|
||||
}
|
||||
|
||||
// Purge is used to completely clear the cache.
|
||||
func (c *Cache[K, V]) Purge() {
|
||||
var ks []K
|
||||
var vs []V
|
||||
c.lock.Lock()
|
||||
c.lru.Purge()
|
||||
if c.onEvictedCB != nil && len(c.evictedKeys) > 0 {
|
||||
ks, vs = c.evictedKeys, c.evictedVals
|
||||
c.initEvictBuffers()
|
||||
}
|
||||
c.lock.Unlock()
|
||||
// invoke callback outside of critical section
|
||||
if c.onEvictedCB != nil {
|
||||
for i := 0; i < len(ks); i++ {
|
||||
c.onEvictedCB(ks[i], vs[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a value to the cache. Returns true if an eviction occurred.
|
||||
func (c *Cache[K, V]) Add(key K, value V) (evicted bool) {
|
||||
var k K
|
||||
var v V
|
||||
c.lock.Lock()
|
||||
evicted = c.lru.Add(key, value)
|
||||
if c.onEvictedCB != nil && evicted {
|
||||
k, v = c.evictedKeys[0], c.evictedVals[0]
|
||||
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
||||
}
|
||||
c.lock.Unlock()
|
||||
if c.onEvictedCB != nil && evicted {
|
||||
c.onEvictedCB(k, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get looks up a key's value from the cache.
|
||||
func (c *Cache[K, V]) Get(key K) (value V, ok bool) {
|
||||
c.lock.Lock()
|
||||
value, ok = c.lru.Get(key)
|
||||
c.lock.Unlock()
|
||||
return value, ok
|
||||
}
|
||||
|
||||
// Contains checks if a key is in the cache, without updating the
|
||||
// recent-ness or deleting it for being stale.
|
||||
func (c *Cache[K, V]) Contains(key K) bool {
|
||||
c.lock.RLock()
|
||||
containKey := c.lru.Contains(key)
|
||||
c.lock.RUnlock()
|
||||
return containKey
|
||||
}
|
||||
|
||||
// Peek returns the key value (or undefined if not found) without updating
|
||||
// the "recently used"-ness of the key.
|
||||
func (c *Cache[K, V]) Peek(key K) (value V, ok bool) {
|
||||
c.lock.RLock()
|
||||
value, ok = c.lru.Peek(key)
|
||||
c.lock.RUnlock()
|
||||
return value, ok
|
||||
}
|
||||
|
||||
// ContainsOrAdd checks if a key is in the cache without updating the
|
||||
// recent-ness or deleting it for being stale, and if not, adds the value.
|
||||
// Returns whether found and whether an eviction occurred.
|
||||
func (c *Cache[K, V]) ContainsOrAdd(key K, value V) (ok, evicted bool) {
|
||||
var k K
|
||||
var v V
|
||||
c.lock.Lock()
|
||||
if c.lru.Contains(key) {
|
||||
c.lock.Unlock()
|
||||
return true, false
|
||||
}
|
||||
evicted = c.lru.Add(key, value)
|
||||
if c.onEvictedCB != nil && evicted {
|
||||
k, v = c.evictedKeys[0], c.evictedVals[0]
|
||||
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
||||
}
|
||||
c.lock.Unlock()
|
||||
if c.onEvictedCB != nil && evicted {
|
||||
c.onEvictedCB(k, v)
|
||||
}
|
||||
return false, evicted
|
||||
}
|
||||
|
||||
// PeekOrAdd checks if a key is in the cache without updating the
|
||||
// recent-ness or deleting it for being stale, and if not, adds the value.
|
||||
// Returns whether found and whether an eviction occurred.
|
||||
func (c *Cache[K, V]) PeekOrAdd(key K, value V) (previous V, ok, evicted bool) {
|
||||
var k K
|
||||
var v V
|
||||
c.lock.Lock()
|
||||
previous, ok = c.lru.Peek(key)
|
||||
if ok {
|
||||
c.lock.Unlock()
|
||||
return previous, true, false
|
||||
}
|
||||
evicted = c.lru.Add(key, value)
|
||||
if c.onEvictedCB != nil && evicted {
|
||||
k, v = c.evictedKeys[0], c.evictedVals[0]
|
||||
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
||||
}
|
||||
c.lock.Unlock()
|
||||
if c.onEvictedCB != nil && evicted {
|
||||
c.onEvictedCB(k, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Remove removes the provided key from the cache.
|
||||
func (c *Cache[K, V]) Remove(key K) (present bool) {
|
||||
var k K
|
||||
var v V
|
||||
c.lock.Lock()
|
||||
present = c.lru.Remove(key)
|
||||
if c.onEvictedCB != nil && present {
|
||||
k, v = c.evictedKeys[0], c.evictedVals[0]
|
||||
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
||||
}
|
||||
c.lock.Unlock()
|
||||
if c.onEvictedCB != nil && present {
|
||||
c.onEvictedCB(k, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Resize changes the cache size.
|
||||
func (c *Cache[K, V]) Resize(size int) (evicted int) {
|
||||
var ks []K
|
||||
var vs []V
|
||||
c.lock.Lock()
|
||||
evicted = c.lru.Resize(size)
|
||||
if c.onEvictedCB != nil && evicted > 0 {
|
||||
ks, vs = c.evictedKeys, c.evictedVals
|
||||
c.initEvictBuffers()
|
||||
}
|
||||
c.lock.Unlock()
|
||||
if c.onEvictedCB != nil && evicted > 0 {
|
||||
for i := 0; i < len(ks); i++ {
|
||||
c.onEvictedCB(ks[i], vs[i])
|
||||
}
|
||||
}
|
||||
return evicted
|
||||
}
|
||||
|
||||
// RemoveOldest removes the oldest item from the cache.
|
||||
func (c *Cache[K, V]) RemoveOldest() (key K, value V, ok bool) {
|
||||
var k K
|
||||
var v V
|
||||
c.lock.Lock()
|
||||
key, value, ok = c.lru.RemoveOldest()
|
||||
if c.onEvictedCB != nil && ok {
|
||||
k, v = c.evictedKeys[0], c.evictedVals[0]
|
||||
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
|
||||
}
|
||||
c.lock.Unlock()
|
||||
if c.onEvictedCB != nil && ok {
|
||||
c.onEvictedCB(k, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetOldest returns the oldest entry
|
||||
func (c *Cache[K, V]) GetOldest() (key K, value V, ok bool) {
|
||||
c.lock.RLock()
|
||||
key, value, ok = c.lru.GetOldest()
|
||||
c.lock.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Keys returns a slice of the keys in the cache, from oldest to newest.
|
||||
func (c *Cache[K, V]) Keys() []K {
|
||||
c.lock.RLock()
|
||||
keys := c.lru.Keys()
|
||||
c.lock.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns a slice of the values in the cache, from oldest to newest.
|
||||
func (c *Cache[K, V]) Values() []V {
|
||||
c.lock.RLock()
|
||||
values := c.lru.Values()
|
||||
c.lock.RUnlock()
|
||||
return values
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (c *Cache[K, V]) Len() int {
|
||||
c.lock.RLock()
|
||||
length := c.lru.Len()
|
||||
c.lock.RUnlock()
|
||||
return length
|
||||
}
|
29
vendor/github.com/hashicorp/golang-lru/v2/simplelru/LICENSE_list
generated
vendored
29
vendor/github.com/hashicorp/golang-lru/v2/simplelru/LICENSE_list
generated
vendored
|
@ -1,29 +0,0 @@
|
|||
This license applies to simplelru/list.go
|
||||
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
177
vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru.go
generated
vendored
177
vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru.go
generated
vendored
|
@ -1,177 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package simplelru
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/hashicorp/golang-lru/v2/internal"
|
||||
)
|
||||
|
||||
// EvictCallback is used to get a callback when a cache entry is evicted
|
||||
type EvictCallback[K comparable, V any] func(key K, value V)
|
||||
|
||||
// LRU implements a non-thread safe fixed size LRU cache
|
||||
type LRU[K comparable, V any] struct {
|
||||
size int
|
||||
evictList *internal.LruList[K, V]
|
||||
items map[K]*internal.Entry[K, V]
|
||||
onEvict EvictCallback[K, V]
|
||||
}
|
||||
|
||||
// NewLRU constructs an LRU of the given size
|
||||
func NewLRU[K comparable, V any](size int, onEvict EvictCallback[K, V]) (*LRU[K, V], error) {
|
||||
if size <= 0 {
|
||||
return nil, errors.New("must provide a positive size")
|
||||
}
|
||||
|
||||
c := &LRU[K, V]{
|
||||
size: size,
|
||||
evictList: internal.NewList[K, V](),
|
||||
items: make(map[K]*internal.Entry[K, V]),
|
||||
onEvict: onEvict,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Purge is used to completely clear the cache.
|
||||
func (c *LRU[K, V]) Purge() {
|
||||
for k, v := range c.items {
|
||||
if c.onEvict != nil {
|
||||
c.onEvict(k, v.Value)
|
||||
}
|
||||
delete(c.items, k)
|
||||
}
|
||||
c.evictList.Init()
|
||||
}
|
||||
|
||||
// Add adds a value to the cache. Returns true if an eviction occurred.
|
||||
func (c *LRU[K, V]) Add(key K, value V) (evicted bool) {
|
||||
// Check for existing item
|
||||
if ent, ok := c.items[key]; ok {
|
||||
c.evictList.MoveToFront(ent)
|
||||
ent.Value = value
|
||||
return false
|
||||
}
|
||||
|
||||
// Add new item
|
||||
ent := c.evictList.PushFront(key, value)
|
||||
c.items[key] = ent
|
||||
|
||||
evict := c.evictList.Length() > c.size
|
||||
// Verify size not exceeded
|
||||
if evict {
|
||||
c.removeOldest()
|
||||
}
|
||||
return evict
|
||||
}
|
||||
|
||||
// Get looks up a key's value from the cache.
|
||||
func (c *LRU[K, V]) Get(key K) (value V, ok bool) {
|
||||
if ent, ok := c.items[key]; ok {
|
||||
c.evictList.MoveToFront(ent)
|
||||
return ent.Value, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Contains checks if a key is in the cache, without updating the recent-ness
|
||||
// or deleting it for being stale.
|
||||
func (c *LRU[K, V]) Contains(key K) (ok bool) {
|
||||
_, ok = c.items[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Peek returns the key value (or undefined if not found) without updating
|
||||
// the "recently used"-ness of the key.
|
||||
func (c *LRU[K, V]) Peek(key K) (value V, ok bool) {
|
||||
var ent *internal.Entry[K, V]
|
||||
if ent, ok = c.items[key]; ok {
|
||||
return ent.Value, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Remove removes the provided key from the cache, returning if the
|
||||
// key was contained.
|
||||
func (c *LRU[K, V]) Remove(key K) (present bool) {
|
||||
if ent, ok := c.items[key]; ok {
|
||||
c.removeElement(ent)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RemoveOldest removes the oldest item from the cache.
|
||||
func (c *LRU[K, V]) RemoveOldest() (key K, value V, ok bool) {
|
||||
if ent := c.evictList.Back(); ent != nil {
|
||||
c.removeElement(ent)
|
||||
return ent.Key, ent.Value, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetOldest returns the oldest entry
|
||||
func (c *LRU[K, V]) GetOldest() (key K, value V, ok bool) {
|
||||
if ent := c.evictList.Back(); ent != nil {
|
||||
return ent.Key, ent.Value, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Keys returns a slice of the keys in the cache, from oldest to newest.
|
||||
func (c *LRU[K, V]) Keys() []K {
|
||||
keys := make([]K, c.evictList.Length())
|
||||
i := 0
|
||||
for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() {
|
||||
keys[i] = ent.Key
|
||||
i++
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// Values returns a slice of the values in the cache, from oldest to newest.
|
||||
func (c *LRU[K, V]) Values() []V {
|
||||
values := make([]V, len(c.items))
|
||||
i := 0
|
||||
for ent := c.evictList.Back(); ent != nil; ent = ent.PrevEntry() {
|
||||
values[i] = ent.Value
|
||||
i++
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (c *LRU[K, V]) Len() int {
|
||||
return c.evictList.Length()
|
||||
}
|
||||
|
||||
// Resize changes the cache size.
|
||||
func (c *LRU[K, V]) Resize(size int) (evicted int) {
|
||||
diff := c.Len() - size
|
||||
if diff < 0 {
|
||||
diff = 0
|
||||
}
|
||||
for i := 0; i < diff; i++ {
|
||||
c.removeOldest()
|
||||
}
|
||||
c.size = size
|
||||
return diff
|
||||
}
|
||||
|
||||
// removeOldest removes the oldest item from the cache.
|
||||
func (c *LRU[K, V]) removeOldest() {
|
||||
if ent := c.evictList.Back(); ent != nil {
|
||||
c.removeElement(ent)
|
||||
}
|
||||
}
|
||||
|
||||
// removeElement is used to remove a given list element from the cache
|
||||
func (c *LRU[K, V]) removeElement(e *internal.Entry[K, V]) {
|
||||
c.evictList.Remove(e)
|
||||
delete(c.items, e.Key)
|
||||
if c.onEvict != nil {
|
||||
c.onEvict(e.Key, e.Value)
|
||||
}
|
||||
}
|
46
vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru_interface.go
generated
vendored
46
vendor/github.com/hashicorp/golang-lru/v2/simplelru/lru_interface.go
generated
vendored
|
@ -1,46 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Package simplelru provides simple LRU implementation based on build-in container/list.
|
||||
package simplelru
|
||||
|
||||
// LRUCache is the interface for simple LRU cache.
|
||||
type LRUCache[K comparable, V any] interface {
|
||||
// Adds a value to the cache, returns true if an eviction occurred and
|
||||
// updates the "recently used"-ness of the key.
|
||||
Add(key K, value V) bool
|
||||
|
||||
// Returns key's value from the cache and
|
||||
// updates the "recently used"-ness of the key. #value, isFound
|
||||
Get(key K) (value V, ok bool)
|
||||
|
||||
// Checks if a key exists in cache without updating the recent-ness.
|
||||
Contains(key K) (ok bool)
|
||||
|
||||
// Returns key's value without updating the "recently used"-ness of the key.
|
||||
Peek(key K) (value V, ok bool)
|
||||
|
||||
// Removes a key from the cache.
|
||||
Remove(key K) bool
|
||||
|
||||
// Removes the oldest entry from cache.
|
||||
RemoveOldest() (K, V, bool)
|
||||
|
||||
// Returns the oldest entry from the cache. #key, value, isFound
|
||||
GetOldest() (K, V, bool)
|
||||
|
||||
// Returns a slice of the keys in the cache, from oldest to newest.
|
||||
Keys() []K
|
||||
|
||||
// Values returns a slice of the values in the cache, from oldest to newest.
|
||||
Values() []V
|
||||
|
||||
// Returns the number of items in the cache.
|
||||
Len() int
|
||||
|
||||
// Clears all cache entries.
|
||||
Purge()
|
||||
|
||||
// Resizes cache, returning number evicted
|
||||
Resize(int) int
|
||||
}
|
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.
|
103
vendor/github.com/ncruces/go-sqlite3/README.md
generated
vendored
Normal file
103
vendor/github.com/ncruces/go-sqlite3/README.md
generated
vendored
Normal file
|
@ -0,0 +1,103 @@
|
|||
# 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 `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 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.
|
||||
|
||||
### 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.
|
||||
|
||||
### 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)
|
||||
- [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.
|
||||
|
||||
The Go VFS is tested by running SQLite's
|
||||
[mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c)
|
||||
on Linux, macOS, Windows and FreeBSD.
|
||||
|
||||
### 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)
|
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)))
|
||||
}
|
389
vendor/github.com/ncruces/go-sqlite3/conn.go
generated
vendored
Normal file
389
vendor/github.com/ncruces/go-sqlite3/conn.go
generated
vendored
Normal file
|
@ -0,0 +1,389 @@
|
|||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"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(`;`)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
if errors.Is(err, ERROR) {
|
||||
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_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)
|
||||
}
|
||||
|
||||
// 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.commit != nil {
|
||||
if 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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
}
|
571
vendor/github.com/ncruces/go-sqlite3/driver/driver.go
generated
vendored
Normal file
571
vendor/github.com/ncruces/go-sqlite3/driver/driver.go
generated
vendored
Normal file
|
@ -0,0 +1,571 @@
|
|||
// 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 conn can be used to execute queries, register functions, etc.
|
||||
// Any error return closes the conn and passes the error to [database/sql].
|
||||
func Open(dataSourceName string, init func(*sqlite3.Conn) error) (*sql.DB, error) {
|
||||
c, err := newConnector(dataSourceName, init)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sql.OpenDB(c), nil
|
||||
}
|
||||
|
||||
type sqlite struct{}
|
||||
|
||||
func (sqlite) Open(name string) (driver.Conn, error) {
|
||||
c, err := newConnector(name, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Connect(context.Background())
|
||||
}
|
||||
|
||||
func (sqlite) OpenConnector(name string) (driver.Connector, error) {
|
||||
return newConnector(name, nil)
|
||||
}
|
||||
|
||||
func newConnector(name string, init func(*sqlite3.Conn) error) (*connector, error) {
|
||||
c := connector{name: name, init: init}
|
||||
|
||||
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 {
|
||||
init func(*sqlite3.Conn) error
|
||||
name string
|
||||
txBegin string
|
||||
tmRead sqlite3.TimeFormat
|
||||
tmWrite sqlite3.TimeFormat
|
||||
pragmas bool
|
||||
}
|
||||
|
||||
func (n *connector) Driver() driver.Driver {
|
||||
return sqlite{}
|
||||
}
|
||||
|
||||
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.init != nil {
|
||||
err = n.init(c.Conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if n.pragmas || n.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 (*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
|
||||
}
|
26
vendor/github.com/ncruces/go-sqlite3/embed/README.md
generated
vendored
Normal file
26
vendor/github.com/ncruces/go-sqlite3/embed/README.md
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Embeddable Wasm build of SQLite
|
||||
|
||||
This folder includes an embeddable Wasm build of SQLite 3.45.3 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)
|
||||
- [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).
|
31
vendor/github.com/ncruces/go-sqlite3/embed/build.sh
generated
vendored
Normal file
31
vendor/github.com/ncruces/go-sqlite3/embed/build.sh
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
#!/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 \
|
||||
-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 \
|
||||
$(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
|
122
vendor/github.com/ncruces/go-sqlite3/embed/exports.txt
generated
vendored
Normal file
122
vendor/github.com/ncruces/go-sqlite3/embed/exports.txt
generated
vendored
Normal file
|
@ -0,0 +1,122 @@
|
|||
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_decltype
|
||||
sqlite3_column_double
|
||||
sqlite3_column_int64
|
||||
sqlite3_column_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_db_config
|
||||
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_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
|
||||
)
|
8
vendor/github.com/ncruces/go-sqlite3/go.work.sum
generated
vendored
Normal file
8
vendor/github.com/ncruces/go-sqlite3/go.work.sum
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
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/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
71
vendor/github.com/ncruces/go-sqlite3/internal/util/alloc.go
generated
vendored
Normal file
71
vendor/github.com/ncruces/go-sqlite3/internal/util/alloc.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
//go:build unix
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func mmappedAllocator(cap, max uint64) experimental.LinearMemory {
|
||||
// Round up to the page size.
|
||||
rnd := uint64(unix.Getpagesize() - 1)
|
||||
max = (max + rnd) &^ rnd
|
||||
cap = (cap + 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)
|
||||
}
|
||||
// Commit the initial cap bytes of memory.
|
||||
err = unix.Mprotect(b[:cap], unix.PROT_READ|unix.PROT_WRITE)
|
||||
if err != nil {
|
||||
unix.Munmap(b)
|
||||
panic(err)
|
||||
}
|
||||
return &mmappedMemory{buf: b[:cap]}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if com := uint64(len(m.buf)); com < size {
|
||||
// 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
|
||||
}
|
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)
|
||||
}
|
106
vendor/github.com/ncruces/go-sqlite3/internal/util/mmap.go
generated
vendored
Normal file
106
vendor/github.com/ncruces/go-sqlite3/internal/util/mmap.go
generated
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
//go:build (darwin || linux || illumos) && (amd64 || arm64 || riscv64) && !sqlite3_flock && !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"
|
||||
)
|
||||
|
||||
type mmapState struct {
|
||||
regions []*MappedRegion
|
||||
enabled bool
|
||||
}
|
||||
|
||||
func (s *mmapState) init(ctx context.Context, enabled bool) context.Context {
|
||||
if s.enabled = enabled; enabled {
|
||||
return experimental.WithMemoryAllocator(ctx,
|
||||
experimental.MemoryAllocatorFunc(mmappedAllocator))
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func CanMap(ctx context.Context) bool {
|
||||
s := ctx.Value(moduleKey{}).(*moduleState)
|
||||
return s.mmapState.enabled
|
||||
}
|
||||
|
||||
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)
|
15
vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_other.go
generated
vendored
Normal file
15
vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_other.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
//go:build !(darwin || linux || illumos) || !(amd64 || arm64 || riscv64) || sqlite3_flock || sqlite3_noshm || sqlite3_nosys
|
||||
|
||||
package util
|
||||
|
||||
import "context"
|
||||
|
||||
type mmapState struct{}
|
||||
|
||||
func (s *mmapState) init(ctx context.Context, _ bool) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
func CanMap(ctx context.Context) bool {
|
||||
return false
|
||||
}
|
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 {
|
||||
handleState
|
||||
mmapState
|
||||
}
|
||||
|
||||
func NewContext(ctx context.Context, mappableMemory bool) context.Context {
|
||||
state := new(moduleState)
|
||||
ctx = context.WithValue(ctx, moduleKey{}, state)
|
||||
ctx = experimental.WithCloseNotifier(ctx, state)
|
||||
ctx = state.mmapState.init(ctx, mappableMemory)
|
||||
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))
|
||||
}
|
333
vendor/github.com/ncruces/go-sqlite3/sqlite.go
generated
vendored
Normal file
333
vendor/github.com/ncruces/go-sqlite3/sqlite.go
generated
vendored
Normal file
|
@ -0,0 +1,333 @@
|
|||
// 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
|
||||
)
|
||||
|
||||
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) {
|
||||
instance.once.Do(compileSQLite)
|
||||
if instance.err != nil {
|
||||
return nil, instance.err
|
||||
}
|
||||
|
||||
sqlt = new(sqlite)
|
||||
sqlt.ctx = util.NewContext(context.Background(), vfs.SupportsSharedMemory)
|
||||
|
||||
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.ExportFuncIII(env, "go_busy_handler", busyCallback)
|
||||
util.ExportFuncII(env, "go_progress_handler", progressCallback)
|
||||
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
|
||||
}
|
599
vendor/github.com/ncruces/go-sqlite3/stmt.go
generated
vendored
Normal file
599
vendor/github.com/ncruces/go-sqlite3/stmt.go
generated
vendored
Normal file
|
@ -0,0 +1,599 @@
|
|||
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))
|
||||
|
||||
ptr := uint32(r)
|
||||
if ptr == 0 {
|
||||
panic(util.OOMErr)
|
||||
}
|
||||
return util.ReadString(s.c.mod, ptr, _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)
|
||||
}
|
||||
|
||||
// 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(int64(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(int64(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, int64(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
|
||||
}
|
80
vendor/github.com/ncruces/go-sqlite3/vfs/README.md
generated
vendored
Normal file
80
vendor/github.com/ncruces/go-sqlite3/vfs/README.md
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
# 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.
|
||||
|
||||
It also 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://sqlite.org/src/artifact/2e8b12?ln=1073-1161).
|
||||
|
||||
On Linux, macOS and illumos, 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.
|
||||
|
||||
On BSD Unixes, this module uses
|
||||
[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2).
|
||||
On BSD Unixes, BSD locks are fully compatible with POSIX advisory locks.
|
||||
However, concurrency is reduced with BSD locks
|
||||
(`BEGIN IMMEDIATE` behaves the same as `BEGIN EXCLUSIVE`).
|
||||
|
||||
On Windows, this module uses `LockFileEx` and `UnlockFileEx`,
|
||||
like SQLite.
|
||||
|
||||
On all other platforms, 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 platform supports file locking.
|
||||
|
||||
### Write-Ahead Logging
|
||||
|
||||
On 64-bit Linux, macOS and illumos, 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 amount of address space each connection needs,
|
||||
use [`WithMemoryLimitPages`](../tests/parallel/parallel_test.go#L21).
|
||||
|
||||
On all other platforms, [WAL](https://sqlite.org/wal.html) support is
|
||||
[limited](https://sqlite.org/wal.html#noshm).
|
||||
|
||||
To work around this limitation, SQLite is [patched](sqlite3/locking_mode.patch)
|
||||
to automatically use `EXCLUSIVE` locking mode for WAL databases on such platforms.
|
||||
|
||||
To use the [`database/sql`](https://pkg.go.dev/database/sql) driver
|
||||
with `EXCLUSIVE` locking mode you should 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 platform 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 macOS to test the BSD locking implementation.
|
||||
- `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.
|
122
vendor/github.com/ncruces/go-sqlite3/vfs/api.go
generated
vendored
Normal file
122
vendor/github.com/ncruces/go-sqlite3/vfs/api.go
generated
vendored
Normal file
|
@ -0,0 +1,122 @@
|
|||
// Package vfs wraps the C SQLite VFS API.
|
||||
package vfs
|
||||
|
||||
import "net/url"
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// VFSParams extends VFS with the ability to handle URI parameters
|
||||
// through the OpenParams method.
|
||||
//
|
||||
// https://sqlite.org/c3ref/uri_boolean.html
|
||||
type VFSParams interface {
|
||||
VFS
|
||||
OpenParams(name string, flags OpenFlag, params url.Values) (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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
230
vendor/github.com/ncruces/go-sqlite3/vfs/const.go
generated
vendored
Normal file
230
vendor/github.com/ncruces/go-sqlite3/vfs/const.go
generated
vendored
Normal file
|
@ -0,0 +1,230 @@
|
|||
package vfs
|
||||
|
||||
import "github.com/ncruces/go-sqlite3/internal/util"
|
||||
|
||||
const (
|
||||
_MAX_NAME = 1e6 // Self-imposed limit for most NUL terminated strings.
|
||||
_MAX_PATHNAME = 1024
|
||||
_DEFAULT_SECTOR_SIZE = 4096
|
||||
)
|
||||
|
||||
// https://sqlite.org/rescode.html
|
||||
type _ErrorCode uint32
|
||||
|
||||
func (e _ErrorCode) Error() string {
|
||||
return util.ErrorCodeString(uint32(e))
|
||||
}
|
||||
|
||||
const (
|
||||
_OK _ErrorCode = util.OK
|
||||
_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
|
||||
)
|
223
vendor/github.com/ncruces/go-sqlite3/vfs/file.go
generated
vendored
Normal file
223
vendor/github.com/ncruces/go-sqlite3/vfs/file.go
generated
vendored
Normal file
|
@ -0,0 +1,223 @@
|
|||
package vfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/util/osutil"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
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 vfsOS{}.OpenParams(name, flags, nil)
|
||||
}
|
||||
|
||||
func (vfsOS) OpenParams(name string, flags OpenFlag, params url.Values) (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 == "" {
|
||||
f, err = os.CreateTemp("", "*.db")
|
||||
} else {
|
||||
f, err = osutil.OpenFile(name, oflags, 0666)
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EISDIR) {
|
||||
return nil, flags, _CANTOPEN_ISDIR
|
||||
}
|
||||
return nil, flags, err
|
||||
}
|
||||
|
||||
if modeof := params.Get("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,
|
||||
}
|
||||
return &file, flags, nil
|
||||
}
|
||||
|
||||
type vfsFile struct {
|
||||
*os.File
|
||||
shm vfsShm
|
||||
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 {
|
||||
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 (*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 }
|
||||
|
||||
type fileShm interface {
|
||||
shmMap(context.Context, api.Module, int32, int32, bool) (uint32, error)
|
||||
shmLock(int32, int32, _ShmFlag) error
|
||||
shmUnmap(bool)
|
||||
}
|
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_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_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
|
||||
}
|
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 || 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)
|
||||
}
|
21
vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go
generated
vendored
Normal file
21
vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
//go:build !sqlite3_nosys
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"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)
|
||||
}
|
59
vendor/github.com/ncruces/go-sqlite3/vfs/os_ofd.go
generated
vendored
Normal file
59
vendor/github.com/ncruces/go-sqlite3/vfs/os_ofd.go
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
//go:build (linux || illumos) && !sqlite3_nosys
|
||||
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
103
vendor/github.com/ncruces/go-sqlite3/vfs/os_unix_lock.go
generated
vendored
Normal file
103
vendor/github.com/ncruces/go-sqlite3/vfs/os_unix_lock.go
generated
vendored
Normal file
|
@ -0,0 +1,103 @@
|
|||
//go:build (linux || darwin || freebsd || openbsd || netbsd || dragonfly || illumos) && !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
|
||||
}
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
47
vendor/github.com/ncruces/go-sqlite3/vfs/registry.go
generated
vendored
Normal file
47
vendor/github.com/ncruces/go-sqlite3/vfs/registry.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
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.
|
||||
//
|
||||
// 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)
|
||||
}
|
151
vendor/github.com/ncruces/go-sqlite3/vfs/shm.go
generated
vendored
Normal file
151
vendor/github.com/ncruces/go-sqlite3/vfs/shm.go
generated
vendored
Normal file
|
@ -0,0 +1,151 @@
|
|||
//go:build (darwin || linux || illumos) && (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 true on platforms that support shared memory.
|
||||
// To enable shared memory support on those platforms,
|
||||
// you need to set the appropriate [wazero.RuntimeConfig];
|
||||
// otherwise, [EXCLUSIVE locking mode] is activated automatically
|
||||
// to use [WAL without shared-memory].
|
||||
//
|
||||
// [WAL without shared-memory]: https://sqlite.org/wal.html#noshm
|
||||
// [EXCLUSIVE locking mode]: https://sqlite.org/pragma.html#pragma_locking_mode
|
||||
const SupportsSharedMemory = true
|
||||
|
||||
type vfsShm struct {
|
||||
*os.File
|
||||
regions []*util.MappedRegion
|
||||
}
|
||||
|
||||
const (
|
||||
_SHM_NLOCK = 8
|
||||
_SHM_BASE = 120
|
||||
_SHM_DMS = _SHM_BASE + _SHM_NLOCK
|
||||
)
|
||||
|
||||
func (f *vfsFile) 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 f.shm.File == nil {
|
||||
var flag int
|
||||
if f.readOnly {
|
||||
flag = unix.O_RDONLY
|
||||
} else {
|
||||
flag = unix.O_RDWR
|
||||
}
|
||||
s, err := os.OpenFile(f.Name()+"-shm",
|
||||
flag|unix.O_CREAT|unix.O_NOFOLLOW, 0666)
|
||||
if err != nil {
|
||||
return 0, _CANTOPEN
|
||||
}
|
||||
f.shm.File = s
|
||||
}
|
||||
|
||||
// Dead man's switch.
|
||||
if lock, rc := osGetLock(f.shm.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 f.readOnly {
|
||||
return 0, _READONLY_CANTINIT
|
||||
}
|
||||
if rc := osWriteLock(f.shm.File, _SHM_DMS, 1, 0); rc != _OK {
|
||||
return 0, rc
|
||||
}
|
||||
if err := f.shm.Truncate(0); err != nil {
|
||||
return 0, _IOERR_SHMOPEN
|
||||
}
|
||||
}
|
||||
if rc := osReadLock(f.shm.File, _SHM_DMS, 1, 0); rc != _OK {
|
||||
return 0, rc
|
||||
}
|
||||
|
||||
// Check if file is big enough.
|
||||
s, err := f.shm.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return 0, _IOERR_SHMSIZE
|
||||
}
|
||||
if n := (int64(id) + 1) * int64(size); n > s {
|
||||
if !extend {
|
||||
return 0, nil
|
||||
}
|
||||
err := osAllocate(f.shm.File, n)
|
||||
if err != nil {
|
||||
return 0, _IOERR_SHMSIZE
|
||||
}
|
||||
}
|
||||
|
||||
var prot int
|
||||
if f.readOnly {
|
||||
prot = unix.PROT_READ
|
||||
} else {
|
||||
prot = unix.PROT_READ | unix.PROT_WRITE
|
||||
}
|
||||
r, err := util.MapRegion(ctx, mod, f.shm.File, int64(id)*int64(size), size, prot)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
f.shm.regions = append(f.shm.regions, r)
|
||||
return r.Ptr, nil
|
||||
}
|
||||
|
||||
func (f *vfsFile) 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(f.shm.File, _SHM_BASE+int64(offset), int64(n))
|
||||
case flags&_SHM_SHARED != 0:
|
||||
return osReadLock(f.shm.File, _SHM_BASE+int64(offset), int64(n), 0)
|
||||
case flags&_SHM_EXCLUSIVE != 0:
|
||||
return osWriteLock(f.shm.File, _SHM_BASE+int64(offset), int64(n), 0)
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
}
|
||||
|
||||
func (f *vfsFile) shmUnmap(delete bool) {
|
||||
// Unmap regions.
|
||||
for _, r := range f.shm.regions {
|
||||
r.Unmap()
|
||||
}
|
||||
clear(f.shm.regions)
|
||||
f.shm.regions = f.shm.regions[:0]
|
||||
|
||||
// Close the file.
|
||||
if delete && f.shm.File != nil {
|
||||
os.Remove(f.shm.Name())
|
||||
}
|
||||
f.shm.Close()
|
||||
f.shm.File = nil
|
||||
}
|
17
vendor/github.com/ncruces/go-sqlite3/vfs/shm_other.go
generated
vendored
Normal file
17
vendor/github.com/ncruces/go-sqlite3/vfs/shm_other.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
//go:build !(darwin || linux || illumos) || !(amd64 || arm64 || riscv64) || sqlite3_flock || sqlite3_noshm || sqlite3_nosys
|
||||
|
||||
package vfs
|
||||
|
||||
// SupportsSharedMemory is true on platforms that support shared memory.
|
||||
// To enable shared memory support on those platforms,
|
||||
// you need to set the appropriate [wazero.RuntimeConfig];
|
||||
// otherwise, [EXCLUSIVE locking mode] is activated automatically
|
||||
// to use [WAL without shared-memory].
|
||||
//
|
||||
// [WAL without shared-memory]: https://sqlite.org/wal.html#noshm
|
||||
// [EXCLUSIVE locking mode]: https://sqlite.org/pragma.html#pragma_locking_mode
|
||||
const SupportsSharedMemory = false
|
||||
|
||||
type vfsShm struct{}
|
||||
|
||||
func (vfsShm) Close() error { return nil }
|
464
vendor/github.com/ncruces/go-sqlite3/vfs/vfs.go
generated
vendored
Normal file
464
vendor/github.com/ncruces/go-sqlite3/vfs/vfs.go
generated
vendored
Normal file
|
@ -0,0 +1,464 @@
|
|||
package vfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/julianday"
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// ExportHostFunctions is an internal API users need not call directly.
|
||||
//
|
||||
// ExportHostFunctions registers the required VFS host functions
|
||||
// with the provided env module.
|
||||
func ExportHostFunctions(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
|
||||
util.ExportFuncII(env, "go_vfs_find", vfsFind)
|
||||
util.ExportFuncIIJ(env, "go_localtime", vfsLocaltime)
|
||||
util.ExportFuncIIII(env, "go_randomness", vfsRandomness)
|
||||
util.ExportFuncIII(env, "go_sleep", vfsSleep)
|
||||
util.ExportFuncIII(env, "go_current_time_64", vfsCurrentTime64)
|
||||
util.ExportFuncIIIII(env, "go_full_pathname", vfsFullPathname)
|
||||
util.ExportFuncIIII(env, "go_delete", vfsDelete)
|
||||
util.ExportFuncIIIII(env, "go_access", vfsAccess)
|
||||
util.ExportFuncIIIIIII(env, "go_open", vfsOpen)
|
||||
util.ExportFuncII(env, "go_close", vfsClose)
|
||||
util.ExportFuncIIIIJ(env, "go_read", vfsRead)
|
||||
util.ExportFuncIIIIJ(env, "go_write", vfsWrite)
|
||||
util.ExportFuncIIJ(env, "go_truncate", vfsTruncate)
|
||||
util.ExportFuncIII(env, "go_sync", vfsSync)
|
||||
util.ExportFuncIII(env, "go_file_size", vfsFileSize)
|
||||
util.ExportFuncIIII(env, "go_file_control", vfsFileControl)
|
||||
util.ExportFuncII(env, "go_sector_size", vfsSectorSize)
|
||||
util.ExportFuncII(env, "go_device_characteristics", vfsDeviceCharacteristics)
|
||||
util.ExportFuncIII(env, "go_lock", vfsLock)
|
||||
util.ExportFuncIII(env, "go_unlock", vfsUnlock)
|
||||
util.ExportFuncIII(env, "go_check_reserved_lock", vfsCheckReservedLock)
|
||||
util.ExportFuncIIIIII(env, "go_shm_map", vfsShmMap)
|
||||
util.ExportFuncIIIII(env, "go_shm_lock", vfsShmLock)
|
||||
util.ExportFuncIII(env, "go_shm_unmap", vfsShmUnmap)
|
||||
util.ExportFuncVI(env, "go_shm_barrier", vfsShmBarrier)
|
||||
return env
|
||||
}
|
||||
|
||||
func vfsFind(ctx context.Context, mod api.Module, zVfsName uint32) uint32 {
|
||||
name := util.ReadString(mod, zVfsName, _MAX_NAME)
|
||||
if vfs := Find(name); vfs != nil && vfs != (vfsOS{}) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func vfsLocaltime(ctx context.Context, mod api.Module, pTm uint32, t int64) _ErrorCode {
|
||||
tm := time.Unix(t, 0)
|
||||
var isdst int
|
||||
if tm.IsDST() {
|
||||
isdst = 1
|
||||
}
|
||||
|
||||
const size = 32 / 8
|
||||
// https://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html
|
||||
util.WriteUint32(mod, pTm+0*size, uint32(tm.Second()))
|
||||
util.WriteUint32(mod, pTm+1*size, uint32(tm.Minute()))
|
||||
util.WriteUint32(mod, pTm+2*size, uint32(tm.Hour()))
|
||||
util.WriteUint32(mod, pTm+3*size, uint32(tm.Day()))
|
||||
util.WriteUint32(mod, pTm+4*size, uint32(tm.Month()-time.January))
|
||||
util.WriteUint32(mod, pTm+5*size, uint32(tm.Year()-1900))
|
||||
util.WriteUint32(mod, pTm+6*size, uint32(tm.Weekday()-time.Sunday))
|
||||
util.WriteUint32(mod, pTm+7*size, uint32(tm.YearDay()-1))
|
||||
util.WriteUint32(mod, pTm+8*size, uint32(isdst))
|
||||
return _OK
|
||||
}
|
||||
|
||||
func vfsRandomness(ctx context.Context, mod api.Module, pVfs uint32, nByte int32, zByte uint32) uint32 {
|
||||
mem := util.View(mod, zByte, uint64(nByte))
|
||||
n, _ := rand.Reader.Read(mem)
|
||||
return uint32(n)
|
||||
}
|
||||
|
||||
func vfsSleep(ctx context.Context, mod api.Module, pVfs uint32, nMicro int32) _ErrorCode {
|
||||
osSleep(time.Duration(nMicro) * time.Microsecond)
|
||||
return _OK
|
||||
}
|
||||
|
||||
func vfsCurrentTime64(ctx context.Context, mod api.Module, pVfs, piNow uint32) _ErrorCode {
|
||||
day, nsec := julianday.Date(time.Now())
|
||||
msec := day*86_400_000 + nsec/1_000_000
|
||||
util.WriteUint64(mod, piNow, uint64(msec))
|
||||
return _OK
|
||||
}
|
||||
|
||||
func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative uint32, nFull int32, zFull uint32) _ErrorCode {
|
||||
vfs := vfsGet(mod, pVfs)
|
||||
path := util.ReadString(mod, zRelative, _MAX_PATHNAME)
|
||||
|
||||
path, err := vfs.FullPathname(path)
|
||||
|
||||
if len(path) >= int(nFull) {
|
||||
return _CANTOPEN_FULLPATH
|
||||
}
|
||||
util.WriteString(mod, zFull, path)
|
||||
|
||||
return vfsErrorCode(err, _CANTOPEN_FULLPATH)
|
||||
}
|
||||
|
||||
func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath, syncDir uint32) _ErrorCode {
|
||||
vfs := vfsGet(mod, pVfs)
|
||||
path := util.ReadString(mod, zPath, _MAX_PATHNAME)
|
||||
|
||||
err := vfs.Delete(path, syncDir != 0)
|
||||
return vfsErrorCode(err, _IOERR_DELETE)
|
||||
}
|
||||
|
||||
func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath uint32, flags AccessFlag, pResOut uint32) _ErrorCode {
|
||||
vfs := vfsGet(mod, pVfs)
|
||||
path := util.ReadString(mod, zPath, _MAX_PATHNAME)
|
||||
|
||||
ok, err := vfs.Access(path, flags)
|
||||
var res uint32
|
||||
if ok {
|
||||
res = 1
|
||||
}
|
||||
|
||||
util.WriteUint32(mod, pResOut, res)
|
||||
return vfsErrorCode(err, _IOERR_ACCESS)
|
||||
}
|
||||
|
||||
func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, flags OpenFlag, pOutFlags, pOutVFS uint32) _ErrorCode {
|
||||
vfs := vfsGet(mod, pVfs)
|
||||
|
||||
var path string
|
||||
if zPath != 0 {
|
||||
path = util.ReadString(mod, zPath, _MAX_PATHNAME)
|
||||
}
|
||||
|
||||
var file File
|
||||
var err error
|
||||
var parsed bool
|
||||
var params url.Values
|
||||
if pfs, ok := vfs.(VFSParams); ok {
|
||||
parsed = true
|
||||
params = vfsURIParameters(ctx, mod, zPath, flags)
|
||||
file, flags, err = pfs.OpenParams(path, flags, params)
|
||||
} else {
|
||||
file, flags, err = vfs.Open(path, flags)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return vfsErrorCode(err, _CANTOPEN)
|
||||
}
|
||||
|
||||
if file, ok := file.(FilePowersafeOverwrite); ok {
|
||||
if !parsed {
|
||||
params = vfsURIParameters(ctx, mod, zPath, flags)
|
||||
}
|
||||
if b, ok := util.ParseBool(params.Get("psow")); ok {
|
||||
file.SetPowersafeOverwrite(b)
|
||||
}
|
||||
}
|
||||
|
||||
if pOutFlags != 0 {
|
||||
util.WriteUint32(mod, pOutFlags, uint32(flags))
|
||||
}
|
||||
if pOutVFS != 0 && util.CanMap(ctx) {
|
||||
util.WriteUint32(mod, pOutVFS, 1)
|
||||
}
|
||||
vfsFileRegister(ctx, mod, pFile, file)
|
||||
return _OK
|
||||
}
|
||||
|
||||
func vfsClose(ctx context.Context, mod api.Module, pFile uint32) _ErrorCode {
|
||||
err := vfsFileClose(ctx, mod, pFile)
|
||||
if err != nil {
|
||||
return vfsErrorCode(err, _IOERR_CLOSE)
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
|
||||
func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int32, iOfst int64) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
buf := util.View(mod, zBuf, uint64(iAmt))
|
||||
|
||||
n, err := file.ReadAt(buf, iOfst)
|
||||
if n == int(iAmt) {
|
||||
return _OK
|
||||
}
|
||||
if err != io.EOF {
|
||||
return vfsErrorCode(err, _IOERR_READ)
|
||||
}
|
||||
clear(buf[n:])
|
||||
return _IOERR_SHORT_READ
|
||||
}
|
||||
|
||||
func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int32, iOfst int64) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
buf := util.View(mod, zBuf, uint64(iAmt))
|
||||
|
||||
_, err := file.WriteAt(buf, iOfst)
|
||||
if err != nil {
|
||||
return vfsErrorCode(err, _IOERR_WRITE)
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
|
||||
func vfsTruncate(ctx context.Context, mod api.Module, pFile uint32, nByte int64) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
err := file.Truncate(nByte)
|
||||
return vfsErrorCode(err, _IOERR_TRUNCATE)
|
||||
}
|
||||
|
||||
func vfsSync(ctx context.Context, mod api.Module, pFile uint32, flags SyncFlag) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
err := file.Sync(flags)
|
||||
return vfsErrorCode(err, _IOERR_FSYNC)
|
||||
}
|
||||
|
||||
func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize uint32) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
size, err := file.Size()
|
||||
util.WriteUint64(mod, pSize, uint64(size))
|
||||
return vfsErrorCode(err, _IOERR_SEEK)
|
||||
}
|
||||
|
||||
func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock LockLevel) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
err := file.Lock(eLock)
|
||||
return vfsErrorCode(err, _IOERR_LOCK)
|
||||
}
|
||||
|
||||
func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock LockLevel) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
err := file.Unlock(eLock)
|
||||
return vfsErrorCode(err, _IOERR_UNLOCK)
|
||||
}
|
||||
|
||||
func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
locked, err := file.CheckReservedLock()
|
||||
|
||||
var res uint32
|
||||
if locked {
|
||||
res = 1
|
||||
}
|
||||
|
||||
util.WriteUint32(mod, pResOut, res)
|
||||
return vfsErrorCode(err, _IOERR_CHECKRESERVEDLOCK)
|
||||
}
|
||||
|
||||
func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _FcntlOpcode, pArg uint32) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
|
||||
switch op {
|
||||
case _FCNTL_LOCKSTATE:
|
||||
if file, ok := file.(FileLockState); ok {
|
||||
util.WriteUint32(mod, pArg, uint32(file.LockState()))
|
||||
return _OK
|
||||
}
|
||||
|
||||
case _FCNTL_PERSIST_WAL:
|
||||
if file, ok := file.(FilePersistentWAL); ok {
|
||||
if i := util.ReadUint32(mod, pArg); int32(i) >= 0 {
|
||||
file.SetPersistentWAL(i != 0)
|
||||
} else if file.PersistentWAL() {
|
||||
util.WriteUint32(mod, pArg, 1)
|
||||
} else {
|
||||
util.WriteUint32(mod, pArg, 0)
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
|
||||
case _FCNTL_POWERSAFE_OVERWRITE:
|
||||
if file, ok := file.(FilePowersafeOverwrite); ok {
|
||||
if i := util.ReadUint32(mod, pArg); int32(i) >= 0 {
|
||||
file.SetPowersafeOverwrite(i != 0)
|
||||
} else if file.PowersafeOverwrite() {
|
||||
util.WriteUint32(mod, pArg, 1)
|
||||
} else {
|
||||
util.WriteUint32(mod, pArg, 0)
|
||||
}
|
||||
return _OK
|
||||
}
|
||||
|
||||
case _FCNTL_SIZE_HINT:
|
||||
if file, ok := file.(FileSizeHint); ok {
|
||||
size := util.ReadUint64(mod, pArg)
|
||||
err := file.SizeHint(int64(size))
|
||||
return vfsErrorCode(err, _IOERR_TRUNCATE)
|
||||
}
|
||||
|
||||
case _FCNTL_HAS_MOVED:
|
||||
if file, ok := file.(FileHasMoved); ok {
|
||||
moved, err := file.HasMoved()
|
||||
var res uint32
|
||||
if moved {
|
||||
res = 1
|
||||
}
|
||||
util.WriteUint32(mod, pArg, res)
|
||||
return vfsErrorCode(err, _IOERR_FSTAT)
|
||||
}
|
||||
|
||||
case _FCNTL_OVERWRITE:
|
||||
if file, ok := file.(FileOverwrite); ok {
|
||||
err := file.Overwrite()
|
||||
return vfsErrorCode(err, _IOERR)
|
||||
}
|
||||
|
||||
case _FCNTL_COMMIT_PHASETWO:
|
||||
if file, ok := file.(FileCommitPhaseTwo); ok {
|
||||
err := file.CommitPhaseTwo()
|
||||
return vfsErrorCode(err, _IOERR)
|
||||
}
|
||||
|
||||
case _FCNTL_BEGIN_ATOMIC_WRITE:
|
||||
if file, ok := file.(FileBatchAtomicWrite); ok {
|
||||
err := file.BeginAtomicWrite()
|
||||
return vfsErrorCode(err, _IOERR_BEGIN_ATOMIC)
|
||||
}
|
||||
case _FCNTL_COMMIT_ATOMIC_WRITE:
|
||||
if file, ok := file.(FileBatchAtomicWrite); ok {
|
||||
err := file.CommitAtomicWrite()
|
||||
return vfsErrorCode(err, _IOERR_COMMIT_ATOMIC)
|
||||
}
|
||||
case _FCNTL_ROLLBACK_ATOMIC_WRITE:
|
||||
if file, ok := file.(FileBatchAtomicWrite); ok {
|
||||
err := file.RollbackAtomicWrite()
|
||||
return vfsErrorCode(err, _IOERR_ROLLBACK_ATOMIC)
|
||||
}
|
||||
}
|
||||
|
||||
// Consider also implementing these opcodes (in use by SQLite):
|
||||
// _FCNTL_BUSYHANDLER
|
||||
// _FCNTL_CHUNK_SIZE
|
||||
// _FCNTL_CKPT_DONE
|
||||
// _FCNTL_CKPT_START
|
||||
// _FCNTL_PRAGMA
|
||||
// _FCNTL_SYNC
|
||||
return _NOTFOUND
|
||||
}
|
||||
|
||||
func vfsSectorSize(ctx context.Context, mod api.Module, pFile uint32) uint32 {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
return uint32(file.SectorSize())
|
||||
}
|
||||
|
||||
func vfsDeviceCharacteristics(ctx context.Context, mod api.Module, pFile uint32) DeviceCharacteristic {
|
||||
file := vfsFileGet(ctx, mod, pFile)
|
||||
return file.DeviceCharacteristics()
|
||||
}
|
||||
|
||||
var shmBarrier sync.Mutex
|
||||
|
||||
func vfsShmBarrier(ctx context.Context, mod api.Module, pFile uint32) {
|
||||
shmBarrier.Lock()
|
||||
defer shmBarrier.Unlock()
|
||||
}
|
||||
|
||||
func vfsShmMap(ctx context.Context, mod api.Module, pFile uint32, iRegion, szRegion int32, bExtend, pp uint32) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile).(fileShm)
|
||||
p, err := file.shmMap(ctx, mod, iRegion, szRegion, bExtend != 0)
|
||||
if err != nil {
|
||||
return vfsErrorCode(err, _IOERR_SHMMAP)
|
||||
}
|
||||
util.WriteUint32(mod, pp, p)
|
||||
return _OK
|
||||
}
|
||||
|
||||
func vfsShmLock(ctx context.Context, mod api.Module, pFile uint32, offset, n int32, flags _ShmFlag) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile).(fileShm)
|
||||
err := file.shmLock(offset, n, flags)
|
||||
return vfsErrorCode(err, _IOERR_SHMLOCK)
|
||||
}
|
||||
|
||||
func vfsShmUnmap(ctx context.Context, mod api.Module, pFile, bDelete uint32) _ErrorCode {
|
||||
file := vfsFileGet(ctx, mod, pFile).(fileShm)
|
||||
file.shmUnmap(bDelete != 0)
|
||||
return _OK
|
||||
}
|
||||
|
||||
func vfsURIParameters(ctx context.Context, mod api.Module, zPath uint32, flags OpenFlag) url.Values {
|
||||
if flags&OPEN_URI == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
uriParam := mod.ExportedFunction("sqlite3_uri_parameter")
|
||||
uriKey := mod.ExportedFunction("sqlite3_uri_key")
|
||||
if uriParam == nil || uriKey == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var stack [2]uint64
|
||||
var params url.Values
|
||||
|
||||
for i := 0; ; i++ {
|
||||
stack[1] = uint64(i)
|
||||
stack[0] = uint64(zPath)
|
||||
if err := uriKey.CallWithStack(ctx, stack[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if stack[0] == 0 {
|
||||
return params
|
||||
}
|
||||
key := util.ReadString(mod, uint32(stack[0]), _MAX_NAME)
|
||||
if params.Has(key) {
|
||||
continue
|
||||
}
|
||||
|
||||
stack[1] = stack[0]
|
||||
stack[0] = uint64(zPath)
|
||||
if err := uriParam.CallWithStack(ctx, stack[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if params == nil {
|
||||
params = url.Values{}
|
||||
}
|
||||
params.Set(key, util.ReadString(mod, uint32(stack[0]), _MAX_NAME))
|
||||
}
|
||||
}
|
||||
|
||||
func vfsGet(mod api.Module, pVfs uint32) VFS {
|
||||
var name string
|
||||
if pVfs != 0 {
|
||||
const zNameOffset = 16
|
||||
name = util.ReadString(mod, util.ReadUint32(mod, pVfs+zNameOffset), _MAX_NAME)
|
||||
}
|
||||
if vfs := Find(name); vfs != nil {
|
||||
return vfs
|
||||
}
|
||||
panic(util.NoVFSErr + util.ErrorString(name))
|
||||
}
|
||||
|
||||
func vfsFileRegister(ctx context.Context, mod api.Module, pFile uint32, file File) {
|
||||
const fileHandleOffset = 4
|
||||
id := util.AddHandle(ctx, file)
|
||||
util.WriteUint32(mod, pFile+fileHandleOffset, id)
|
||||
}
|
||||
|
||||
func vfsFileGet(ctx context.Context, mod api.Module, pFile uint32) File {
|
||||
const fileHandleOffset = 4
|
||||
id := util.ReadUint32(mod, pFile+fileHandleOffset)
|
||||
return util.GetHandle(ctx, id).(File)
|
||||
}
|
||||
|
||||
func vfsFileClose(ctx context.Context, mod api.Module, pFile uint32) error {
|
||||
const fileHandleOffset = 4
|
||||
id := util.ReadUint32(mod, pFile+fileHandleOffset)
|
||||
return util.DelHandle(ctx, id)
|
||||
}
|
||||
|
||||
func vfsErrorCode(err error, def _ErrorCode) _ErrorCode {
|
||||
if err == nil {
|
||||
return _OK
|
||||
}
|
||||
switch v := reflect.ValueOf(err); v.Kind() {
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
return _ErrorCode(v.Uint())
|
||||
}
|
||||
return def
|
||||
}
|
663
vendor/github.com/ncruces/go-sqlite3/vtab.go
generated
vendored
Normal file
663
vendor/github.com/ncruces/go-sqlite3/vtab.go
generated
vendored
Normal file
|
@ -0,0 +1,663 @@
|
|||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// CreateModule registers a new virtual table module name.
|
||||
// If create is nil, the virtual table is eponymous.
|
||||
//
|
||||
// https://sqlite.org/c3ref/create_module.html
|
||||
func CreateModule[T VTab](db *Conn, name string, create, connect VTabConstructor[T]) error {
|
||||
var flags int
|
||||
|
||||
const (
|
||||
VTAB_CREATOR = 0x01
|
||||
VTAB_DESTROYER = 0x02
|
||||
VTAB_UPDATER = 0x04
|
||||
VTAB_RENAMER = 0x08
|
||||
VTAB_OVERLOADER = 0x10
|
||||
VTAB_CHECKER = 0x20
|
||||
VTAB_TXN = 0x40
|
||||
VTAB_SAVEPOINTER = 0x80
|
||||
)
|
||||
|
||||
if create != nil {
|
||||
flags |= VTAB_CREATOR
|
||||
}
|
||||
|
||||
vtab := reflect.TypeOf(connect).Out(0)
|
||||
if implements[VTabDestroyer](vtab) {
|
||||
flags |= VTAB_DESTROYER
|
||||
}
|
||||
if implements[VTabUpdater](vtab) {
|
||||
flags |= VTAB_UPDATER
|
||||
}
|
||||
if implements[VTabRenamer](vtab) {
|
||||
flags |= VTAB_RENAMER
|
||||
}
|
||||
if implements[VTabOverloader](vtab) {
|
||||
flags |= VTAB_OVERLOADER
|
||||
}
|
||||
if implements[VTabChecker](vtab) {
|
||||
flags |= VTAB_CHECKER
|
||||
}
|
||||
if implements[VTabTxn](vtab) {
|
||||
flags |= VTAB_TXN
|
||||
}
|
||||
if implements[VTabSavepointer](vtab) {
|
||||
flags |= VTAB_SAVEPOINTER
|
||||
}
|
||||
|
||||
defer db.arena.mark()()
|
||||
namePtr := db.arena.string(name)
|
||||
modulePtr := util.AddHandle(db.ctx, module[T]{create, connect})
|
||||
r := db.call("sqlite3_create_module_go", uint64(db.handle),
|
||||
uint64(namePtr), uint64(flags), uint64(modulePtr))
|
||||
return db.error(r)
|
||||
}
|
||||
|
||||
func implements[T any](typ reflect.Type) bool {
|
||||
var ptr *T
|
||||
return typ.Implements(reflect.TypeOf(ptr).Elem())
|
||||
}
|
||||
|
||||
// DeclareVTab declares the schema of a virtual table.
|
||||
//
|
||||
// https://sqlite.org/c3ref/declare_vtab.html
|
||||
func (c *Conn) DeclareVTab(sql string) error {
|
||||
defer c.arena.mark()()
|
||||
sqlPtr := c.arena.string(sql)
|
||||
r := c.call("sqlite3_declare_vtab", uint64(c.handle), uint64(sqlPtr))
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
// VTabConflictMode is a virtual table conflict resolution mode.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_fail.html
|
||||
type VTabConflictMode uint8
|
||||
|
||||
const (
|
||||
VTAB_ROLLBACK VTabConflictMode = 1
|
||||
VTAB_IGNORE VTabConflictMode = 2
|
||||
VTAB_FAIL VTabConflictMode = 3
|
||||
VTAB_ABORT VTabConflictMode = 4
|
||||
VTAB_REPLACE VTabConflictMode = 5
|
||||
)
|
||||
|
||||
// VTabOnConflict determines the virtual table conflict policy.
|
||||
//
|
||||
// https://sqlite.org/c3ref/vtab_on_conflict.html
|
||||
func (c *Conn) VTabOnConflict() VTabConflictMode {
|
||||
r := c.call("sqlite3_vtab_on_conflict", uint64(c.handle))
|
||||
return VTabConflictMode(r)
|
||||
}
|
||||
|
||||
// VTabConfigOption is a virtual table configuration option.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_vtab_constraint_support.html
|
||||
type VTabConfigOption uint8
|
||||
|
||||
const (
|
||||
VTAB_CONSTRAINT_SUPPORT VTabConfigOption = 1
|
||||
VTAB_INNOCUOUS VTabConfigOption = 2
|
||||
VTAB_DIRECTONLY VTabConfigOption = 3
|
||||
VTAB_USES_ALL_SCHEMAS VTabConfigOption = 4
|
||||
)
|
||||
|
||||
// VTabConfig configures various facets of the virtual table interface.
|
||||
//
|
||||
// https://sqlite.org/c3ref/vtab_config.html
|
||||
func (c *Conn) VTabConfig(op VTabConfigOption, args ...any) error {
|
||||
var i uint64
|
||||
if op == VTAB_CONSTRAINT_SUPPORT && len(args) > 0 {
|
||||
if b, ok := args[0].(bool); ok && b {
|
||||
i = 1
|
||||
}
|
||||
}
|
||||
r := c.call("sqlite3_vtab_config_go", uint64(c.handle), uint64(op), i)
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
// VTabConstructor is a virtual table constructor function.
|
||||
type VTabConstructor[T VTab] func(db *Conn, module, schema, table string, arg ...string) (T, error)
|
||||
|
||||
type module[T VTab] [2]VTabConstructor[T]
|
||||
|
||||
type vtabConstructor int
|
||||
|
||||
const (
|
||||
xCreate vtabConstructor = 0
|
||||
xConnect vtabConstructor = 1
|
||||
)
|
||||
|
||||
// A VTab describes a particular instance of the virtual table.
|
||||
// A VTab may optionally implement [io.Closer] to free resources.
|
||||
//
|
||||
// https://sqlite.org/c3ref/vtab.html
|
||||
type VTab interface {
|
||||
// https://sqlite.org/vtab.html#xbestindex
|
||||
BestIndex(*IndexInfo) error
|
||||
// https://sqlite.org/vtab.html#xopen
|
||||
Open() (VTabCursor, error)
|
||||
}
|
||||
|
||||
// A VTabDestroyer allows a virtual table to drop persistent state.
|
||||
type VTabDestroyer interface {
|
||||
VTab
|
||||
// https://sqlite.org/vtab.html#sqlite3_module.xDestroy
|
||||
Destroy() error
|
||||
}
|
||||
|
||||
// A VTabUpdater allows a virtual table to be updated.
|
||||
type VTabUpdater interface {
|
||||
VTab
|
||||
// https://sqlite.org/vtab.html#xupdate
|
||||
Update(arg ...Value) (rowid int64, err error)
|
||||
}
|
||||
|
||||
// A VTabRenamer allows a virtual table to be renamed.
|
||||
type VTabRenamer interface {
|
||||
VTab
|
||||
// https://sqlite.org/vtab.html#xrename
|
||||
Rename(new string) error
|
||||
}
|
||||
|
||||
// A VTabOverloader allows a virtual table to overload SQL functions.
|
||||
type VTabOverloader interface {
|
||||
VTab
|
||||
// https://sqlite.org/vtab.html#xfindfunction
|
||||
FindFunction(arg int, name string) (ScalarFunction, IndexConstraintOp)
|
||||
}
|
||||
|
||||
// A VTabChecker allows a virtual table to report errors
|
||||
// to the PRAGMA integrity_check and PRAGMA quick_check commands.
|
||||
//
|
||||
// Integrity should return an error if it finds problems in the content of the virtual table,
|
||||
// but should avoid returning a (wrapped) [Error], [ErrorCode] or [ExtendedErrorCode],
|
||||
// as those indicate the Integrity method itself encountered problems
|
||||
// while trying to evaluate the virtual table content.
|
||||
type VTabChecker interface {
|
||||
VTab
|
||||
// https://sqlite.org/vtab.html#xintegrity
|
||||
Integrity(schema, table string, flags int) error
|
||||
}
|
||||
|
||||
// A VTabTxn allows a virtual table to implement
|
||||
// transactions with two-phase commit.
|
||||
//
|
||||
// Anything that is required as part of a commit that may fail
|
||||
// should be performed in the Sync() callback.
|
||||
// Current versions of SQLite ignore any errors
|
||||
// returned by Commit() and Rollback().
|
||||
type VTabTxn interface {
|
||||
VTab
|
||||
// https://sqlite.org/vtab.html#xBegin
|
||||
Begin() error
|
||||
// https://sqlite.org/vtab.html#xsync
|
||||
Sync() error
|
||||
// https://sqlite.org/vtab.html#xcommit
|
||||
Commit() error
|
||||
// https://sqlite.org/vtab.html#xrollback
|
||||
Rollback() error
|
||||
}
|
||||
|
||||
// A VTabSavepointer allows a virtual table to implement
|
||||
// nested transactions.
|
||||
//
|
||||
// https://sqlite.org/vtab.html#xsavepoint
|
||||
type VTabSavepointer interface {
|
||||
VTabTxn
|
||||
Savepoint(id int) error
|
||||
Release(id int) error
|
||||
RollbackTo(id int) error
|
||||
}
|
||||
|
||||
// A VTabCursor describes cursors that point
|
||||
// into the virtual table and are used
|
||||
// to loop through the virtual table.
|
||||
// A VTabCursor may optionally implement
|
||||
// [io.Closer] to free resources.
|
||||
//
|
||||
// http://sqlite.org/c3ref/vtab_cursor.html
|
||||
type VTabCursor interface {
|
||||
// https://sqlite.org/vtab.html#xfilter
|
||||
Filter(idxNum int, idxStr string, arg ...Value) error
|
||||
// https://sqlite.org/vtab.html#xnext
|
||||
Next() error
|
||||
// https://sqlite.org/vtab.html#xeof
|
||||
EOF() bool
|
||||
// https://sqlite.org/vtab.html#xcolumn
|
||||
Column(ctx *Context, n int) error
|
||||
// https://sqlite.org/vtab.html#xrowid
|
||||
RowID() (int64, error)
|
||||
}
|
||||
|
||||
// An IndexInfo describes virtual table indexing information.
|
||||
//
|
||||
// https://sqlite.org/c3ref/index_info.html
|
||||
type IndexInfo struct {
|
||||
// Inputs
|
||||
Constraint []IndexConstraint
|
||||
OrderBy []IndexOrderBy
|
||||
ColumnsUsed int64
|
||||
// Outputs
|
||||
ConstraintUsage []IndexConstraintUsage
|
||||
IdxNum int
|
||||
IdxStr string
|
||||
IdxFlags IndexScanFlag
|
||||
OrderByConsumed bool
|
||||
EstimatedCost float64
|
||||
EstimatedRows int64
|
||||
// Internal
|
||||
c *Conn
|
||||
handle uint32
|
||||
}
|
||||
|
||||
// An IndexConstraint describes virtual table indexing constraint information.
|
||||
//
|
||||
// https://sqlite.org/c3ref/index_info.html
|
||||
type IndexConstraint struct {
|
||||
Column int
|
||||
Op IndexConstraintOp
|
||||
Usable bool
|
||||
}
|
||||
|
||||
// An IndexOrderBy describes virtual table indexing order by information.
|
||||
//
|
||||
// https://sqlite.org/c3ref/index_info.html
|
||||
type IndexOrderBy struct {
|
||||
Column int
|
||||
Desc bool
|
||||
}
|
||||
|
||||
// An IndexConstraintUsage describes how virtual table indexing constraints will be used.
|
||||
//
|
||||
// https://sqlite.org/c3ref/index_info.html
|
||||
type IndexConstraintUsage struct {
|
||||
ArgvIndex int
|
||||
Omit bool
|
||||
}
|
||||
|
||||
// RHSValue returns the value of the right-hand operand of a constraint
|
||||
// if the right-hand operand is known.
|
||||
//
|
||||
// https://sqlite.org/c3ref/vtab_rhs_value.html
|
||||
func (idx *IndexInfo) RHSValue(column int) (Value, error) {
|
||||
defer idx.c.arena.mark()()
|
||||
valPtr := idx.c.arena.new(ptrlen)
|
||||
r := idx.c.call("sqlite3_vtab_rhs_value", uint64(idx.handle),
|
||||
uint64(column), uint64(valPtr))
|
||||
if err := idx.c.error(r); err != nil {
|
||||
return Value{}, err
|
||||
}
|
||||
return Value{
|
||||
c: idx.c,
|
||||
handle: util.ReadUint32(idx.c.mod, valPtr),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Collation returns the name of the collation for a virtual table constraint.
|
||||
//
|
||||
// https://sqlite.org/c3ref/vtab_collation.html
|
||||
func (idx *IndexInfo) Collation(column int) string {
|
||||
r := idx.c.call("sqlite3_vtab_collation", uint64(idx.handle),
|
||||
uint64(column))
|
||||
return util.ReadString(idx.c.mod, uint32(r), _MAX_NAME)
|
||||
}
|
||||
|
||||
// Distinct determines if a virtual table query is DISTINCT.
|
||||
//
|
||||
// https://sqlite.org/c3ref/vtab_distinct.html
|
||||
func (idx *IndexInfo) Distinct() int {
|
||||
r := idx.c.call("sqlite3_vtab_distinct", uint64(idx.handle))
|
||||
return int(r)
|
||||
}
|
||||
|
||||
// In identifies and handles IN constraints.
|
||||
//
|
||||
// https://sqlite.org/c3ref/vtab_in.html
|
||||
func (idx *IndexInfo) In(column, handle int) bool {
|
||||
r := idx.c.call("sqlite3_vtab_in", uint64(idx.handle),
|
||||
uint64(column), uint64(handle))
|
||||
return r != 0
|
||||
}
|
||||
|
||||
func (idx *IndexInfo) load() {
|
||||
// https://sqlite.org/c3ref/index_info.html
|
||||
mod := idx.c.mod
|
||||
ptr := idx.handle
|
||||
|
||||
idx.Constraint = make([]IndexConstraint, util.ReadUint32(mod, ptr+0))
|
||||
idx.ConstraintUsage = make([]IndexConstraintUsage, util.ReadUint32(mod, ptr+0))
|
||||
idx.OrderBy = make([]IndexOrderBy, util.ReadUint32(mod, ptr+8))
|
||||
|
||||
constraintPtr := util.ReadUint32(mod, ptr+4)
|
||||
for i := range idx.Constraint {
|
||||
idx.Constraint[i] = IndexConstraint{
|
||||
Column: int(int32(util.ReadUint32(mod, constraintPtr+0))),
|
||||
Op: IndexConstraintOp(util.ReadUint8(mod, constraintPtr+4)),
|
||||
Usable: util.ReadUint8(mod, constraintPtr+5) != 0,
|
||||
}
|
||||
constraintPtr += 12
|
||||
}
|
||||
|
||||
orderByPtr := util.ReadUint32(mod, ptr+12)
|
||||
for i := range idx.OrderBy {
|
||||
idx.OrderBy[i] = IndexOrderBy{
|
||||
Column: int(int32(util.ReadUint32(mod, orderByPtr+0))),
|
||||
Desc: util.ReadUint8(mod, orderByPtr+4) != 0,
|
||||
}
|
||||
orderByPtr += 8
|
||||
}
|
||||
|
||||
idx.EstimatedCost = util.ReadFloat64(mod, ptr+40)
|
||||
idx.EstimatedRows = int64(util.ReadUint64(mod, ptr+48))
|
||||
idx.ColumnsUsed = int64(util.ReadUint64(mod, ptr+64))
|
||||
}
|
||||
|
||||
func (idx *IndexInfo) save() {
|
||||
// https://sqlite.org/c3ref/index_info.html
|
||||
mod := idx.c.mod
|
||||
ptr := idx.handle
|
||||
|
||||
usagePtr := util.ReadUint32(mod, ptr+16)
|
||||
for _, usage := range idx.ConstraintUsage {
|
||||
util.WriteUint32(mod, usagePtr+0, uint32(usage.ArgvIndex))
|
||||
if usage.Omit {
|
||||
util.WriteUint8(mod, usagePtr+4, 1)
|
||||
}
|
||||
usagePtr += 8
|
||||
}
|
||||
|
||||
util.WriteUint32(mod, ptr+20, uint32(idx.IdxNum))
|
||||
if idx.IdxStr != "" {
|
||||
util.WriteUint32(mod, ptr+24, idx.c.newString(idx.IdxStr))
|
||||
util.WriteUint32(mod, ptr+28, 1) // needToFreeIdxStr
|
||||
}
|
||||
if idx.OrderByConsumed {
|
||||
util.WriteUint32(mod, ptr+32, 1)
|
||||
}
|
||||
util.WriteFloat64(mod, ptr+40, idx.EstimatedCost)
|
||||
util.WriteUint64(mod, ptr+48, uint64(idx.EstimatedRows))
|
||||
util.WriteUint32(mod, ptr+56, uint32(idx.IdxFlags))
|
||||
}
|
||||
|
||||
// IndexConstraintOp is a virtual table constraint operator code.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_index_constraint_eq.html
|
||||
type IndexConstraintOp uint8
|
||||
|
||||
const (
|
||||
INDEX_CONSTRAINT_EQ IndexConstraintOp = 2
|
||||
INDEX_CONSTRAINT_GT IndexConstraintOp = 4
|
||||
INDEX_CONSTRAINT_LE IndexConstraintOp = 8
|
||||
INDEX_CONSTRAINT_LT IndexConstraintOp = 16
|
||||
INDEX_CONSTRAINT_GE IndexConstraintOp = 32
|
||||
INDEX_CONSTRAINT_MATCH IndexConstraintOp = 64
|
||||
INDEX_CONSTRAINT_LIKE IndexConstraintOp = 65
|
||||
INDEX_CONSTRAINT_GLOB IndexConstraintOp = 66
|
||||
INDEX_CONSTRAINT_REGEXP IndexConstraintOp = 67
|
||||
INDEX_CONSTRAINT_NE IndexConstraintOp = 68
|
||||
INDEX_CONSTRAINT_ISNOT IndexConstraintOp = 69
|
||||
INDEX_CONSTRAINT_ISNOTNULL IndexConstraintOp = 70
|
||||
INDEX_CONSTRAINT_ISNULL IndexConstraintOp = 71
|
||||
INDEX_CONSTRAINT_IS IndexConstraintOp = 72
|
||||
INDEX_CONSTRAINT_LIMIT IndexConstraintOp = 73
|
||||
INDEX_CONSTRAINT_OFFSET IndexConstraintOp = 74
|
||||
INDEX_CONSTRAINT_FUNCTION IndexConstraintOp = 150
|
||||
)
|
||||
|
||||
// IndexScanFlag is a virtual table scan flag.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_index_scan_unique.html
|
||||
type IndexScanFlag uint32
|
||||
|
||||
const (
|
||||
INDEX_SCAN_UNIQUE IndexScanFlag = 1
|
||||
)
|
||||
|
||||
func vtabModuleCallback(i vtabConstructor) func(_ context.Context, _ api.Module, _, _, _, _, _ uint32) uint32 {
|
||||
return func(ctx context.Context, mod api.Module, pMod, nArg, pArg, ppVTab, pzErr uint32) uint32 {
|
||||
arg := make([]reflect.Value, 1+nArg)
|
||||
arg[0] = reflect.ValueOf(ctx.Value(connKey{}))
|
||||
|
||||
for i := uint32(0); i < nArg; i++ {
|
||||
ptr := util.ReadUint32(mod, pArg+i*ptrlen)
|
||||
arg[i+1] = reflect.ValueOf(util.ReadString(mod, ptr, _MAX_SQL_LENGTH))
|
||||
}
|
||||
|
||||
module := vtabGetHandle(ctx, mod, pMod)
|
||||
res := reflect.ValueOf(module).Index(int(i)).Call(arg)
|
||||
err, _ := res[1].Interface().(error)
|
||||
if err == nil {
|
||||
vtabPutHandle(ctx, mod, ppVTab, res[0].Interface())
|
||||
}
|
||||
|
||||
return vtabError(ctx, mod, pzErr, _PTR_ERROR, err)
|
||||
}
|
||||
}
|
||||
|
||||
func vtabDisconnectCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
err := vtabDelHandle(ctx, mod, pVTab)
|
||||
return vtabError(ctx, mod, 0, _PTR_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabDestroyCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabDestroyer)
|
||||
err := vtab.Destroy()
|
||||
if cerr := vtabDelHandle(ctx, mod, pVTab); err == nil {
|
||||
err = cerr
|
||||
}
|
||||
return vtabError(ctx, mod, 0, _PTR_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabBestIndexCallback(ctx context.Context, mod api.Module, pVTab, pIdxInfo uint32) uint32 {
|
||||
var info IndexInfo
|
||||
info.handle = pIdxInfo
|
||||
info.c = ctx.Value(connKey{}).(*Conn)
|
||||
info.load()
|
||||
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTab)
|
||||
err := vtab.BestIndex(&info)
|
||||
|
||||
info.save()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabUpdateCallback(ctx context.Context, mod api.Module, pVTab, nArg, pArg, pRowID uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabUpdater)
|
||||
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
args := make([]Value, nArg)
|
||||
callbackArgs(db, args, pArg)
|
||||
rowID, err := vtab.Update(args...)
|
||||
if err == nil {
|
||||
util.WriteUint64(mod, pRowID, uint64(rowID))
|
||||
}
|
||||
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabRenameCallback(ctx context.Context, mod api.Module, pVTab, zNew uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabRenamer)
|
||||
err := vtab.Rename(util.ReadString(mod, zNew, _MAX_NAME))
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabFindFuncCallback(ctx context.Context, mod api.Module, pVTab uint32, nArg int32, zName, pxFunc uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabOverloader)
|
||||
f, op := vtab.FindFunction(int(nArg), util.ReadString(mod, zName, _MAX_NAME))
|
||||
if op != 0 {
|
||||
var wrapper uint32
|
||||
wrapper = util.AddHandle(ctx, func(c Context, arg ...Value) {
|
||||
defer util.DelHandle(ctx, wrapper)
|
||||
f(c, arg...)
|
||||
})
|
||||
util.WriteUint32(mod, pxFunc, wrapper)
|
||||
}
|
||||
return uint32(op)
|
||||
}
|
||||
|
||||
func vtabIntegrityCallback(ctx context.Context, mod api.Module, pVTab, zSchema, zTabName, mFlags, pzErr uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabChecker)
|
||||
schema := util.ReadString(mod, zSchema, _MAX_NAME)
|
||||
table := util.ReadString(mod, zTabName, _MAX_NAME)
|
||||
err := vtab.Integrity(schema, table, int(mFlags))
|
||||
// xIntegrity should return OK - even if it finds problems in the content of the virtual table.
|
||||
// https://sqlite.org/vtab.html#xintegrity
|
||||
vtabError(ctx, mod, pzErr, _PTR_ERROR, err)
|
||||
_, code := errorCode(err, _OK)
|
||||
return code
|
||||
}
|
||||
|
||||
func vtabBeginCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
||||
err := vtab.Begin()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabSyncCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
||||
err := vtab.Sync()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabCommitCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
||||
err := vtab.Commit()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabRollbackCallback(ctx context.Context, mod api.Module, pVTab uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn)
|
||||
err := vtab.Rollback()
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabSavepointCallback(ctx context.Context, mod api.Module, pVTab uint32, id int32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
|
||||
err := vtab.Savepoint(int(id))
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabReleaseCallback(ctx context.Context, mod api.Module, pVTab uint32, id int32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
|
||||
err := vtab.Release(int(id))
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func vtabRollbackToCallback(ctx context.Context, mod api.Module, pVTab uint32, id int32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer)
|
||||
err := vtab.RollbackTo(int(id))
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func cursorOpenCallback(ctx context.Context, mod api.Module, pVTab, ppCur uint32) uint32 {
|
||||
vtab := vtabGetHandle(ctx, mod, pVTab).(VTab)
|
||||
|
||||
cursor, err := vtab.Open()
|
||||
if err == nil {
|
||||
vtabPutHandle(ctx, mod, ppCur, cursor)
|
||||
}
|
||||
|
||||
return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func cursorCloseCallback(ctx context.Context, mod api.Module, pCur uint32) uint32 {
|
||||
err := vtabDelHandle(ctx, mod, pCur)
|
||||
return vtabError(ctx, mod, 0, _VTAB_ERROR, err)
|
||||
}
|
||||
|
||||
func cursorFilterCallback(ctx context.Context, mod api.Module, pCur uint32, idxNum int32, idxStr, nArg, pArg uint32) uint32 {
|
||||
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
args := make([]Value, nArg)
|
||||
callbackArgs(db, args, pArg)
|
||||
var idxName string
|
||||
if idxStr != 0 {
|
||||
idxName = util.ReadString(mod, idxStr, _MAX_LENGTH)
|
||||
}
|
||||
err := cursor.Filter(int(idxNum), idxName, args...)
|
||||
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
|
||||
}
|
||||
|
||||
func cursorEOFCallback(ctx context.Context, mod api.Module, pCur uint32) uint32 {
|
||||
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
|
||||
if cursor.EOF() {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func cursorNextCallback(ctx context.Context, mod api.Module, pCur uint32) uint32 {
|
||||
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
|
||||
err := cursor.Next()
|
||||
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
|
||||
}
|
||||
|
||||
func cursorColumnCallback(ctx context.Context, mod api.Module, pCur, pCtx uint32, n int32) uint32 {
|
||||
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
err := cursor.Column(&Context{db, pCtx}, int(n))
|
||||
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
|
||||
}
|
||||
|
||||
func cursorRowIDCallback(ctx context.Context, mod api.Module, pCur, pRowID uint32) uint32 {
|
||||
cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor)
|
||||
|
||||
rowID, err := cursor.RowID()
|
||||
if err == nil {
|
||||
util.WriteUint64(mod, pRowID, uint64(rowID))
|
||||
}
|
||||
|
||||
return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err)
|
||||
}
|
||||
|
||||
const (
|
||||
_PTR_ERROR = iota
|
||||
_VTAB_ERROR
|
||||
_CURSOR_ERROR
|
||||
)
|
||||
|
||||
func vtabError(ctx context.Context, mod api.Module, ptr, kind uint32, err error) uint32 {
|
||||
const zErrMsgOffset = 8
|
||||
msg, code := errorCode(err, ERROR)
|
||||
if msg != "" && ptr != 0 {
|
||||
switch kind {
|
||||
case _VTAB_ERROR:
|
||||
ptr = ptr + zErrMsgOffset // zErrMsg
|
||||
case _CURSOR_ERROR:
|
||||
ptr = util.ReadUint32(mod, ptr) + zErrMsgOffset // pVTab->zErrMsg
|
||||
}
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
if ptr := util.ReadUint32(mod, ptr); ptr != 0 {
|
||||
db.free(ptr)
|
||||
}
|
||||
util.WriteUint32(mod, ptr, db.newString(msg))
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
func vtabGetHandle(ctx context.Context, mod api.Module, ptr uint32) any {
|
||||
const handleOffset = 4
|
||||
handle := util.ReadUint32(mod, ptr-handleOffset)
|
||||
return util.GetHandle(ctx, handle)
|
||||
}
|
||||
|
||||
func vtabDelHandle(ctx context.Context, mod api.Module, ptr uint32) error {
|
||||
const handleOffset = 4
|
||||
handle := util.ReadUint32(mod, ptr-handleOffset)
|
||||
return util.DelHandle(ctx, handle)
|
||||
}
|
||||
|
||||
func vtabPutHandle(ctx context.Context, mod api.Module, pptr uint32, val any) {
|
||||
const handleOffset = 4
|
||||
handle := util.AddHandle(ctx, val)
|
||||
ptr := util.ReadUint32(mod, pptr)
|
||||
util.WriteUint32(mod, ptr-handleOffset, handle)
|
||||
}
|
5
vendor/github.com/ncruces/go-strftime/README.md
generated
vendored
5
vendor/github.com/ncruces/go-strftime/README.md
generated
vendored
|
@ -1,5 +0,0 @@
|
|||
# `strftime`/`strptime` compatible time formatting and parsing for Go
|
||||
|
||||
[![Go Reference](https://pkg.go.dev/badge/image)](https://pkg.go.dev/github.com/ncruces/go-strftime)
|
||||
[![Go Report](https://goreportcard.com/badge/github.com/ncruces/go-strftime)](https://goreportcard.com/report/github.com/ncruces/go-strftime)
|
||||
[![Go Coverage](https://github.com/ncruces/go-strftime/wiki/coverage.svg)](https://raw.githack.com/wiki/ncruces/go-strftime/coverage.html)
|
107
vendor/github.com/ncruces/go-strftime/parser.go
generated
vendored
107
vendor/github.com/ncruces/go-strftime/parser.go
generated
vendored
|
@ -1,107 +0,0 @@
|
|||
package strftime
|
||||
|
||||
import "unicode/utf8"
|
||||
|
||||
type parser struct {
|
||||
format func(spec, flag byte) error
|
||||
literal func(byte) error
|
||||
}
|
||||
|
||||
func (p *parser) parse(fmt string) error {
|
||||
const (
|
||||
initial = iota
|
||||
percent
|
||||
flagged
|
||||
modified
|
||||
)
|
||||
|
||||
var flag, modifier byte
|
||||
var err error
|
||||
state := initial
|
||||
start := 0
|
||||
for i, b := range []byte(fmt) {
|
||||
switch state {
|
||||
default:
|
||||
if b == '%' {
|
||||
state = percent
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
err = p.literal(b)
|
||||
|
||||
case percent:
|
||||
if b == '-' || b == ':' {
|
||||
state = flagged
|
||||
flag = b
|
||||
continue
|
||||
}
|
||||
if b == 'E' || b == 'O' {
|
||||
state = modified
|
||||
modifier = b
|
||||
flag = 0
|
||||
continue
|
||||
}
|
||||
err = p.format(b, 0)
|
||||
state = initial
|
||||
|
||||
case flagged:
|
||||
if b == 'E' || b == 'O' {
|
||||
state = modified
|
||||
modifier = b
|
||||
continue
|
||||
}
|
||||
err = p.format(b, flag)
|
||||
state = initial
|
||||
|
||||
case modified:
|
||||
if okModifier(modifier, b) {
|
||||
err = p.format(b, flag)
|
||||
} else {
|
||||
err = p.literals(fmt[start : i+1])
|
||||
}
|
||||
state = initial
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err, ok := err.(formatError); ok {
|
||||
err.setDirective(fmt, start, i)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if state != initial {
|
||||
return p.literals(fmt[start:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) literals(literal string) error {
|
||||
for _, b := range []byte(literal) {
|
||||
if err := p.literal(b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type literalErr string
|
||||
|
||||
func (e literalErr) Error() string {
|
||||
return "strftime: unsupported literal: " + string(e)
|
||||
}
|
||||
|
||||
type formatError struct {
|
||||
message string
|
||||
directive string
|
||||
}
|
||||
|
||||
func (e formatError) Error() string {
|
||||
return "strftime: unsupported directive: " + e.directive + " " + e.message
|
||||
}
|
||||
|
||||
func (e *formatError) setDirective(str string, i, j int) {
|
||||
_, n := utf8.DecodeRuneInString(str[j:])
|
||||
e.directive = str[i : j+n]
|
||||
}
|
96
vendor/github.com/ncruces/go-strftime/pkg.go
generated
vendored
96
vendor/github.com/ncruces/go-strftime/pkg.go
generated
vendored
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
Package strftime provides strftime/strptime compatible time formatting and parsing.
|
||||
|
||||
The following specifiers are available:
|
||||
|
||||
Date (Year, Month, Day):
|
||||
%Y - Year with century (can be negative, 4 digits at least)
|
||||
-0001, 0000, 1995, 2009, 14292, etc.
|
||||
%C - year / 100 (round down, 20 in 2009)
|
||||
%y - year % 100 (00..99)
|
||||
|
||||
%m - Month of the year, zero-padded (01..12)
|
||||
%-m no-padded (1..12)
|
||||
%B - Full month name (January)
|
||||
%b - Abbreviated month name (Jan)
|
||||
%h - Equivalent to %b
|
||||
|
||||
%d - Day of the month, zero-padded (01..31)
|
||||
%-d no-padded (1..31)
|
||||
%e - Day of the month, blank-padded ( 1..31)
|
||||
|
||||
%j - Day of the year (001..366)
|
||||
%-j no-padded (1..366)
|
||||
|
||||
Time (Hour, Minute, Second, Subsecond):
|
||||
%H - Hour of the day, 24-hour clock, zero-padded (00..23)
|
||||
%-H no-padded (0..23)
|
||||
%k - Hour of the day, 24-hour clock, blank-padded ( 0..23)
|
||||
%I - Hour of the day, 12-hour clock, zero-padded (01..12)
|
||||
%-I no-padded (1..12)
|
||||
%l - Hour of the day, 12-hour clock, blank-padded ( 1..12)
|
||||
%P - Meridian indicator, lowercase (am or pm)
|
||||
%p - Meridian indicator, uppercase (AM or PM)
|
||||
|
||||
%M - Minute of the hour (00..59)
|
||||
%-M no-padded (0..59)
|
||||
|
||||
%S - Second of the minute (00..60)
|
||||
%-S no-padded (0..60)
|
||||
|
||||
%L - Millisecond of the second (000..999)
|
||||
%f - Microsecond of the second (000000..999999)
|
||||
%N - Nanosecond of the second (000000000..999999999)
|
||||
|
||||
Time zone:
|
||||
%z - Time zone as hour and minute offset from UTC (e.g. +0900)
|
||||
%:z - hour and minute offset from UTC with a colon (e.g. +09:00)
|
||||
%Z - Time zone abbreviation (e.g. MST)
|
||||
|
||||
Weekday:
|
||||
%A - Full weekday name (Sunday)
|
||||
%a - Abbreviated weekday name (Sun)
|
||||
%u - Day of the week (Monday is 1, 1..7)
|
||||
%w - Day of the week (Sunday is 0, 0..6)
|
||||
|
||||
ISO 8601 week-based year and week number:
|
||||
Week 1 of YYYY starts with a Monday and includes YYYY-01-04.
|
||||
The days in the year before the first week are in the last week of
|
||||
the previous year.
|
||||
%G - Week-based year
|
||||
%g - Last 2 digits of the week-based year (00..99)
|
||||
%V - Week number of the week-based year (01..53)
|
||||
%-V no-padded (1..53)
|
||||
|
||||
Week number:
|
||||
Week 1 of YYYY starts with a Sunday or Monday (according to %U or %W).
|
||||
The days in the year before the first week are in week 0.
|
||||
%U - Week number of the year. The week starts with Sunday. (00..53)
|
||||
%-U no-padded (0..53)
|
||||
%W - Week number of the year. The week starts with Monday. (00..53)
|
||||
%-W no-padded (0..53)
|
||||
|
||||
Seconds since the Unix Epoch:
|
||||
%s - Number of seconds since 1970-01-01 00:00:00 UTC.
|
||||
%Q - Number of milliseconds since 1970-01-01 00:00:00 UTC.
|
||||
|
||||
Literal string:
|
||||
%n - Newline character (\n)
|
||||
%t - Tab character (\t)
|
||||
%% - Literal % character
|
||||
|
||||
Combination:
|
||||
%c - date and time (%a %b %e %T %Y)
|
||||
%D - Date (%m/%d/%y)
|
||||
%F - ISO 8601 date format (%Y-%m-%d)
|
||||
%v - VMS date (%e-%b-%Y)
|
||||
%x - Same as %D
|
||||
%X - Same as %T
|
||||
%r - 12-hour time (%I:%M:%S %p)
|
||||
%R - 24-hour time (%H:%M)
|
||||
%T - 24-hour time (%H:%M:%S)
|
||||
%+ - date(1) (%a %b %e %H:%M:%S %Z %Y)
|
||||
|
||||
The modifiers ``E'' and ``O'' are ignored.
|
||||
*/
|
||||
package strftime
|
241
vendor/github.com/ncruces/go-strftime/specifiers.go
generated
vendored
241
vendor/github.com/ncruces/go-strftime/specifiers.go
generated
vendored
|
@ -1,241 +0,0 @@
|
|||
package strftime
|
||||
|
||||
import "strings"
|
||||
|
||||
// https://strftime.org/
|
||||
func goLayout(spec, flag byte, parsing bool) string {
|
||||
switch spec {
|
||||
default:
|
||||
return ""
|
||||
|
||||
case 'B':
|
||||
return "January"
|
||||
case 'b', 'h':
|
||||
return "Jan"
|
||||
case 'm':
|
||||
if flag == '-' || parsing {
|
||||
return "1"
|
||||
}
|
||||
return "01"
|
||||
case 'A':
|
||||
return "Monday"
|
||||
case 'a':
|
||||
return "Mon"
|
||||
case 'e':
|
||||
return "_2"
|
||||
case 'd':
|
||||
if flag == '-' || parsing {
|
||||
return "2"
|
||||
}
|
||||
return "02"
|
||||
case 'j':
|
||||
if flag == '-' {
|
||||
if parsing {
|
||||
return "__2"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return "002"
|
||||
case 'I':
|
||||
if flag == '-' || parsing {
|
||||
return "3"
|
||||
}
|
||||
return "03"
|
||||
case 'H':
|
||||
if flag == '-' && !parsing {
|
||||
return ""
|
||||
}
|
||||
return "15"
|
||||
case 'M':
|
||||
if flag == '-' || parsing {
|
||||
return "4"
|
||||
}
|
||||
return "04"
|
||||
case 'S':
|
||||
if flag == '-' || parsing {
|
||||
return "5"
|
||||
}
|
||||
return "05"
|
||||
case 'y':
|
||||
return "06"
|
||||
case 'Y':
|
||||
return "2006"
|
||||
case 'p':
|
||||
return "PM"
|
||||
case 'P':
|
||||
return "pm"
|
||||
case 'Z':
|
||||
return "MST"
|
||||
case 'z':
|
||||
if flag == ':' {
|
||||
if parsing {
|
||||
return "Z07:00"
|
||||
}
|
||||
return "-07:00"
|
||||
}
|
||||
if parsing {
|
||||
return "Z0700"
|
||||
}
|
||||
return "-0700"
|
||||
|
||||
case '+':
|
||||
if parsing {
|
||||
return "Mon Jan _2 15:4:5 MST 2006"
|
||||
}
|
||||
return "Mon Jan _2 15:04:05 MST 2006"
|
||||
case 'c':
|
||||
if parsing {
|
||||
return "Mon Jan _2 15:4:5 2006"
|
||||
}
|
||||
return "Mon Jan _2 15:04:05 2006"
|
||||
case 'v':
|
||||
return "_2-Jan-2006"
|
||||
case 'F':
|
||||
if parsing {
|
||||
return "2006-1-2"
|
||||
}
|
||||
return "2006-01-02"
|
||||
case 'D', 'x':
|
||||
if parsing {
|
||||
return "1/2/06"
|
||||
}
|
||||
return "01/02/06"
|
||||
case 'r':
|
||||
if parsing {
|
||||
return "3:4:5 PM"
|
||||
}
|
||||
return "03:04:05 PM"
|
||||
case 'T', 'X':
|
||||
if parsing {
|
||||
return "15:4:5"
|
||||
}
|
||||
return "15:04:05"
|
||||
case 'R':
|
||||
if parsing {
|
||||
return "15:4"
|
||||
}
|
||||
return "15:04"
|
||||
|
||||
case '%':
|
||||
return "%"
|
||||
case 't':
|
||||
return "\t"
|
||||
case 'n':
|
||||
return "\n"
|
||||
}
|
||||
}
|
||||
|
||||
// https://nsdateformatter.com/
|
||||
func uts35Pattern(spec, flag byte) string {
|
||||
switch spec {
|
||||
default:
|
||||
return ""
|
||||
|
||||
case 'B':
|
||||
return "MMMM"
|
||||
case 'b', 'h':
|
||||
return "MMM"
|
||||
case 'm':
|
||||
if flag == '-' {
|
||||
return "M"
|
||||
}
|
||||
return "MM"
|
||||
case 'A':
|
||||
return "EEEE"
|
||||
case 'a':
|
||||
return "E"
|
||||
case 'd':
|
||||
if flag == '-' {
|
||||
return "d"
|
||||
}
|
||||
return "dd"
|
||||
case 'j':
|
||||
if flag == '-' {
|
||||
return "D"
|
||||
}
|
||||
return "DDD"
|
||||
case 'I':
|
||||
if flag == '-' {
|
||||
return "h"
|
||||
}
|
||||
return "hh"
|
||||
case 'H':
|
||||
if flag == '-' {
|
||||
return "H"
|
||||
}
|
||||
return "HH"
|
||||
case 'M':
|
||||
if flag == '-' {
|
||||
return "m"
|
||||
}
|
||||
return "mm"
|
||||
case 'S':
|
||||
if flag == '-' {
|
||||
return "s"
|
||||
}
|
||||
return "ss"
|
||||
case 'y':
|
||||
return "yy"
|
||||
case 'Y':
|
||||
return "yyyy"
|
||||
case 'g':
|
||||
return "YY"
|
||||
case 'G':
|
||||
return "YYYY"
|
||||
case 'V':
|
||||
if flag == '-' {
|
||||
return "w"
|
||||
}
|
||||
return "ww"
|
||||
case 'p':
|
||||
return "a"
|
||||
case 'Z':
|
||||
return "zzz"
|
||||
case 'z':
|
||||
if flag == ':' {
|
||||
return "xxx"
|
||||
}
|
||||
return "xx"
|
||||
case 'L':
|
||||
return "SSS"
|
||||
case 'f':
|
||||
return "SSSSSS"
|
||||
case 'N':
|
||||
return "SSSSSSSSS"
|
||||
|
||||
case '+':
|
||||
return "E MMM d HH:mm:ss zzz yyyy"
|
||||
case 'c':
|
||||
return "E MMM d HH:mm:ss yyyy"
|
||||
case 'v':
|
||||
return "d-MMM-yyyy"
|
||||
case 'F':
|
||||
return "yyyy-MM-dd"
|
||||
case 'D', 'x':
|
||||
return "MM/dd/yy"
|
||||
case 'r':
|
||||
return "hh:mm:ss a"
|
||||
case 'T', 'X':
|
||||
return "HH:mm:ss"
|
||||
case 'R':
|
||||
return "HH:mm"
|
||||
|
||||
case '%':
|
||||
return "%"
|
||||
case 't':
|
||||
return "\t"
|
||||
case 'n':
|
||||
return "\n"
|
||||
}
|
||||
}
|
||||
|
||||
// http://man.he.net/man3/strftime
|
||||
func okModifier(mod, spec byte) bool {
|
||||
if mod == 'E' {
|
||||
return strings.Contains("cCxXyY", string(spec))
|
||||
}
|
||||
if mod == 'O' {
|
||||
return strings.Contains("deHImMSuUVwWy", string(spec))
|
||||
}
|
||||
return false
|
||||
}
|
324
vendor/github.com/ncruces/go-strftime/strftime.go
generated
vendored
324
vendor/github.com/ncruces/go-strftime/strftime.go
generated
vendored
|
@ -1,324 +0,0 @@
|
|||
package strftime
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Format returns a textual representation of the time value
|
||||
// formatted according to the strftime format specification.
|
||||
func Format(fmt string, t time.Time) string {
|
||||
buf := buffer(fmt)
|
||||
return string(AppendFormat(buf, fmt, t))
|
||||
}
|
||||
|
||||
// AppendFormat is like Format, but appends the textual representation
|
||||
// to dst and returns the extended buffer.
|
||||
func AppendFormat(dst []byte, fmt string, t time.Time) []byte {
|
||||
var parser parser
|
||||
|
||||
parser.literal = func(b byte) error {
|
||||
dst = append(dst, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
parser.format = func(spec, flag byte) error {
|
||||
switch spec {
|
||||
case 'A':
|
||||
dst = append(dst, t.Weekday().String()...)
|
||||
return nil
|
||||
case 'a':
|
||||
dst = append(dst, t.Weekday().String()[:3]...)
|
||||
return nil
|
||||
case 'B':
|
||||
dst = append(dst, t.Month().String()...)
|
||||
return nil
|
||||
case 'b', 'h':
|
||||
dst = append(dst, t.Month().String()[:3]...)
|
||||
return nil
|
||||
case 'm':
|
||||
dst = appendInt2(dst, int(t.Month()), flag)
|
||||
return nil
|
||||
case 'd':
|
||||
dst = appendInt2(dst, int(t.Day()), flag)
|
||||
return nil
|
||||
case 'e':
|
||||
dst = appendInt2(dst, int(t.Day()), ' ')
|
||||
return nil
|
||||
case 'I':
|
||||
dst = append12Hour(dst, t, flag)
|
||||
return nil
|
||||
case 'l':
|
||||
dst = append12Hour(dst, t, ' ')
|
||||
return nil
|
||||
case 'H':
|
||||
dst = appendInt2(dst, t.Hour(), flag)
|
||||
return nil
|
||||
case 'k':
|
||||
dst = appendInt2(dst, t.Hour(), ' ')
|
||||
return nil
|
||||
case 'M':
|
||||
dst = appendInt2(dst, t.Minute(), flag)
|
||||
return nil
|
||||
case 'S':
|
||||
dst = appendInt2(dst, t.Second(), flag)
|
||||
return nil
|
||||
case 'L':
|
||||
dst = append(dst, t.Format(".000")[1:]...)
|
||||
return nil
|
||||
case 'f':
|
||||
dst = append(dst, t.Format(".000000")[1:]...)
|
||||
return nil
|
||||
case 'N':
|
||||
dst = append(dst, t.Format(".000000000")[1:]...)
|
||||
return nil
|
||||
case 'y':
|
||||
dst = t.AppendFormat(dst, "06")
|
||||
return nil
|
||||
case 'Y':
|
||||
dst = t.AppendFormat(dst, "2006")
|
||||
return nil
|
||||
case 'C':
|
||||
dst = t.AppendFormat(dst, "2006")
|
||||
dst = dst[:len(dst)-2]
|
||||
return nil
|
||||
case 'U':
|
||||
dst = appendWeekNumber(dst, t, flag, true)
|
||||
return nil
|
||||
case 'W':
|
||||
dst = appendWeekNumber(dst, t, flag, false)
|
||||
return nil
|
||||
case 'V':
|
||||
_, w := t.ISOWeek()
|
||||
dst = appendInt2(dst, w, flag)
|
||||
return nil
|
||||
case 'g':
|
||||
y, _ := t.ISOWeek()
|
||||
dst = year(y).AppendFormat(dst, "06")
|
||||
return nil
|
||||
case 'G':
|
||||
y, _ := t.ISOWeek()
|
||||
dst = year(y).AppendFormat(dst, "2006")
|
||||
return nil
|
||||
case 's':
|
||||
dst = strconv.AppendInt(dst, t.Unix(), 10)
|
||||
return nil
|
||||
case 'Q':
|
||||
dst = strconv.AppendInt(dst, t.UnixMilli(), 10)
|
||||
return nil
|
||||
case 'w':
|
||||
w := t.Weekday()
|
||||
dst = appendInt1(dst, int(w))
|
||||
return nil
|
||||
case 'u':
|
||||
if w := t.Weekday(); w == 0 {
|
||||
dst = append(dst, '7')
|
||||
} else {
|
||||
dst = appendInt1(dst, int(w))
|
||||
}
|
||||
return nil
|
||||
case 'j':
|
||||
if flag == '-' {
|
||||
dst = strconv.AppendInt(dst, int64(t.YearDay()), 10)
|
||||
} else {
|
||||
dst = t.AppendFormat(dst, "002")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if layout := goLayout(spec, flag, false); layout != "" {
|
||||
dst = t.AppendFormat(dst, layout)
|
||||
return nil
|
||||
}
|
||||
|
||||
dst = append(dst, '%')
|
||||
if flag != 0 {
|
||||
dst = append(dst, flag)
|
||||
}
|
||||
dst = append(dst, spec)
|
||||
return nil
|
||||
}
|
||||
|
||||
parser.parse(fmt)
|
||||
return dst
|
||||
}
|
||||
|
||||
// Parse converts a textual representation of time to the time value it represents
|
||||
// according to the strptime format specification.
|
||||
func Parse(fmt, value string) (time.Time, error) {
|
||||
pattern, err := layout(fmt, true)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return time.Parse(pattern, value)
|
||||
}
|
||||
|
||||
// Layout converts a strftime format specification
|
||||
// to a Go time pattern specification.
|
||||
func Layout(fmt string) (string, error) {
|
||||
return layout(fmt, false)
|
||||
}
|
||||
|
||||
func layout(fmt string, parsing bool) (string, error) {
|
||||
dst := buffer(fmt)
|
||||
var parser parser
|
||||
|
||||
parser.literal = func(b byte) error {
|
||||
if '0' <= b && b <= '9' {
|
||||
return literalErr(b)
|
||||
}
|
||||
dst = append(dst, b)
|
||||
if b == 'M' || b == 'T' || b == 'm' || b == 'n' {
|
||||
switch {
|
||||
case bytes.HasSuffix(dst, []byte("Jan")):
|
||||
return literalErr("Jan")
|
||||
case bytes.HasSuffix(dst, []byte("Mon")):
|
||||
return literalErr("Mon")
|
||||
case bytes.HasSuffix(dst, []byte("MST")):
|
||||
return literalErr("MST")
|
||||
case bytes.HasSuffix(dst, []byte("PM")):
|
||||
return literalErr("PM")
|
||||
case bytes.HasSuffix(dst, []byte("pm")):
|
||||
return literalErr("pm")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
parser.format = func(spec, flag byte) error {
|
||||
if layout := goLayout(spec, flag, parsing); layout != "" {
|
||||
dst = append(dst, layout...)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch spec {
|
||||
default:
|
||||
return formatError{}
|
||||
|
||||
case 'L', 'f', 'N':
|
||||
if bytes.HasSuffix(dst, []byte(".")) || bytes.HasSuffix(dst, []byte(",")) {
|
||||
switch spec {
|
||||
default:
|
||||
dst = append(dst, "000"...)
|
||||
case 'f':
|
||||
dst = append(dst, "000000"...)
|
||||
case 'N':
|
||||
dst = append(dst, "000000000"...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return formatError{message: "must follow '.' or ','"}
|
||||
}
|
||||
}
|
||||
|
||||
if err := parser.parse(fmt); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(dst), nil
|
||||
}
|
||||
|
||||
// UTS35 converts a strftime format specification
|
||||
// to a Unicode Technical Standard #35 Date Format Pattern.
|
||||
func UTS35(fmt string) (string, error) {
|
||||
const quote = '\''
|
||||
var quoted bool
|
||||
dst := buffer(fmt)
|
||||
|
||||
var parser parser
|
||||
|
||||
parser.literal = func(b byte) error {
|
||||
if b == quote {
|
||||
dst = append(dst, quote, quote)
|
||||
return nil
|
||||
}
|
||||
if !quoted && ('a' <= b && b <= 'z' || 'A' <= b && b <= 'Z') {
|
||||
dst = append(dst, quote)
|
||||
quoted = true
|
||||
}
|
||||
dst = append(dst, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
parser.format = func(spec, flag byte) error {
|
||||
if quoted {
|
||||
dst = append(dst, quote)
|
||||
quoted = false
|
||||
}
|
||||
if pattern := uts35Pattern(spec, flag); pattern != "" {
|
||||
dst = append(dst, pattern...)
|
||||
return nil
|
||||
}
|
||||
return formatError{}
|
||||
}
|
||||
|
||||
if err := parser.parse(fmt); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if quoted {
|
||||
dst = append(dst, quote)
|
||||
}
|
||||
return string(dst), nil
|
||||
}
|
||||
|
||||
func buffer(format string) (buf []byte) {
|
||||
const bufSize = 64
|
||||
max := len(format) + 10
|
||||
if max < bufSize {
|
||||
var b [bufSize]byte
|
||||
buf = b[:0]
|
||||
} else {
|
||||
buf = make([]byte, 0, max)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func year(y int) time.Time {
|
||||
return time.Date(y, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
}
|
||||
|
||||
func appendWeekNumber(dst []byte, t time.Time, flag byte, sunday bool) []byte {
|
||||
offset := int(t.Weekday())
|
||||
if sunday {
|
||||
offset = 6 - offset
|
||||
} else if offset != 0 {
|
||||
offset = 7 - offset
|
||||
}
|
||||
return appendInt2(dst, (t.YearDay()+offset)/7, flag)
|
||||
}
|
||||
|
||||
func append12Hour(dst []byte, t time.Time, flag byte) []byte {
|
||||
h := t.Hour()
|
||||
if h == 0 {
|
||||
h = 12
|
||||
} else if h > 12 {
|
||||
h -= 12
|
||||
}
|
||||
return appendInt2(dst, h, flag)
|
||||
}
|
||||
|
||||
func appendInt1(dst []byte, i int) []byte {
|
||||
return append(dst, byte('0'+i))
|
||||
}
|
||||
|
||||
func appendInt2(dst []byte, i int, flag byte) []byte {
|
||||
if flag == 0 || i >= 10 {
|
||||
return append(dst, smallsString[i*2:i*2+2]...)
|
||||
}
|
||||
if flag == ' ' {
|
||||
dst = append(dst, flag)
|
||||
}
|
||||
return appendInt1(dst, i)
|
||||
}
|
||||
|
||||
const smallsString = "" +
|
||||
"00010203040506070809" +
|
||||
"10111213141516171819" +
|
||||
"20212223242526272829" +
|
||||
"30313233343536373839" +
|
||||
"40414243444546474849" +
|
||||
"50515253545556575859" +
|
||||
"60616263646566676869" +
|
||||
"70717273747576777879" +
|
||||
"80818283848586878889" +
|
||||
"90919293949596979899"
|
9
vendor/github.com/ncruces/julianday/README.md
generated
vendored
Normal file
9
vendor/github.com/ncruces/julianday/README.md
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Julian Day calculator
|
||||
|
||||
[![Go Reference](https://pkg.go.dev/badge/image)](https://pkg.go.dev/github.com/ncruces/julianday)
|
||||
[![Go Report](https://goreportcard.com/badge/github.com/ncruces/julianday)](https://goreportcard.com/report/github.com/ncruces/julianday)
|
||||
[![Go Coverage](https://github.com/ncruces/julianday/wiki/coverage.svg)](https://raw.githack.com/wiki/ncruces/julianday/coverage.html)
|
||||
|
||||
https://en.wikipedia.org/wiki/Julian_day
|
||||
|
||||
Compatible with [SQLite](https://www.sqlite.org/lang_datefunc.html).
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue