mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-28 17:42:25 -05:00 
			
		
		
		
	[experiment] add alternative wasm sqlite3 implementation available via build-tag (#2863)
This allows for building GoToSocial with [SQLite transpiled to WASM](https://github.com/ncruces/go-sqlite3) and accessed through [Wazero](https://wazero.io/).
This commit is contained in:
		
					parent
					
						
							
								cce21c11cb
							
						
					
				
			
			
				commit
				
					
						1e7b32490d
					
				
			
		
					 398 changed files with 86174 additions and 684 deletions
				
			
		
							
								
								
									
										5
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										5
									
								
								go.mod
									
										
									
									
									
								
							|  | @ -44,6 +44,7 @@ require ( | ||||||
| 	github.com/miekg/dns v1.1.59 | 	github.com/miekg/dns v1.1.59 | ||||||
| 	github.com/minio/minio-go/v7 v7.0.70 | 	github.com/minio/minio-go/v7 v7.0.70 | ||||||
| 	github.com/mitchellh/mapstructure v1.5.0 | 	github.com/mitchellh/mapstructure v1.5.0 | ||||||
|  | 	github.com/ncruces/go-sqlite3 v0.16.0 | ||||||
| 	github.com/oklog/ulid v1.3.1 | 	github.com/oklog/ulid v1.3.1 | ||||||
| 	github.com/prometheus/client_golang v1.19.1 | 	github.com/prometheus/client_golang v1.19.1 | ||||||
| 	github.com/spf13/cobra v1.8.0 | 	github.com/spf13/cobra v1.8.0 | ||||||
|  | @ -78,7 +79,7 @@ require ( | ||||||
| 	golang.org/x/text v0.15.0 | 	golang.org/x/text v0.15.0 | ||||||
| 	gopkg.in/mcuadros/go-syslog.v2 v2.3.0 | 	gopkg.in/mcuadros/go-syslog.v2 v2.3.0 | ||||||
| 	gopkg.in/yaml.v3 v3.0.1 | 	gopkg.in/yaml.v3 v3.0.1 | ||||||
| 	modernc.org/sqlite v1.29.8 | 	modernc.org/sqlite v0.0.0-00010101000000-000000000000 | ||||||
| 	mvdan.cc/xurls/v2 v2.5.0 | 	mvdan.cc/xurls/v2 v2.5.0 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -173,6 +174,7 @@ require ( | ||||||
| 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||||||
| 	github.com/modern-go/reflect2 v1.0.2 // indirect | 	github.com/modern-go/reflect2 v1.0.2 // indirect | ||||||
| 	github.com/ncruces/go-strftime v0.1.9 // indirect | 	github.com/ncruces/go-strftime v0.1.9 // indirect | ||||||
|  | 	github.com/ncruces/julianday v1.0.0 // indirect | ||||||
| 	github.com/opencontainers/runtime-spec v1.0.2 // indirect | 	github.com/opencontainers/runtime-spec v1.0.2 // indirect | ||||||
| 	github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect | 	github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect | ||||||
| 	github.com/pelletier/go-toml/v2 v2.2.2 // indirect | 	github.com/pelletier/go-toml/v2 v2.2.2 // indirect | ||||||
|  | @ -197,6 +199,7 @@ require ( | ||||||
| 	github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe // indirect | 	github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe // indirect | ||||||
| 	github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB // indirect | 	github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB // indirect | ||||||
| 	github.com/tdewolff/parse/v2 v2.7.14 // indirect | 	github.com/tdewolff/parse/v2 v2.7.14 // indirect | ||||||
|  | 	github.com/tetratelabs/wazero v1.7.2 // indirect | ||||||
| 	github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect | 	github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect | ||||||
| 	github.com/toqueteos/webbrowser v1.2.0 // indirect | 	github.com/toqueteos/webbrowser v1.2.0 // indirect | ||||||
| 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect | 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								go.sum
									
										
									
									
									
								
							
							
						
						
									
										6
									
								
								go.sum
									
										
									
									
									
								
							|  | @ -445,8 +445,12 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G | ||||||
| github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= | ||||||
| github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= | github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= | ||||||
| github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= | github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= | ||||||
|  | github.com/ncruces/go-sqlite3 v0.16.0 h1:O7eULuEjvSBnS1QCN+dDL/ixLQZoUGWr466A02Gx1xc= | ||||||
|  | github.com/ncruces/go-sqlite3 v0.16.0/go.mod h1:2TmAeD93ImsKXJRsUIKohfMvt17dZSbS6pzJ3k6YYFg= | ||||||
| github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= | github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= | ||||||
| github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= | github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= | ||||||
|  | github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= | ||||||
|  | github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= | ||||||
| github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= | ||||||
| github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= | github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= | ||||||
| github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= | ||||||
|  | @ -558,6 +562,8 @@ github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03 | ||||||
| github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= | github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= | ||||||
| github.com/technologize/otel-go-contrib v1.1.1 h1:wZH9aSPNWZWIkEh3vfaKfMb15AJ80jJ1aVj/4GZdqIw= | github.com/technologize/otel-go-contrib v1.1.1 h1:wZH9aSPNWZWIkEh3vfaKfMb15AJ80jJ1aVj/4GZdqIw= | ||||||
| github.com/technologize/otel-go-contrib v1.1.1/go.mod h1:dCN/wj2WyUO8aFZFdIN+6tfJHImjTML/8r2YVYAy3So= | github.com/technologize/otel-go-contrib v1.1.1/go.mod h1:dCN/wj2WyUO8aFZFdIN+6tfJHImjTML/8r2YVYAy3So= | ||||||
|  | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= | ||||||
|  | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= | ||||||
| github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E= | github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E= | ||||||
| github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= | github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= | ||||||
| github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo= | github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo= | ||||||
|  |  | ||||||
|  | @ -38,7 +38,6 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/filter/visibility" | 	"github.com/superseriousbusiness/gotosocial/internal/filter/visibility" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/log" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/media" | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing" | 	"github.com/superseriousbusiness/gotosocial/internal/processing" | ||||||
|  | @ -77,19 +76,22 @@ type MediaCreateTestSuite struct { | ||||||
| 	TEST INFRASTRUCTURE | 	TEST INFRASTRUCTURE | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| func (suite *MediaCreateTestSuite) SetupSuite() { | func (suite *MediaCreateTestSuite) SetupTest() { | ||||||
| 	suite.state.Caches.Init() |  | ||||||
| 	testrig.StartNoopWorkers(&suite.state) | 	testrig.StartNoopWorkers(&suite.state) | ||||||
| 
 | 
 | ||||||
| 	// setup standard items | 	// setup standard items | ||||||
| 	testrig.InitTestConfig() | 	testrig.InitTestConfig() | ||||||
| 	testrig.InitTestLog() | 	testrig.InitTestLog() | ||||||
| 
 | 
 | ||||||
| 	suite.db = testrig.NewTestDB(&suite.state) | 	suite.state.Caches.Init() | ||||||
| 	suite.state.DB = suite.db | 
 | ||||||
| 	suite.storage = testrig.NewInMemoryStorage() | 	suite.storage = testrig.NewInMemoryStorage() | ||||||
| 	suite.state.Storage = suite.storage | 	suite.state.Storage = suite.storage | ||||||
| 
 | 
 | ||||||
|  | 	suite.db = testrig.NewTestDB(&suite.state) | ||||||
|  | 	testrig.StandardDBSetup(suite.db, nil) | ||||||
|  | 	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") | ||||||
|  | 
 | ||||||
| 	suite.tc = typeutils.NewConverter(&suite.state) | 	suite.tc = typeutils.NewConverter(&suite.state) | ||||||
| 
 | 
 | ||||||
| 	testrig.StartTimelines( | 	testrig.StartTimelines( | ||||||
|  | @ -106,21 +108,8 @@ func (suite *MediaCreateTestSuite) SetupSuite() { | ||||||
| 
 | 
 | ||||||
| 	// setup module being tested | 	// setup module being tested | ||||||
| 	suite.mediaModule = mediamodule.New(suite.processor) | 	suite.mediaModule = mediamodule.New(suite.processor) | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (suite *MediaCreateTestSuite) TearDownSuite() { |  | ||||||
| 	if err := suite.db.Close(); err != nil { |  | ||||||
| 		log.Panicf(nil, "error closing db connection: %s", err) |  | ||||||
| 	} |  | ||||||
| 	testrig.StopWorkers(&suite.state) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (suite *MediaCreateTestSuite) SetupTest() { |  | ||||||
| 	suite.state.Caches.Init() |  | ||||||
| 
 |  | ||||||
| 	testrig.StandardDBSetup(suite.db, nil) |  | ||||||
| 	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") |  | ||||||
| 
 | 
 | ||||||
|  | 	// setup test data | ||||||
| 	suite.testTokens = testrig.NewTestTokens() | 	suite.testTokens = testrig.NewTestTokens() | ||||||
| 	suite.testClients = testrig.NewTestClients() | 	suite.testClients = testrig.NewTestClients() | ||||||
| 	suite.testApplications = testrig.NewTestApplications() | 	suite.testApplications = testrig.NewTestApplications() | ||||||
|  | @ -132,6 +121,7 @@ func (suite *MediaCreateTestSuite) SetupTest() { | ||||||
| func (suite *MediaCreateTestSuite) TearDownTest() { | func (suite *MediaCreateTestSuite) TearDownTest() { | ||||||
| 	testrig.StandardDBTeardown(suite.db) | 	testrig.StandardDBTeardown(suite.db) | ||||||
| 	testrig.StandardStorageTeardown(suite.storage) | 	testrig.StandardStorageTeardown(suite.storage) | ||||||
|  | 	testrig.StopWorkers(&suite.state) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  |  | ||||||
|  | @ -36,7 +36,6 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/filter/visibility" | 	"github.com/superseriousbusiness/gotosocial/internal/filter/visibility" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/log" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/media" | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing" | 	"github.com/superseriousbusiness/gotosocial/internal/processing" | ||||||
|  | @ -75,18 +74,22 @@ type MediaUpdateTestSuite struct { | ||||||
| 	TEST INFRASTRUCTURE | 	TEST INFRASTRUCTURE | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| func (suite *MediaUpdateTestSuite) SetupSuite() { | func (suite *MediaUpdateTestSuite) SetupTest() { | ||||||
| 	testrig.StartNoopWorkers(&suite.state) | 	testrig.StartNoopWorkers(&suite.state) | ||||||
| 
 | 
 | ||||||
| 	// setup standard items | 	// setup standard items | ||||||
| 	testrig.InitTestConfig() | 	testrig.InitTestConfig() | ||||||
| 	testrig.InitTestLog() | 	testrig.InitTestLog() | ||||||
| 
 | 
 | ||||||
| 	suite.db = testrig.NewTestDB(&suite.state) | 	suite.state.Caches.Init() | ||||||
| 	suite.state.DB = suite.db | 
 | ||||||
| 	suite.storage = testrig.NewInMemoryStorage() | 	suite.storage = testrig.NewInMemoryStorage() | ||||||
| 	suite.state.Storage = suite.storage | 	suite.state.Storage = suite.storage | ||||||
| 
 | 
 | ||||||
|  | 	suite.db = testrig.NewTestDB(&suite.state) | ||||||
|  | 	testrig.StandardDBSetup(suite.db, nil) | ||||||
|  | 	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") | ||||||
|  | 
 | ||||||
| 	suite.tc = typeutils.NewConverter(&suite.state) | 	suite.tc = typeutils.NewConverter(&suite.state) | ||||||
| 
 | 
 | ||||||
| 	testrig.StartTimelines( | 	testrig.StartTimelines( | ||||||
|  | @ -103,21 +106,8 @@ func (suite *MediaUpdateTestSuite) SetupSuite() { | ||||||
| 
 | 
 | ||||||
| 	// setup module being tested | 	// setup module being tested | ||||||
| 	suite.mediaModule = mediamodule.New(suite.processor) | 	suite.mediaModule = mediamodule.New(suite.processor) | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (suite *MediaUpdateTestSuite) TearDownSuite() { |  | ||||||
| 	if err := suite.db.Close(); err != nil { |  | ||||||
| 		log.Panicf(nil, "error closing db connection: %s", err) |  | ||||||
| 	} |  | ||||||
| 	testrig.StopWorkers(&suite.state) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (suite *MediaUpdateTestSuite) SetupTest() { |  | ||||||
| 	suite.state.Caches.Init() |  | ||||||
| 
 |  | ||||||
| 	testrig.StandardDBSetup(suite.db, nil) |  | ||||||
| 	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") |  | ||||||
| 
 | 
 | ||||||
|  | 	// setup test data | ||||||
| 	suite.testTokens = testrig.NewTestTokens() | 	suite.testTokens = testrig.NewTestTokens() | ||||||
| 	suite.testClients = testrig.NewTestClients() | 	suite.testClients = testrig.NewTestClients() | ||||||
| 	suite.testApplications = testrig.NewTestApplications() | 	suite.testApplications = testrig.NewTestApplications() | ||||||
|  | @ -129,6 +119,7 @@ func (suite *MediaUpdateTestSuite) SetupTest() { | ||||||
| func (suite *MediaUpdateTestSuite) TearDownTest() { | func (suite *MediaUpdateTestSuite) TearDownTest() { | ||||||
| 	testrig.StandardDBTeardown(suite.db) | 	testrig.StandardDBTeardown(suite.db) | ||||||
| 	testrig.StandardStorageTeardown(suite.storage) | 	testrig.StandardStorageTeardown(suite.storage) | ||||||
|  | 	testrig.StopWorkers(&suite.state) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  |  | ||||||
|  | @ -70,8 +70,6 @@ func (suite *FileserverTestSuite) SetupSuite() { | ||||||
| 	testrig.InitTestConfig() | 	testrig.InitTestConfig() | ||||||
| 	testrig.InitTestLog() | 	testrig.InitTestLog() | ||||||
| 
 | 
 | ||||||
| 	suite.db = testrig.NewTestDB(&suite.state) |  | ||||||
| 	suite.state.DB = suite.db |  | ||||||
| 	suite.storage = testrig.NewInMemoryStorage() | 	suite.storage = testrig.NewInMemoryStorage() | ||||||
| 	suite.state.Storage = suite.storage | 	suite.state.Storage = suite.storage | ||||||
| 
 | 
 | ||||||
|  | @ -98,8 +96,12 @@ func (suite *FileserverTestSuite) SetupTest() { | ||||||
| 	suite.state.Caches.Init() | 	suite.state.Caches.Init() | ||||||
| 	testrig.StartNoopWorkers(&suite.state) | 	testrig.StartNoopWorkers(&suite.state) | ||||||
| 
 | 
 | ||||||
|  | 	suite.db = testrig.NewTestDB(&suite.state) | ||||||
|  | 	suite.state.DB = suite.db | ||||||
|  | 
 | ||||||
| 	testrig.StandardDBSetup(suite.db, nil) | 	testrig.StandardDBSetup(suite.db, nil) | ||||||
| 	testrig.StandardStorageSetup(suite.storage, "../../../testrig/media") | 	testrig.StandardStorageSetup(suite.storage, "../../../testrig/media") | ||||||
|  | 
 | ||||||
| 	suite.testTokens = testrig.NewTestTokens() | 	suite.testTokens = testrig.NewTestTokens() | ||||||
| 	suite.testClients = testrig.NewTestClients() | 	suite.testClients = testrig.NewTestClients() | ||||||
| 	suite.testApplications = testrig.NewTestApplications() | 	suite.testApplications = testrig.NewTestApplications() | ||||||
|  |  | ||||||
|  | @ -52,3 +52,9 @@ func LoadEarlyFlags(cmd *cobra.Command) error { | ||||||
| func BindFlags(cmd *cobra.Command) error { | func BindFlags(cmd *cobra.Command) error { | ||||||
| 	return global.BindFlags(cmd) | 	return global.BindFlags(cmd) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // Reset will totally clear global | ||||||
|  | // ConfigState{}, loading defaults. | ||||||
|  | func Reset() { | ||||||
|  | 	global.Reset() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -37,25 +37,9 @@ type ConfigState struct { //nolint | ||||||
| 
 | 
 | ||||||
| // NewState returns a new initialized ConfigState instance. | // NewState returns a new initialized ConfigState instance. | ||||||
| func NewState() *ConfigState { | func NewState() *ConfigState { | ||||||
| 	viper := viper.New() | 	st := new(ConfigState) | ||||||
| 
 | 	st.Reset() | ||||||
| 	// Flag 'some-flag-name' becomes env var 'GTS_SOME_FLAG_NAME' | 	return st | ||||||
| 	viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) |  | ||||||
| 	viper.SetEnvPrefix("gts") |  | ||||||
| 
 |  | ||||||
| 	// Load appropriate named vals from env |  | ||||||
| 	viper.AutomaticEnv() |  | ||||||
| 
 |  | ||||||
| 	// Create new ConfigState with defaults |  | ||||||
| 	state := &ConfigState{ |  | ||||||
| 		viper:  viper, |  | ||||||
| 		config: Defaults, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Perform initial load into viper |  | ||||||
| 	state.reloadToViper() |  | ||||||
| 
 |  | ||||||
| 	return state |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Config provides safe access to the ConfigState's contained Configuration, | // Config provides safe access to the ConfigState's contained Configuration, | ||||||
|  | @ -116,6 +100,32 @@ func (st *ConfigState) Reload() (err error) { | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Reset will totally clear | ||||||
|  | // ConfigState{}, loading defaults. | ||||||
|  | func (st *ConfigState) Reset() { | ||||||
|  | 	// Do within lock. | ||||||
|  | 	st.mutex.Lock() | ||||||
|  | 	defer st.mutex.Unlock() | ||||||
|  | 
 | ||||||
|  | 	// Create new viper. | ||||||
|  | 	viper := viper.New() | ||||||
|  | 
 | ||||||
|  | 	// Flag 'some-flag-name' becomes env var 'GTS_SOME_FLAG_NAME' | ||||||
|  | 	viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) | ||||||
|  | 	viper.SetEnvPrefix("gts") | ||||||
|  | 
 | ||||||
|  | 	// Load appropriate | ||||||
|  | 	// named vals from env. | ||||||
|  | 	viper.AutomaticEnv() | ||||||
|  | 
 | ||||||
|  | 	// Reset variables. | ||||||
|  | 	st.viper = viper | ||||||
|  | 	st.config = Defaults | ||||||
|  | 
 | ||||||
|  | 	// Load into viper. | ||||||
|  | 	st.reloadToViper() | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // reloadToViper will reload Configuration{} values into viper. | // reloadToViper will reload Configuration{} values into viper. | ||||||
| func (st *ConfigState) reloadToViper() { | func (st *ConfigState) reloadToViper() { | ||||||
| 	raw, err := st.config.MarshalMap() | 	raw, err := st.config.MarshalMap() | ||||||
|  |  | ||||||
|  | @ -74,6 +74,7 @@ func (suite *AdminTestSuite) TestCreateInstanceAccount() { | ||||||
| 	// we need to take an empty db for this... | 	// we need to take an empty db for this... | ||||||
| 	testrig.StandardDBTeardown(suite.db) | 	testrig.StandardDBTeardown(suite.db) | ||||||
| 	// ...with tables created but no data | 	// ...with tables created but no data | ||||||
|  | 	suite.db = testrig.NewTestDB(&suite.state) | ||||||
| 	testrig.CreateTestTables(suite.db) | 	testrig.CreateTestTables(suite.db) | ||||||
| 
 | 
 | ||||||
| 	// make sure there's no instance account in the db yet | 	// make sure there's no instance account in the db yet | ||||||
|  |  | ||||||
|  | @ -48,8 +48,6 @@ import ( | ||||||
| 	"github.com/uptrace/bun/dialect/pgdialect" | 	"github.com/uptrace/bun/dialect/pgdialect" | ||||||
| 	"github.com/uptrace/bun/dialect/sqlitedialect" | 	"github.com/uptrace/bun/dialect/sqlitedialect" | ||||||
| 	"github.com/uptrace/bun/migrate" | 	"github.com/uptrace/bun/migrate" | ||||||
| 
 |  | ||||||
| 	"modernc.org/sqlite" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // DBService satisfies the DB interface | // DBService satisfies the DB interface | ||||||
|  | @ -133,12 +131,12 @@ func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) { | ||||||
| 
 | 
 | ||||||
| 	switch t { | 	switch t { | ||||||
| 	case "postgres": | 	case "postgres": | ||||||
| 		db, err = pgConn(ctx, state) | 		db, err = pgConn(ctx) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 	case "sqlite": | 	case "sqlite": | ||||||
| 		db, err = sqliteConn(ctx, state) | 		db, err = sqliteConn(ctx) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  | @ -295,7 +293,7 @@ func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) { | ||||||
| 	return ps, nil | 	return ps, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func pgConn(ctx context.Context, state *state.State) (*bun.DB, error) { | func pgConn(ctx context.Context) (*bun.DB, error) { | ||||||
| 	opts, err := deriveBunDBPGOptions() //nolint:contextcheck | 	opts, err := deriveBunDBPGOptions() //nolint:contextcheck | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("could not create bundb postgres options: %w", err) | 		return nil, fmt.Errorf("could not create bundb postgres options: %w", err) | ||||||
|  | @ -326,7 +324,7 @@ func pgConn(ctx context.Context, state *state.State) (*bun.DB, error) { | ||||||
| 	return db, nil | 	return db, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func sqliteConn(ctx context.Context, state *state.State) (*bun.DB, error) { | func sqliteConn(ctx context.Context) (*bun.DB, error) { | ||||||
| 	// validate db address has actually been set | 	// validate db address has actually been set | ||||||
| 	address := config.GetDbAddress() | 	address := config.GetDbAddress() | ||||||
| 	if address == "" { | 	if address == "" { | ||||||
|  | @ -339,9 +337,6 @@ func sqliteConn(ctx context.Context, state *state.State) (*bun.DB, error) { | ||||||
| 	// Open new DB instance | 	// Open new DB instance | ||||||
| 	sqldb, err := sql.Open("sqlite-gts", address) | 	sqldb, err := sql.Open("sqlite-gts", address) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if errWithCode, ok := err.(*sqlite.Error); ok { |  | ||||||
| 			err = errors.New(sqlite.ErrorCodeString[errWithCode.Code()]) |  | ||||||
| 		} |  | ||||||
| 		return nil, fmt.Errorf("could not open sqlite db with address %s: %w", address, err) | 		return nil, fmt.Errorf("could not open sqlite db with address %s: %w", address, err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -356,11 +351,9 @@ func sqliteConn(ctx context.Context, state *state.State) (*bun.DB, error) { | ||||||
| 
 | 
 | ||||||
| 	// ping to check the db is there and listening | 	// ping to check the db is there and listening | ||||||
| 	if err := db.PingContext(ctx); err != nil { | 	if err := db.PingContext(ctx); err != nil { | ||||||
| 		if errWithCode, ok := err.(*sqlite.Error); ok { |  | ||||||
| 			err = errors.New(sqlite.ErrorCodeString[errWithCode.Code()]) |  | ||||||
| 		} |  | ||||||
| 		return nil, fmt.Errorf("sqlite ping: %w", err) | 		return nil, fmt.Errorf("sqlite ping: %w", err) | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	log.Infof(ctx, "connected to SQLITE database with address %s", address) | 	log.Infof(ctx, "connected to SQLITE database with address %s", address) | ||||||
| 
 | 
 | ||||||
| 	return db, nil | 	return db, nil | ||||||
|  | @ -528,12 +521,8 @@ func buildSQLiteAddress(addr string) string { | ||||||
| 
 | 
 | ||||||
| 		// Use random name for in-memory instead of ':memory:', so | 		// Use random name for in-memory instead of ':memory:', so | ||||||
| 		// multiple in-mem databases can be created without conflict. | 		// multiple in-mem databases can be created without conflict. | ||||||
| 		addr = uuid.NewString() | 		addr = "/" + uuid.NewString() | ||||||
| 
 | 		prefs.Add("vfs", "memdb") | ||||||
| 		// in-mem-specific preferences |  | ||||||
| 		// (shared cache so that tests don't fail) |  | ||||||
| 		prefs.Add("mode", "memory") |  | ||||||
| 		prefs.Add("cache", "shared") |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if dur := config.GetDbSqliteBusyTimeout(); dur > 0 { | 	if dur := config.GetDbSqliteBusyTimeout(); dur > 0 { | ||||||
|  |  | ||||||
|  | @ -18,350 +18,14 @@ | ||||||
| package bundb | package bundb | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" |  | ||||||
| 	"database/sql" | 	"database/sql" | ||||||
| 	"database/sql/driver" |  | ||||||
| 	"time" |  | ||||||
| 	_ "unsafe" // linkname shenanigans |  | ||||||
| 
 | 
 | ||||||
| 	pgx "github.com/jackc/pgx/v5/stdlib" | 	"github.com/superseriousbusiness/gotosocial/internal/db/postgres" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db/sqlite" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" |  | ||||||
| 	"modernc.org/sqlite" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( |  | ||||||
| 	// global SQL driver instances. |  | ||||||
| 	postgresDriver = pgx.GetDefaultDriver() |  | ||||||
| 	sqliteDriver   = getSQLiteDriver() |  | ||||||
| 
 |  | ||||||
| 	// check the postgres connection |  | ||||||
| 	// conforms to our conn{} interface. |  | ||||||
| 	// (note SQLite doesn't export their |  | ||||||
| 	// conn type, and gets checked in |  | ||||||
| 	// tests very regularly anywho). |  | ||||||
| 	_ conn = (*pgx.Conn)(nil) |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| //go:linkname getSQLiteDriver modernc.org/sqlite.newDriver |  | ||||||
| func getSQLiteDriver() *sqlite.Driver |  | ||||||
| 
 |  | ||||||
| func init() { | func init() { | ||||||
| 	sql.Register("pgx-gts", &PostgreSQLDriver{}) | 	// register our SQL driver implementations. | ||||||
| 	sql.Register("sqlite-gts", &SQLiteDriver{}) | 	sql.Register("pgx-gts", &postgres.Driver{}) | ||||||
| } | 	sql.Register("sqlite-gts", &sqlite.Driver{}) | ||||||
| 
 |  | ||||||
| // PostgreSQLDriver is our own wrapper around the |  | ||||||
| // pgx/stdlib.Driver{} type in order to wrap further |  | ||||||
| // SQL driver types with our own err processing. |  | ||||||
| type PostgreSQLDriver struct{} |  | ||||||
| 
 |  | ||||||
| func (d *PostgreSQLDriver) Open(name string) (driver.Conn, error) { |  | ||||||
| 	c, err := postgresDriver.Open(name) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return &PostgreSQLConn{conn: c.(conn)}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type PostgreSQLConn struct{ conn } |  | ||||||
| 
 |  | ||||||
| func (c *PostgreSQLConn) Begin() (driver.Tx, error) { |  | ||||||
| 	return c.BeginTx(context.Background(), driver.TxOptions{}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *PostgreSQLConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { |  | ||||||
| 	tx, err := c.conn.BeginTx(ctx, opts) |  | ||||||
| 	err = processPostgresError(err) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return &PostgreSQLTx{tx}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *PostgreSQLConn) Prepare(query string) (driver.Stmt, error) { |  | ||||||
| 	return c.PrepareContext(context.Background(), query) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *PostgreSQLConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { |  | ||||||
| 	st, err := c.conn.PrepareContext(ctx, query) |  | ||||||
| 	err = processPostgresError(err) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return &PostgreSQLStmt{stmt: st.(stmt)}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *PostgreSQLConn) Exec(query string, args []driver.Value) (driver.Result, error) { |  | ||||||
| 	return c.ExecContext(context.Background(), query, toNamedValues(args)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *PostgreSQLConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { |  | ||||||
| 	result, err := c.conn.ExecContext(ctx, query, args) |  | ||||||
| 	err = processPostgresError(err) |  | ||||||
| 	return result, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *PostgreSQLConn) Query(query string, args []driver.Value) (driver.Rows, error) { |  | ||||||
| 	return c.QueryContext(context.Background(), query, toNamedValues(args)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *PostgreSQLConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { |  | ||||||
| 	rows, err := c.conn.QueryContext(ctx, query, args) |  | ||||||
| 	err = processPostgresError(err) |  | ||||||
| 	return rows, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *PostgreSQLConn) Close() error { |  | ||||||
| 	return c.conn.Close() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type PostgreSQLTx struct{ driver.Tx } |  | ||||||
| 
 |  | ||||||
| func (tx *PostgreSQLTx) Commit() error { |  | ||||||
| 	err := tx.Tx.Commit() |  | ||||||
| 	return processPostgresError(err) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (tx *PostgreSQLTx) Rollback() error { |  | ||||||
| 	err := tx.Tx.Rollback() |  | ||||||
| 	return processPostgresError(err) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type PostgreSQLStmt struct{ stmt } |  | ||||||
| 
 |  | ||||||
| func (stmt *PostgreSQLStmt) Exec(args []driver.Value) (driver.Result, error) { |  | ||||||
| 	return stmt.ExecContext(context.Background(), toNamedValues(args)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (stmt *PostgreSQLStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { |  | ||||||
| 	res, err := stmt.stmt.ExecContext(ctx, args) |  | ||||||
| 	err = processPostgresError(err) |  | ||||||
| 	return res, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (stmt *PostgreSQLStmt) Query(args []driver.Value) (driver.Rows, error) { |  | ||||||
| 	return stmt.QueryContext(context.Background(), toNamedValues(args)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (stmt *PostgreSQLStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { |  | ||||||
| 	rows, err := stmt.stmt.QueryContext(ctx, args) |  | ||||||
| 	err = processPostgresError(err) |  | ||||||
| 	return rows, err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // SQLiteDriver is our own wrapper around the |  | ||||||
| // sqlite.Driver{} type in order to wrap further |  | ||||||
| // SQL driver types with our own functionality, |  | ||||||
| // e.g. hooks, retries and err processing. |  | ||||||
| type SQLiteDriver struct{} |  | ||||||
| 
 |  | ||||||
| func (d *SQLiteDriver) Open(name string) (driver.Conn, error) { |  | ||||||
| 	c, err := sqliteDriver.Open(name) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return &SQLiteConn{conn: c.(conn)}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type SQLiteConn struct{ conn } |  | ||||||
| 
 |  | ||||||
| func (c *SQLiteConn) Begin() (driver.Tx, error) { |  | ||||||
| 	return c.BeginTx(context.Background(), driver.TxOptions{}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *SQLiteConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx driver.Tx, err error) { |  | ||||||
| 	err = retryOnBusy(ctx, func() error { |  | ||||||
| 		tx, err = c.conn.BeginTx(ctx, opts) |  | ||||||
| 		err = processSQLiteError(err) |  | ||||||
| 		return err |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return &SQLiteTx{Context: ctx, Tx: tx}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *SQLiteConn) Prepare(query string) (driver.Stmt, error) { |  | ||||||
| 	return c.PrepareContext(context.Background(), query) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *SQLiteConn) PrepareContext(ctx context.Context, query string) (st driver.Stmt, err error) { |  | ||||||
| 	err = retryOnBusy(ctx, func() error { |  | ||||||
| 		st, err = c.conn.PrepareContext(ctx, query) |  | ||||||
| 		err = processSQLiteError(err) |  | ||||||
| 		return err |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return &SQLiteStmt{st.(stmt)}, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *SQLiteConn) Exec(query string, args []driver.Value) (driver.Result, error) { |  | ||||||
| 	return c.ExecContext(context.Background(), query, toNamedValues(args)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *SQLiteConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (result driver.Result, err error) { |  | ||||||
| 	err = retryOnBusy(ctx, func() error { |  | ||||||
| 		result, err = c.conn.ExecContext(ctx, query, args) |  | ||||||
| 		err = processSQLiteError(err) |  | ||||||
| 		return err |  | ||||||
| 	}) |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *SQLiteConn) Query(query string, args []driver.Value) (driver.Rows, error) { |  | ||||||
| 	return c.QueryContext(context.Background(), query, toNamedValues(args)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *SQLiteConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) { |  | ||||||
| 	err = retryOnBusy(ctx, func() error { |  | ||||||
| 		rows, err = c.conn.QueryContext(ctx, query, args) |  | ||||||
| 		err = processSQLiteError(err) |  | ||||||
| 		return err |  | ||||||
| 	}) |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c *SQLiteConn) Close() error { |  | ||||||
| 	// see: https://www.sqlite.org/pragma.html#pragma_optimize |  | ||||||
| 	const onClose = "PRAGMA analysis_limit=1000; PRAGMA optimize;" |  | ||||||
| 	_, _ = c.conn.ExecContext(context.Background(), onClose, nil) |  | ||||||
| 	return c.conn.Close() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type SQLiteTx struct { |  | ||||||
| 	context.Context |  | ||||||
| 	driver.Tx |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (tx *SQLiteTx) Commit() (err error) { |  | ||||||
| 	err = retryOnBusy(tx.Context, func() error { |  | ||||||
| 		err = tx.Tx.Commit() |  | ||||||
| 		err = processSQLiteError(err) |  | ||||||
| 		return err |  | ||||||
| 	}) |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (tx *SQLiteTx) Rollback() (err error) { |  | ||||||
| 	err = retryOnBusy(tx.Context, func() error { |  | ||||||
| 		err = tx.Tx.Rollback() |  | ||||||
| 		err = processSQLiteError(err) |  | ||||||
| 		return err |  | ||||||
| 	}) |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type SQLiteStmt struct{ stmt } |  | ||||||
| 
 |  | ||||||
| func (stmt *SQLiteStmt) Exec(args []driver.Value) (driver.Result, error) { |  | ||||||
| 	return stmt.ExecContext(context.Background(), toNamedValues(args)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (stmt *SQLiteStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) { |  | ||||||
| 	err = retryOnBusy(ctx, func() error { |  | ||||||
| 		res, err = stmt.stmt.ExecContext(ctx, args) |  | ||||||
| 		err = processSQLiteError(err) |  | ||||||
| 		return err |  | ||||||
| 	}) |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (stmt *SQLiteStmt) Query(args []driver.Value) (driver.Rows, error) { |  | ||||||
| 	return stmt.QueryContext(context.Background(), toNamedValues(args)) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (stmt *SQLiteStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) { |  | ||||||
| 	err = retryOnBusy(ctx, func() error { |  | ||||||
| 		rows, err = stmt.stmt.QueryContext(ctx, args) |  | ||||||
| 		err = processSQLiteError(err) |  | ||||||
| 		return err |  | ||||||
| 	}) |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type conn interface { |  | ||||||
| 	driver.Conn |  | ||||||
| 	driver.ConnPrepareContext |  | ||||||
| 	driver.ExecerContext |  | ||||||
| 	driver.QueryerContext |  | ||||||
| 	driver.ConnBeginTx |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type stmt interface { |  | ||||||
| 	driver.Stmt |  | ||||||
| 	driver.StmtExecContext |  | ||||||
| 	driver.StmtQueryContext |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // retryOnBusy will retry given function on returned 'errBusy'. |  | ||||||
| func retryOnBusy(ctx context.Context, fn func() error) error { |  | ||||||
| 	if err := fn(); err != errBusy { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return retryOnBusySlow(ctx, fn) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // retryOnBusySlow is the outlined form of retryOnBusy, to allow the fast path (i.e. only |  | ||||||
| // 1 attempt) to be inlined, leaving the slow retry loop to be a separate function call. |  | ||||||
| func retryOnBusySlow(ctx context.Context, fn func() error) error { |  | ||||||
| 	var backoff time.Duration |  | ||||||
| 
 |  | ||||||
| 	for i := 0; ; i++ { |  | ||||||
| 		// backoff according to a multiplier of 2ms * 2^2n, |  | ||||||
| 		// up to a maximum possible backoff time of 5 minutes. |  | ||||||
| 		// |  | ||||||
| 		// this works out as the following: |  | ||||||
| 		// 4ms |  | ||||||
| 		// 16ms |  | ||||||
| 		// 64ms |  | ||||||
| 		// 256ms |  | ||||||
| 		// 1.024s |  | ||||||
| 		// 4.096s |  | ||||||
| 		// 16.384s |  | ||||||
| 		// 1m5.536s |  | ||||||
| 		// 4m22.144s |  | ||||||
| 		backoff = 2 * time.Millisecond * (1 << (2*i + 1)) |  | ||||||
| 		if backoff >= 5*time.Minute { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		select { |  | ||||||
| 		// Context cancelled. |  | ||||||
| 		case <-ctx.Done(): |  | ||||||
| 			return ctx.Err() |  | ||||||
| 
 |  | ||||||
| 		// Backoff for some time. |  | ||||||
| 		case <-time.After(backoff): |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Perform func. |  | ||||||
| 		err := fn() |  | ||||||
| 
 |  | ||||||
| 		if err != errBusy { |  | ||||||
| 			// May be nil, or may be |  | ||||||
| 			// some other error, either |  | ||||||
| 			// way return here. |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return gtserror.Newf("%w (waited > %s)", db.ErrBusyTimeout, backoff) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // toNamedValues converts older driver.Value types to driver.NamedValue types. |  | ||||||
| func toNamedValues(args []driver.Value) []driver.NamedValue { |  | ||||||
| 	if args == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	args2 := make([]driver.NamedValue, len(args)) |  | ||||||
| 	for i := range args { |  | ||||||
| 		args2[i] = driver.NamedValue{ |  | ||||||
| 			Ordinal: i + 1, |  | ||||||
| 			Value:   args[i], |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return args2 |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,105 +0,0 @@ | ||||||
| // GoToSocial |  | ||||||
| // Copyright (C) GoToSocial Authors admin@gotosocial.org |  | ||||||
| // SPDX-License-Identifier: AGPL-3.0-or-later |  | ||||||
| // |  | ||||||
| // This program is free software: you can redistribute it and/or modify |  | ||||||
| // it under the terms of the GNU Affero General Public License as published by |  | ||||||
| // the Free Software Foundation, either version 3 of the License, or |  | ||||||
| // (at your option) any later version. |  | ||||||
| // |  | ||||||
| // This program is distributed in the hope that it will be useful, |  | ||||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
| // GNU Affero General Public License for more details. |  | ||||||
| // |  | ||||||
| // You should have received a copy of the GNU Affero General Public License |  | ||||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
| 
 |  | ||||||
| package bundb |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"database/sql/driver" |  | ||||||
| 	"errors" |  | ||||||
| 
 |  | ||||||
| 	"github.com/jackc/pgx/v5/pgconn" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" |  | ||||||
| 	"modernc.org/sqlite" |  | ||||||
| 	sqlite3 "modernc.org/sqlite/lib" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // errBusy is a sentinel error indicating |  | ||||||
| // busy database (e.g. retry needed). |  | ||||||
| var errBusy = errors.New("busy") |  | ||||||
| 
 |  | ||||||
| // processPostgresError processes an error, replacing any postgres specific errors with our own error type |  | ||||||
| func processPostgresError(err error) error { |  | ||||||
| 	// Catch nil errs. |  | ||||||
| 	if err == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Attempt to cast as postgres |  | ||||||
| 	pgErr, ok := err.(*pgconn.PgError) |  | ||||||
| 	if !ok { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Handle supplied error code: |  | ||||||
| 	// (https://www.postgresql.org/docs/10/errcodes-appendix.html) |  | ||||||
| 	switch pgErr.Code { //nolint |  | ||||||
| 	case "23505" /* unique_violation */ : |  | ||||||
| 		return db.ErrAlreadyExists |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // processSQLiteError processes an error, replacing any sqlite specific errors with our own error type |  | ||||||
| func processSQLiteError(err error) error { |  | ||||||
| 	// Catch nil errs. |  | ||||||
| 	if err == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Attempt to cast as sqlite |  | ||||||
| 	sqliteErr, ok := err.(*sqlite.Error) |  | ||||||
| 	if !ok { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Handle supplied error code: |  | ||||||
| 	switch sqliteErr.Code() { |  | ||||||
| 	case sqlite3.SQLITE_CONSTRAINT_UNIQUE, |  | ||||||
| 		sqlite3.SQLITE_CONSTRAINT_PRIMARYKEY: |  | ||||||
| 		return db.ErrAlreadyExists |  | ||||||
| 	case sqlite3.SQLITE_BUSY, |  | ||||||
| 		sqlite3.SQLITE_BUSY_SNAPSHOT, |  | ||||||
| 		sqlite3.SQLITE_BUSY_RECOVERY: |  | ||||||
| 		return errBusy |  | ||||||
| 	case sqlite3.SQLITE_BUSY_TIMEOUT: |  | ||||||
| 		return db.ErrBusyTimeout |  | ||||||
| 
 |  | ||||||
| 	// WORKAROUND: |  | ||||||
| 	// text copied from matrix dev chat: |  | ||||||
| 	// |  | ||||||
| 	// okay i've found a workaround for now. so between |  | ||||||
| 	// v1.29.0 and v1.29.2 (modernc.org/sqlite) is that |  | ||||||
| 	// slightly tweaked interruptOnDone() behaviour, which |  | ||||||
| 	// causes interrupt to (imo, correctly) get called when |  | ||||||
| 	// a context is cancelled to cancel the running query. the |  | ||||||
| 	// issue is that every single query after that point seems |  | ||||||
| 	// to still then return interrupted. so as you thought, |  | ||||||
| 	// maybe that query count isn't being decremented. i don't |  | ||||||
| 	// think it's our code, but i haven't ruled it out yet. |  | ||||||
| 	// |  | ||||||
| 	// the workaround for now is adding to our sqlite error |  | ||||||
| 	// processor to replace an SQLITE_INTERRUPTED code with |  | ||||||
| 	// driver.ErrBadConn, which hints to the golang sql package |  | ||||||
| 	// that the conn needs to be closed and a new one opened |  | ||||||
| 	// |  | ||||||
| 	case sqlite3.SQLITE_INTERRUPT: |  | ||||||
| 		return driver.ErrBadConn |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
|  | @ -19,6 +19,7 @@ package bundb_test | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"errors" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"github.com/stretchr/testify/suite" | 	"github.com/stretchr/testify/suite" | ||||||
|  | @ -82,10 +83,20 @@ func (suite *TagTestSuite) TestPutTag() { | ||||||
| 
 | 
 | ||||||
| 		// Subsequent inserts should fail | 		// Subsequent inserts should fail | ||||||
| 		// since all these tags are equivalent. | 		// since all these tags are equivalent. | ||||||
| 		suite.ErrorIs(err, db.ErrAlreadyExists) | 		if !suite.ErrorIs(err, db.ErrAlreadyExists) { | ||||||
|  | 			suite.T().Logf("%T(%v) %v", err, err, unwrap(err)) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestTagTestSuite(t *testing.T) { | func TestTagTestSuite(t *testing.T) { | ||||||
| 	suite.Run(t, new(TagTestSuite)) | 	suite.Run(t, new(TagTestSuite)) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func unwrap(err error) (errs []error) { | ||||||
|  | 	for err != nil { | ||||||
|  | 		errs = append(errs, err) | ||||||
|  | 		err = errors.Unwrap(err) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -29,8 +29,4 @@ var ( | ||||||
| 
 | 
 | ||||||
| 	// ErrAlreadyExists is returned when a conflict was encountered in the db when doing an insert. | 	// ErrAlreadyExists is returned when a conflict was encountered in the db when doing an insert. | ||||||
| 	ErrAlreadyExists = errors.New("already exists") | 	ErrAlreadyExists = errors.New("already exists") | ||||||
| 
 |  | ||||||
| 	// ErrBusyTimeout is returned if the database connection indicates the connection is too busy |  | ||||||
| 	// to complete the supplied query. This is generally intended to be handled internally by the DB. |  | ||||||
| 	ErrBusyTimeout = errors.New("busy timeout") |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
							
								
								
									
										209
									
								
								internal/db/postgres/driver.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								internal/db/postgres/driver.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,209 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU Affero General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU Affero General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU Affero General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | package postgres | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"database/sql/driver" | ||||||
|  | 
 | ||||||
|  | 	pgx "github.com/jackc/pgx/v5/stdlib" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	// global PostgreSQL driver instances. | ||||||
|  | 	postgresDriver = pgx.GetDefaultDriver().(*pgx.Driver) | ||||||
|  | 
 | ||||||
|  | 	// check the postgres driver types | ||||||
|  | 	// conforms to our interface types. | ||||||
|  | 	// (note SQLite doesn't export their | ||||||
|  | 	// driver types, and gets checked in | ||||||
|  | 	// tests very regularly anywho). | ||||||
|  | 	_ connIface = (*pgx.Conn)(nil) | ||||||
|  | 	_ stmtIface = (*pgx.Stmt)(nil) | ||||||
|  | 	_ rowsIface = (*pgx.Rows)(nil) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Driver is our own wrapper around the | ||||||
|  | // pgx/stdlib.Driver{} type in order to wrap further | ||||||
|  | // SQL driver types with our own err processing. | ||||||
|  | type Driver struct{} | ||||||
|  | 
 | ||||||
|  | func (d *Driver) Open(name string) (driver.Conn, error) { | ||||||
|  | 	conn, err := postgresDriver.Open(name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		err = processPostgresError(err) | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &postgresConn{conn.(connIface)}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (d *Driver) OpenConnector(name string) (driver.Connector, error) { | ||||||
|  | 	cc, err := postgresDriver.OpenConnector(name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		err = processPostgresError(err) | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &postgresConnector{driver: d, Connector: cc}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type postgresConnector struct { | ||||||
|  | 	driver *Driver | ||||||
|  | 	driver.Connector | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *postgresConnector) Driver() driver.Driver { return c.driver } | ||||||
|  | 
 | ||||||
|  | func (c *postgresConnector) Connect(ctx context.Context) (driver.Conn, error) { | ||||||
|  | 	conn, err := c.Connector.Connect(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		err = processPostgresError(err) | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &postgresConn{conn.(connIface)}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type postgresConn struct{ connIface } | ||||||
|  | 
 | ||||||
|  | func (c *postgresConn) Begin() (driver.Tx, error) { | ||||||
|  | 	return c.BeginTx(context.Background(), driver.TxOptions{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *postgresConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { | ||||||
|  | 	tx, err := c.connIface.BeginTx(ctx, opts) | ||||||
|  | 	err = processPostgresError(err) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &postgresTx{tx}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *postgresConn) Prepare(query string) (driver.Stmt, error) { | ||||||
|  | 	return c.PrepareContext(context.Background(), query) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *postgresConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { | ||||||
|  | 	st, err := c.connIface.PrepareContext(ctx, query) | ||||||
|  | 	err = processPostgresError(err) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &postgresStmt{st.(stmtIface)}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *postgresConn) Exec(query string, args []driver.Value) (driver.Result, error) { | ||||||
|  | 	return c.ExecContext(context.Background(), query, db.ToNamedValues(args)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *postgresConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { | ||||||
|  | 	result, err := c.connIface.ExecContext(ctx, query, args) | ||||||
|  | 	err = processPostgresError(err) | ||||||
|  | 	return result, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *postgresConn) Query(query string, args []driver.Value) (driver.Rows, error) { | ||||||
|  | 	return c.QueryContext(context.Background(), query, db.ToNamedValues(args)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *postgresConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { | ||||||
|  | 	rows, err := c.connIface.QueryContext(ctx, query, args) | ||||||
|  | 	err = processPostgresError(err) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &postgresRows{rows.(rowsIface)}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *postgresConn) Close() error { | ||||||
|  | 	err := c.connIface.Close() | ||||||
|  | 	return processPostgresError(err) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type postgresTx struct{ driver.Tx } | ||||||
|  | 
 | ||||||
|  | func (tx *postgresTx) Commit() error { | ||||||
|  | 	err := tx.Tx.Commit() | ||||||
|  | 	return processPostgresError(err) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tx *postgresTx) Rollback() error { | ||||||
|  | 	err := tx.Tx.Rollback() | ||||||
|  | 	return processPostgresError(err) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type postgresStmt struct{ stmtIface } | ||||||
|  | 
 | ||||||
|  | func (stmt *postgresStmt) Exec(args []driver.Value) (driver.Result, error) { | ||||||
|  | 	return stmt.ExecContext(context.Background(), db.ToNamedValues(args)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (stmt *postgresStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { | ||||||
|  | 	res, err := stmt.stmtIface.ExecContext(ctx, args) | ||||||
|  | 	err = processPostgresError(err) | ||||||
|  | 	return res, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (stmt *postgresStmt) Query(args []driver.Value) (driver.Rows, error) { | ||||||
|  | 	return stmt.QueryContext(context.Background(), db.ToNamedValues(args)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (stmt *postgresStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { | ||||||
|  | 	rows, err := stmt.stmtIface.QueryContext(ctx, args) | ||||||
|  | 	err = processPostgresError(err) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &postgresRows{rows.(rowsIface)}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type postgresRows struct{ rowsIface } | ||||||
|  | 
 | ||||||
|  | func (r *postgresRows) Next(dest []driver.Value) error { | ||||||
|  | 	err := r.rowsIface.Next(dest) | ||||||
|  | 	err = processPostgresError(err) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *postgresRows) Close() error { | ||||||
|  | 	err := r.rowsIface.Close() | ||||||
|  | 	err = processPostgresError(err) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type connIface interface { | ||||||
|  | 	driver.Conn | ||||||
|  | 	driver.ConnPrepareContext | ||||||
|  | 	driver.ExecerContext | ||||||
|  | 	driver.QueryerContext | ||||||
|  | 	driver.ConnBeginTx | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type stmtIface interface { | ||||||
|  | 	driver.Stmt | ||||||
|  | 	driver.StmtExecContext | ||||||
|  | 	driver.StmtQueryContext | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type rowsIface interface { | ||||||
|  | 	driver.Rows | ||||||
|  | 	driver.RowsColumnTypeDatabaseTypeName | ||||||
|  | 	driver.RowsColumnTypeLength | ||||||
|  | 	driver.RowsColumnTypePrecisionScale | ||||||
|  | 	driver.RowsColumnTypeScanType | ||||||
|  | 	driver.RowsColumnTypeScanType | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								internal/db/postgres/errors.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								internal/db/postgres/errors.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU Affero General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU Affero General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU Affero General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | package postgres | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 
 | ||||||
|  | 	"github.com/jackc/pgx/v5/pgconn" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // processPostgresError processes an error, replacing any | ||||||
|  | // postgres specific errors with our own error type | ||||||
|  | func processPostgresError(err error) error { | ||||||
|  | 	// Attempt to cast as postgres | ||||||
|  | 	pgErr, ok := err.(*pgconn.PgError) | ||||||
|  | 	if !ok { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Handle supplied error code: | ||||||
|  | 	// (https://www.postgresql.org/docs/10/errcodes-appendix.html) | ||||||
|  | 	switch pgErr.Code { //nolint | ||||||
|  | 	case "23505" /* unique_violation */ : | ||||||
|  | 		return db.ErrAlreadyExists | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Wrap the returned error with the code and | ||||||
|  | 	// extended code for easier debugging later. | ||||||
|  | 	return fmt.Errorf("%w (code=%s)", err, pgErr.Code) | ||||||
|  | } | ||||||
							
								
								
									
										197
									
								
								internal/db/sqlite/driver.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								internal/db/sqlite/driver.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,197 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU Affero General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU Affero General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU Affero General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | //go:build !wasmsqlite3 | ||||||
|  | 
 | ||||||
|  | package sqlite | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"database/sql/driver" | ||||||
|  | 
 | ||||||
|  | 	"modernc.org/sqlite" | ||||||
|  | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Driver is our own wrapper around the | ||||||
|  | // sqlite.Driver{} type in order to wrap | ||||||
|  | // further SQL types with our own | ||||||
|  | // functionality, e.g. err processing. | ||||||
|  | type Driver struct{ sqlite.Driver } | ||||||
|  | 
 | ||||||
|  | func (d *Driver) Open(name string) (driver.Conn, error) { | ||||||
|  | 	conn, err := d.Driver.Open(name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		err = processSQLiteError(err) | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &sqliteConn{conn.(connIface)}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type sqliteConn struct{ connIface } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) Begin() (driver.Tx, error) { | ||||||
|  | 	return c.BeginTx(context.Background(), driver.TxOptions{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx driver.Tx, err error) { | ||||||
|  | 	tx, err = c.connIface.BeginTx(ctx, opts) | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &sqliteTx{tx}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) Prepare(query string) (driver.Stmt, error) { | ||||||
|  | 	return c.PrepareContext(context.Background(), query) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) { | ||||||
|  | 	stmt, err = c.connIface.PrepareContext(ctx, query) | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &sqliteStmt{stmtIface: stmt.(stmtIface)}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) Exec(query string, args []driver.Value) (driver.Result, error) { | ||||||
|  | 	return c.ExecContext(context.Background(), query, db.ToNamedValues(args)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (res driver.Result, err error) { | ||||||
|  | 	res, err = c.connIface.ExecContext(ctx, query, args) | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) Query(query string, args []driver.Value) (driver.Rows, error) { | ||||||
|  | 	return c.QueryContext(context.Background(), query, db.ToNamedValues(args)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) { | ||||||
|  | 	rows, err = c.connIface.QueryContext(ctx, query, args) | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &sqliteRows{rows.(rowsIface)}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) Close() (err error) { | ||||||
|  | 	// see: https://www.sqlite.org/pragma.html#pragma_optimize | ||||||
|  | 	const onClose = "PRAGMA analysis_limit=1000; PRAGMA optimize;" | ||||||
|  | 	_, _ = c.connIface.ExecContext(context.Background(), onClose, nil) | ||||||
|  | 
 | ||||||
|  | 	// Finally, close the conn. | ||||||
|  | 	err = c.connIface.Close() | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type sqliteTx struct{ driver.Tx } | ||||||
|  | 
 | ||||||
|  | func (tx *sqliteTx) Commit() (err error) { | ||||||
|  | 	err = tx.Tx.Commit() | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tx *sqliteTx) Rollback() (err error) { | ||||||
|  | 	err = tx.Tx.Rollback() | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type sqliteStmt struct{ stmtIface } | ||||||
|  | 
 | ||||||
|  | func (stmt *sqliteStmt) Exec(args []driver.Value) (driver.Result, error) { | ||||||
|  | 	return stmt.ExecContext(context.Background(), db.ToNamedValues(args)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (stmt *sqliteStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) { | ||||||
|  | 	res, err = stmt.stmtIface.ExecContext(ctx, args) | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (stmt *sqliteStmt) Query(args []driver.Value) (driver.Rows, error) { | ||||||
|  | 	return stmt.QueryContext(context.Background(), db.ToNamedValues(args)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (stmt *sqliteStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) { | ||||||
|  | 	rows, err = stmt.stmtIface.QueryContext(ctx, args) | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &sqliteRows{rows.(rowsIface)}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (stmt *sqliteStmt) Close() (err error) { | ||||||
|  | 	err = stmt.stmtIface.Close() | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type sqliteRows struct{ rowsIface } | ||||||
|  | 
 | ||||||
|  | func (r *sqliteRows) Next(dest []driver.Value) (err error) { | ||||||
|  | 	err = r.rowsIface.Next(dest) | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *sqliteRows) Close() (err error) { | ||||||
|  | 	err = r.rowsIface.Close() | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // connIface is the driver.Conn interface | ||||||
|  | // types (and the like) that modernc.org/sqlite.conn | ||||||
|  | // conforms to. Useful so you don't need | ||||||
|  | // to repeatedly perform checks yourself. | ||||||
|  | type connIface interface { | ||||||
|  | 	driver.Conn | ||||||
|  | 	driver.ConnBeginTx | ||||||
|  | 	driver.ConnPrepareContext | ||||||
|  | 	driver.ExecerContext | ||||||
|  | 	driver.QueryerContext | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // StmtIface is the driver.Stmt interface | ||||||
|  | // types (and the like) that modernc.org/sqlite.stmt | ||||||
|  | // conforms to. Useful so you don't need | ||||||
|  | // to repeatedly perform checks yourself. | ||||||
|  | type stmtIface interface { | ||||||
|  | 	driver.Stmt | ||||||
|  | 	driver.StmtExecContext | ||||||
|  | 	driver.StmtQueryContext | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RowsIface is the driver.Rows interface | ||||||
|  | // types (and the like) that modernc.org/sqlite.rows | ||||||
|  | // conforms to. Useful so you don't need | ||||||
|  | // to repeatedly perform checks yourself. | ||||||
|  | type rowsIface interface { | ||||||
|  | 	driver.Rows | ||||||
|  | 	driver.RowsColumnTypeDatabaseTypeName | ||||||
|  | 	driver.RowsColumnTypeLength | ||||||
|  | 	driver.RowsColumnTypeScanType | ||||||
|  | } | ||||||
							
								
								
									
										211
									
								
								internal/db/sqlite/driver_wasmsqlite3.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								internal/db/sqlite/driver_wasmsqlite3.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,211 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU Affero General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU Affero General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU Affero General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | //go:build wasmsqlite3 | ||||||
|  | 
 | ||||||
|  | package sqlite | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"database/sql/driver" | ||||||
|  | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3" | ||||||
|  | 	sqlite3driver "github.com/ncruces/go-sqlite3/driver" | ||||||
|  | 	_ "github.com/ncruces/go-sqlite3/embed"     // embed wasm binary | ||||||
|  | 	_ "github.com/ncruces/go-sqlite3/vfs/memdb" // include memdb vfs | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Driver is our own wrapper around the | ||||||
|  | // driver.SQLite{} type in order to wrap | ||||||
|  | // further SQL types with our own | ||||||
|  | // functionality, e.g. err processing. | ||||||
|  | type Driver struct{ sqlite3driver.SQLite } | ||||||
|  | 
 | ||||||
|  | func (d *Driver) Open(name string) (driver.Conn, error) { | ||||||
|  | 	conn, err := d.SQLite.Open(name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		err = processSQLiteError(err) | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &sqliteConn{conn.(connIface)}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (d *Driver) OpenConnector(name string) (driver.Connector, error) { | ||||||
|  | 	cc, err := d.SQLite.OpenConnector(name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &sqliteConnector{driver: d, Connector: cc}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type sqliteConnector struct { | ||||||
|  | 	driver *Driver | ||||||
|  | 	driver.Connector | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConnector) Driver() driver.Driver { return c.driver } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConnector) Connect(ctx context.Context) (driver.Conn, error) { | ||||||
|  | 	conn, err := c.Connector.Connect(ctx) | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &sqliteConn{conn.(connIface)}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type sqliteConn struct{ connIface } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) Begin() (driver.Tx, error) { | ||||||
|  | 	return c.BeginTx(context.Background(), driver.TxOptions{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx driver.Tx, err error) { | ||||||
|  | 	tx, err = c.connIface.BeginTx(ctx, opts) | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &sqliteTx{tx}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) Prepare(query string) (driver.Stmt, error) { | ||||||
|  | 	return c.PrepareContext(context.Background(), query) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) { | ||||||
|  | 	stmt, err = c.connIface.PrepareContext(ctx, query) | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &sqliteStmt{stmtIface: stmt.(stmtIface)}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) Exec(query string, args []driver.Value) (driver.Result, error) { | ||||||
|  | 	return c.ExecContext(context.Background(), query, db.ToNamedValues(args)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (res driver.Result, err error) { | ||||||
|  | 	res, err = c.connIface.ExecContext(ctx, query, args) | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *sqliteConn) Close() (err error) { | ||||||
|  | 	// Get acces the underlying raw sqlite3 conn. | ||||||
|  | 	raw := c.connIface.(sqlite3.DriverConn).Raw() | ||||||
|  | 
 | ||||||
|  | 	// see: https://www.sqlite.org/pragma.html#pragma_optimize | ||||||
|  | 	const onClose = "PRAGMA analysis_limit=1000; PRAGMA optimize;" | ||||||
|  | 	_ = raw.Exec(onClose) | ||||||
|  | 
 | ||||||
|  | 	// Finally, close. | ||||||
|  | 	err = raw.Close() | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type sqliteTx struct{ driver.Tx } | ||||||
|  | 
 | ||||||
|  | func (tx *sqliteTx) Commit() (err error) { | ||||||
|  | 	err = tx.Tx.Commit() | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (tx *sqliteTx) Rollback() (err error) { | ||||||
|  | 	err = tx.Tx.Rollback() | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type sqliteStmt struct{ stmtIface } | ||||||
|  | 
 | ||||||
|  | func (stmt *sqliteStmt) Exec(args []driver.Value) (driver.Result, error) { | ||||||
|  | 	return stmt.ExecContext(context.Background(), db.ToNamedValues(args)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (stmt *sqliteStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) { | ||||||
|  | 	res, err = stmt.stmtIface.ExecContext(ctx, args) | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (stmt *sqliteStmt) Query(args []driver.Value) (driver.Rows, error) { | ||||||
|  | 	return stmt.QueryContext(context.Background(), db.ToNamedValues(args)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (stmt *sqliteStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) { | ||||||
|  | 	rows, err = stmt.stmtIface.QueryContext(ctx, args) | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &sqliteRows{rows.(rowsIface)}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (stmt *sqliteStmt) Close() (err error) { | ||||||
|  | 	err = stmt.stmtIface.Close() | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type sqliteRows struct{ rowsIface } | ||||||
|  | 
 | ||||||
|  | func (r *sqliteRows) Next(dest []driver.Value) (err error) { | ||||||
|  | 	err = r.rowsIface.Next(dest) | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *sqliteRows) Close() (err error) { | ||||||
|  | 	err = r.rowsIface.Close() | ||||||
|  | 	err = processSQLiteError(err) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // connIface is the driver.Conn interface | ||||||
|  | // types (and the like) that go-sqlite3/driver.conn | ||||||
|  | // conforms to. Useful so you don't need | ||||||
|  | // to repeatedly perform checks yourself. | ||||||
|  | type connIface interface { | ||||||
|  | 	driver.Conn | ||||||
|  | 	driver.ConnBeginTx | ||||||
|  | 	driver.ConnPrepareContext | ||||||
|  | 	driver.ExecerContext | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // StmtIface is the driver.Stmt interface | ||||||
|  | // types (and the like) that go-sqlite3/driver.stmt | ||||||
|  | // conforms to. Useful so you don't need | ||||||
|  | // to repeatedly perform checks yourself. | ||||||
|  | type stmtIface interface { | ||||||
|  | 	driver.Stmt | ||||||
|  | 	driver.StmtExecContext | ||||||
|  | 	driver.StmtQueryContext | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RowsIface is the driver.Rows interface | ||||||
|  | // types (and the like) that go-sqlite3/driver.rows | ||||||
|  | // conforms to. Useful so you don't need | ||||||
|  | // to repeatedly perform checks yourself. | ||||||
|  | type rowsIface interface { | ||||||
|  | 	driver.Rows | ||||||
|  | 	driver.RowsColumnTypeDatabaseTypeName | ||||||
|  | } | ||||||
							
								
								
									
										62
									
								
								internal/db/sqlite/errors.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								internal/db/sqlite/errors.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,62 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU Affero General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU Affero General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU Affero General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | //go:build !wasmsqlite3 | ||||||
|  | 
 | ||||||
|  | package sqlite | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"database/sql/driver" | ||||||
|  | 	"fmt" | ||||||
|  | 
 | ||||||
|  | 	"modernc.org/sqlite" | ||||||
|  | 	sqlite3 "modernc.org/sqlite/lib" | ||||||
|  | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // processSQLiteError processes an sqlite3.Error to | ||||||
|  | // handle conversion to any of our common db types. | ||||||
|  | func processSQLiteError(err error) error { | ||||||
|  | 	// Attempt to cast as sqlite error. | ||||||
|  | 	sqliteErr, ok := err.(*sqlite.Error) | ||||||
|  | 	if !ok { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Handle supplied error code: | ||||||
|  | 	switch sqliteErr.Code() { | ||||||
|  | 	case sqlite3.SQLITE_CONSTRAINT_UNIQUE, | ||||||
|  | 		sqlite3.SQLITE_CONSTRAINT_PRIMARYKEY: | ||||||
|  | 		return db.ErrAlreadyExists | ||||||
|  | 
 | ||||||
|  | 	// Busy should be very rare, but | ||||||
|  | 	// on busy tell the database to close | ||||||
|  | 	// the connection, re-open and re-attempt | ||||||
|  | 	// which should give a necessary timeout. | ||||||
|  | 	case sqlite3.SQLITE_BUSY, | ||||||
|  | 		sqlite3.SQLITE_BUSY_RECOVERY, | ||||||
|  | 		sqlite3.SQLITE_BUSY_SNAPSHOT: | ||||||
|  | 		return driver.ErrBadConn | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Wrap the returned error with the code and | ||||||
|  | 	// extended code for easier debugging later. | ||||||
|  | 	return fmt.Errorf("%w (code=%d)", err, | ||||||
|  | 		sqliteErr.Code(), | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								internal/db/sqlite/errors_wasmsqlite3.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								internal/db/sqlite/errors_wasmsqlite3.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU Affero General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU Affero General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU Affero General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | //go:build wasmsqlite3 | ||||||
|  | 
 | ||||||
|  | package sqlite | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"database/sql/driver" | ||||||
|  | 	"fmt" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // processSQLiteError processes an sqlite3.Error to | ||||||
|  | // handle conversion to any of our common db types. | ||||||
|  | func processSQLiteError(err error) error { | ||||||
|  | 	// Attempt to cast as sqlite error. | ||||||
|  | 	sqliteErr, ok := err.(*sqlite3.Error) | ||||||
|  | 	if !ok { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Handle supplied error code: | ||||||
|  | 	switch sqliteErr.ExtendedCode() { | ||||||
|  | 	case sqlite3.CONSTRAINT_UNIQUE, | ||||||
|  | 		sqlite3.CONSTRAINT_PRIMARYKEY: | ||||||
|  | 		return db.ErrAlreadyExists | ||||||
|  | 
 | ||||||
|  | 	// Busy should be very rare, but on | ||||||
|  | 	// busy tell the database to close the | ||||||
|  | 	// connection, re-open and re-attempt | ||||||
|  | 	// which should give necessary timeout. | ||||||
|  | 	case sqlite3.BUSY_RECOVERY, | ||||||
|  | 		sqlite3.BUSY_SNAPSHOT: | ||||||
|  | 		return driver.ErrBadConn | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Wrap the returned error with the code and | ||||||
|  | 	// extended code for easier debugging later. | ||||||
|  | 	return fmt.Errorf("%w (code=%d extended=%d)", err, | ||||||
|  | 		sqliteErr.Code(), | ||||||
|  | 		sqliteErr.ExtendedCode(), | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								internal/db/util.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								internal/db/util.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU Affero General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU Affero General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU Affero General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | package db | ||||||
|  | 
 | ||||||
|  | import "database/sql/driver" | ||||||
|  | 
 | ||||||
|  | // ToNamedValues converts older driver.Value types to driver.NamedValue types. | ||||||
|  | func ToNamedValues(args []driver.Value) []driver.NamedValue { | ||||||
|  | 	if args == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	args2 := make([]driver.NamedValue, len(args)) | ||||||
|  | 	for i := range args { | ||||||
|  | 		args2[i] = driver.NamedValue{ | ||||||
|  | 			Ordinal: i + 1, | ||||||
|  | 			Value:   args[i], | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return args2 | ||||||
|  | } | ||||||
|  | @ -43,10 +43,6 @@ func TestMultiError(t *testing.T) { | ||||||
| 		t.Error("should be db.ErrAlreadyExists") | 		t.Error("should be db.ErrAlreadyExists") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if errors.Is(err, db.ErrBusyTimeout) { |  | ||||||
| 		t.Error("should not be db.ErrBusyTimeout") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	errString := err.Error() | 	errString := err.Error() | ||||||
| 	expected := `sql: no rows in result set | 	expected := `sql: no rows in result set | ||||||
| oopsie woopsie we did a fucky wucky etc | oopsie woopsie we did a fucky wucky etc | ||||||
|  |  | ||||||
|  | @ -30,6 +30,7 @@ type PagingSuite struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *PagingSuite) TestPagingStandard() { | func (suite *PagingSuite) TestPagingStandard() { | ||||||
|  | 	config.SetProtocol("https") | ||||||
| 	config.SetHost("example.org") | 	config.SetHost("example.org") | ||||||
| 
 | 
 | ||||||
| 	params := util.PageableResponseParams{ | 	params := util.PageableResponseParams{ | ||||||
|  | @ -52,6 +53,7 @@ func (suite *PagingSuite) TestPagingStandard() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *PagingSuite) TestPagingNoLimit() { | func (suite *PagingSuite) TestPagingNoLimit() { | ||||||
|  | 	config.SetProtocol("https") | ||||||
| 	config.SetHost("example.org") | 	config.SetHost("example.org") | ||||||
| 
 | 
 | ||||||
| 	params := util.PageableResponseParams{ | 	params := util.PageableResponseParams{ | ||||||
|  | @ -73,6 +75,7 @@ func (suite *PagingSuite) TestPagingNoLimit() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *PagingSuite) TestPagingNoNextID() { | func (suite *PagingSuite) TestPagingNoNextID() { | ||||||
|  | 	config.SetProtocol("https") | ||||||
| 	config.SetHost("example.org") | 	config.SetHost("example.org") | ||||||
| 
 | 
 | ||||||
| 	params := util.PageableResponseParams{ | 	params := util.PageableResponseParams{ | ||||||
|  | @ -94,6 +97,7 @@ func (suite *PagingSuite) TestPagingNoNextID() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *PagingSuite) TestPagingNoPrevID() { | func (suite *PagingSuite) TestPagingNoPrevID() { | ||||||
|  | 	config.SetProtocol("https") | ||||||
| 	config.SetHost("example.org") | 	config.SetHost("example.org") | ||||||
| 
 | 
 | ||||||
| 	params := util.PageableResponseParams{ | 	params := util.PageableResponseParams{ | ||||||
|  | @ -115,6 +119,7 @@ func (suite *PagingSuite) TestPagingNoPrevID() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *PagingSuite) TestPagingNoItems() { | func (suite *PagingSuite) TestPagingNoItems() { | ||||||
|  | 	config.SetProtocol("https") | ||||||
| 	config.SetHost("example.org") | 	config.SetHost("example.org") | ||||||
| 
 | 
 | ||||||
| 	params := util.PageableResponseParams{ | 	params := util.PageableResponseParams{ | ||||||
|  |  | ||||||
|  | @ -2,6 +2,11 @@ | ||||||
| 
 | 
 | ||||||
| set -e | set -e | ||||||
| 
 | 
 | ||||||
|  | # Determine available docker binary | ||||||
|  | _docker=$(command -v 'podman') || \ | ||||||
|  | _docker=$(command -v 'docker') || \ | ||||||
|  | { echo 'docker not found'; exit 1; } | ||||||
|  | 
 | ||||||
| # Ensure test args are set. | # Ensure test args are set. | ||||||
| ARGS=${@}; [ -z "$ARGS" ] && \ | ARGS=${@}; [ -z "$ARGS" ] && \ | ||||||
| ARGS='./...' | ARGS='./...' | ||||||
|  | @ -10,33 +15,32 @@ ARGS='./...' | ||||||
| DB_NAME='postgres' | DB_NAME='postgres' | ||||||
| DB_USER='postgres' | DB_USER='postgres' | ||||||
| DB_PASS='postgres' | DB_PASS='postgres' | ||||||
|  | DB_IP='127.0.0.1' | ||||||
| DB_PORT=5432 | DB_PORT=5432 | ||||||
| 
 | 
 | ||||||
| # Start postgres container | # Start postgres container | ||||||
| CID=$(docker run --detach \ | CID=$($_docker run --detach \ | ||||||
|  |     --publish "${DB_IP}:${DB_PORT}:${DB_PORT}" \ | ||||||
|     --env "POSTGRES_DB=${DB_NAME}" \ |     --env "POSTGRES_DB=${DB_NAME}" \ | ||||||
|     --env "POSTGRES_USER=${DB_USER}" \ |     --env "POSTGRES_USER=${DB_USER}" \ | ||||||
|     --env "POSTGRES_PASSWORD=${DB_PASS}" \ |     --env "POSTGRES_PASSWORD=${DB_PASS}" \ | ||||||
|     --env "POSTGRES_HOST_AUTH_METHOD=trust" \ |     --env "POSTGRES_HOST_AUTH_METHOD=trust" \ | ||||||
|     --env "PGHOST=0.0.0.0" \ |     --env "PGHOST=0.0.0.0" \ | ||||||
|     --env "PGPORT=${DB_PORT}" \ |     --env "PGPORT=${DB_PORT}" \ | ||||||
|     'postgres:latest') |     'docker.io/postgres:latest') | ||||||
| 
 | 
 | ||||||
| # On exit kill the container | # On exit kill the container | ||||||
| trap "docker kill ${CID}" exit | trap "$_docker kill ${CID}" exit | ||||||
| 
 | 
 | ||||||
| sleep 5 | sleep 5 | ||||||
| #docker exec "$CID" psql --user "$DB_USER" --password "$DB_PASS" -c "CREATE DATABASE \"${DB_NAME}\" WITH LOCALE \"C.UTF-8\" TEMPLATE \"template0\";" | #docker exec "$CID" psql --user "$DB_USER" --password "$DB_PASS" -c "CREATE DATABASE \"${DB_NAME}\" WITH LOCALE \"C.UTF-8\" TEMPLATE \"template0\";" | ||||||
| docker exec "$CID" psql --user "$DB_USER" --password "$DB_PASS" -c "GRANT ALL PRIVILEGES ON DATABASE \"${DB_NAME}\" TO \"${DB_USER}\";" | $_docker exec "$CID" psql --user "$DB_USER" --password "$DB_PASS" -c "GRANT ALL PRIVILEGES ON DATABASE \"${DB_NAME}\" TO \"${DB_USER}\";" | ||||||
| 
 |  | ||||||
| # Get running container IP |  | ||||||
| IP=$(docker container inspect "${CID}" \ |  | ||||||
|     --format '{{ .NetworkSettings.IPAddress }}') |  | ||||||
| 
 | 
 | ||||||
|  | env \ | ||||||
| GTS_DB_TYPE=postgres \ | GTS_DB_TYPE=postgres \ | ||||||
| GTS_DB_ADDRESS=${IP} \ | GTS_DB_ADDRESS=${DB_IP} \ | ||||||
| GTS_DB_PORT=${DB_PORT} \ | GTS_DB_PORT=${DB_PORT} \ | ||||||
| GTS_DB_USER=${DB_USER} \ | GTS_DB_USER=${DB_USER} \ | ||||||
| GTS_DB_PASSWORD=${DB_PASS} \ | GTS_DB_PASSWORD=${DB_PASS} \ | ||||||
| GTS_DB_DATABASE=${DB_NAME} \ | GTS_DB_DATABASE=${DB_NAME} \ | ||||||
| go test ./... -p 1 ${ARGS} | go test -p 1 ${ARGS} | ||||||
|  | @ -18,8 +18,8 @@ | ||||||
| package testrig | package testrig | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"cmp" |  | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"strconv" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"codeberg.org/gruf/go-bytesize" | 	"codeberg.org/gruf/go-bytesize" | ||||||
|  | @ -28,128 +28,149 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/language" | 	"github.com/superseriousbusiness/gotosocial/internal/language" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // InitTestConfig initializes viper configuration with test defaults. | // InitTestConfig initializes viper | ||||||
|  | // configuration with test defaults. | ||||||
| func InitTestConfig() { | func InitTestConfig() { | ||||||
| 	config.Config(func(cfg *config.Configuration) { | 	config.Defaults = testDefaults() | ||||||
| 		*cfg = testDefaults | 	config.Reset() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func testDefaults() config.Configuration { | ||||||
|  | 	return config.Configuration{ | ||||||
|  | 		LogLevel:                 envStr("GTS_LOG_LEVEL", "error"), | ||||||
|  | 		LogTimestampFormat:       "02/01/2006 15:04:05.000", | ||||||
|  | 		LogDbQueries:             true, | ||||||
|  | 		ApplicationName:          "gotosocial", | ||||||
|  | 		LandingPageUser:          "", | ||||||
|  | 		ConfigPath:               "", | ||||||
|  | 		Host:                     "localhost:8080", | ||||||
|  | 		AccountDomain:            "localhost:8080", | ||||||
|  | 		Protocol:                 "http", | ||||||
|  | 		BindAddress:              "127.0.0.1", | ||||||
|  | 		Port:                     8080, | ||||||
|  | 		TrustedProxies:           []string{"127.0.0.1/32", "::1"}, | ||||||
|  | 		DbType:                   envStr("GTS_DB_TYPE", "sqlite"), | ||||||
|  | 		DbAddress:                envStr("GTS_DB_ADDRESS", ":memory:"), | ||||||
|  | 		DbPort:                   envInt("GTS_DB_PORT", 0), | ||||||
|  | 		DbUser:                   envStr("GTS_DB_USER", ""), | ||||||
|  | 		DbPassword:               envStr("GTS_DB_PASSWORD", ""), | ||||||
|  | 		DbDatabase:               envStr("GTS_DB_DATABASE", ""), | ||||||
|  | 		DbTLSMode:                envStr("GTS_DB_TLS_MODE", ""), | ||||||
|  | 		DbTLSCACert:              envStr("GTS_DB_TLS_CA_CERT", ""), | ||||||
|  | 		DbMaxOpenConnsMultiplier: 8, | ||||||
|  | 		DbSqliteJournalMode:      "WAL", | ||||||
|  | 		DbSqliteSynchronous:      "NORMAL", | ||||||
|  | 		DbSqliteCacheSize:        8 * bytesize.MiB, | ||||||
|  | 		DbSqliteBusyTimeout:      time.Minute * 5, | ||||||
|  | 
 | ||||||
|  | 		WebTemplateBaseDir: "./web/template/", | ||||||
|  | 		WebAssetBaseDir:    "./web/assets/", | ||||||
|  | 
 | ||||||
|  | 		InstanceFederationMode:         config.InstanceFederationModeDefault, | ||||||
|  | 		InstanceFederationSpamFilter:   true, | ||||||
|  | 		InstanceExposePeers:            true, | ||||||
|  | 		InstanceExposeSuspended:        true, | ||||||
|  | 		InstanceExposeSuspendedWeb:     true, | ||||||
|  | 		InstanceDeliverToSharedInboxes: true, | ||||||
|  | 		InstanceLanguages: language.Languages{ | ||||||
|  | 			{ | ||||||
|  | 				TagStr: "nl", | ||||||
|  | 			}, | ||||||
|  | 			{ | ||||||
|  | 				TagStr: "en-gb", | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		AccountsRegistrationOpen: true, | ||||||
|  | 		AccountsReasonRequired:   true, | ||||||
|  | 		AccountsAllowCustomCSS:   true, | ||||||
|  | 		AccountsCustomCSSLength:  10000, | ||||||
|  | 
 | ||||||
|  | 		MediaImageMaxSize:        10485760, // 10MiB | ||||||
|  | 		MediaVideoMaxSize:        41943040, // 40MiB | ||||||
|  | 		MediaDescriptionMinChars: 0, | ||||||
|  | 		MediaDescriptionMaxChars: 500, | ||||||
|  | 		MediaRemoteCacheDays:     7, | ||||||
|  | 		MediaEmojiLocalMaxSize:   51200,          // 50KiB | ||||||
|  | 		MediaEmojiRemoteMaxSize:  102400,         // 100KiB | ||||||
|  | 		MediaCleanupFrom:         "00:00",        // midnight. | ||||||
|  | 		MediaCleanupEvery:        24 * time.Hour, // 1/day. | ||||||
|  | 
 | ||||||
|  | 		// the testrig only uses in-memory storage, so we can | ||||||
|  | 		// safely set this value to 'test' to avoid running storage | ||||||
|  | 		// migrations, and other silly things like that | ||||||
|  | 		StorageBackend:       "test", | ||||||
|  | 		StorageLocalBasePath: "", | ||||||
|  | 
 | ||||||
|  | 		StatusesMaxChars:           5000, | ||||||
|  | 		StatusesPollMaxOptions:     6, | ||||||
|  | 		StatusesPollOptionMaxChars: 50, | ||||||
|  | 		StatusesMediaMaxFiles:      6, | ||||||
|  | 
 | ||||||
|  | 		LetsEncryptEnabled:      false, | ||||||
|  | 		LetsEncryptPort:         0, | ||||||
|  | 		LetsEncryptCertDir:      "", | ||||||
|  | 		LetsEncryptEmailAddress: "", | ||||||
|  | 
 | ||||||
|  | 		OIDCEnabled:          false, | ||||||
|  | 		OIDCIdpName:          "", | ||||||
|  | 		OIDCSkipVerification: false, | ||||||
|  | 		OIDCIssuer:           "", | ||||||
|  | 		OIDCClientID:         "", | ||||||
|  | 		OIDCClientSecret:     "", | ||||||
|  | 		OIDCScopes:           []string{oidc.ScopeOpenID, "profile", "email", "groups"}, | ||||||
|  | 		OIDCLinkExisting:     false, | ||||||
|  | 		OIDCAdminGroups:      []string{"adminRole"}, | ||||||
|  | 		OIDCAllowedGroups:    []string{"allowedRole"}, | ||||||
|  | 
 | ||||||
|  | 		SMTPHost:               "", | ||||||
|  | 		SMTPPort:               0, | ||||||
|  | 		SMTPUsername:           "", | ||||||
|  | 		SMTPPassword:           "", | ||||||
|  | 		SMTPFrom:               "GoToSocial", | ||||||
|  | 		SMTPDiscloseRecipients: false, | ||||||
|  | 
 | ||||||
|  | 		TracingEnabled:           false, | ||||||
|  | 		TracingEndpoint:          "localhost:4317", | ||||||
|  | 		TracingTransport:         "grpc", | ||||||
|  | 		TracingInsecureTransport: true, | ||||||
|  | 
 | ||||||
|  | 		MetricsEnabled:     false, | ||||||
|  | 		MetricsAuthEnabled: false, | ||||||
|  | 
 | ||||||
|  | 		SyslogEnabled:  false, | ||||||
|  | 		SyslogProtocol: "udp", | ||||||
|  | 		SyslogAddress:  "localhost:514", | ||||||
|  | 
 | ||||||
|  | 		AdvancedCookiesSamesite:      "lax", | ||||||
|  | 		AdvancedRateLimitRequests:    0, // disabled | ||||||
|  | 		AdvancedThrottlingMultiplier: 0, // disabled | ||||||
|  | 		AdvancedSenderMultiplier:     0, // 1 sender only, regardless of CPU | ||||||
|  | 
 | ||||||
|  | 		SoftwareVersion: "0.0.0-testrig", | ||||||
|  | 
 | ||||||
|  | 		// simply use cache defaults. | ||||||
|  | 		Cache: config.Defaults.Cache, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func envInt(key string, _default int) int { | ||||||
|  | 	return env(key, _default, func(value string) int { | ||||||
|  | 		i, _ := strconv.Atoi(value) | ||||||
|  | 		return i | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var testDefaults = config.Configuration{ | func envStr(key string, _default string) string { | ||||||
| 	LogLevel:           cmp.Or(os.Getenv("GTS_LOG_LEVEL"), "error"), | 	return env(key, _default, func(value string) string { | ||||||
| 	LogTimestampFormat: "02/01/2006 15:04:05.000", | 		return value | ||||||
| 	LogDbQueries:       true, | 	}) | ||||||
| 	ApplicationName:    "gotosocial", | } | ||||||
| 	LandingPageUser:    "", | 
 | ||||||
| 	ConfigPath:         "", | func env[T any](key string, _default T, parse func(string) T) T { | ||||||
| 	Host:               "localhost:8080", | 	value, ok := os.LookupEnv(key) | ||||||
| 	AccountDomain:      "localhost:8080", | 	if ok { | ||||||
| 	Protocol:           "http", | 		return parse(value) | ||||||
| 	BindAddress:        "127.0.0.1", | 	} | ||||||
| 	Port:               8080, | 	return _default | ||||||
| 	TrustedProxies:     []string{"127.0.0.1/32", "::1"}, |  | ||||||
| 
 |  | ||||||
| 	DbType:                   "sqlite", |  | ||||||
| 	DbAddress:                ":memory:", |  | ||||||
| 	DbPort:                   5432, |  | ||||||
| 	DbUser:                   "postgres", |  | ||||||
| 	DbPassword:               "postgres", |  | ||||||
| 	DbDatabase:               "postgres", |  | ||||||
| 	DbTLSMode:                "disable", |  | ||||||
| 	DbTLSCACert:              "", |  | ||||||
| 	DbMaxOpenConnsMultiplier: 8, |  | ||||||
| 	DbSqliteJournalMode:      "WAL", |  | ||||||
| 	DbSqliteSynchronous:      "NORMAL", |  | ||||||
| 	DbSqliteCacheSize:        8 * bytesize.MiB, |  | ||||||
| 	DbSqliteBusyTimeout:      time.Minute * 5, |  | ||||||
| 
 |  | ||||||
| 	WebTemplateBaseDir: "./web/template/", |  | ||||||
| 	WebAssetBaseDir:    "./web/assets/", |  | ||||||
| 
 |  | ||||||
| 	InstanceFederationMode:         config.InstanceFederationModeDefault, |  | ||||||
| 	InstanceFederationSpamFilter:   true, |  | ||||||
| 	InstanceExposePeers:            true, |  | ||||||
| 	InstanceExposeSuspended:        true, |  | ||||||
| 	InstanceExposeSuspendedWeb:     true, |  | ||||||
| 	InstanceDeliverToSharedInboxes: true, |  | ||||||
| 	InstanceLanguages: language.Languages{ |  | ||||||
| 		{ |  | ||||||
| 			TagStr: "nl", |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			TagStr: "en-gb", |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	AccountsRegistrationOpen: true, |  | ||||||
| 	AccountsReasonRequired:   true, |  | ||||||
| 	AccountsAllowCustomCSS:   true, |  | ||||||
| 	AccountsCustomCSSLength:  10000, |  | ||||||
| 
 |  | ||||||
| 	MediaImageMaxSize:        10485760, // 10MiB |  | ||||||
| 	MediaVideoMaxSize:        41943040, // 40MiB |  | ||||||
| 	MediaDescriptionMinChars: 0, |  | ||||||
| 	MediaDescriptionMaxChars: 500, |  | ||||||
| 	MediaRemoteCacheDays:     7, |  | ||||||
| 	MediaEmojiLocalMaxSize:   51200,          // 50KiB |  | ||||||
| 	MediaEmojiRemoteMaxSize:  102400,         // 100KiB |  | ||||||
| 	MediaCleanupFrom:         "00:00",        // midnight. |  | ||||||
| 	MediaCleanupEvery:        24 * time.Hour, // 1/day. |  | ||||||
| 
 |  | ||||||
| 	// the testrig only uses in-memory storage, so we can |  | ||||||
| 	// safely set this value to 'test' to avoid running storage |  | ||||||
| 	// migrations, and other silly things like that |  | ||||||
| 	StorageBackend:       "test", |  | ||||||
| 	StorageLocalBasePath: "", |  | ||||||
| 
 |  | ||||||
| 	StatusesMaxChars:           5000, |  | ||||||
| 	StatusesPollMaxOptions:     6, |  | ||||||
| 	StatusesPollOptionMaxChars: 50, |  | ||||||
| 	StatusesMediaMaxFiles:      6, |  | ||||||
| 
 |  | ||||||
| 	LetsEncryptEnabled:      false, |  | ||||||
| 	LetsEncryptPort:         0, |  | ||||||
| 	LetsEncryptCertDir:      "", |  | ||||||
| 	LetsEncryptEmailAddress: "", |  | ||||||
| 
 |  | ||||||
| 	OIDCEnabled:          false, |  | ||||||
| 	OIDCIdpName:          "", |  | ||||||
| 	OIDCSkipVerification: false, |  | ||||||
| 	OIDCIssuer:           "", |  | ||||||
| 	OIDCClientID:         "", |  | ||||||
| 	OIDCClientSecret:     "", |  | ||||||
| 	OIDCScopes:           []string{oidc.ScopeOpenID, "profile", "email", "groups"}, |  | ||||||
| 	OIDCLinkExisting:     false, |  | ||||||
| 	OIDCAdminGroups:      []string{"adminRole"}, |  | ||||||
| 	OIDCAllowedGroups:    []string{"allowedRole"}, |  | ||||||
| 
 |  | ||||||
| 	SMTPHost:               "", |  | ||||||
| 	SMTPPort:               0, |  | ||||||
| 	SMTPUsername:           "", |  | ||||||
| 	SMTPPassword:           "", |  | ||||||
| 	SMTPFrom:               "GoToSocial", |  | ||||||
| 	SMTPDiscloseRecipients: false, |  | ||||||
| 
 |  | ||||||
| 	TracingEnabled:           false, |  | ||||||
| 	TracingEndpoint:          "localhost:4317", |  | ||||||
| 	TracingTransport:         "grpc", |  | ||||||
| 	TracingInsecureTransport: true, |  | ||||||
| 
 |  | ||||||
| 	MetricsEnabled:     false, |  | ||||||
| 	MetricsAuthEnabled: false, |  | ||||||
| 
 |  | ||||||
| 	SyslogEnabled:  false, |  | ||||||
| 	SyslogProtocol: "udp", |  | ||||||
| 	SyslogAddress:  "localhost:514", |  | ||||||
| 
 |  | ||||||
| 	AdvancedCookiesSamesite:      "lax", |  | ||||||
| 	AdvancedRateLimitRequests:    0, // disabled |  | ||||||
| 	AdvancedThrottlingMultiplier: 0, // disabled |  | ||||||
| 	AdvancedSenderMultiplier:     0, // 1 sender only, regardless of CPU |  | ||||||
| 	AdvancedHeaderFilterMode:     config.RequestHeaderFilterModeBlock, |  | ||||||
| 
 |  | ||||||
| 	SoftwareVersion: "0.0.0-testrig", |  | ||||||
| 
 |  | ||||||
| 	// simply use cache defaults. |  | ||||||
| 	Cache: config.Defaults.Cache, |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -19,10 +19,7 @@ package testrig | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"os" |  | ||||||
| 	"strconv" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db/bundb" | 	"github.com/superseriousbusiness/gotosocial/internal/db/bundb" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | @ -84,22 +81,6 @@ var testModels = []interface{}{ | ||||||
| // If the environment variable GTS_DB_PORT is set, it will take that | // If the environment variable GTS_DB_PORT is set, it will take that | ||||||
| // value as the port instead. | // value as the port instead. | ||||||
| func NewTestDB(state *state.State) db.DB { | func NewTestDB(state *state.State) db.DB { | ||||||
| 	if alternateAddress := os.Getenv("GTS_DB_ADDRESS"); alternateAddress != "" { |  | ||||||
| 		config.SetDbAddress(alternateAddress) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if alternateDBType := os.Getenv("GTS_DB_TYPE"); alternateDBType != "" { |  | ||||||
| 		config.SetDbType(alternateDBType) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if alternateDBPort := os.Getenv("GTS_DB_PORT"); alternateDBPort != "" { |  | ||||||
| 		port, err := strconv.ParseUint(alternateDBPort, 10, 16) |  | ||||||
| 		if err != nil { |  | ||||||
| 			panic(err) |  | ||||||
| 		} |  | ||||||
| 		config.SetDbPort(int(port)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	state.Caches.Init() | 	state.Caches.Init() | ||||||
| 
 | 
 | ||||||
| 	testDB, err := bundb.NewBunDBService(context.Background(), state) | 	testDB, err := bundb.NewBunDBService(context.Background(), state) | ||||||
|  | @ -374,9 +355,10 @@ func StandardDBTeardown(db db.DB) { | ||||||
| 	if db == nil { | 	if db == nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	defer db.Close() | ||||||
| 	for _, m := range testModels { | 	for _, m := range testModels { | ||||||
| 		if err := db.DropTable(ctx, m); err != nil { | 		if err := db.DropTable(ctx, m); err != nil { | ||||||
| 			log.Panic(nil, err) | 			log.Error(ctx, err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								vendor/github.com/ncruces/go-sqlite3/.gitignore
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								vendor/github.com/ncruces/go-sqlite3/.gitignore
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | ||||||
|  | # Binaries for programs and plugins | ||||||
|  | *.exe | ||||||
|  | *.exe~ | ||||||
|  | *.dll | ||||||
|  | *.so | ||||||
|  | *.dylib | ||||||
|  | 
 | ||||||
|  | # Test binary, built with `go test -c` | ||||||
|  | *.test | ||||||
|  | 
 | ||||||
|  | # Output of the go coverage tool, specifically when used with LiteIDE | ||||||
|  | *.out | ||||||
|  | 
 | ||||||
|  | # Dependency directories (remove the comment below to include it) | ||||||
|  | # vendor/ | ||||||
|  | tools | ||||||
							
								
								
									
										21
									
								
								vendor/github.com/ncruces/go-sqlite3/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/ncruces/go-sqlite3/LICENSE
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | MIT License | ||||||
|  | 
 | ||||||
|  | Copyright (c) 2023 Nuno Cruces | ||||||
|  | 
 | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  | of this software and associated documentation files (the "Software"), to deal | ||||||
|  | in the Software without restriction, including without limitation the rights | ||||||
|  | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | copies of the Software, and to permit persons to whom the Software is | ||||||
|  | furnished to do so, subject to the following conditions: | ||||||
|  | 
 | ||||||
|  | The above copyright notice and this permission notice shall be included in all | ||||||
|  | copies or substantial portions of the Software. | ||||||
|  | 
 | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||||
|  | SOFTWARE. | ||||||
							
								
								
									
										113
									
								
								vendor/github.com/ncruces/go-sqlite3/README.md
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								vendor/github.com/ncruces/go-sqlite3/README.md
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,113 @@ | ||||||
|  | # Go bindings to SQLite using Wazero | ||||||
|  | 
 | ||||||
|  | [](https://pkg.go.dev/github.com/ncruces/go-sqlite3) | ||||||
|  | [](https://goreportcard.com/report/github.com/ncruces/go-sqlite3) | ||||||
|  | [](https://github.com/ncruces/go-sqlite3/wiki/Test-coverage-report) | ||||||
|  | 
 | ||||||
|  | Go module `github.com/ncruces/go-sqlite3` is a `cgo`-free [SQLite](https://sqlite.org/) wrapper.\ | ||||||
|  | It provides a [`database/sql`](https://pkg.go.dev/database/sql) compatible driver, | ||||||
|  | as well as direct access to most of the [C SQLite API](https://sqlite.org/cintro.html). | ||||||
|  | 
 | ||||||
|  | It wraps a [Wasm](https://webassembly.org/) [build](embed/) of SQLite, | ||||||
|  | and uses [wazero](https://wazero.io/) as the runtime.\ | ||||||
|  | Go, wazero and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ runtime dependencies [^1]. | ||||||
|  | 
 | ||||||
|  | ### Packages | ||||||
|  | 
 | ||||||
|  | - [`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3) | ||||||
|  |   wraps the [C SQLite API](https://sqlite.org/cintro.html) | ||||||
|  |   ([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3#example-package)). | ||||||
|  | - [`github.com/ncruces/go-sqlite3/driver`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver) | ||||||
|  |   provides a [`database/sql`](https://pkg.go.dev/database/sql) driver | ||||||
|  |   ([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package)). | ||||||
|  | - [`github.com/ncruces/go-sqlite3/embed`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/embed) | ||||||
|  |   embeds a build of SQLite into your application. | ||||||
|  | - [`github.com/ncruces/go-sqlite3/vfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs) | ||||||
|  |   wraps the [C SQLite VFS API](https://sqlite.org/vfs.html) and provides a pure Go implementation. | ||||||
|  | - [`github.com/ncruces/go-sqlite3/gormlite`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/gormlite) | ||||||
|  |   provides a [GORM](https://gorm.io) driver. | ||||||
|  | 
 | ||||||
|  | ### Extensions | ||||||
|  | 
 | ||||||
|  | - [`github.com/ncruces/go-sqlite3/ext/array`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/array) | ||||||
|  |   provides the [`array`](https://sqlite.org/carray.html) table-valued function. | ||||||
|  | - [`github.com/ncruces/go-sqlite3/ext/blobio`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/blobio) | ||||||
|  |   simplifies [incremental BLOB I/O](https://sqlite.org/c3ref/blob_open.html). | ||||||
|  | - [`github.com/ncruces/go-sqlite3/ext/csv`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/csv) | ||||||
|  |   reads [comma-separated values](https://sqlite.org/csv.html). | ||||||
|  | - [`github.com/ncruces/go-sqlite3/ext/fileio`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/fileio) | ||||||
|  |   reads, writes and lists files. | ||||||
|  | - [`github.com/ncruces/go-sqlite3/ext/hash`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/hash) | ||||||
|  |   provides cryptographic hash functions. | ||||||
|  | - [`github.com/ncruces/go-sqlite3/ext/lines`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/lines) | ||||||
|  |   reads data [line-by-line](https://github.com/asg017/sqlite-lines). | ||||||
|  | - [`github.com/ncruces/go-sqlite3/ext/pivot`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/pivot) | ||||||
|  |   creates [pivot tables](https://github.com/jakethaw/pivot_vtab). | ||||||
|  | - [`github.com/ncruces/go-sqlite3/ext/statement`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/statement) | ||||||
|  |   creates [parameterized views](https://github.com/0x09/sqlite-statement-vtab). | ||||||
|  | - [`github.com/ncruces/go-sqlite3/ext/stats`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/stats) | ||||||
|  |   provides [statistics](https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html) functions. | ||||||
|  | - [`github.com/ncruces/go-sqlite3/ext/unicode`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/unicode) | ||||||
|  |   provides [Unicode aware](https://sqlite.org/src/dir/ext/icu) functions. | ||||||
|  | - [`github.com/ncruces/go-sqlite3/ext/zorder`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/zorder) | ||||||
|  |   maps multidimensional data to one dimension. | ||||||
|  | - [`github.com/ncruces/go-sqlite3/vfs/memdb`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/memdb) | ||||||
|  |   implements an in-memory VFS. | ||||||
|  | - [`github.com/ncruces/go-sqlite3/vfs/readervfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/readervfs) | ||||||
|  |   implements a VFS for immutable databases. | ||||||
|  | - [`github.com/ncruces/go-sqlite3/vfs/adiantum`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/adiantum) | ||||||
|  |   wraps a VFS to offer encryption at rest. | ||||||
|  | 
 | ||||||
|  | ### Advanced features | ||||||
|  | 
 | ||||||
|  | - [incremental BLOB I/O](https://sqlite.org/c3ref/blob_open.html) | ||||||
|  | - [nested transactions](https://sqlite.org/lang_savepoint.html) | ||||||
|  | - [custom functions](https://sqlite.org/c3ref/create_function.html) | ||||||
|  | - [virtual tables](https://sqlite.org/vtab.html) | ||||||
|  | - [custom VFSes](https://sqlite.org/vfs.html) | ||||||
|  | - [online backup](https://sqlite.org/backup.html) | ||||||
|  | - [JSON support](https://sqlite.org/json1.html) | ||||||
|  | - [math functions](https://sqlite.org/lang_mathfunc.html) | ||||||
|  | - [full-text search](https://sqlite.org/fts5.html) | ||||||
|  | - [geospatial search](https://sqlite.org/geopoly.html) | ||||||
|  | - [encryption at rest](vfs/adiantum/README.md) | ||||||
|  | - [and more…](embed/README.md) | ||||||
|  | 
 | ||||||
|  | ### Caveats | ||||||
|  | 
 | ||||||
|  | This module replaces the SQLite [OS Interface](https://sqlite.org/vfs.html) | ||||||
|  | (aka VFS) with a [pure Go](vfs/) implementation, | ||||||
|  | which has advantages and disadvantages. | ||||||
|  | 
 | ||||||
|  | Read more about the Go VFS design [here](vfs/README.md). | ||||||
|  | 
 | ||||||
|  | ### Testing | ||||||
|  | 
 | ||||||
|  | This project aims for [high test coverage](https://github.com/ncruces/go-sqlite3/wiki/Test-coverage-report). | ||||||
|  | It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and | ||||||
|  | [wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach) thorough testing. | ||||||
|  | 
 | ||||||
|  | Every commit is [tested](.github/workflows/test.yml) on | ||||||
|  | Linux (amd64/arm64/386/riscv64/s390x), macOS (amd64/arm64), | ||||||
|  | Windows (amd64), FreeBSD (amd64), illumos (amd64), and Solaris (amd64). | ||||||
|  | 
 | ||||||
|  | The Go VFS is tested by running SQLite's | ||||||
|  | [mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c). | ||||||
|  | 
 | ||||||
|  | ### Performance | ||||||
|  | 
 | ||||||
|  | Perfomance of the [`database/sql`](https://pkg.go.dev/database/sql) driver is | ||||||
|  | [competitive](https://github.com/cvilsmeier/go-sqlite-bench) with alternatives. | ||||||
|  | 
 | ||||||
|  | The Wasm and VFS layers are also tested by running SQLite's | ||||||
|  | [speedtest1](https://github.com/sqlite/sqlite/blob/master/test/speedtest1.c). | ||||||
|  | 
 | ||||||
|  | ### Alternatives | ||||||
|  | 
 | ||||||
|  | - [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite) | ||||||
|  | - [`crawshaw.io/sqlite`](https://pkg.go.dev/crawshaw.io/sqlite) | ||||||
|  | - [`github.com/mattn/go-sqlite3`](https://pkg.go.dev/github.com/mattn/go-sqlite3) | ||||||
|  | - [`github.com/zombiezen/go-sqlite`](https://pkg.go.dev/github.com/zombiezen/go-sqlite) | ||||||
|  | 
 | ||||||
|  | [^1]: anything else you find in `go.mod` is either a test dependency, | ||||||
|  |       or needed by one of the extensions. | ||||||
							
								
								
									
										134
									
								
								vendor/github.com/ncruces/go-sqlite3/backup.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								vendor/github.com/ncruces/go-sqlite3/backup.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,134 @@ | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | // Backup is an handle to an ongoing online backup operation. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/backup.html | ||||||
|  | type Backup struct { | ||||||
|  | 	c      *Conn | ||||||
|  | 	handle uint32 | ||||||
|  | 	otherc uint32 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Backup backs up srcDB on the src connection to the "main" database in dstURI. | ||||||
|  | // | ||||||
|  | // Backup opens the SQLite database file dstURI, | ||||||
|  | // and blocks until the entire backup is complete. | ||||||
|  | // Use [Conn.BackupInit] for incremental backup. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/backup.html | ||||||
|  | func (src *Conn) Backup(srcDB, dstURI string) error { | ||||||
|  | 	b, err := src.BackupInit(srcDB, dstURI) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer b.Close() | ||||||
|  | 	_, err = b.Step(-1) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Restore restores dstDB on the dst connection from the "main" database in srcURI. | ||||||
|  | // | ||||||
|  | // Restore opens the SQLite database file srcURI, | ||||||
|  | // and blocks until the entire restore is complete. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/backup.html | ||||||
|  | func (dst *Conn) Restore(dstDB, srcURI string) error { | ||||||
|  | 	src, err := dst.openDB(srcURI, OPEN_READONLY|OPEN_URI) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	b, err := dst.backupInit(dst.handle, dstDB, src, "main") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer b.Close() | ||||||
|  | 	_, err = b.Step(-1) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BackupInit initializes a backup operation to copy the content of one database into another. | ||||||
|  | // | ||||||
|  | // BackupInit opens the SQLite database file dstURI, | ||||||
|  | // then initializes a backup that copies the contents of srcDB on the src connection | ||||||
|  | // to the "main" database in dstURI. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/backup_finish.html#sqlite3backupinit | ||||||
|  | func (src *Conn) BackupInit(srcDB, dstURI string) (*Backup, error) { | ||||||
|  | 	dst, err := src.openDB(dstURI, OPEN_READWRITE|OPEN_CREATE|OPEN_URI) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return src.backupInit(dst, "main", src.handle, srcDB) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *Conn) backupInit(dst uint32, dstName string, src uint32, srcName string) (*Backup, error) { | ||||||
|  | 	defer c.arena.mark()() | ||||||
|  | 	dstPtr := c.arena.string(dstName) | ||||||
|  | 	srcPtr := c.arena.string(srcName) | ||||||
|  | 
 | ||||||
|  | 	other := dst | ||||||
|  | 	if c.handle == dst { | ||||||
|  | 		other = src | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r := c.call("sqlite3_backup_init", | ||||||
|  | 		uint64(dst), uint64(dstPtr), | ||||||
|  | 		uint64(src), uint64(srcPtr)) | ||||||
|  | 	if r == 0 { | ||||||
|  | 		defer c.closeDB(other) | ||||||
|  | 		r = c.call("sqlite3_errcode", uint64(dst)) | ||||||
|  | 		return nil, c.sqlite.error(r, dst) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &Backup{ | ||||||
|  | 		c:      c, | ||||||
|  | 		otherc: other, | ||||||
|  | 		handle: uint32(r), | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Close finishes a backup operation. | ||||||
|  | // | ||||||
|  | // It is safe to close a nil, zero or closed Backup. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish | ||||||
|  | func (b *Backup) Close() error { | ||||||
|  | 	if b == nil || b.handle == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r := b.c.call("sqlite3_backup_finish", uint64(b.handle)) | ||||||
|  | 	b.c.closeDB(b.otherc) | ||||||
|  | 	b.handle = 0 | ||||||
|  | 	return b.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Step copies up to nPage pages between the source and destination databases. | ||||||
|  | // If nPage is negative, all remaining source pages are copied. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/backup_finish.html#sqlite3backupstep | ||||||
|  | func (b *Backup) Step(nPage int) (done bool, err error) { | ||||||
|  | 	r := b.c.call("sqlite3_backup_step", uint64(b.handle), uint64(nPage)) | ||||||
|  | 	if r == _DONE { | ||||||
|  | 		return true, nil | ||||||
|  | 	} | ||||||
|  | 	return false, b.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Remaining returns the number of pages still to be backed up | ||||||
|  | // at the conclusion of the most recent [Backup.Step]. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining | ||||||
|  | func (b *Backup) Remaining() int { | ||||||
|  | 	r := b.c.call("sqlite3_backup_remaining", uint64(b.handle)) | ||||||
|  | 	return int(int32(r)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // PageCount returns the total number of pages in the source database | ||||||
|  | // at the conclusion of the most recent [Backup.Step]. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/backup_finish.html#sqlite3backuppagecount | ||||||
|  | func (b *Backup) PageCount() int { | ||||||
|  | 	r := b.c.call("sqlite3_backup_pagecount", uint64(b.handle)) | ||||||
|  | 	return int(int32(r)) | ||||||
|  | } | ||||||
							
								
								
									
										250
									
								
								vendor/github.com/ncruces/go-sqlite3/blob.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								vendor/github.com/ncruces/go-sqlite3/blob.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,250 @@ | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"io" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ZeroBlob represents a zero-filled, length n BLOB | ||||||
|  | // that can be used as an argument to | ||||||
|  | // [database/sql.DB.Exec] and similar methods. | ||||||
|  | type ZeroBlob int64 | ||||||
|  | 
 | ||||||
|  | // Blob is an handle to an open BLOB. | ||||||
|  | // | ||||||
|  | // It implements [io.ReadWriteSeeker] for incremental BLOB I/O. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/blob.html | ||||||
|  | type Blob struct { | ||||||
|  | 	c      *Conn | ||||||
|  | 	bytes  int64 | ||||||
|  | 	offset int64 | ||||||
|  | 	handle uint32 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var _ io.ReadWriteSeeker = &Blob{} | ||||||
|  | 
 | ||||||
|  | // OpenBlob opens a BLOB for incremental I/O. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/blob_open.html | ||||||
|  | func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob, error) { | ||||||
|  | 	c.checkInterrupt() | ||||||
|  | 	defer c.arena.mark()() | ||||||
|  | 	blobPtr := c.arena.new(ptrlen) | ||||||
|  | 	dbPtr := c.arena.string(db) | ||||||
|  | 	tablePtr := c.arena.string(table) | ||||||
|  | 	columnPtr := c.arena.string(column) | ||||||
|  | 
 | ||||||
|  | 	var flags uint64 | ||||||
|  | 	if write { | ||||||
|  | 		flags = 1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r := c.call("sqlite3_blob_open", uint64(c.handle), | ||||||
|  | 		uint64(dbPtr), uint64(tablePtr), uint64(columnPtr), | ||||||
|  | 		uint64(row), flags, uint64(blobPtr)) | ||||||
|  | 
 | ||||||
|  | 	if err := c.error(r); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	blob := Blob{c: c} | ||||||
|  | 	blob.handle = util.ReadUint32(c.mod, blobPtr) | ||||||
|  | 	blob.bytes = int64(c.call("sqlite3_blob_bytes", uint64(blob.handle))) | ||||||
|  | 	return &blob, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Close closes a BLOB handle. | ||||||
|  | // | ||||||
|  | // It is safe to close a nil, zero or closed Blob. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/blob_close.html | ||||||
|  | func (b *Blob) Close() error { | ||||||
|  | 	if b == nil || b.handle == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r := b.c.call("sqlite3_blob_close", uint64(b.handle)) | ||||||
|  | 
 | ||||||
|  | 	b.handle = 0 | ||||||
|  | 	return b.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Size returns the size of the BLOB in bytes. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/blob_bytes.html | ||||||
|  | func (b *Blob) Size() int64 { | ||||||
|  | 	return b.bytes | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Read implements the [io.Reader] interface. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/blob_read.html | ||||||
|  | func (b *Blob) Read(p []byte) (n int, err error) { | ||||||
|  | 	if b.offset >= b.bytes { | ||||||
|  | 		return 0, io.EOF | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	avail := b.bytes - b.offset | ||||||
|  | 	want := int64(len(p)) | ||||||
|  | 	if want > avail { | ||||||
|  | 		want = avail | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	defer b.c.arena.mark()() | ||||||
|  | 	ptr := b.c.arena.new(uint64(want)) | ||||||
|  | 
 | ||||||
|  | 	r := b.c.call("sqlite3_blob_read", uint64(b.handle), | ||||||
|  | 		uint64(ptr), uint64(want), uint64(b.offset)) | ||||||
|  | 	err = b.c.error(r) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 	b.offset += want | ||||||
|  | 	if b.offset >= b.bytes { | ||||||
|  | 		err = io.EOF | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	copy(p, util.View(b.c.mod, ptr, uint64(want))) | ||||||
|  | 	return int(want), err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WriteTo implements the [io.WriterTo] interface. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/blob_read.html | ||||||
|  | func (b *Blob) WriteTo(w io.Writer) (n int64, err error) { | ||||||
|  | 	if b.offset >= b.bytes { | ||||||
|  | 		return 0, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	want := int64(1024 * 1024) | ||||||
|  | 	avail := b.bytes - b.offset | ||||||
|  | 	if want > avail { | ||||||
|  | 		want = avail | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	defer b.c.arena.mark()() | ||||||
|  | 	ptr := b.c.arena.new(uint64(want)) | ||||||
|  | 
 | ||||||
|  | 	for want > 0 { | ||||||
|  | 		r := b.c.call("sqlite3_blob_read", uint64(b.handle), | ||||||
|  | 			uint64(ptr), uint64(want), uint64(b.offset)) | ||||||
|  | 		err = b.c.error(r) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return n, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		mem := util.View(b.c.mod, ptr, uint64(want)) | ||||||
|  | 		m, err := w.Write(mem[:want]) | ||||||
|  | 		b.offset += int64(m) | ||||||
|  | 		n += int64(m) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return n, err | ||||||
|  | 		} | ||||||
|  | 		if int64(m) != want { | ||||||
|  | 			return n, io.ErrShortWrite | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		avail = b.bytes - b.offset | ||||||
|  | 		if want > avail { | ||||||
|  | 			want = avail | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return n, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Write implements the [io.Writer] interface. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/blob_write.html | ||||||
|  | func (b *Blob) Write(p []byte) (n int, err error) { | ||||||
|  | 	defer b.c.arena.mark()() | ||||||
|  | 	ptr := b.c.arena.bytes(p) | ||||||
|  | 
 | ||||||
|  | 	r := b.c.call("sqlite3_blob_write", uint64(b.handle), | ||||||
|  | 		uint64(ptr), uint64(len(p)), uint64(b.offset)) | ||||||
|  | 	err = b.c.error(r) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 	b.offset += int64(len(p)) | ||||||
|  | 	return len(p), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ReadFrom implements the [io.ReaderFrom] interface. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/blob_write.html | ||||||
|  | func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) { | ||||||
|  | 	want := int64(1024 * 1024) | ||||||
|  | 	avail := b.bytes - b.offset | ||||||
|  | 	if l, ok := r.(*io.LimitedReader); ok && want > l.N { | ||||||
|  | 		want = l.N | ||||||
|  | 	} | ||||||
|  | 	if want > avail { | ||||||
|  | 		want = avail | ||||||
|  | 	} | ||||||
|  | 	if want < 1 { | ||||||
|  | 		want = 1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	defer b.c.arena.mark()() | ||||||
|  | 	ptr := b.c.arena.new(uint64(want)) | ||||||
|  | 
 | ||||||
|  | 	for { | ||||||
|  | 		mem := util.View(b.c.mod, ptr, uint64(want)) | ||||||
|  | 		m, err := r.Read(mem[:want]) | ||||||
|  | 		if m > 0 { | ||||||
|  | 			r := b.c.call("sqlite3_blob_write", uint64(b.handle), | ||||||
|  | 				uint64(ptr), uint64(m), uint64(b.offset)) | ||||||
|  | 			err := b.c.error(r) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return n, err | ||||||
|  | 			} | ||||||
|  | 			b.offset += int64(m) | ||||||
|  | 			n += int64(m) | ||||||
|  | 		} | ||||||
|  | 		if err == io.EOF { | ||||||
|  | 			return n, nil | ||||||
|  | 		} | ||||||
|  | 		if err != nil { | ||||||
|  | 			return n, err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		avail = b.bytes - b.offset | ||||||
|  | 		if want > avail { | ||||||
|  | 			want = avail | ||||||
|  | 		} | ||||||
|  | 		if want < 1 { | ||||||
|  | 			want = 1 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Seek implements the [io.Seeker] interface. | ||||||
|  | func (b *Blob) Seek(offset int64, whence int) (int64, error) { | ||||||
|  | 	switch whence { | ||||||
|  | 	default: | ||||||
|  | 		return 0, util.WhenceErr | ||||||
|  | 	case io.SeekStart: | ||||||
|  | 		break | ||||||
|  | 	case io.SeekCurrent: | ||||||
|  | 		offset += b.offset | ||||||
|  | 	case io.SeekEnd: | ||||||
|  | 		offset += b.bytes | ||||||
|  | 	} | ||||||
|  | 	if offset < 0 { | ||||||
|  | 		return 0, util.OffsetErr | ||||||
|  | 	} | ||||||
|  | 	b.offset = offset | ||||||
|  | 	return offset, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Reopen moves a BLOB handle to a new row of the same database table. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/blob_reopen.html | ||||||
|  | func (b *Blob) Reopen(row int64) error { | ||||||
|  | 	err := b.c.error(b.c.call("sqlite3_blob_reopen", uint64(b.handle), uint64(row))) | ||||||
|  | 	b.bytes = int64(b.c.call("sqlite3_blob_bytes", uint64(b.handle))) | ||||||
|  | 	b.offset = 0 | ||||||
|  | 	return err | ||||||
|  | } | ||||||
							
								
								
									
										164
									
								
								vendor/github.com/ncruces/go-sqlite3/config.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								vendor/github.com/ncruces/go-sqlite3/config.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,164 @@ | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | 	"github.com/tetratelabs/wazero/api" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Config makes configuration changes to a database connection. | ||||||
|  | // Only boolean configuration options are supported. | ||||||
|  | // Called with no arg reads the current configuration value, | ||||||
|  | // called with one arg sets and returns the new value. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/db_config.html | ||||||
|  | func (c *Conn) Config(op DBConfig, arg ...bool) (bool, error) { | ||||||
|  | 	defer c.arena.mark()() | ||||||
|  | 	argsPtr := c.arena.new(2 * ptrlen) | ||||||
|  | 
 | ||||||
|  | 	var flag int | ||||||
|  | 	switch { | ||||||
|  | 	case len(arg) == 0: | ||||||
|  | 		flag = -1 | ||||||
|  | 	case arg[0]: | ||||||
|  | 		flag = 1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	util.WriteUint32(c.mod, argsPtr+0*ptrlen, uint32(flag)) | ||||||
|  | 	util.WriteUint32(c.mod, argsPtr+1*ptrlen, argsPtr) | ||||||
|  | 
 | ||||||
|  | 	r := c.call("sqlite3_db_config", uint64(c.handle), | ||||||
|  | 		uint64(op), uint64(argsPtr)) | ||||||
|  | 	return util.ReadUint32(c.mod, argsPtr) != 0, c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ConfigLog sets up the error logging callback for the connection. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/errlog.html | ||||||
|  | func (c *Conn) ConfigLog(cb func(code ExtendedErrorCode, msg string)) error { | ||||||
|  | 	var enable uint64 | ||||||
|  | 	if cb != nil { | ||||||
|  | 		enable = 1 | ||||||
|  | 	} | ||||||
|  | 	r := c.call("sqlite3_config_log_go", enable) | ||||||
|  | 	if err := c.error(r); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	c.log = cb | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func logCallback(ctx context.Context, mod api.Module, _, iCode, zMsg uint32) { | ||||||
|  | 	if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.log != nil { | ||||||
|  | 		msg := util.ReadString(mod, zMsg, _MAX_LENGTH) | ||||||
|  | 		c.log(xErrorCode(iCode), msg) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Limit allows the size of various constructs to be | ||||||
|  | // limited on a connection by connection basis. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/limit.html | ||||||
|  | func (c *Conn) Limit(id LimitCategory, value int) int { | ||||||
|  | 	r := c.call("sqlite3_limit", uint64(c.handle), uint64(id), uint64(value)) | ||||||
|  | 	return int(int32(r)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetAuthorizer registers an authorizer callback with the database connection. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/set_authorizer.html | ||||||
|  | func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4th, schema, nameInner string) AuthorizerReturnCode) error { | ||||||
|  | 	var enable uint64 | ||||||
|  | 	if cb != nil { | ||||||
|  | 		enable = 1 | ||||||
|  | 	} | ||||||
|  | 	r := c.call("sqlite3_set_authorizer_go", uint64(c.handle), enable) | ||||||
|  | 	if err := c.error(r); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	c.authorizer = cb | ||||||
|  | 	return nil | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zName3rd, zName4th, zSchema, zNameInner uint32) (rc AuthorizerReturnCode) { | ||||||
|  | 	if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.authorizer != nil { | ||||||
|  | 		var name3rd, name4th, schema, nameInner string | ||||||
|  | 		if zName3rd != 0 { | ||||||
|  | 			name3rd = util.ReadString(mod, zName3rd, _MAX_NAME) | ||||||
|  | 		} | ||||||
|  | 		if zName4th != 0 { | ||||||
|  | 			name4th = util.ReadString(mod, zName4th, _MAX_NAME) | ||||||
|  | 		} | ||||||
|  | 		if zSchema != 0 { | ||||||
|  | 			schema = util.ReadString(mod, zSchema, _MAX_NAME) | ||||||
|  | 		} | ||||||
|  | 		if zNameInner != 0 { | ||||||
|  | 			nameInner = util.ReadString(mod, zNameInner, _MAX_NAME) | ||||||
|  | 		} | ||||||
|  | 		rc = c.authorizer(action, name3rd, name4th, schema, nameInner) | ||||||
|  | 	} | ||||||
|  | 	return rc | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WalCheckpoint checkpoints a WAL database. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/wal_checkpoint_v2.html | ||||||
|  | func (c *Conn) WalCheckpoint(schema string, mode CheckpointMode) (nLog, nCkpt int, err error) { | ||||||
|  | 	defer c.arena.mark()() | ||||||
|  | 	nLogPtr := c.arena.new(ptrlen) | ||||||
|  | 	nCkptPtr := c.arena.new(ptrlen) | ||||||
|  | 	schemaPtr := c.arena.string(schema) | ||||||
|  | 	r := c.call("sqlite3_wal_checkpoint_v2", | ||||||
|  | 		uint64(c.handle), uint64(schemaPtr), uint64(mode), | ||||||
|  | 		uint64(nLogPtr), uint64(nCkptPtr)) | ||||||
|  | 	nLog = int(int32(util.ReadUint32(c.mod, nLogPtr))) | ||||||
|  | 	nCkpt = int(int32(util.ReadUint32(c.mod, nCkptPtr))) | ||||||
|  | 	return nLog, nCkpt, c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WalAutoCheckpoint configures WAL auto-checkpoints. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/wal_autocheckpoint.html | ||||||
|  | func (c *Conn) WalAutoCheckpoint(pages int) error { | ||||||
|  | 	r := c.call("sqlite3_wal_autocheckpoint", uint64(c.handle), uint64(pages)) | ||||||
|  | 	return c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WalHook registers a callback function to be invoked | ||||||
|  | // each time data is committed to a database in WAL mode. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/wal_hook.html | ||||||
|  | func (c *Conn) WalHook(cb func(db *Conn, schema string, pages int) error) { | ||||||
|  | 	var enable uint64 | ||||||
|  | 	if cb != nil { | ||||||
|  | 		enable = 1 | ||||||
|  | 	} | ||||||
|  | 	c.call("sqlite3_wal_hook_go", uint64(c.handle), enable) | ||||||
|  | 	c.wal = cb | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func walCallback(ctx context.Context, mod api.Module, _, pDB, zSchema uint32, pages int32) (rc uint32) { | ||||||
|  | 	if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.wal != nil { | ||||||
|  | 		schema := util.ReadString(mod, zSchema, _MAX_NAME) | ||||||
|  | 		err := c.wal(c, schema, int(pages)) | ||||||
|  | 		_, rc = errorCode(err, ERROR) | ||||||
|  | 	} | ||||||
|  | 	return rc | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AutoVacuumPages registers a autovacuum compaction amount callback. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/autovacuum_pages.html | ||||||
|  | func (c *Conn) AutoVacuumPages(cb func(schema string, dbPages, freePages, bytesPerPage uint) uint) error { | ||||||
|  | 	funcPtr := util.AddHandle(c.ctx, cb) | ||||||
|  | 	r := c.call("sqlite3_autovacuum_pages_go", uint64(c.handle), uint64(funcPtr)) | ||||||
|  | 	return c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func autoVacuumCallback(ctx context.Context, mod api.Module, pApp, zSchema, nDbPage, nFreePage, nBytePerPage uint32) uint32 { | ||||||
|  | 	fn := util.GetHandle(ctx, pApp).(func(schema string, dbPages, freePages, bytesPerPage uint) uint) | ||||||
|  | 	schema := util.ReadString(mod, zSchema, _MAX_NAME) | ||||||
|  | 	return uint32(fn(schema, uint(nDbPage), uint(nFreePage), uint(nBytePerPage))) | ||||||
|  | } | ||||||
							
								
								
									
										426
									
								
								vendor/github.com/ncruces/go-sqlite3/conn.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										426
									
								
								vendor/github.com/ncruces/go-sqlite3/conn.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,426 @@ | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"math" | ||||||
|  | 	"net/url" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | 	"github.com/ncruces/go-sqlite3/vfs" | ||||||
|  | 	"github.com/tetratelabs/wazero/api" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Conn is a database connection handle. | ||||||
|  | // A Conn is not safe for concurrent use by multiple goroutines. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/sqlite3.html | ||||||
|  | type Conn struct { | ||||||
|  | 	*sqlite | ||||||
|  | 
 | ||||||
|  | 	interrupt  context.Context | ||||||
|  | 	pending    *Stmt | ||||||
|  | 	busy       func(int) bool | ||||||
|  | 	log        func(xErrorCode, string) | ||||||
|  | 	collation  func(*Conn, string) | ||||||
|  | 	authorizer func(AuthorizerActionCode, string, string, string, string) AuthorizerReturnCode | ||||||
|  | 	update     func(AuthorizerActionCode, string, string, int64) | ||||||
|  | 	commit     func() bool | ||||||
|  | 	rollback   func() | ||||||
|  | 	wal        func(*Conn, string, int) error | ||||||
|  | 	arena      arena | ||||||
|  | 
 | ||||||
|  | 	handle uint32 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Open calls [OpenFlags] with [OPEN_READWRITE], [OPEN_CREATE], [OPEN_URI] and [OPEN_NOFOLLOW]. | ||||||
|  | func Open(filename string) (*Conn, error) { | ||||||
|  | 	return newConn(filename, OPEN_READWRITE|OPEN_CREATE|OPEN_URI|OPEN_NOFOLLOW) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // OpenFlags opens an SQLite database file as specified by the filename argument. | ||||||
|  | // | ||||||
|  | // If none of the required flags is used, a combination of [OPEN_READWRITE] and [OPEN_CREATE] is used. | ||||||
|  | // If a URI filename is used, PRAGMA statements to execute can be specified using "_pragma": | ||||||
|  | // | ||||||
|  | //	sqlite3.Open("file:demo.db?_pragma=busy_timeout(10000)") | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/open.html | ||||||
|  | func OpenFlags(filename string, flags OpenFlag) (*Conn, error) { | ||||||
|  | 	if flags&(OPEN_READONLY|OPEN_READWRITE|OPEN_CREATE) == 0 { | ||||||
|  | 		flags |= OPEN_READWRITE | OPEN_CREATE | ||||||
|  | 	} | ||||||
|  | 	return newConn(filename, flags) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type connKey struct{} | ||||||
|  | 
 | ||||||
|  | func newConn(filename string, flags OpenFlag) (conn *Conn, err error) { | ||||||
|  | 	sqlite, err := instantiateSQLite() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	defer func() { | ||||||
|  | 		if conn == nil { | ||||||
|  | 			sqlite.close() | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	c := &Conn{sqlite: sqlite} | ||||||
|  | 	c.arena = c.newArena(1024) | ||||||
|  | 	c.ctx = context.WithValue(c.ctx, connKey{}, c) | ||||||
|  | 	c.handle, err = c.openDB(filename, flags) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return c, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) { | ||||||
|  | 	defer c.arena.mark()() | ||||||
|  | 	connPtr := c.arena.new(ptrlen) | ||||||
|  | 	namePtr := c.arena.string(filename) | ||||||
|  | 
 | ||||||
|  | 	flags |= OPEN_EXRESCODE | ||||||
|  | 	r := c.call("sqlite3_open_v2", uint64(namePtr), uint64(connPtr), uint64(flags), 0) | ||||||
|  | 
 | ||||||
|  | 	handle := util.ReadUint32(c.mod, connPtr) | ||||||
|  | 	if err := c.sqlite.error(r, handle); err != nil { | ||||||
|  | 		c.closeDB(handle) | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if flags|OPEN_URI != 0 && strings.HasPrefix(filename, "file:") { | ||||||
|  | 		var pragmas strings.Builder | ||||||
|  | 		if _, after, ok := strings.Cut(filename, "?"); ok { | ||||||
|  | 			query, _ := url.ParseQuery(after) | ||||||
|  | 			for _, p := range query["_pragma"] { | ||||||
|  | 				pragmas.WriteString(`PRAGMA `) | ||||||
|  | 				pragmas.WriteString(p) | ||||||
|  | 				pragmas.WriteString(`;`) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if pragmas.Len() != 0 { | ||||||
|  | 			pragmaPtr := c.arena.string(pragmas.String()) | ||||||
|  | 			r := c.call("sqlite3_exec", uint64(handle), uint64(pragmaPtr), 0, 0, 0) | ||||||
|  | 			if err := c.sqlite.error(r, handle, pragmas.String()); err != nil { | ||||||
|  | 				err = fmt.Errorf("sqlite3: invalid _pragma: %w", err) | ||||||
|  | 				c.closeDB(handle) | ||||||
|  | 				return 0, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	c.call("sqlite3_progress_handler_go", uint64(handle), 100) | ||||||
|  | 	return handle, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *Conn) closeDB(handle uint32) { | ||||||
|  | 	r := c.call("sqlite3_close_v2", uint64(handle)) | ||||||
|  | 	if err := c.sqlite.error(r, handle); err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Close closes the database connection. | ||||||
|  | // | ||||||
|  | // If the database connection is associated with unfinalized prepared statements, | ||||||
|  | // open blob handles, and/or unfinished backup objects, | ||||||
|  | // Close will leave the database connection open and return [BUSY]. | ||||||
|  | // | ||||||
|  | // It is safe to close a nil, zero or closed Conn. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/close.html | ||||||
|  | func (c *Conn) Close() error { | ||||||
|  | 	if c == nil || c.handle == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	c.pending.Close() | ||||||
|  | 	c.pending = nil | ||||||
|  | 
 | ||||||
|  | 	r := c.call("sqlite3_close", uint64(c.handle)) | ||||||
|  | 	if err := c.error(r); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	c.handle = 0 | ||||||
|  | 	return c.close() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Exec is a convenience function that allows an application to run | ||||||
|  | // multiple statements of SQL without having to use a lot of code. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/exec.html | ||||||
|  | func (c *Conn) Exec(sql string) error { | ||||||
|  | 	c.checkInterrupt() | ||||||
|  | 	defer c.arena.mark()() | ||||||
|  | 	sqlPtr := c.arena.string(sql) | ||||||
|  | 
 | ||||||
|  | 	r := c.call("sqlite3_exec", uint64(c.handle), uint64(sqlPtr), 0, 0, 0) | ||||||
|  | 	return c.error(r, sql) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Prepare calls [Conn.PrepareFlags] with no flags. | ||||||
|  | func (c *Conn) Prepare(sql string) (stmt *Stmt, tail string, err error) { | ||||||
|  | 	return c.PrepareFlags(sql, 0) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // PrepareFlags compiles the first SQL statement in sql; | ||||||
|  | // tail is left pointing to what remains uncompiled. | ||||||
|  | // If the input text contains no SQL (if the input is an empty string or a comment), | ||||||
|  | // both stmt and err will be nil. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/prepare.html | ||||||
|  | func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) { | ||||||
|  | 	if len(sql) > _MAX_SQL_LENGTH { | ||||||
|  | 		return nil, "", TOOBIG | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	defer c.arena.mark()() | ||||||
|  | 	stmtPtr := c.arena.new(ptrlen) | ||||||
|  | 	tailPtr := c.arena.new(ptrlen) | ||||||
|  | 	sqlPtr := c.arena.string(sql) | ||||||
|  | 
 | ||||||
|  | 	r := c.call("sqlite3_prepare_v3", uint64(c.handle), | ||||||
|  | 		uint64(sqlPtr), uint64(len(sql)+1), uint64(flags), | ||||||
|  | 		uint64(stmtPtr), uint64(tailPtr)) | ||||||
|  | 
 | ||||||
|  | 	stmt = &Stmt{c: c} | ||||||
|  | 	stmt.handle = util.ReadUint32(c.mod, stmtPtr) | ||||||
|  | 	if sql := sql[util.ReadUint32(c.mod, tailPtr)-sqlPtr:]; sql != "" { | ||||||
|  | 		tail = sql | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := c.error(r, sql); err != nil { | ||||||
|  | 		return nil, "", err | ||||||
|  | 	} | ||||||
|  | 	if stmt.handle == 0 { | ||||||
|  | 		return nil, "", nil | ||||||
|  | 	} | ||||||
|  | 	return stmt, tail, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DBName returns the schema name for n-th database on the database connection. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/db_name.html | ||||||
|  | func (c *Conn) DBName(n int) string { | ||||||
|  | 	r := c.call("sqlite3_db_name", uint64(c.handle), uint64(n)) | ||||||
|  | 
 | ||||||
|  | 	ptr := uint32(r) | ||||||
|  | 	if ptr == 0 { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return util.ReadString(c.mod, ptr, _MAX_NAME) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Filename returns the filename for a database. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/db_filename.html | ||||||
|  | func (c *Conn) Filename(schema string) *vfs.Filename { | ||||||
|  | 	var ptr uint32 | ||||||
|  | 	if schema != "" { | ||||||
|  | 		defer c.arena.mark()() | ||||||
|  | 		ptr = c.arena.string(schema) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r := c.call("sqlite3_db_filename", uint64(c.handle), uint64(ptr)) | ||||||
|  | 	return vfs.OpenFilename(c.ctx, c.mod, uint32(r), vfs.OPEN_MAIN_DB) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ReadOnly determines if a database is read-only. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/db_readonly.html | ||||||
|  | func (c *Conn) ReadOnly(schema string) (ro bool, ok bool) { | ||||||
|  | 	var ptr uint32 | ||||||
|  | 	if schema != "" { | ||||||
|  | 		defer c.arena.mark()() | ||||||
|  | 		ptr = c.arena.string(schema) | ||||||
|  | 	} | ||||||
|  | 	r := c.call("sqlite3_db_readonly", uint64(c.handle), uint64(ptr)) | ||||||
|  | 	return int32(r) > 0, int32(r) < 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetAutocommit tests the connection for auto-commit mode. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/get_autocommit.html | ||||||
|  | func (c *Conn) GetAutocommit() bool { | ||||||
|  | 	r := c.call("sqlite3_get_autocommit", uint64(c.handle)) | ||||||
|  | 	return r != 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // LastInsertRowID returns the rowid of the most recent successful INSERT | ||||||
|  | // on the database connection. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/last_insert_rowid.html | ||||||
|  | func (c *Conn) LastInsertRowID() int64 { | ||||||
|  | 	r := c.call("sqlite3_last_insert_rowid", uint64(c.handle)) | ||||||
|  | 	return int64(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetLastInsertRowID allows the application to set the value returned by | ||||||
|  | // [Conn.LastInsertRowID]. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/set_last_insert_rowid.html | ||||||
|  | func (c *Conn) SetLastInsertRowID(id int64) { | ||||||
|  | 	c.call("sqlite3_set_last_insert_rowid", uint64(c.handle), uint64(id)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Changes returns the number of rows modified, inserted or deleted | ||||||
|  | // by the most recently completed INSERT, UPDATE or DELETE statement | ||||||
|  | // on the database connection. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/changes.html | ||||||
|  | func (c *Conn) Changes() int64 { | ||||||
|  | 	r := c.call("sqlite3_changes64", uint64(c.handle)) | ||||||
|  | 	return int64(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TotalChanges returns the number of rows modified, inserted or deleted | ||||||
|  | // by all INSERT, UPDATE or DELETE statements completed | ||||||
|  | // since the database connection was opened. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/total_changes.html | ||||||
|  | func (c *Conn) TotalChanges() int64 { | ||||||
|  | 	r := c.call("sqlite3_total_changes64", uint64(c.handle)) | ||||||
|  | 	return int64(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ReleaseMemory frees memory used by a database connection. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/db_release_memory.html | ||||||
|  | func (c *Conn) ReleaseMemory() error { | ||||||
|  | 	r := c.call("sqlite3_db_release_memory", uint64(c.handle)) | ||||||
|  | 	return c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetInterrupt gets the context set with [Conn.SetInterrupt], | ||||||
|  | // or nil if none was set. | ||||||
|  | func (c *Conn) GetInterrupt() context.Context { | ||||||
|  | 	return c.interrupt | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetInterrupt interrupts a long-running query when a context is done. | ||||||
|  | // | ||||||
|  | // Subsequent uses of the connection will return [INTERRUPT] | ||||||
|  | // until the context is reset by another call to SetInterrupt. | ||||||
|  | // | ||||||
|  | // To associate a timeout with a connection: | ||||||
|  | // | ||||||
|  | //	ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond) | ||||||
|  | //	conn.SetInterrupt(ctx) | ||||||
|  | //	defer cancel() | ||||||
|  | // | ||||||
|  | // SetInterrupt returns the old context assigned to the connection. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/interrupt.html | ||||||
|  | func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) { | ||||||
|  | 	// Is it the same context? | ||||||
|  | 	if ctx == c.interrupt { | ||||||
|  | 		return ctx | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// A busy SQL statement prevents SQLite from ignoring an interrupt | ||||||
|  | 	// that comes before any other statements are started. | ||||||
|  | 	if c.pending == nil { | ||||||
|  | 		c.pending, _, _ = c.Prepare(`WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x FROM c) SELECT x FROM c`) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	old = c.interrupt | ||||||
|  | 	c.interrupt = ctx | ||||||
|  | 
 | ||||||
|  | 	if old != nil && old.Done() != nil && (ctx == nil || ctx.Err() == nil) { | ||||||
|  | 		c.pending.Reset() | ||||||
|  | 	} | ||||||
|  | 	if ctx != nil && ctx.Done() != nil { | ||||||
|  | 		c.pending.Step() | ||||||
|  | 	} | ||||||
|  | 	return old | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *Conn) checkInterrupt() { | ||||||
|  | 	if c.interrupt != nil && c.interrupt.Err() != nil { | ||||||
|  | 		c.call("sqlite3_interrupt", uint64(c.handle)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func progressCallback(ctx context.Context, mod api.Module, pDB uint32) (interrupt uint32) { | ||||||
|  | 	if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && | ||||||
|  | 		c.interrupt != nil && c.interrupt.Err() != nil { | ||||||
|  | 		interrupt = 1 | ||||||
|  | 	} | ||||||
|  | 	return interrupt | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BusyTimeout sets a busy timeout. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/busy_timeout.html | ||||||
|  | func (c *Conn) BusyTimeout(timeout time.Duration) error { | ||||||
|  | 	ms := min((timeout+time.Millisecond-1)/time.Millisecond, math.MaxInt32) | ||||||
|  | 	r := c.call("sqlite3_busy_timeout", uint64(c.handle), uint64(ms)) | ||||||
|  | 	return c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func timeoutCallback(ctx context.Context, mod api.Module, pDB uint32, count, tmout int32) (retry uint32) { | ||||||
|  | 	if c, ok := ctx.Value(connKey{}).(*Conn); ok && | ||||||
|  | 		(c.interrupt == nil || c.interrupt.Err() == nil) { | ||||||
|  | 		const delays = "\x01\x02\x05\x0a\x0f\x14\x19\x19\x19\x32\x32\x64" | ||||||
|  | 		const totals = "\x00\x01\x03\x08\x12\x21\x35\x4e\x67\x80\xb2\xe4" | ||||||
|  | 		const ndelay = int32(len(delays) - 1) | ||||||
|  | 
 | ||||||
|  | 		var delay, prior int32 | ||||||
|  | 		if count <= ndelay { | ||||||
|  | 			delay = int32(delays[count]) | ||||||
|  | 			prior = int32(totals[count]) | ||||||
|  | 		} else { | ||||||
|  | 			delay = int32(delays[ndelay]) | ||||||
|  | 			prior = int32(totals[ndelay]) + delay*(count-ndelay) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if delay = min(delay, tmout-prior); delay > 0 { | ||||||
|  | 			time.Sleep(time.Duration(delay) * time.Millisecond) | ||||||
|  | 			retry = 1 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return retry | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BusyHandler registers a callback to handle [BUSY] errors. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/busy_handler.html | ||||||
|  | func (c *Conn) BusyHandler(cb func(count int) (retry bool)) error { | ||||||
|  | 	var enable uint64 | ||||||
|  | 	if cb != nil { | ||||||
|  | 		enable = 1 | ||||||
|  | 	} | ||||||
|  | 	r := c.call("sqlite3_busy_handler_go", uint64(c.handle), enable) | ||||||
|  | 	if err := c.error(r); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	c.busy = cb | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func busyCallback(ctx context.Context, mod api.Module, pDB uint32, count int32) (retry uint32) { | ||||||
|  | 	if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.busy != nil && | ||||||
|  | 		(c.interrupt == nil || c.interrupt.Err() == nil) { | ||||||
|  | 		if c.busy(int(count)) { | ||||||
|  | 			retry = 1 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return retry | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *Conn) error(rc uint64, sql ...string) error { | ||||||
|  | 	return c.sqlite.error(rc, c.handle, sql...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DriverConn is implemented by the SQLite [database/sql] driver connection. | ||||||
|  | // | ||||||
|  | // It can be used to access SQLite features like [online backup]. | ||||||
|  | // | ||||||
|  | // [online backup]: https://sqlite.org/backup.html | ||||||
|  | type DriverConn interface { | ||||||
|  | 	Raw() *Conn | ||||||
|  | } | ||||||
							
								
								
									
										360
									
								
								vendor/github.com/ncruces/go-sqlite3/const.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										360
									
								
								vendor/github.com/ncruces/go-sqlite3/const.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,360 @@ | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | import "strconv" | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	_OK   = 0   /* Successful result */ | ||||||
|  | 	_ROW  = 100 /* sqlite3_step() has another row ready */ | ||||||
|  | 	_DONE = 101 /* sqlite3_step() has finished executing */ | ||||||
|  | 
 | ||||||
|  | 	_UTF8 = 1 | ||||||
|  | 
 | ||||||
|  | 	_MAX_NAME            = 1e6 // Self-imposed limit for most NUL terminated strings. | ||||||
|  | 	_MAX_LENGTH          = 1e9 | ||||||
|  | 	_MAX_SQL_LENGTH      = 1e9 | ||||||
|  | 	_MAX_ALLOCATION_SIZE = 0x7ffffeff | ||||||
|  | 	_MAX_FUNCTION_ARG    = 100 | ||||||
|  | 
 | ||||||
|  | 	ptrlen = 4 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ErrorCode is a result code that [Error.Code] might return. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/rescode.html | ||||||
|  | type ErrorCode uint8 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	ERROR      ErrorCode = 1  /* Generic error */ | ||||||
|  | 	INTERNAL   ErrorCode = 2  /* Internal logic error in SQLite */ | ||||||
|  | 	PERM       ErrorCode = 3  /* Access permission denied */ | ||||||
|  | 	ABORT      ErrorCode = 4  /* Callback routine requested an abort */ | ||||||
|  | 	BUSY       ErrorCode = 5  /* The database file is locked */ | ||||||
|  | 	LOCKED     ErrorCode = 6  /* A table in the database is locked */ | ||||||
|  | 	NOMEM      ErrorCode = 7  /* A malloc() failed */ | ||||||
|  | 	READONLY   ErrorCode = 8  /* Attempt to write a readonly database */ | ||||||
|  | 	INTERRUPT  ErrorCode = 9  /* Operation terminated by sqlite3_interrupt() */ | ||||||
|  | 	IOERR      ErrorCode = 10 /* Some kind of disk I/O error occurred */ | ||||||
|  | 	CORRUPT    ErrorCode = 11 /* The database disk image is malformed */ | ||||||
|  | 	NOTFOUND   ErrorCode = 12 /* Unknown opcode in sqlite3_file_control() */ | ||||||
|  | 	FULL       ErrorCode = 13 /* Insertion failed because database is full */ | ||||||
|  | 	CANTOPEN   ErrorCode = 14 /* Unable to open the database file */ | ||||||
|  | 	PROTOCOL   ErrorCode = 15 /* Database lock protocol error */ | ||||||
|  | 	EMPTY      ErrorCode = 16 /* Internal use only */ | ||||||
|  | 	SCHEMA     ErrorCode = 17 /* The database schema changed */ | ||||||
|  | 	TOOBIG     ErrorCode = 18 /* String or BLOB exceeds size limit */ | ||||||
|  | 	CONSTRAINT ErrorCode = 19 /* Abort due to constraint violation */ | ||||||
|  | 	MISMATCH   ErrorCode = 20 /* Data type mismatch */ | ||||||
|  | 	MISUSE     ErrorCode = 21 /* Library used incorrectly */ | ||||||
|  | 	NOLFS      ErrorCode = 22 /* Uses OS features not supported on host */ | ||||||
|  | 	AUTH       ErrorCode = 23 /* Authorization denied */ | ||||||
|  | 	FORMAT     ErrorCode = 24 /* Not used */ | ||||||
|  | 	RANGE      ErrorCode = 25 /* 2nd parameter to sqlite3_bind out of range */ | ||||||
|  | 	NOTADB     ErrorCode = 26 /* File opened that is not a database file */ | ||||||
|  | 	NOTICE     ErrorCode = 27 /* Notifications from sqlite3_log() */ | ||||||
|  | 	WARNING    ErrorCode = 28 /* Warnings from sqlite3_log() */ | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ExtendedErrorCode is a result code that [Error.ExtendedCode] might return. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/rescode.html | ||||||
|  | type ( | ||||||
|  | 	ExtendedErrorCode uint16 | ||||||
|  | 	xErrorCode        = ExtendedErrorCode | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	ERROR_MISSING_COLLSEQ   ExtendedErrorCode = xErrorCode(ERROR) | (1 << 8) | ||||||
|  | 	ERROR_RETRY             ExtendedErrorCode = xErrorCode(ERROR) | (2 << 8) | ||||||
|  | 	ERROR_SNAPSHOT          ExtendedErrorCode = xErrorCode(ERROR) | (3 << 8) | ||||||
|  | 	IOERR_READ              ExtendedErrorCode = xErrorCode(IOERR) | (1 << 8) | ||||||
|  | 	IOERR_SHORT_READ        ExtendedErrorCode = xErrorCode(IOERR) | (2 << 8) | ||||||
|  | 	IOERR_WRITE             ExtendedErrorCode = xErrorCode(IOERR) | (3 << 8) | ||||||
|  | 	IOERR_FSYNC             ExtendedErrorCode = xErrorCode(IOERR) | (4 << 8) | ||||||
|  | 	IOERR_DIR_FSYNC         ExtendedErrorCode = xErrorCode(IOERR) | (5 << 8) | ||||||
|  | 	IOERR_TRUNCATE          ExtendedErrorCode = xErrorCode(IOERR) | (6 << 8) | ||||||
|  | 	IOERR_FSTAT             ExtendedErrorCode = xErrorCode(IOERR) | (7 << 8) | ||||||
|  | 	IOERR_UNLOCK            ExtendedErrorCode = xErrorCode(IOERR) | (8 << 8) | ||||||
|  | 	IOERR_RDLOCK            ExtendedErrorCode = xErrorCode(IOERR) | (9 << 8) | ||||||
|  | 	IOERR_DELETE            ExtendedErrorCode = xErrorCode(IOERR) | (10 << 8) | ||||||
|  | 	IOERR_BLOCKED           ExtendedErrorCode = xErrorCode(IOERR) | (11 << 8) | ||||||
|  | 	IOERR_NOMEM             ExtendedErrorCode = xErrorCode(IOERR) | (12 << 8) | ||||||
|  | 	IOERR_ACCESS            ExtendedErrorCode = xErrorCode(IOERR) | (13 << 8) | ||||||
|  | 	IOERR_CHECKRESERVEDLOCK ExtendedErrorCode = xErrorCode(IOERR) | (14 << 8) | ||||||
|  | 	IOERR_LOCK              ExtendedErrorCode = xErrorCode(IOERR) | (15 << 8) | ||||||
|  | 	IOERR_CLOSE             ExtendedErrorCode = xErrorCode(IOERR) | (16 << 8) | ||||||
|  | 	IOERR_DIR_CLOSE         ExtendedErrorCode = xErrorCode(IOERR) | (17 << 8) | ||||||
|  | 	IOERR_SHMOPEN           ExtendedErrorCode = xErrorCode(IOERR) | (18 << 8) | ||||||
|  | 	IOERR_SHMSIZE           ExtendedErrorCode = xErrorCode(IOERR) | (19 << 8) | ||||||
|  | 	IOERR_SHMLOCK           ExtendedErrorCode = xErrorCode(IOERR) | (20 << 8) | ||||||
|  | 	IOERR_SHMMAP            ExtendedErrorCode = xErrorCode(IOERR) | (21 << 8) | ||||||
|  | 	IOERR_SEEK              ExtendedErrorCode = xErrorCode(IOERR) | (22 << 8) | ||||||
|  | 	IOERR_DELETE_NOENT      ExtendedErrorCode = xErrorCode(IOERR) | (23 << 8) | ||||||
|  | 	IOERR_MMAP              ExtendedErrorCode = xErrorCode(IOERR) | (24 << 8) | ||||||
|  | 	IOERR_GETTEMPPATH       ExtendedErrorCode = xErrorCode(IOERR) | (25 << 8) | ||||||
|  | 	IOERR_CONVPATH          ExtendedErrorCode = xErrorCode(IOERR) | (26 << 8) | ||||||
|  | 	IOERR_VNODE             ExtendedErrorCode = xErrorCode(IOERR) | (27 << 8) | ||||||
|  | 	IOERR_AUTH              ExtendedErrorCode = xErrorCode(IOERR) | (28 << 8) | ||||||
|  | 	IOERR_BEGIN_ATOMIC      ExtendedErrorCode = xErrorCode(IOERR) | (29 << 8) | ||||||
|  | 	IOERR_COMMIT_ATOMIC     ExtendedErrorCode = xErrorCode(IOERR) | (30 << 8) | ||||||
|  | 	IOERR_ROLLBACK_ATOMIC   ExtendedErrorCode = xErrorCode(IOERR) | (31 << 8) | ||||||
|  | 	IOERR_DATA              ExtendedErrorCode = xErrorCode(IOERR) | (32 << 8) | ||||||
|  | 	IOERR_CORRUPTFS         ExtendedErrorCode = xErrorCode(IOERR) | (33 << 8) | ||||||
|  | 	IOERR_IN_PAGE           ExtendedErrorCode = xErrorCode(IOERR) | (34 << 8) | ||||||
|  | 	LOCKED_SHAREDCACHE      ExtendedErrorCode = xErrorCode(LOCKED) | (1 << 8) | ||||||
|  | 	LOCKED_VTAB             ExtendedErrorCode = xErrorCode(LOCKED) | (2 << 8) | ||||||
|  | 	BUSY_RECOVERY           ExtendedErrorCode = xErrorCode(BUSY) | (1 << 8) | ||||||
|  | 	BUSY_SNAPSHOT           ExtendedErrorCode = xErrorCode(BUSY) | (2 << 8) | ||||||
|  | 	BUSY_TIMEOUT            ExtendedErrorCode = xErrorCode(BUSY) | (3 << 8) | ||||||
|  | 	CANTOPEN_NOTEMPDIR      ExtendedErrorCode = xErrorCode(CANTOPEN) | (1 << 8) | ||||||
|  | 	CANTOPEN_ISDIR          ExtendedErrorCode = xErrorCode(CANTOPEN) | (2 << 8) | ||||||
|  | 	CANTOPEN_FULLPATH       ExtendedErrorCode = xErrorCode(CANTOPEN) | (3 << 8) | ||||||
|  | 	CANTOPEN_CONVPATH       ExtendedErrorCode = xErrorCode(CANTOPEN) | (4 << 8) | ||||||
|  | 	CANTOPEN_DIRTYWAL       ExtendedErrorCode = xErrorCode(CANTOPEN) | (5 << 8) /* Not Used */ | ||||||
|  | 	CANTOPEN_SYMLINK        ExtendedErrorCode = xErrorCode(CANTOPEN) | (6 << 8) | ||||||
|  | 	CORRUPT_VTAB            ExtendedErrorCode = xErrorCode(CORRUPT) | (1 << 8) | ||||||
|  | 	CORRUPT_SEQUENCE        ExtendedErrorCode = xErrorCode(CORRUPT) | (2 << 8) | ||||||
|  | 	CORRUPT_INDEX           ExtendedErrorCode = xErrorCode(CORRUPT) | (3 << 8) | ||||||
|  | 	READONLY_RECOVERY       ExtendedErrorCode = xErrorCode(READONLY) | (1 << 8) | ||||||
|  | 	READONLY_CANTLOCK       ExtendedErrorCode = xErrorCode(READONLY) | (2 << 8) | ||||||
|  | 	READONLY_ROLLBACK       ExtendedErrorCode = xErrorCode(READONLY) | (3 << 8) | ||||||
|  | 	READONLY_DBMOVED        ExtendedErrorCode = xErrorCode(READONLY) | (4 << 8) | ||||||
|  | 	READONLY_CANTINIT       ExtendedErrorCode = xErrorCode(READONLY) | (5 << 8) | ||||||
|  | 	READONLY_DIRECTORY      ExtendedErrorCode = xErrorCode(READONLY) | (6 << 8) | ||||||
|  | 	ABORT_ROLLBACK          ExtendedErrorCode = xErrorCode(ABORT) | (2 << 8) | ||||||
|  | 	CONSTRAINT_CHECK        ExtendedErrorCode = xErrorCode(CONSTRAINT) | (1 << 8) | ||||||
|  | 	CONSTRAINT_COMMITHOOK   ExtendedErrorCode = xErrorCode(CONSTRAINT) | (2 << 8) | ||||||
|  | 	CONSTRAINT_FOREIGNKEY   ExtendedErrorCode = xErrorCode(CONSTRAINT) | (3 << 8) | ||||||
|  | 	CONSTRAINT_FUNCTION     ExtendedErrorCode = xErrorCode(CONSTRAINT) | (4 << 8) | ||||||
|  | 	CONSTRAINT_NOTNULL      ExtendedErrorCode = xErrorCode(CONSTRAINT) | (5 << 8) | ||||||
|  | 	CONSTRAINT_PRIMARYKEY   ExtendedErrorCode = xErrorCode(CONSTRAINT) | (6 << 8) | ||||||
|  | 	CONSTRAINT_TRIGGER      ExtendedErrorCode = xErrorCode(CONSTRAINT) | (7 << 8) | ||||||
|  | 	CONSTRAINT_UNIQUE       ExtendedErrorCode = xErrorCode(CONSTRAINT) | (8 << 8) | ||||||
|  | 	CONSTRAINT_VTAB         ExtendedErrorCode = xErrorCode(CONSTRAINT) | (9 << 8) | ||||||
|  | 	CONSTRAINT_ROWID        ExtendedErrorCode = xErrorCode(CONSTRAINT) | (10 << 8) | ||||||
|  | 	CONSTRAINT_PINNED       ExtendedErrorCode = xErrorCode(CONSTRAINT) | (11 << 8) | ||||||
|  | 	CONSTRAINT_DATATYPE     ExtendedErrorCode = xErrorCode(CONSTRAINT) | (12 << 8) | ||||||
|  | 	NOTICE_RECOVER_WAL      ExtendedErrorCode = xErrorCode(NOTICE) | (1 << 8) | ||||||
|  | 	NOTICE_RECOVER_ROLLBACK ExtendedErrorCode = xErrorCode(NOTICE) | (2 << 8) | ||||||
|  | 	NOTICE_RBU              ExtendedErrorCode = xErrorCode(NOTICE) | (3 << 8) | ||||||
|  | 	WARNING_AUTOINDEX       ExtendedErrorCode = xErrorCode(WARNING) | (1 << 8) | ||||||
|  | 	AUTH_USER               ExtendedErrorCode = xErrorCode(AUTH) | (1 << 8) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // OpenFlag is a flag for the [OpenFlags] function. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_open_autoproxy.html | ||||||
|  | type OpenFlag uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	OPEN_READONLY     OpenFlag = 0x00000001 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_READWRITE    OpenFlag = 0x00000002 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_CREATE       OpenFlag = 0x00000004 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_URI          OpenFlag = 0x00000040 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_MEMORY       OpenFlag = 0x00000080 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_NOMUTEX      OpenFlag = 0x00008000 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_FULLMUTEX    OpenFlag = 0x00010000 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_SHAREDCACHE  OpenFlag = 0x00020000 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_PRIVATECACHE OpenFlag = 0x00040000 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_NOFOLLOW     OpenFlag = 0x01000000 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_EXRESCODE    OpenFlag = 0x02000000 /* Extended result codes */ | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // PrepareFlag is a flag that can be passed to [Conn.PrepareFlags]. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_prepare_normalize.html | ||||||
|  | type PrepareFlag uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	PREPARE_PERSISTENT PrepareFlag = 0x01 | ||||||
|  | 	PREPARE_NORMALIZE  PrepareFlag = 0x02 | ||||||
|  | 	PREPARE_NO_VTAB    PrepareFlag = 0x04 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // FunctionFlag is a flag that can be passed to | ||||||
|  | // [Conn.CreateFunction] and [Conn.CreateWindowFunction]. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_deterministic.html | ||||||
|  | type FunctionFlag uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	DETERMINISTIC  FunctionFlag = 0x000000800 | ||||||
|  | 	DIRECTONLY     FunctionFlag = 0x000080000 | ||||||
|  | 	SUBTYPE        FunctionFlag = 0x000100000 | ||||||
|  | 	INNOCUOUS      FunctionFlag = 0x000200000 | ||||||
|  | 	RESULT_SUBTYPE FunctionFlag = 0x001000000 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // StmtStatus name counter values associated with the [Stmt.Status] method. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_stmtstatus_counter.html | ||||||
|  | type StmtStatus uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	STMTSTATUS_FULLSCAN_STEP StmtStatus = 1 | ||||||
|  | 	STMTSTATUS_SORT          StmtStatus = 2 | ||||||
|  | 	STMTSTATUS_AUTOINDEX     StmtStatus = 3 | ||||||
|  | 	STMTSTATUS_VM_STEP       StmtStatus = 4 | ||||||
|  | 	STMTSTATUS_REPREPARE     StmtStatus = 5 | ||||||
|  | 	STMTSTATUS_RUN           StmtStatus = 6 | ||||||
|  | 	STMTSTATUS_FILTER_MISS   StmtStatus = 7 | ||||||
|  | 	STMTSTATUS_FILTER_HIT    StmtStatus = 8 | ||||||
|  | 	STMTSTATUS_MEMUSED       StmtStatus = 99 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // DBConfig are the available database connection configuration options. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_dbconfig_defensive.html | ||||||
|  | type DBConfig uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// DBCONFIG_MAINDBNAME         DBConfig = 1000 | ||||||
|  | 	// DBCONFIG_LOOKASIDE          DBConfig = 1001 | ||||||
|  | 	DBCONFIG_ENABLE_FKEY           DBConfig = 1002 | ||||||
|  | 	DBCONFIG_ENABLE_TRIGGER        DBConfig = 1003 | ||||||
|  | 	DBCONFIG_ENABLE_FTS3_TOKENIZER DBConfig = 1004 | ||||||
|  | 	DBCONFIG_ENABLE_LOAD_EXTENSION DBConfig = 1005 | ||||||
|  | 	DBCONFIG_NO_CKPT_ON_CLOSE      DBConfig = 1006 | ||||||
|  | 	DBCONFIG_ENABLE_QPSG           DBConfig = 1007 | ||||||
|  | 	DBCONFIG_TRIGGER_EQP           DBConfig = 1008 | ||||||
|  | 	DBCONFIG_RESET_DATABASE        DBConfig = 1009 | ||||||
|  | 	DBCONFIG_DEFENSIVE             DBConfig = 1010 | ||||||
|  | 	DBCONFIG_WRITABLE_SCHEMA       DBConfig = 1011 | ||||||
|  | 	DBCONFIG_LEGACY_ALTER_TABLE    DBConfig = 1012 | ||||||
|  | 	DBCONFIG_DQS_DML               DBConfig = 1013 | ||||||
|  | 	DBCONFIG_DQS_DDL               DBConfig = 1014 | ||||||
|  | 	DBCONFIG_ENABLE_VIEW           DBConfig = 1015 | ||||||
|  | 	DBCONFIG_LEGACY_FILE_FORMAT    DBConfig = 1016 | ||||||
|  | 	DBCONFIG_TRUSTED_SCHEMA        DBConfig = 1017 | ||||||
|  | 	DBCONFIG_STMT_SCANSTATUS       DBConfig = 1018 | ||||||
|  | 	DBCONFIG_REVERSE_SCANORDER     DBConfig = 1019 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // LimitCategory are the available run-time limit categories. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_limit_attached.html | ||||||
|  | type LimitCategory uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	LIMIT_LENGTH              LimitCategory = 0 | ||||||
|  | 	LIMIT_SQL_LENGTH          LimitCategory = 1 | ||||||
|  | 	LIMIT_COLUMN              LimitCategory = 2 | ||||||
|  | 	LIMIT_EXPR_DEPTH          LimitCategory = 3 | ||||||
|  | 	LIMIT_COMPOUND_SELECT     LimitCategory = 4 | ||||||
|  | 	LIMIT_VDBE_OP             LimitCategory = 5 | ||||||
|  | 	LIMIT_FUNCTION_ARG        LimitCategory = 6 | ||||||
|  | 	LIMIT_ATTACHED            LimitCategory = 7 | ||||||
|  | 	LIMIT_LIKE_PATTERN_LENGTH LimitCategory = 8 | ||||||
|  | 	LIMIT_VARIABLE_NUMBER     LimitCategory = 9 | ||||||
|  | 	LIMIT_TRIGGER_DEPTH       LimitCategory = 10 | ||||||
|  | 	LIMIT_WORKER_THREADS      LimitCategory = 11 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // AuthorizerActionCode are the integer action codes | ||||||
|  | // that the authorizer callback may be passed. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_alter_table.html | ||||||
|  | type AuthorizerActionCode uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	/***************************************************** 3rd ************ 4th ***********/ | ||||||
|  | 	AUTH_CREATE_INDEX        AuthorizerActionCode = 1  /* Index Name      Table Name      */ | ||||||
|  | 	AUTH_CREATE_TABLE        AuthorizerActionCode = 2  /* Table Name      NULL            */ | ||||||
|  | 	AUTH_CREATE_TEMP_INDEX   AuthorizerActionCode = 3  /* Index Name      Table Name      */ | ||||||
|  | 	AUTH_CREATE_TEMP_TABLE   AuthorizerActionCode = 4  /* Table Name      NULL            */ | ||||||
|  | 	AUTH_CREATE_TEMP_TRIGGER AuthorizerActionCode = 5  /* Trigger Name    Table Name      */ | ||||||
|  | 	AUTH_CREATE_TEMP_VIEW    AuthorizerActionCode = 6  /* View Name       NULL            */ | ||||||
|  | 	AUTH_CREATE_TRIGGER      AuthorizerActionCode = 7  /* Trigger Name    Table Name      */ | ||||||
|  | 	AUTH_CREATE_VIEW         AuthorizerActionCode = 8  /* View Name       NULL            */ | ||||||
|  | 	AUTH_DELETE              AuthorizerActionCode = 9  /* Table Name      NULL            */ | ||||||
|  | 	AUTH_DROP_INDEX          AuthorizerActionCode = 10 /* Index Name      Table Name      */ | ||||||
|  | 	AUTH_DROP_TABLE          AuthorizerActionCode = 11 /* Table Name      NULL            */ | ||||||
|  | 	AUTH_DROP_TEMP_INDEX     AuthorizerActionCode = 12 /* Index Name      Table Name      */ | ||||||
|  | 	AUTH_DROP_TEMP_TABLE     AuthorizerActionCode = 13 /* Table Name      NULL            */ | ||||||
|  | 	AUTH_DROP_TEMP_TRIGGER   AuthorizerActionCode = 14 /* Trigger Name    Table Name      */ | ||||||
|  | 	AUTH_DROP_TEMP_VIEW      AuthorizerActionCode = 15 /* View Name       NULL            */ | ||||||
|  | 	AUTH_DROP_TRIGGER        AuthorizerActionCode = 16 /* Trigger Name    Table Name      */ | ||||||
|  | 	AUTH_DROP_VIEW           AuthorizerActionCode = 17 /* View Name       NULL            */ | ||||||
|  | 	AUTH_INSERT              AuthorizerActionCode = 18 /* Table Name      NULL            */ | ||||||
|  | 	AUTH_PRAGMA              AuthorizerActionCode = 19 /* Pragma Name     1st arg or NULL */ | ||||||
|  | 	AUTH_READ                AuthorizerActionCode = 20 /* Table Name      Column Name     */ | ||||||
|  | 	AUTH_SELECT              AuthorizerActionCode = 21 /* NULL            NULL            */ | ||||||
|  | 	AUTH_TRANSACTION         AuthorizerActionCode = 22 /* Operation       NULL            */ | ||||||
|  | 	AUTH_UPDATE              AuthorizerActionCode = 23 /* Table Name      Column Name     */ | ||||||
|  | 	AUTH_ATTACH              AuthorizerActionCode = 24 /* Filename        NULL            */ | ||||||
|  | 	AUTH_DETACH              AuthorizerActionCode = 25 /* Database Name   NULL            */ | ||||||
|  | 	AUTH_ALTER_TABLE         AuthorizerActionCode = 26 /* Database Name   Table Name      */ | ||||||
|  | 	AUTH_REINDEX             AuthorizerActionCode = 27 /* Index Name      NULL            */ | ||||||
|  | 	AUTH_ANALYZE             AuthorizerActionCode = 28 /* Table Name      NULL            */ | ||||||
|  | 	AUTH_CREATE_VTABLE       AuthorizerActionCode = 29 /* Table Name      Module Name     */ | ||||||
|  | 	AUTH_DROP_VTABLE         AuthorizerActionCode = 30 /* Table Name      Module Name     */ | ||||||
|  | 	AUTH_FUNCTION            AuthorizerActionCode = 31 /* NULL            Function Name   */ | ||||||
|  | 	AUTH_SAVEPOINT           AuthorizerActionCode = 32 /* Operation       Savepoint Name  */ | ||||||
|  | 	AUTH_COPY                AuthorizerActionCode = 0  /* No longer used */ | ||||||
|  | 	AUTH_RECURSIVE           AuthorizerActionCode = 33 /* NULL            NULL            */ | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // AuthorizerReturnCode are the integer codes | ||||||
|  | // that the authorizer callback may return. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_deny.html | ||||||
|  | type AuthorizerReturnCode uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	AUTH_OK     AuthorizerReturnCode = 0 | ||||||
|  | 	AUTH_DENY   AuthorizerReturnCode = 1 /* Abort the SQL statement with an error */ | ||||||
|  | 	AUTH_IGNORE AuthorizerReturnCode = 2 /* Don't allow access, but don't generate an error */ | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // CheckpointMode are all the checkpoint mode values. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_checkpoint_full.html | ||||||
|  | type CheckpointMode uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	CHECKPOINT_PASSIVE  CheckpointMode = 0 /* Do as much as possible w/o blocking */ | ||||||
|  | 	CHECKPOINT_FULL     CheckpointMode = 1 /* Wait for writers, then checkpoint */ | ||||||
|  | 	CHECKPOINT_RESTART  CheckpointMode = 2 /* Like FULL but wait for readers */ | ||||||
|  | 	CHECKPOINT_TRUNCATE CheckpointMode = 3 /* Like RESTART but also truncate WAL */ | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // TxnState are the allowed return values from [Conn.TxnState]. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_txn_none.html | ||||||
|  | type TxnState uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	TXN_NONE  TxnState = 0 | ||||||
|  | 	TXN_READ  TxnState = 1 | ||||||
|  | 	TXN_WRITE TxnState = 2 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Datatype is a fundamental datatype of SQLite. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_blob.html | ||||||
|  | type Datatype uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	INTEGER Datatype = 1 | ||||||
|  | 	FLOAT   Datatype = 2 | ||||||
|  | 	TEXT    Datatype = 3 | ||||||
|  | 	BLOB    Datatype = 4 | ||||||
|  | 	NULL    Datatype = 5 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // String implements the [fmt.Stringer] interface. | ||||||
|  | func (t Datatype) String() string { | ||||||
|  | 	const name = "INTEGERFLOATEXTBLOBNULL" | ||||||
|  | 	switch t { | ||||||
|  | 	case INTEGER: | ||||||
|  | 		return name[0:7] | ||||||
|  | 	case FLOAT: | ||||||
|  | 		return name[7:12] | ||||||
|  | 	case TEXT: | ||||||
|  | 		return name[11:15] | ||||||
|  | 	case BLOB: | ||||||
|  | 		return name[15:19] | ||||||
|  | 	case NULL: | ||||||
|  | 		return name[19:23] | ||||||
|  | 	} | ||||||
|  | 	return strconv.FormatUint(uint64(t), 10) | ||||||
|  | } | ||||||
							
								
								
									
										229
									
								
								vendor/github.com/ncruces/go-sqlite3/context.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								vendor/github.com/ncruces/go-sqlite3/context.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,229 @@ | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"math" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Context is the context in which an SQL function executes. | ||||||
|  | // An SQLite [Context] is in no way related to a Go [context.Context]. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/context.html | ||||||
|  | type Context struct { | ||||||
|  | 	c      *Conn | ||||||
|  | 	handle uint32 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Conn returns the database connection of the | ||||||
|  | // [Conn.CreateFunction] or [Conn.CreateWindowFunction] | ||||||
|  | // routines that originally registered the application defined function. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/context_db_handle.html | ||||||
|  | func (ctx Context) Conn() *Conn { | ||||||
|  | 	return ctx.c | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetAuxData saves metadata for argument n of the function. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/get_auxdata.html | ||||||
|  | func (ctx Context) SetAuxData(n int, data any) { | ||||||
|  | 	ptr := util.AddHandle(ctx.c.ctx, data) | ||||||
|  | 	ctx.c.call("sqlite3_set_auxdata_go", uint64(ctx.handle), uint64(n), uint64(ptr)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // GetAuxData returns metadata for argument n of the function. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/get_auxdata.html | ||||||
|  | func (ctx Context) GetAuxData(n int) any { | ||||||
|  | 	ptr := uint32(ctx.c.call("sqlite3_get_auxdata", uint64(ctx.handle), uint64(n))) | ||||||
|  | 	return util.GetHandle(ctx.c.ctx, ptr) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ResultBool sets the result of the function to a bool. | ||||||
|  | // SQLite does not have a separate boolean storage class. | ||||||
|  | // Instead, boolean values are stored as integers 0 (false) and 1 (true). | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/result_blob.html | ||||||
|  | func (ctx Context) ResultBool(value bool) { | ||||||
|  | 	var i int64 | ||||||
|  | 	if value { | ||||||
|  | 		i = 1 | ||||||
|  | 	} | ||||||
|  | 	ctx.ResultInt64(i) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ResultInt sets the result of the function to an int. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/result_blob.html | ||||||
|  | func (ctx Context) ResultInt(value int) { | ||||||
|  | 	ctx.ResultInt64(int64(value)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ResultInt64 sets the result of the function to an int64. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/result_blob.html | ||||||
|  | func (ctx Context) ResultInt64(value int64) { | ||||||
|  | 	ctx.c.call("sqlite3_result_int64", | ||||||
|  | 		uint64(ctx.handle), uint64(value)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ResultFloat sets the result of the function to a float64. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/result_blob.html | ||||||
|  | func (ctx Context) ResultFloat(value float64) { | ||||||
|  | 	ctx.c.call("sqlite3_result_double", | ||||||
|  | 		uint64(ctx.handle), math.Float64bits(value)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ResultText sets the result of the function to a string. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/result_blob.html | ||||||
|  | func (ctx Context) ResultText(value string) { | ||||||
|  | 	ptr := ctx.c.newString(value) | ||||||
|  | 	ctx.c.call("sqlite3_result_text64", | ||||||
|  | 		uint64(ctx.handle), uint64(ptr), uint64(len(value)), | ||||||
|  | 		uint64(ctx.c.freer), _UTF8) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ResultRawText sets the text result of the function to a []byte. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/result_blob.html | ||||||
|  | func (ctx Context) ResultRawText(value []byte) { | ||||||
|  | 	ptr := ctx.c.newBytes(value) | ||||||
|  | 	ctx.c.call("sqlite3_result_text64", | ||||||
|  | 		uint64(ctx.handle), uint64(ptr), uint64(len(value)), | ||||||
|  | 		uint64(ctx.c.freer), _UTF8) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ResultBlob sets the result of the function to a []byte. | ||||||
|  | // Returning a nil slice is the same as calling [Context.ResultNull]. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/result_blob.html | ||||||
|  | func (ctx Context) ResultBlob(value []byte) { | ||||||
|  | 	ptr := ctx.c.newBytes(value) | ||||||
|  | 	ctx.c.call("sqlite3_result_blob64", | ||||||
|  | 		uint64(ctx.handle), uint64(ptr), uint64(len(value)), | ||||||
|  | 		uint64(ctx.c.freer)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ResultZeroBlob sets the result of the function to a zero-filled, length n BLOB. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/result_blob.html | ||||||
|  | func (ctx Context) ResultZeroBlob(n int64) { | ||||||
|  | 	ctx.c.call("sqlite3_result_zeroblob64", | ||||||
|  | 		uint64(ctx.handle), uint64(n)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ResultNull sets the result of the function to NULL. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/result_blob.html | ||||||
|  | func (ctx Context) ResultNull() { | ||||||
|  | 	ctx.c.call("sqlite3_result_null", | ||||||
|  | 		uint64(ctx.handle)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ResultTime sets the result of the function to a [time.Time]. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/result_blob.html | ||||||
|  | func (ctx Context) ResultTime(value time.Time, format TimeFormat) { | ||||||
|  | 	if format == TimeFormatDefault { | ||||||
|  | 		ctx.resultRFC3339Nano(value) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	switch v := format.Encode(value).(type) { | ||||||
|  | 	case string: | ||||||
|  | 		ctx.ResultText(v) | ||||||
|  | 	case int64: | ||||||
|  | 		ctx.ResultInt64(v) | ||||||
|  | 	case float64: | ||||||
|  | 		ctx.ResultFloat(v) | ||||||
|  | 	default: | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ctx Context) resultRFC3339Nano(value time.Time) { | ||||||
|  | 	const maxlen = uint64(len(time.RFC3339Nano)) + 5 | ||||||
|  | 
 | ||||||
|  | 	ptr := ctx.c.new(maxlen) | ||||||
|  | 	buf := util.View(ctx.c.mod, ptr, maxlen) | ||||||
|  | 	buf = value.AppendFormat(buf[:0], time.RFC3339Nano) | ||||||
|  | 
 | ||||||
|  | 	ctx.c.call("sqlite3_result_text64", | ||||||
|  | 		uint64(ctx.handle), uint64(ptr), uint64(len(buf)), | ||||||
|  | 		uint64(ctx.c.freer), _UTF8) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ResultPointer sets the result of the function to NULL, just like [Context.ResultNull], | ||||||
|  | // except that it also associates ptr with that NULL value such that it can be retrieved | ||||||
|  | // within an application-defined SQL function using [Value.Pointer]. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/result_blob.html | ||||||
|  | func (ctx Context) ResultPointer(ptr any) { | ||||||
|  | 	valPtr := util.AddHandle(ctx.c.ctx, ptr) | ||||||
|  | 	ctx.c.call("sqlite3_result_pointer_go", uint64(valPtr)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ResultJSON sets the result of the function to the JSON encoding of value. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/result_blob.html | ||||||
|  | func (ctx Context) ResultJSON(value any) { | ||||||
|  | 	data, err := json.Marshal(value) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.ResultError(err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	ctx.ResultRawText(data) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ResultValue sets the result of the function to a copy of [Value]. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/result_blob.html | ||||||
|  | func (ctx Context) ResultValue(value Value) { | ||||||
|  | 	if value.c != ctx.c { | ||||||
|  | 		ctx.ResultError(MISUSE) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	ctx.c.call("sqlite3_result_value", | ||||||
|  | 		uint64(ctx.handle), uint64(value.handle)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ResultError sets the result of the function an error. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/result_blob.html | ||||||
|  | func (ctx Context) ResultError(err error) { | ||||||
|  | 	if errors.Is(err, NOMEM) { | ||||||
|  | 		ctx.c.call("sqlite3_result_error_nomem", uint64(ctx.handle)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if errors.Is(err, TOOBIG) { | ||||||
|  | 		ctx.c.call("sqlite3_result_error_toobig", uint64(ctx.handle)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	msg, code := errorCode(err, _OK) | ||||||
|  | 	if msg != "" { | ||||||
|  | 		defer ctx.c.arena.mark()() | ||||||
|  | 		ptr := ctx.c.arena.string(msg) | ||||||
|  | 		ctx.c.call("sqlite3_result_error", | ||||||
|  | 			uint64(ctx.handle), uint64(ptr), uint64(len(msg))) | ||||||
|  | 	} | ||||||
|  | 	if code != _OK { | ||||||
|  | 		ctx.c.call("sqlite3_result_error_code", | ||||||
|  | 			uint64(ctx.handle), uint64(code)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // VTabNoChange may return true if a column is being fetched as part | ||||||
|  | // of an update during which the column value will not change. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/vtab_nochange.html | ||||||
|  | func (ctx Context) VTabNoChange() bool { | ||||||
|  | 	r := ctx.c.call("sqlite3_vtab_nochange", uint64(ctx.handle)) | ||||||
|  | 	return r != 0 | ||||||
|  | } | ||||||
							
								
								
									
										579
									
								
								vendor/github.com/ncruces/go-sqlite3/driver/driver.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										579
									
								
								vendor/github.com/ncruces/go-sqlite3/driver/driver.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,579 @@ | ||||||
|  | // Package driver provides a database/sql driver for SQLite. | ||||||
|  | // | ||||||
|  | // Importing package driver registers a [database/sql] driver named "sqlite3". | ||||||
|  | // You may also need to import package embed. | ||||||
|  | // | ||||||
|  | //	import _ "github.com/ncruces/go-sqlite3/driver" | ||||||
|  | //	import _ "github.com/ncruces/go-sqlite3/embed" | ||||||
|  | // | ||||||
|  | // The data source name for "sqlite3" databases can be a filename or a "file:" [URI]. | ||||||
|  | // | ||||||
|  | // The [TRANSACTION] mode can be specified using "_txlock": | ||||||
|  | // | ||||||
|  | //	sql.Open("sqlite3", "file:demo.db?_txlock=immediate") | ||||||
|  | // | ||||||
|  | // Possible values are: "deferred", "immediate", "exclusive". | ||||||
|  | // A [read-only] transaction is always "deferred", regardless of "_txlock". | ||||||
|  | // | ||||||
|  | // The time encoding/decoding format can be specified using "_timefmt": | ||||||
|  | // | ||||||
|  | //	sql.Open("sqlite3", "file:demo.db?_timefmt=sqlite") | ||||||
|  | // | ||||||
|  | // Possible values are: "auto" (the default), "sqlite", "rfc3339"; | ||||||
|  | // "auto" encodes as RFC 3339 and decodes any [format] supported by SQLite; | ||||||
|  | // "sqlite" encodes as SQLite and decodes any [format] supported by SQLite; | ||||||
|  | // "rfc3339" encodes and decodes RFC 3339 only. | ||||||
|  | // | ||||||
|  | // [PRAGMA] statements can be specified using "_pragma": | ||||||
|  | // | ||||||
|  | //	sql.Open("sqlite3", "file:demo.db?_pragma=busy_timeout(10000)") | ||||||
|  | // | ||||||
|  | // If no PRAGMAs are specified, a busy timeout of 1 minute is set. | ||||||
|  | // | ||||||
|  | // Order matters: | ||||||
|  | // busy timeout and locking mode should be the first PRAGMAs set, in that order. | ||||||
|  | // | ||||||
|  | // [URI]: https://sqlite.org/uri.html | ||||||
|  | // [PRAGMA]: https://sqlite.org/pragma.html | ||||||
|  | // [format]: https://sqlite.org/lang_datefunc.html#time_values | ||||||
|  | // [TRANSACTION]: https://sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions | ||||||
|  | // [read-only]: https://pkg.go.dev/database/sql#TxOptions | ||||||
|  | package driver | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"database/sql" | ||||||
|  | 	"database/sql/driver" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"net/url" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | 	"unsafe" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3" | ||||||
|  | 	"github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // This variable can be replaced with -ldflags: | ||||||
|  | // | ||||||
|  | //	go build -ldflags="-X github.com/ncruces/go-sqlite3/driver.driverName=sqlite" | ||||||
|  | var driverName = "sqlite3" | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	if driverName != "" { | ||||||
|  | 		sql.Register(driverName, &SQLite{}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Open opens the SQLite database specified by dataSourceName as a [database/sql.DB]. | ||||||
|  | // | ||||||
|  | // The init function is called by the driver on new connections. | ||||||
|  | // The [sqlite3.Conn] can be used to execute queries, register functions, etc. | ||||||
|  | // Any error returned closes the connection and is returned to [database/sql]. | ||||||
|  | func Open(dataSourceName string, init func(*sqlite3.Conn) error) (*sql.DB, error) { | ||||||
|  | 	c, err := (&SQLite{Init: init}).OpenConnector(dataSourceName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return sql.OpenDB(c), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SQLite implements [database/sql/driver.Driver]. | ||||||
|  | type SQLite struct { | ||||||
|  | 	// Init function is called by the driver on new connections. | ||||||
|  | 	// The [sqlite3.Conn] can be used to execute queries, register functions, etc. | ||||||
|  | 	// Any error returned closes the connection and is returned to [database/sql]. | ||||||
|  | 	Init func(*sqlite3.Conn) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Open implements [database/sql/driver.Driver]. | ||||||
|  | func (d *SQLite) Open(name string) (driver.Conn, error) { | ||||||
|  | 	c, err := d.newConnector(name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return c.Connect(context.Background()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // OpenConnector implements [database/sql/driver.DriverContext]. | ||||||
|  | func (d *SQLite) OpenConnector(name string) (driver.Connector, error) { | ||||||
|  | 	return d.newConnector(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (d *SQLite) newConnector(name string) (*connector, error) { | ||||||
|  | 	c := connector{driver: d, name: name} | ||||||
|  | 
 | ||||||
|  | 	var txlock, timefmt string | ||||||
|  | 	if strings.HasPrefix(name, "file:") { | ||||||
|  | 		if _, after, ok := strings.Cut(name, "?"); ok { | ||||||
|  | 			query, err := url.ParseQuery(after) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			txlock = query.Get("_txlock") | ||||||
|  | 			timefmt = query.Get("_timefmt") | ||||||
|  | 			c.pragmas = query.Has("_pragma") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch txlock { | ||||||
|  | 	case "": | ||||||
|  | 		c.txBegin = "BEGIN" | ||||||
|  | 	case "deferred", "immediate", "exclusive": | ||||||
|  | 		c.txBegin = "BEGIN " + txlock | ||||||
|  | 	default: | ||||||
|  | 		return nil, fmt.Errorf("sqlite3: invalid _txlock: %s", txlock) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch timefmt { | ||||||
|  | 	case "": | ||||||
|  | 		c.tmRead = sqlite3.TimeFormatAuto | ||||||
|  | 		c.tmWrite = sqlite3.TimeFormatDefault | ||||||
|  | 	case "sqlite": | ||||||
|  | 		c.tmRead = sqlite3.TimeFormatAuto | ||||||
|  | 		c.tmWrite = sqlite3.TimeFormat3 | ||||||
|  | 	case "rfc3339": | ||||||
|  | 		c.tmRead = sqlite3.TimeFormatDefault | ||||||
|  | 		c.tmWrite = sqlite3.TimeFormatDefault | ||||||
|  | 	default: | ||||||
|  | 		c.tmRead = sqlite3.TimeFormat(timefmt) | ||||||
|  | 		c.tmWrite = sqlite3.TimeFormat(timefmt) | ||||||
|  | 	} | ||||||
|  | 	return &c, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type connector struct { | ||||||
|  | 	driver  *SQLite | ||||||
|  | 	name    string | ||||||
|  | 	txBegin string | ||||||
|  | 	tmRead  sqlite3.TimeFormat | ||||||
|  | 	tmWrite sqlite3.TimeFormat | ||||||
|  | 	pragmas bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *connector) Driver() driver.Driver { | ||||||
|  | 	return n.driver | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) { | ||||||
|  | 	c := &conn{ | ||||||
|  | 		txBegin: n.txBegin, | ||||||
|  | 		tmRead:  n.tmRead, | ||||||
|  | 		tmWrite: n.tmWrite, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	c.Conn, err = sqlite3.Open(n.name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	defer func() { | ||||||
|  | 		if err != nil { | ||||||
|  | 			c.Close() | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	old := c.Conn.SetInterrupt(ctx) | ||||||
|  | 	defer c.Conn.SetInterrupt(old) | ||||||
|  | 
 | ||||||
|  | 	if !n.pragmas { | ||||||
|  | 		err = c.Conn.BusyTimeout(60 * time.Second) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if n.driver.Init != nil { | ||||||
|  | 		err = n.driver.Init(c.Conn) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if n.pragmas || n.driver.Init != nil { | ||||||
|  | 		s, _, err := c.Conn.Prepare(`PRAGMA query_only`) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		if s.Step() && s.ColumnBool(0) { | ||||||
|  | 			c.readOnly = '1' | ||||||
|  | 		} else { | ||||||
|  | 			c.readOnly = '0' | ||||||
|  | 		} | ||||||
|  | 		err = s.Close() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return c, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type conn struct { | ||||||
|  | 	*sqlite3.Conn | ||||||
|  | 	txBegin    string | ||||||
|  | 	txCommit   string | ||||||
|  | 	txRollback string | ||||||
|  | 	tmRead     sqlite3.TimeFormat | ||||||
|  | 	tmWrite    sqlite3.TimeFormat | ||||||
|  | 	readOnly   byte | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	// Ensure these interfaces are implemented: | ||||||
|  | 	_ driver.ConnPrepareContext = &conn{} | ||||||
|  | 	_ driver.ExecerContext      = &conn{} | ||||||
|  | 	_ driver.ConnBeginTx        = &conn{} | ||||||
|  | 	_ sqlite3.DriverConn        = &conn{} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (c *conn) Raw() *sqlite3.Conn { | ||||||
|  | 	return c.Conn | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *conn) Begin() (driver.Tx, error) { | ||||||
|  | 	return c.BeginTx(context.Background(), driver.TxOptions{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { | ||||||
|  | 	txBegin := c.txBegin | ||||||
|  | 	c.txCommit = `COMMIT` | ||||||
|  | 	c.txRollback = `ROLLBACK` | ||||||
|  | 
 | ||||||
|  | 	if opts.ReadOnly { | ||||||
|  | 		txBegin = ` | ||||||
|  | 			BEGIN deferred; | ||||||
|  | 			PRAGMA query_only=on` | ||||||
|  | 		c.txRollback = ` | ||||||
|  | 			ROLLBACK; | ||||||
|  | 			PRAGMA query_only=` + string(c.readOnly) | ||||||
|  | 		c.txCommit = c.txRollback | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch opts.Isolation { | ||||||
|  | 	default: | ||||||
|  | 		return nil, util.IsolationErr | ||||||
|  | 	case | ||||||
|  | 		driver.IsolationLevel(sql.LevelDefault), | ||||||
|  | 		driver.IsolationLevel(sql.LevelSerializable): | ||||||
|  | 		break | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	old := c.Conn.SetInterrupt(ctx) | ||||||
|  | 	defer c.Conn.SetInterrupt(old) | ||||||
|  | 
 | ||||||
|  | 	err := c.Conn.Exec(txBegin) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return c, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *conn) Commit() error { | ||||||
|  | 	err := c.Conn.Exec(c.txCommit) | ||||||
|  | 	if err != nil && !c.Conn.GetAutocommit() { | ||||||
|  | 		c.Rollback() | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *conn) Rollback() error { | ||||||
|  | 	err := c.Conn.Exec(c.txRollback) | ||||||
|  | 	if errors.Is(err, sqlite3.INTERRUPT) { | ||||||
|  | 		old := c.Conn.SetInterrupt(context.Background()) | ||||||
|  | 		defer c.Conn.SetInterrupt(old) | ||||||
|  | 		err = c.Conn.Exec(c.txRollback) | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *conn) Prepare(query string) (driver.Stmt, error) { | ||||||
|  | 	return c.PrepareContext(context.Background(), query) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { | ||||||
|  | 	old := c.Conn.SetInterrupt(ctx) | ||||||
|  | 	defer c.Conn.SetInterrupt(old) | ||||||
|  | 
 | ||||||
|  | 	s, tail, err := c.Conn.Prepare(query) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if tail != "" { | ||||||
|  | 		s.Close() | ||||||
|  | 		return nil, util.TailErr | ||||||
|  | 	} | ||||||
|  | 	return &stmt{Stmt: s, tmRead: c.tmRead, tmWrite: c.tmWrite}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { | ||||||
|  | 	if len(args) != 0 { | ||||||
|  | 		// Slow path. | ||||||
|  | 		return nil, driver.ErrSkip | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if savept, ok := ctx.(*saveptCtx); ok { | ||||||
|  | 		// Called from driver.Savepoint. | ||||||
|  | 		savept.Savepoint = c.Conn.Savepoint() | ||||||
|  | 		return resultRowsAffected(0), nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	old := c.Conn.SetInterrupt(ctx) | ||||||
|  | 	defer c.Conn.SetInterrupt(old) | ||||||
|  | 
 | ||||||
|  | 	err := c.Conn.Exec(query) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return newResult(c.Conn), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *conn) CheckNamedValue(arg *driver.NamedValue) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type stmt struct { | ||||||
|  | 	*sqlite3.Stmt | ||||||
|  | 	tmWrite sqlite3.TimeFormat | ||||||
|  | 	tmRead  sqlite3.TimeFormat | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	// Ensure these interfaces are implemented: | ||||||
|  | 	_ driver.StmtExecContext   = &stmt{} | ||||||
|  | 	_ driver.StmtQueryContext  = &stmt{} | ||||||
|  | 	_ driver.NamedValueChecker = &stmt{} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (s *stmt) NumInput() int { | ||||||
|  | 	n := s.Stmt.BindCount() | ||||||
|  | 	for i := 1; i <= n; i++ { | ||||||
|  | 		if s.Stmt.BindName(i) != "" { | ||||||
|  | 			return -1 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return n | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Deprecated: use ExecContext instead. | ||||||
|  | func (s *stmt) Exec(args []driver.Value) (driver.Result, error) { | ||||||
|  | 	return s.ExecContext(context.Background(), namedValues(args)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Deprecated: use QueryContext instead. | ||||||
|  | func (s *stmt) Query(args []driver.Value) (driver.Rows, error) { | ||||||
|  | 	return s.QueryContext(context.Background(), namedValues(args)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { | ||||||
|  | 	err := s.setupBindings(args) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	old := s.Stmt.Conn().SetInterrupt(ctx) | ||||||
|  | 	defer s.Stmt.Conn().SetInterrupt(old) | ||||||
|  | 
 | ||||||
|  | 	err = s.Stmt.Exec() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return newResult(s.Stmt.Conn()), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { | ||||||
|  | 	err := s.setupBindings(args) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &rows{ctx: ctx, stmt: s}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *stmt) setupBindings(args []driver.NamedValue) error { | ||||||
|  | 	err := s.Stmt.ClearBindings() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var ids [3]int | ||||||
|  | 	for _, arg := range args { | ||||||
|  | 		ids := ids[:0] | ||||||
|  | 		if arg.Name == "" { | ||||||
|  | 			ids = append(ids, arg.Ordinal) | ||||||
|  | 		} else { | ||||||
|  | 			for _, prefix := range []string{":", "@", "$"} { | ||||||
|  | 				if id := s.Stmt.BindIndex(prefix + arg.Name); id != 0 { | ||||||
|  | 					ids = append(ids, id) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for _, id := range ids { | ||||||
|  | 			switch a := arg.Value.(type) { | ||||||
|  | 			case bool: | ||||||
|  | 				err = s.Stmt.BindBool(id, a) | ||||||
|  | 			case int: | ||||||
|  | 				err = s.Stmt.BindInt(id, a) | ||||||
|  | 			case int64: | ||||||
|  | 				err = s.Stmt.BindInt64(id, a) | ||||||
|  | 			case float64: | ||||||
|  | 				err = s.Stmt.BindFloat(id, a) | ||||||
|  | 			case string: | ||||||
|  | 				err = s.Stmt.BindText(id, a) | ||||||
|  | 			case []byte: | ||||||
|  | 				err = s.Stmt.BindBlob(id, a) | ||||||
|  | 			case sqlite3.ZeroBlob: | ||||||
|  | 				err = s.Stmt.BindZeroBlob(id, int64(a)) | ||||||
|  | 			case time.Time: | ||||||
|  | 				err = s.Stmt.BindTime(id, a, s.tmWrite) | ||||||
|  | 			case util.JSON: | ||||||
|  | 				err = s.Stmt.BindJSON(id, a.Value) | ||||||
|  | 			case util.PointerUnwrap: | ||||||
|  | 				err = s.Stmt.BindPointer(id, util.UnwrapPointer(a)) | ||||||
|  | 			case nil: | ||||||
|  | 				err = s.Stmt.BindNull(id) | ||||||
|  | 			default: | ||||||
|  | 				panic(util.AssertErr()) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *stmt) CheckNamedValue(arg *driver.NamedValue) error { | ||||||
|  | 	switch arg.Value.(type) { | ||||||
|  | 	case bool, int, int64, float64, string, []byte, | ||||||
|  | 		time.Time, sqlite3.ZeroBlob, | ||||||
|  | 		util.JSON, util.PointerUnwrap, | ||||||
|  | 		nil: | ||||||
|  | 		return nil | ||||||
|  | 	default: | ||||||
|  | 		return driver.ErrSkip | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newResult(c *sqlite3.Conn) driver.Result { | ||||||
|  | 	rows := c.Changes() | ||||||
|  | 	if rows != 0 { | ||||||
|  | 		id := c.LastInsertRowID() | ||||||
|  | 		if id != 0 { | ||||||
|  | 			return result{id, rows} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return resultRowsAffected(rows) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type result struct{ lastInsertId, rowsAffected int64 } | ||||||
|  | 
 | ||||||
|  | func (r result) LastInsertId() (int64, error) { | ||||||
|  | 	return r.lastInsertId, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r result) RowsAffected() (int64, error) { | ||||||
|  | 	return r.rowsAffected, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type resultRowsAffected int64 | ||||||
|  | 
 | ||||||
|  | func (r resultRowsAffected) LastInsertId() (int64, error) { | ||||||
|  | 	return 0, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r resultRowsAffected) RowsAffected() (int64, error) { | ||||||
|  | 	return int64(r), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type rows struct { | ||||||
|  | 	ctx context.Context | ||||||
|  | 	*stmt | ||||||
|  | 	names []string | ||||||
|  | 	types []string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *rows) Close() error { | ||||||
|  | 	r.Stmt.ClearBindings() | ||||||
|  | 	return r.Stmt.Reset() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *rows) Columns() []string { | ||||||
|  | 	if r.names == nil { | ||||||
|  | 		count := r.Stmt.ColumnCount() | ||||||
|  | 		r.names = make([]string, count) | ||||||
|  | 		for i := range r.names { | ||||||
|  | 			r.names[i] = r.Stmt.ColumnName(i) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return r.names | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *rows) declType(index int) string { | ||||||
|  | 	if r.types == nil { | ||||||
|  | 		count := r.Stmt.ColumnCount() | ||||||
|  | 		r.types = make([]string, count) | ||||||
|  | 		for i := range r.types { | ||||||
|  | 			r.types[i] = strings.ToUpper(r.Stmt.ColumnDeclType(i)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return r.types[index] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *rows) ColumnTypeDatabaseTypeName(index int) string { | ||||||
|  | 	decltype := r.declType(index) | ||||||
|  | 	if len := len(decltype); len > 0 && decltype[len-1] == ')' { | ||||||
|  | 		if i := strings.LastIndexByte(decltype, '('); i >= 0 { | ||||||
|  | 			decltype = decltype[:i] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return strings.TrimSpace(decltype) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *rows) Next(dest []driver.Value) error { | ||||||
|  | 	old := r.Stmt.Conn().SetInterrupt(r.ctx) | ||||||
|  | 	defer r.Stmt.Conn().SetInterrupt(old) | ||||||
|  | 
 | ||||||
|  | 	if !r.Stmt.Step() { | ||||||
|  | 		if err := r.Stmt.Err(); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		return io.EOF | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	data := unsafe.Slice((*any)(unsafe.SliceData(dest)), len(dest)) | ||||||
|  | 	err := r.Stmt.Columns(data) | ||||||
|  | 	for i := range dest { | ||||||
|  | 		if t, ok := r.decodeTime(i, dest[i]); ok { | ||||||
|  | 			dest[i] = t | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if s, ok := dest[i].(string); ok { | ||||||
|  | 			t, ok := maybeTime(s) | ||||||
|  | 			if ok { | ||||||
|  | 				dest[i] = t | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *rows) decodeTime(i int, v any) (_ time.Time, _ bool) { | ||||||
|  | 	if r.tmRead == sqlite3.TimeFormatDefault { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	switch r.declType(i) { | ||||||
|  | 	case "DATE", "TIME", "DATETIME", "TIMESTAMP": | ||||||
|  | 		// maybe | ||||||
|  | 	default: | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	switch v.(type) { | ||||||
|  | 	case int64, float64, string: | ||||||
|  | 		// maybe | ||||||
|  | 	default: | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	t, err := r.tmRead.Decode(v) | ||||||
|  | 	return t, err == nil | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								vendor/github.com/ncruces/go-sqlite3/driver/savepoint.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/ncruces/go-sqlite3/driver/savepoint.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | ||||||
|  | package driver | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"database/sql" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Savepoint establishes a new transaction savepoint. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/lang_savepoint.html | ||||||
|  | func Savepoint(tx *sql.Tx) sqlite3.Savepoint { | ||||||
|  | 	var ctx saveptCtx | ||||||
|  | 	tx.ExecContext(&ctx, "") | ||||||
|  | 	return ctx.Savepoint | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type saveptCtx struct{ sqlite3.Savepoint } | ||||||
|  | 
 | ||||||
|  | func (*saveptCtx) Deadline() (deadline time.Time, ok bool) { return } | ||||||
|  | 
 | ||||||
|  | func (*saveptCtx) Done() <-chan struct{} { return nil } | ||||||
|  | 
 | ||||||
|  | func (*saveptCtx) Err() error { return nil } | ||||||
|  | 
 | ||||||
|  | func (*saveptCtx) Value(key any) any { return nil } | ||||||
							
								
								
									
										31
									
								
								vendor/github.com/ncruces/go-sqlite3/driver/time.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								vendor/github.com/ncruces/go-sqlite3/driver/time.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | ||||||
|  | package driver | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Convert a string in [time.RFC3339Nano] format into a [time.Time] | ||||||
|  | // if it roundtrips back to the same string. | ||||||
|  | // This way times can be persisted to, and recovered from, the database, | ||||||
|  | // but if a string is needed, [database/sql] will recover the same string. | ||||||
|  | func maybeTime(text string) (_ time.Time, _ bool) { | ||||||
|  | 	// Weed out (some) values that can't possibly be | ||||||
|  | 	// [time.RFC3339Nano] timestamps. | ||||||
|  | 	if len(text) < len("2006-01-02T15:04:05Z") { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if len(text) > len(time.RFC3339Nano) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if text[4] != '-' || text[10] != 'T' || text[16] != ':' { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Slow path. | ||||||
|  | 	var buf [len(time.RFC3339Nano)]byte | ||||||
|  | 	date, err := time.Parse(time.RFC3339Nano, text) | ||||||
|  | 	if err == nil && text == string(date.AppendFormat(buf[:0], time.RFC3339Nano)) { | ||||||
|  | 		return date, true | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								vendor/github.com/ncruces/go-sqlite3/driver/util.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								vendor/github.com/ncruces/go-sqlite3/driver/util.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | package driver | ||||||
|  | 
 | ||||||
|  | import "database/sql/driver" | ||||||
|  | 
 | ||||||
|  | func namedValues(args []driver.Value) []driver.NamedValue { | ||||||
|  | 	named := make([]driver.NamedValue, len(args)) | ||||||
|  | 	for i, v := range args { | ||||||
|  | 		named[i] = driver.NamedValue{ | ||||||
|  | 			Ordinal: i + 1, | ||||||
|  | 			Value:   v, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return named | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								vendor/github.com/ncruces/go-sqlite3/embed/README.md
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/ncruces/go-sqlite3/embed/README.md
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | ||||||
|  | # Embeddable Wasm build of SQLite | ||||||
|  | 
 | ||||||
|  | This folder includes an embeddable Wasm build of SQLite 3.46.0 for use with | ||||||
|  | [`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3). | ||||||
|  | 
 | ||||||
|  | The following optional features are compiled in: | ||||||
|  | - [math functions](https://sqlite.org/lang_mathfunc.html) | ||||||
|  | - [FTS5](https://sqlite.org/fts5.html) | ||||||
|  | - [JSON](https://sqlite.org/json1.html) | ||||||
|  | - [R*Tree](https://sqlite.org/rtree.html) | ||||||
|  | - [GeoPoly](https://sqlite.org/geopoly.html) | ||||||
|  | - [soundex](https://sqlite.org/lang_corefunc.html#soundex) | ||||||
|  | - [stat4](https://sqlite.org/compile.html#enable_stat4) | ||||||
|  | - [base64](https://github.com/sqlite/sqlite/blob/master/ext/misc/base64.c) | ||||||
|  | - [decimal](https://github.com/sqlite/sqlite/blob/master/ext/misc/decimal.c) | ||||||
|  | - [ieee754](https://github.com/sqlite/sqlite/blob/master/ext/misc/ieee754.c) | ||||||
|  | - [regexp](https://github.com/sqlite/sqlite/blob/master/ext/misc/regexp.c) | ||||||
|  | - [series](https://github.com/sqlite/sqlite/blob/master/ext/misc/series.c) | ||||||
|  | - [uint](https://github.com/sqlite/sqlite/blob/master/ext/misc/uint.c) | ||||||
|  | - [uuid](https://github.com/sqlite/sqlite/blob/master/ext/misc/uuid.c) | ||||||
|  | - [time](../sqlite3/time.c) | ||||||
|  | 
 | ||||||
|  | See the [configuration options](../sqlite3/sqlite_cfg.h), | ||||||
|  | and [patches](../sqlite3) applied. | ||||||
|  | 
 | ||||||
|  | Built using [`wasi-sdk`](https://github.com/WebAssembly/wasi-sdk), | ||||||
|  | and [`binaryen`](https://github.com/WebAssembly/binaryen). | ||||||
							
								
								
									
										32
									
								
								vendor/github.com/ncruces/go-sqlite3/embed/build.sh
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								vendor/github.com/ncruces/go-sqlite3/embed/build.sh
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | ||||||
|  | #!/usr/bin/env bash | ||||||
|  | set -euo pipefail | ||||||
|  | 
 | ||||||
|  | cd -P -- "$(dirname -- "$0")" | ||||||
|  | 
 | ||||||
|  | ROOT=../ | ||||||
|  | BINARYEN="$ROOT/tools/binaryen-version_117/bin" | ||||||
|  | WASI_SDK="$ROOT/tools/wasi-sdk-22.0/bin" | ||||||
|  | 
 | ||||||
|  | "$WASI_SDK/clang" --target=wasm32-wasi -std=c17 -flto -g0 -O2 \ | ||||||
|  | 	-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \ | ||||||
|  | 	-o sqlite3.wasm "$ROOT/sqlite3/main.c" \ | ||||||
|  | 	-I"$ROOT/sqlite3" \ | ||||||
|  | 	-mexec-model=reactor \ | ||||||
|  | 	-msimd128 -mmutable-globals \ | ||||||
|  | 	-mbulk-memory -mreference-types \ | ||||||
|  | 	-mnontrapping-fptoint -msign-ext \ | ||||||
|  | 	-fno-stack-protector -fno-stack-clash-protection \ | ||||||
|  | 	-Wl,--initial-memory=327680 \ | ||||||
|  | 	-Wl,--stack-first \ | ||||||
|  | 	-Wl,--import-undefined \ | ||||||
|  | 	-D_HAVE_SQLITE_CONFIG_H \ | ||||||
|  | 	-DSQLITE_CUSTOM_INCLUDE=sqlite_opt.h \ | ||||||
|  | 	$(awk '{print "-Wl,--export="$0}' exports.txt) | ||||||
|  | 
 | ||||||
|  | trap 'rm -f sqlite3.tmp' EXIT | ||||||
|  | "$BINARYEN/wasm-ctor-eval" -g -c _initialize sqlite3.wasm -o sqlite3.tmp | ||||||
|  | "$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \ | ||||||
|  | 	sqlite3.tmp -o sqlite3.wasm \ | ||||||
|  | 	--enable-simd --enable-mutable-globals --enable-multivalue \ | ||||||
|  | 	--enable-bulk-memory --enable-reference-types \ | ||||||
|  | 	--enable-nontrapping-float-to-int --enable-sign-ext | ||||||
							
								
								
									
										130
									
								
								vendor/github.com/ncruces/go-sqlite3/embed/exports.txt
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								vendor/github.com/ncruces/go-sqlite3/embed/exports.txt
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,130 @@ | ||||||
|  | aligned_alloc | ||||||
|  | free | ||||||
|  | malloc | ||||||
|  | malloc_destructor | ||||||
|  | sqlite3_anycollseq_init | ||||||
|  | sqlite3_autovacuum_pages_go | ||||||
|  | sqlite3_backup_finish | ||||||
|  | sqlite3_backup_init | ||||||
|  | sqlite3_backup_pagecount | ||||||
|  | sqlite3_backup_remaining | ||||||
|  | sqlite3_backup_step | ||||||
|  | sqlite3_bind_blob64 | ||||||
|  | sqlite3_bind_double | ||||||
|  | sqlite3_bind_int64 | ||||||
|  | sqlite3_bind_null | ||||||
|  | sqlite3_bind_parameter_count | ||||||
|  | sqlite3_bind_parameter_index | ||||||
|  | sqlite3_bind_parameter_name | ||||||
|  | sqlite3_bind_pointer_go | ||||||
|  | sqlite3_bind_text64 | ||||||
|  | sqlite3_bind_value | ||||||
|  | sqlite3_bind_zeroblob64 | ||||||
|  | sqlite3_blob_bytes | ||||||
|  | sqlite3_blob_close | ||||||
|  | sqlite3_blob_open | ||||||
|  | sqlite3_blob_read | ||||||
|  | sqlite3_blob_reopen | ||||||
|  | sqlite3_blob_write | ||||||
|  | sqlite3_busy_handler_go | ||||||
|  | sqlite3_busy_timeout | ||||||
|  | sqlite3_changes64 | ||||||
|  | sqlite3_clear_bindings | ||||||
|  | sqlite3_close | ||||||
|  | sqlite3_close_v2 | ||||||
|  | sqlite3_collation_needed_go | ||||||
|  | sqlite3_column_blob | ||||||
|  | sqlite3_column_bytes | ||||||
|  | sqlite3_column_count | ||||||
|  | sqlite3_column_database_name | ||||||
|  | sqlite3_column_decltype | ||||||
|  | sqlite3_column_double | ||||||
|  | sqlite3_column_int64 | ||||||
|  | sqlite3_column_name | ||||||
|  | sqlite3_column_origin_name | ||||||
|  | sqlite3_column_table_name | ||||||
|  | sqlite3_column_text | ||||||
|  | sqlite3_column_type | ||||||
|  | sqlite3_column_value | ||||||
|  | sqlite3_columns_go | ||||||
|  | sqlite3_commit_hook_go | ||||||
|  | sqlite3_config_log_go | ||||||
|  | sqlite3_create_aggregate_function_go | ||||||
|  | sqlite3_create_collation_go | ||||||
|  | sqlite3_create_function_go | ||||||
|  | sqlite3_create_module_go | ||||||
|  | sqlite3_create_window_function_go | ||||||
|  | sqlite3_database_file_object | ||||||
|  | sqlite3_db_config | ||||||
|  | sqlite3_db_filename | ||||||
|  | sqlite3_db_name | ||||||
|  | sqlite3_db_readonly | ||||||
|  | sqlite3_db_release_memory | ||||||
|  | sqlite3_declare_vtab | ||||||
|  | sqlite3_errcode | ||||||
|  | sqlite3_errmsg | ||||||
|  | sqlite3_error_offset | ||||||
|  | sqlite3_errstr | ||||||
|  | sqlite3_exec | ||||||
|  | sqlite3_filename_database | ||||||
|  | sqlite3_filename_journal | ||||||
|  | sqlite3_filename_wal | ||||||
|  | sqlite3_finalize | ||||||
|  | sqlite3_get_autocommit | ||||||
|  | sqlite3_get_auxdata | ||||||
|  | sqlite3_interrupt | ||||||
|  | sqlite3_last_insert_rowid | ||||||
|  | sqlite3_limit | ||||||
|  | sqlite3_open_v2 | ||||||
|  | sqlite3_overload_function | ||||||
|  | sqlite3_prepare_v3 | ||||||
|  | sqlite3_progress_handler_go | ||||||
|  | sqlite3_reset | ||||||
|  | sqlite3_result_blob64 | ||||||
|  | sqlite3_result_double | ||||||
|  | sqlite3_result_error | ||||||
|  | sqlite3_result_error_code | ||||||
|  | sqlite3_result_error_nomem | ||||||
|  | sqlite3_result_error_toobig | ||||||
|  | sqlite3_result_int64 | ||||||
|  | sqlite3_result_null | ||||||
|  | sqlite3_result_pointer_go | ||||||
|  | sqlite3_result_text64 | ||||||
|  | sqlite3_result_value | ||||||
|  | sqlite3_result_zeroblob64 | ||||||
|  | sqlite3_rollback_hook_go | ||||||
|  | sqlite3_set_authorizer_go | ||||||
|  | sqlite3_set_auxdata_go | ||||||
|  | sqlite3_set_last_insert_rowid | ||||||
|  | sqlite3_step | ||||||
|  | sqlite3_stmt_busy | ||||||
|  | sqlite3_stmt_readonly | ||||||
|  | sqlite3_stmt_status | ||||||
|  | sqlite3_total_changes64 | ||||||
|  | sqlite3_txn_state | ||||||
|  | sqlite3_update_hook_go | ||||||
|  | sqlite3_uri_key | ||||||
|  | sqlite3_uri_parameter | ||||||
|  | sqlite3_value_blob | ||||||
|  | sqlite3_value_bytes | ||||||
|  | sqlite3_value_double | ||||||
|  | sqlite3_value_dup | ||||||
|  | sqlite3_value_free | ||||||
|  | sqlite3_value_int64 | ||||||
|  | sqlite3_value_nochange | ||||||
|  | sqlite3_value_numeric_type | ||||||
|  | sqlite3_value_pointer_go | ||||||
|  | sqlite3_value_text | ||||||
|  | sqlite3_value_type | ||||||
|  | sqlite3_vtab_collation | ||||||
|  | sqlite3_vtab_config_go | ||||||
|  | sqlite3_vtab_distinct | ||||||
|  | sqlite3_vtab_in | ||||||
|  | sqlite3_vtab_in_first | ||||||
|  | sqlite3_vtab_in_next | ||||||
|  | sqlite3_vtab_nochange | ||||||
|  | sqlite3_vtab_on_conflict | ||||||
|  | sqlite3_vtab_rhs_value | ||||||
|  | sqlite3_wal_autocheckpoint | ||||||
|  | sqlite3_wal_checkpoint_v2 | ||||||
|  | sqlite3_wal_hook_go | ||||||
							
								
								
									
										20
									
								
								vendor/github.com/ncruces/go-sqlite3/embed/init.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/ncruces/go-sqlite3/embed/init.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | // Package embed embeds SQLite into your application. | ||||||
|  | // | ||||||
|  | // Importing package embed initializes the [sqlite3.Binary] variable | ||||||
|  | // with an appropriate build of SQLite: | ||||||
|  | // | ||||||
|  | //	import _ "github.com/ncruces/go-sqlite3/embed" | ||||||
|  | package embed | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	_ "embed" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | //go:embed sqlite3.wasm | ||||||
|  | var binary []byte | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	sqlite3.Binary = binary | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										162
									
								
								vendor/github.com/ncruces/go-sqlite3/error.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								vendor/github.com/ncruces/go-sqlite3/error.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,162 @@ | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Error wraps an SQLite Error Code. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/errcode.html | ||||||
|  | type Error struct { | ||||||
|  | 	str  string | ||||||
|  | 	msg  string | ||||||
|  | 	sql  string | ||||||
|  | 	code uint64 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Code returns the primary error code for this error. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/rescode.html | ||||||
|  | func (e *Error) Code() ErrorCode { | ||||||
|  | 	return ErrorCode(e.code) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ExtendedCode returns the extended error code for this error. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/rescode.html | ||||||
|  | func (e *Error) ExtendedCode() ExtendedErrorCode { | ||||||
|  | 	return ExtendedErrorCode(e.code) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Error implements the error interface. | ||||||
|  | func (e *Error) Error() string { | ||||||
|  | 	var b strings.Builder | ||||||
|  | 	b.WriteString("sqlite3: ") | ||||||
|  | 
 | ||||||
|  | 	if e.str != "" { | ||||||
|  | 		b.WriteString(e.str) | ||||||
|  | 	} else { | ||||||
|  | 		b.WriteString(strconv.Itoa(int(e.code))) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if e.msg != "" { | ||||||
|  | 		b.WriteString(": ") | ||||||
|  | 		b.WriteString(e.msg) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return b.String() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Is tests whether this error matches a given [ErrorCode] or [ExtendedErrorCode]. | ||||||
|  | // | ||||||
|  | // It makes it possible to do: | ||||||
|  | // | ||||||
|  | //	if errors.Is(err, sqlite3.BUSY) { | ||||||
|  | //		// ... handle BUSY | ||||||
|  | //	} | ||||||
|  | func (e *Error) Is(err error) bool { | ||||||
|  | 	switch c := err.(type) { | ||||||
|  | 	case ErrorCode: | ||||||
|  | 		return c == e.Code() | ||||||
|  | 	case ExtendedErrorCode: | ||||||
|  | 		return c == e.ExtendedCode() | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // As converts this error to an [ErrorCode] or [ExtendedErrorCode]. | ||||||
|  | func (e *Error) As(err any) bool { | ||||||
|  | 	switch c := err.(type) { | ||||||
|  | 	case *ErrorCode: | ||||||
|  | 		*c = e.Code() | ||||||
|  | 		return true | ||||||
|  | 	case *ExtendedErrorCode: | ||||||
|  | 		*c = e.ExtendedCode() | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Temporary returns true for [BUSY] errors. | ||||||
|  | func (e *Error) Temporary() bool { | ||||||
|  | 	return e.Code() == BUSY | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Timeout returns true for [BUSY_TIMEOUT] errors. | ||||||
|  | func (e *Error) Timeout() bool { | ||||||
|  | 	return e.ExtendedCode() == BUSY_TIMEOUT | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SQL returns the SQL starting at the token that triggered a syntax error. | ||||||
|  | func (e *Error) SQL() string { | ||||||
|  | 	return e.sql | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Error implements the error interface. | ||||||
|  | func (e ErrorCode) Error() string { | ||||||
|  | 	return util.ErrorCodeString(uint32(e)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Temporary returns true for [BUSY] errors. | ||||||
|  | func (e ErrorCode) Temporary() bool { | ||||||
|  | 	return e == BUSY | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Error implements the error interface. | ||||||
|  | func (e ExtendedErrorCode) Error() string { | ||||||
|  | 	return util.ErrorCodeString(uint32(e)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Is tests whether this error matches a given [ErrorCode]. | ||||||
|  | func (e ExtendedErrorCode) Is(err error) bool { | ||||||
|  | 	c, ok := err.(ErrorCode) | ||||||
|  | 	return ok && c == ErrorCode(e) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // As converts this error to an [ErrorCode]. | ||||||
|  | func (e ExtendedErrorCode) As(err any) bool { | ||||||
|  | 	c, ok := err.(*ErrorCode) | ||||||
|  | 	if ok { | ||||||
|  | 		*c = ErrorCode(e) | ||||||
|  | 	} | ||||||
|  | 	return ok | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Temporary returns true for [BUSY] errors. | ||||||
|  | func (e ExtendedErrorCode) Temporary() bool { | ||||||
|  | 	return ErrorCode(e) == BUSY | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Timeout returns true for [BUSY_TIMEOUT] errors. | ||||||
|  | func (e ExtendedErrorCode) Timeout() bool { | ||||||
|  | 	return e == BUSY_TIMEOUT | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func errorCode(err error, def ErrorCode) (msg string, code uint32) { | ||||||
|  | 	switch code := err.(type) { | ||||||
|  | 	case nil: | ||||||
|  | 		return "", _OK | ||||||
|  | 	case ErrorCode: | ||||||
|  | 		return "", uint32(code) | ||||||
|  | 	case xErrorCode: | ||||||
|  | 		return "", uint32(code) | ||||||
|  | 	case *Error: | ||||||
|  | 		return code.msg, uint32(code.code) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var ecode ErrorCode | ||||||
|  | 	var xcode xErrorCode | ||||||
|  | 	switch { | ||||||
|  | 	case errors.As(err, &xcode): | ||||||
|  | 		code = uint32(xcode) | ||||||
|  | 	case errors.As(err, &ecode): | ||||||
|  | 		code = uint32(ecode) | ||||||
|  | 	default: | ||||||
|  | 		code = uint32(def) | ||||||
|  | 	} | ||||||
|  | 	return err.Error(), code | ||||||
|  | } | ||||||
							
								
								
									
										214
									
								
								vendor/github.com/ncruces/go-sqlite3/func.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								vendor/github.com/ncruces/go-sqlite3/func.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,214 @@ | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | 	"github.com/tetratelabs/wazero/api" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // CollationNeeded registers a callback to be invoked | ||||||
|  | // whenever an unknown collation sequence is required. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/collation_needed.html | ||||||
|  | func (c *Conn) CollationNeeded(cb func(db *Conn, name string)) error { | ||||||
|  | 	var enable uint64 | ||||||
|  | 	if cb != nil { | ||||||
|  | 		enable = 1 | ||||||
|  | 	} | ||||||
|  | 	r := c.call("sqlite3_collation_needed_go", uint64(c.handle), enable) | ||||||
|  | 	if err := c.error(r); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	c.collation = cb | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AnyCollationNeeded uses [Conn.CollationNeeded] to register | ||||||
|  | // a fake collating function for any unknown collating sequence. | ||||||
|  | // The fake collating function works like BINARY. | ||||||
|  | // | ||||||
|  | // This can be used to load schemas that contain | ||||||
|  | // one or more unknown collating sequences. | ||||||
|  | func (c *Conn) AnyCollationNeeded() { | ||||||
|  | 	c.call("sqlite3_anycollseq_init", uint64(c.handle), 0, 0) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CreateCollation defines a new collating sequence. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/create_collation.html | ||||||
|  | func (c *Conn) CreateCollation(name string, fn func(a, b []byte) int) error { | ||||||
|  | 	defer c.arena.mark()() | ||||||
|  | 	namePtr := c.arena.string(name) | ||||||
|  | 	funcPtr := util.AddHandle(c.ctx, fn) | ||||||
|  | 	r := c.call("sqlite3_create_collation_go", | ||||||
|  | 		uint64(c.handle), uint64(namePtr), uint64(funcPtr)) | ||||||
|  | 	return c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CreateFunction defines a new scalar SQL function. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/create_function.html | ||||||
|  | func (c *Conn) CreateFunction(name string, nArg int, flag FunctionFlag, fn ScalarFunction) error { | ||||||
|  | 	defer c.arena.mark()() | ||||||
|  | 	namePtr := c.arena.string(name) | ||||||
|  | 	funcPtr := util.AddHandle(c.ctx, fn) | ||||||
|  | 	r := c.call("sqlite3_create_function_go", | ||||||
|  | 		uint64(c.handle), uint64(namePtr), uint64(nArg), | ||||||
|  | 		uint64(flag), uint64(funcPtr)) | ||||||
|  | 	return c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ScalarFunction is the type of a scalar SQL function. | ||||||
|  | // Implementations must not retain arg. | ||||||
|  | type ScalarFunction func(ctx Context, arg ...Value) | ||||||
|  | 
 | ||||||
|  | // CreateWindowFunction defines a new aggregate or aggregate window SQL function. | ||||||
|  | // If fn returns a [WindowFunction], then an aggregate window function is created. | ||||||
|  | // If fn returns an [io.Closer], it will be called to free resources. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/create_function.html | ||||||
|  | func (c *Conn) CreateWindowFunction(name string, nArg int, flag FunctionFlag, fn func() AggregateFunction) error { | ||||||
|  | 	defer c.arena.mark()() | ||||||
|  | 	call := "sqlite3_create_aggregate_function_go" | ||||||
|  | 	namePtr := c.arena.string(name) | ||||||
|  | 	funcPtr := util.AddHandle(c.ctx, fn) | ||||||
|  | 	if _, ok := fn().(WindowFunction); ok { | ||||||
|  | 		call = "sqlite3_create_window_function_go" | ||||||
|  | 	} | ||||||
|  | 	r := c.call(call, | ||||||
|  | 		uint64(c.handle), uint64(namePtr), uint64(nArg), | ||||||
|  | 		uint64(flag), uint64(funcPtr)) | ||||||
|  | 	return c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // AggregateFunction is the interface an aggregate function should implement. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/appfunc.html | ||||||
|  | type AggregateFunction interface { | ||||||
|  | 	// Step is invoked to add a row to the current window. | ||||||
|  | 	// The function arguments, if any, corresponding to the row being added, are passed to Step. | ||||||
|  | 	// Implementations must not retain arg. | ||||||
|  | 	Step(ctx Context, arg ...Value) | ||||||
|  | 
 | ||||||
|  | 	// Value is invoked to return the current (or final) value of the aggregate. | ||||||
|  | 	Value(ctx Context) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // WindowFunction is the interface an aggregate window function should implement. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/windowfunctions.html | ||||||
|  | type WindowFunction interface { | ||||||
|  | 	AggregateFunction | ||||||
|  | 
 | ||||||
|  | 	// Inverse is invoked to remove the oldest presently aggregated result of Step from the current window. | ||||||
|  | 	// The function arguments, if any, are those passed to Step for the row being removed. | ||||||
|  | 	// Implementations must not retain arg. | ||||||
|  | 	Inverse(ctx Context, arg ...Value) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // OverloadFunction overloads a function for a virtual table. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/overload_function.html | ||||||
|  | func (c *Conn) OverloadFunction(name string, nArg int) error { | ||||||
|  | 	defer c.arena.mark()() | ||||||
|  | 	namePtr := c.arena.string(name) | ||||||
|  | 	r := c.call("sqlite3_overload_function", | ||||||
|  | 		uint64(c.handle), uint64(namePtr), uint64(nArg)) | ||||||
|  | 	return c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func destroyCallback(ctx context.Context, mod api.Module, pApp uint32) { | ||||||
|  | 	util.DelHandle(ctx, pApp) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func collationCallback(ctx context.Context, mod api.Module, pArg, pDB, eTextRep, zName uint32) { | ||||||
|  | 	if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.collation != nil { | ||||||
|  | 		name := util.ReadString(mod, zName, _MAX_NAME) | ||||||
|  | 		c.collation(c, name) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func compareCallback(ctx context.Context, mod api.Module, pApp, nKey1, pKey1, nKey2, pKey2 uint32) uint32 { | ||||||
|  | 	fn := util.GetHandle(ctx, pApp).(func(a, b []byte) int) | ||||||
|  | 	return uint32(fn(util.View(mod, pKey1, uint64(nKey1)), util.View(mod, pKey2, uint64(nKey2)))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func funcCallback(ctx context.Context, mod api.Module, pCtx, pApp, nArg, pArg uint32) { | ||||||
|  | 	args := getFuncArgs() | ||||||
|  | 	defer putFuncArgs(args) | ||||||
|  | 	db := ctx.Value(connKey{}).(*Conn) | ||||||
|  | 	fn := util.GetHandle(db.ctx, pApp).(ScalarFunction) | ||||||
|  | 	callbackArgs(db, args[:nArg], pArg) | ||||||
|  | 	fn(Context{db, pCtx}, args[:nArg]...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func stepCallback(ctx context.Context, mod api.Module, pCtx, pAgg, pApp, nArg, pArg uint32) { | ||||||
|  | 	args := getFuncArgs() | ||||||
|  | 	defer putFuncArgs(args) | ||||||
|  | 	db := ctx.Value(connKey{}).(*Conn) | ||||||
|  | 	callbackArgs(db, args[:nArg], pArg) | ||||||
|  | 	fn, _ := callbackAggregate(db, pAgg, pApp) | ||||||
|  | 	fn.Step(Context{db, pCtx}, args[:nArg]...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func finalCallback(ctx context.Context, mod api.Module, pCtx, pAgg, pApp uint32) { | ||||||
|  | 	db := ctx.Value(connKey{}).(*Conn) | ||||||
|  | 	fn, handle := callbackAggregate(db, pAgg, pApp) | ||||||
|  | 	fn.Value(Context{db, pCtx}) | ||||||
|  | 	util.DelHandle(ctx, handle) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func valueCallback(ctx context.Context, mod api.Module, pCtx, pAgg uint32) { | ||||||
|  | 	db := ctx.Value(connKey{}).(*Conn) | ||||||
|  | 	fn := util.GetHandle(db.ctx, pAgg).(AggregateFunction) | ||||||
|  | 	fn.Value(Context{db, pCtx}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func inverseCallback(ctx context.Context, mod api.Module, pCtx, pAgg, nArg, pArg uint32) { | ||||||
|  | 	args := getFuncArgs() | ||||||
|  | 	defer putFuncArgs(args) | ||||||
|  | 	db := ctx.Value(connKey{}).(*Conn) | ||||||
|  | 	callbackArgs(db, args[:nArg], pArg) | ||||||
|  | 	fn := util.GetHandle(db.ctx, pAgg).(WindowFunction) | ||||||
|  | 	fn.Inverse(Context{db, pCtx}, args[:nArg]...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func callbackAggregate(db *Conn, pAgg, pApp uint32) (AggregateFunction, uint32) { | ||||||
|  | 	if pApp == 0 { | ||||||
|  | 		handle := util.ReadUint32(db.mod, pAgg) | ||||||
|  | 		return util.GetHandle(db.ctx, handle).(AggregateFunction), handle | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// We need to create the aggregate. | ||||||
|  | 	fn := util.GetHandle(db.ctx, pApp).(func() AggregateFunction)() | ||||||
|  | 	handle := util.AddHandle(db.ctx, fn) | ||||||
|  | 	if pAgg != 0 { | ||||||
|  | 		util.WriteUint32(db.mod, pAgg, handle) | ||||||
|  | 	} | ||||||
|  | 	return fn, handle | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func callbackArgs(db *Conn, arg []Value, pArg uint32) { | ||||||
|  | 	for i := range arg { | ||||||
|  | 		arg[i] = Value{ | ||||||
|  | 			c:      db, | ||||||
|  | 			handle: util.ReadUint32(db.mod, pArg+ptrlen*uint32(i)), | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var funcArgsPool sync.Pool | ||||||
|  | 
 | ||||||
|  | func putFuncArgs(p *[_MAX_FUNCTION_ARG]Value) { | ||||||
|  | 	funcArgsPool.Put(p) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getFuncArgs() *[_MAX_FUNCTION_ARG]Value { | ||||||
|  | 	if p := funcArgsPool.Get(); p == nil { | ||||||
|  | 		return new([_MAX_FUNCTION_ARG]Value) | ||||||
|  | 	} else { | ||||||
|  | 		return p.(*[_MAX_FUNCTION_ARG]Value) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								vendor/github.com/ncruces/go-sqlite3/go.work
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								vendor/github.com/ncruces/go-sqlite3/go.work
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | ||||||
|  | go 1.21 | ||||||
|  | 
 | ||||||
|  | use ( | ||||||
|  | 	. | ||||||
|  | 	./gormlite | ||||||
|  | ) | ||||||
							
								
								
									
										9
									
								
								vendor/github.com/ncruces/go-sqlite3/go.work.sum
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/ncruces/go-sqlite3/go.work.sum
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= | ||||||
|  | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||||||
|  | golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= | ||||||
|  | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= | ||||||
|  | golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= | ||||||
|  | golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= | ||||||
|  | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= | ||||||
|  | golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= | ||||||
|  | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= | ||||||
							
								
								
									
										9
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_other.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_other.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | //go:build !(unix || windows) || sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | import "github.com/tetratelabs/wazero/experimental" | ||||||
|  | 
 | ||||||
|  | func virtualAlloc(cap, max uint64) experimental.LinearMemory { | ||||||
|  | 	return sliceAlloc(cap, max) | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_slice.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_slice.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | ||||||
|  | //go:build !(darwin || linux) || !(amd64 || arm64 || riscv64) || sqlite3_noshm || sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | import "github.com/tetratelabs/wazero/experimental" | ||||||
|  | 
 | ||||||
|  | func sliceAlloc(cap, max uint64) experimental.LinearMemory { | ||||||
|  | 	return &sliceBuffer{make([]byte, cap), max} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type sliceBuffer struct { | ||||||
|  | 	buf []byte | ||||||
|  | 	max uint64 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (b *sliceBuffer) Free() {} | ||||||
|  | 
 | ||||||
|  | func (b *sliceBuffer) Reallocate(size uint64) []byte { | ||||||
|  | 	if cap := uint64(cap(b.buf)); size > cap { | ||||||
|  | 		b.buf = append(b.buf[:cap], make([]byte, size-cap)...) | ||||||
|  | 	} else { | ||||||
|  | 		b.buf = b.buf[:size] | ||||||
|  | 	} | ||||||
|  | 	return b.buf | ||||||
|  | } | ||||||
							
								
								
									
										67
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_unix.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_unix.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,67 @@ | ||||||
|  | //go:build unix && !sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"math" | ||||||
|  | 
 | ||||||
|  | 	"github.com/tetratelabs/wazero/experimental" | ||||||
|  | 	"golang.org/x/sys/unix" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func virtualAlloc(cap, max uint64) experimental.LinearMemory { | ||||||
|  | 	// Round up to the page size. | ||||||
|  | 	rnd := uint64(unix.Getpagesize() - 1) | ||||||
|  | 	max = (max + rnd) &^ rnd | ||||||
|  | 
 | ||||||
|  | 	if max > math.MaxInt { | ||||||
|  | 		// This ensures int(max) overflows to a negative value, | ||||||
|  | 		// and unix.Mmap returns EINVAL. | ||||||
|  | 		max = math.MaxUint64 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Reserve max bytes of address space, to ensure we won't need to move it. | ||||||
|  | 	// A protected, private, anonymous mapping should not commit memory. | ||||||
|  | 	b, err := unix.Mmap(-1, 0, int(max), unix.PROT_NONE, unix.MAP_PRIVATE|unix.MAP_ANON) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	return &mmappedMemory{buf: b[:0]} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // The slice covers the entire mmapped memory: | ||||||
|  | //   - len(buf) is the already committed memory, | ||||||
|  | //   - cap(buf) is the reserved address space. | ||||||
|  | type mmappedMemory struct { | ||||||
|  | 	buf []byte | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *mmappedMemory) Reallocate(size uint64) []byte { | ||||||
|  | 	com := uint64(len(m.buf)) | ||||||
|  | 	res := uint64(cap(m.buf)) | ||||||
|  | 	if com < size && size < res { | ||||||
|  | 		// Round up to the page size. | ||||||
|  | 		rnd := uint64(unix.Getpagesize() - 1) | ||||||
|  | 		new := (size + rnd) &^ rnd | ||||||
|  | 
 | ||||||
|  | 		// Commit additional memory up to new bytes. | ||||||
|  | 		err := unix.Mprotect(m.buf[com:new], unix.PROT_READ|unix.PROT_WRITE) | ||||||
|  | 		if err != nil { | ||||||
|  | 			panic(err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Update committed memory. | ||||||
|  | 		m.buf = m.buf[:new] | ||||||
|  | 	} | ||||||
|  | 	// Limit returned capacity because bytes beyond | ||||||
|  | 	// len(m.buf) have not yet been committed. | ||||||
|  | 	return m.buf[:size:len(m.buf)] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *mmappedMemory) Free() { | ||||||
|  | 	err := unix.Munmap(m.buf[:cap(m.buf)]) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	m.buf = nil | ||||||
|  | } | ||||||
							
								
								
									
										76
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_windows.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/alloc_windows.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,76 @@ | ||||||
|  | //go:build !sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"math" | ||||||
|  | 	"reflect" | ||||||
|  | 	"unsafe" | ||||||
|  | 
 | ||||||
|  | 	"github.com/tetratelabs/wazero/experimental" | ||||||
|  | 	"golang.org/x/sys/windows" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func virtualAlloc(cap, max uint64) experimental.LinearMemory { | ||||||
|  | 	// Round up to the page size. | ||||||
|  | 	rnd := uint64(windows.Getpagesize() - 1) | ||||||
|  | 	max = (max + rnd) &^ rnd | ||||||
|  | 
 | ||||||
|  | 	if max > math.MaxInt { | ||||||
|  | 		// This ensures uintptr(max) overflows to a large value, | ||||||
|  | 		// and windows.VirtualAlloc returns an error. | ||||||
|  | 		max = math.MaxUint64 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Reserve max bytes of address space, to ensure we won't need to move it. | ||||||
|  | 	// This does not commit memory. | ||||||
|  | 	r, err := windows.VirtualAlloc(0, uintptr(max), windows.MEM_RESERVE, windows.PAGE_READWRITE) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	mem := virtualMemory{addr: r} | ||||||
|  | 	// SliceHeader, although deprecated, avoids a go vet warning. | ||||||
|  | 	sh := (*reflect.SliceHeader)(unsafe.Pointer(&mem.buf)) | ||||||
|  | 	sh.Cap = int(max) // Not a bug. | ||||||
|  | 	sh.Data = r | ||||||
|  | 	return &mem | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // The slice covers the entire mmapped memory: | ||||||
|  | //   - len(buf) is the already committed memory, | ||||||
|  | //   - cap(buf) is the reserved address space. | ||||||
|  | type virtualMemory struct { | ||||||
|  | 	buf  []byte | ||||||
|  | 	addr uintptr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *virtualMemory) Reallocate(size uint64) []byte { | ||||||
|  | 	com := uint64(len(m.buf)) | ||||||
|  | 	res := uint64(cap(m.buf)) | ||||||
|  | 	if com < size && size < res { | ||||||
|  | 		// Round up to the page size. | ||||||
|  | 		rnd := uint64(windows.Getpagesize() - 1) | ||||||
|  | 		new := (size + rnd) &^ rnd | ||||||
|  | 
 | ||||||
|  | 		// Commit additional memory up to new bytes. | ||||||
|  | 		_, err := windows.VirtualAlloc(m.addr, uintptr(new), windows.MEM_COMMIT, windows.PAGE_READWRITE) | ||||||
|  | 		if err != nil { | ||||||
|  | 			panic(err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Update committed memory. | ||||||
|  | 		m.buf = m.buf[:new] | ||||||
|  | 	} | ||||||
|  | 	// Limit returned capacity because bytes beyond | ||||||
|  | 	// len(m.buf) have not yet been committed. | ||||||
|  | 	return m.buf[:size:len(m.buf)] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *virtualMemory) Free() { | ||||||
|  | 	err := windows.VirtualFree(m.addr, 0, windows.MEM_RELEASE) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	m.addr = 0 | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/bool.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/bool.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | import "strings" | ||||||
|  | 
 | ||||||
|  | func ParseBool(s string) (b, ok bool) { | ||||||
|  | 	if len(s) == 0 { | ||||||
|  | 		return false, false | ||||||
|  | 	} | ||||||
|  | 	if s[0] == '0' { | ||||||
|  | 		return false, true | ||||||
|  | 	} | ||||||
|  | 	if '1' <= s[0] && s[0] <= '9' { | ||||||
|  | 		return true, true | ||||||
|  | 	} | ||||||
|  | 	switch strings.ToLower(s) { | ||||||
|  | 	case "true", "yes", "on": | ||||||
|  | 		return true, true | ||||||
|  | 	case "false", "no", "off": | ||||||
|  | 		return false, true | ||||||
|  | 	} | ||||||
|  | 	return false, false | ||||||
|  | } | ||||||
							
								
								
									
										117
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/const.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/const.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,117 @@ | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | // https://sqlite.com/matrix/rescode.html | ||||||
|  | const ( | ||||||
|  | 	OK = 0 /* Successful result */ | ||||||
|  | 
 | ||||||
|  | 	ERROR      = 1  /* Generic error */ | ||||||
|  | 	INTERNAL   = 2  /* Internal logic error in SQLite */ | ||||||
|  | 	PERM       = 3  /* Access permission denied */ | ||||||
|  | 	ABORT      = 4  /* Callback routine requested an abort */ | ||||||
|  | 	BUSY       = 5  /* The database file is locked */ | ||||||
|  | 	LOCKED     = 6  /* A table in the database is locked */ | ||||||
|  | 	NOMEM      = 7  /* A malloc() failed */ | ||||||
|  | 	READONLY   = 8  /* Attempt to write a readonly database */ | ||||||
|  | 	INTERRUPT  = 9  /* Operation terminated by sqlite3_interrupt() */ | ||||||
|  | 	IOERR      = 10 /* Some kind of disk I/O error occurred */ | ||||||
|  | 	CORRUPT    = 11 /* The database disk image is malformed */ | ||||||
|  | 	NOTFOUND   = 12 /* Unknown opcode in sqlite3_file_control() */ | ||||||
|  | 	FULL       = 13 /* Insertion failed because database is full */ | ||||||
|  | 	CANTOPEN   = 14 /* Unable to open the database file */ | ||||||
|  | 	PROTOCOL   = 15 /* Database lock protocol error */ | ||||||
|  | 	EMPTY      = 16 /* Internal use only */ | ||||||
|  | 	SCHEMA     = 17 /* The database schema changed */ | ||||||
|  | 	TOOBIG     = 18 /* String or BLOB exceeds size limit */ | ||||||
|  | 	CONSTRAINT = 19 /* Abort due to constraint violation */ | ||||||
|  | 	MISMATCH   = 20 /* Data type mismatch */ | ||||||
|  | 	MISUSE     = 21 /* Library used incorrectly */ | ||||||
|  | 	NOLFS      = 22 /* Uses OS features not supported on host */ | ||||||
|  | 	AUTH       = 23 /* Authorization denied */ | ||||||
|  | 	FORMAT     = 24 /* Not used */ | ||||||
|  | 	RANGE      = 25 /* 2nd parameter to sqlite3_bind out of range */ | ||||||
|  | 	NOTADB     = 26 /* File opened that is not a database file */ | ||||||
|  | 	NOTICE     = 27 /* Notifications from sqlite3_log() */ | ||||||
|  | 	WARNING    = 28 /* Warnings from sqlite3_log() */ | ||||||
|  | 
 | ||||||
|  | 	ROW  = 100 /* sqlite3_step() has another row ready */ | ||||||
|  | 	DONE = 101 /* sqlite3_step() has finished executing */ | ||||||
|  | 
 | ||||||
|  | 	ERROR_MISSING_COLLSEQ   = ERROR | (1 << 8) | ||||||
|  | 	ERROR_RETRY             = ERROR | (2 << 8) | ||||||
|  | 	ERROR_SNAPSHOT          = ERROR | (3 << 8) | ||||||
|  | 	IOERR_READ              = IOERR | (1 << 8) | ||||||
|  | 	IOERR_SHORT_READ        = IOERR | (2 << 8) | ||||||
|  | 	IOERR_WRITE             = IOERR | (3 << 8) | ||||||
|  | 	IOERR_FSYNC             = IOERR | (4 << 8) | ||||||
|  | 	IOERR_DIR_FSYNC         = IOERR | (5 << 8) | ||||||
|  | 	IOERR_TRUNCATE          = IOERR | (6 << 8) | ||||||
|  | 	IOERR_FSTAT             = IOERR | (7 << 8) | ||||||
|  | 	IOERR_UNLOCK            = IOERR | (8 << 8) | ||||||
|  | 	IOERR_RDLOCK            = IOERR | (9 << 8) | ||||||
|  | 	IOERR_DELETE            = IOERR | (10 << 8) | ||||||
|  | 	IOERR_BLOCKED           = IOERR | (11 << 8) | ||||||
|  | 	IOERR_NOMEM             = IOERR | (12 << 8) | ||||||
|  | 	IOERR_ACCESS            = IOERR | (13 << 8) | ||||||
|  | 	IOERR_CHECKRESERVEDLOCK = IOERR | (14 << 8) | ||||||
|  | 	IOERR_LOCK              = IOERR | (15 << 8) | ||||||
|  | 	IOERR_CLOSE             = IOERR | (16 << 8) | ||||||
|  | 	IOERR_DIR_CLOSE         = IOERR | (17 << 8) | ||||||
|  | 	IOERR_SHMOPEN           = IOERR | (18 << 8) | ||||||
|  | 	IOERR_SHMSIZE           = IOERR | (19 << 8) | ||||||
|  | 	IOERR_SHMLOCK           = IOERR | (20 << 8) | ||||||
|  | 	IOERR_SHMMAP            = IOERR | (21 << 8) | ||||||
|  | 	IOERR_SEEK              = IOERR | (22 << 8) | ||||||
|  | 	IOERR_DELETE_NOENT      = IOERR | (23 << 8) | ||||||
|  | 	IOERR_MMAP              = IOERR | (24 << 8) | ||||||
|  | 	IOERR_GETTEMPPATH       = IOERR | (25 << 8) | ||||||
|  | 	IOERR_CONVPATH          = IOERR | (26 << 8) | ||||||
|  | 	IOERR_VNODE             = IOERR | (27 << 8) | ||||||
|  | 	IOERR_AUTH              = IOERR | (28 << 8) | ||||||
|  | 	IOERR_BEGIN_ATOMIC      = IOERR | (29 << 8) | ||||||
|  | 	IOERR_COMMIT_ATOMIC     = IOERR | (30 << 8) | ||||||
|  | 	IOERR_ROLLBACK_ATOMIC   = IOERR | (31 << 8) | ||||||
|  | 	IOERR_DATA              = IOERR | (32 << 8) | ||||||
|  | 	IOERR_CORRUPTFS         = IOERR | (33 << 8) | ||||||
|  | 	IOERR_IN_PAGE           = IOERR | (34 << 8) | ||||||
|  | 	LOCKED_SHAREDCACHE      = LOCKED | (1 << 8) | ||||||
|  | 	LOCKED_VTAB             = LOCKED | (2 << 8) | ||||||
|  | 	BUSY_RECOVERY           = BUSY | (1 << 8) | ||||||
|  | 	BUSY_SNAPSHOT           = BUSY | (2 << 8) | ||||||
|  | 	BUSY_TIMEOUT            = BUSY | (3 << 8) | ||||||
|  | 	CANTOPEN_NOTEMPDIR      = CANTOPEN | (1 << 8) | ||||||
|  | 	CANTOPEN_ISDIR          = CANTOPEN | (2 << 8) | ||||||
|  | 	CANTOPEN_FULLPATH       = CANTOPEN | (3 << 8) | ||||||
|  | 	CANTOPEN_CONVPATH       = CANTOPEN | (4 << 8) | ||||||
|  | 	CANTOPEN_DIRTYWAL       = CANTOPEN | (5 << 8) /* Not Used */ | ||||||
|  | 	CANTOPEN_SYMLINK        = CANTOPEN | (6 << 8) | ||||||
|  | 	CORRUPT_VTAB            = CORRUPT | (1 << 8) | ||||||
|  | 	CORRUPT_SEQUENCE        = CORRUPT | (2 << 8) | ||||||
|  | 	CORRUPT_INDEX           = CORRUPT | (3 << 8) | ||||||
|  | 	READONLY_RECOVERY       = READONLY | (1 << 8) | ||||||
|  | 	READONLY_CANTLOCK       = READONLY | (2 << 8) | ||||||
|  | 	READONLY_ROLLBACK       = READONLY | (3 << 8) | ||||||
|  | 	READONLY_DBMOVED        = READONLY | (4 << 8) | ||||||
|  | 	READONLY_CANTINIT       = READONLY | (5 << 8) | ||||||
|  | 	READONLY_DIRECTORY      = READONLY | (6 << 8) | ||||||
|  | 	ABORT_ROLLBACK          = ABORT | (2 << 8) | ||||||
|  | 	CONSTRAINT_CHECK        = CONSTRAINT | (1 << 8) | ||||||
|  | 	CONSTRAINT_COMMITHOOK   = CONSTRAINT | (2 << 8) | ||||||
|  | 	CONSTRAINT_FOREIGNKEY   = CONSTRAINT | (3 << 8) | ||||||
|  | 	CONSTRAINT_FUNCTION     = CONSTRAINT | (4 << 8) | ||||||
|  | 	CONSTRAINT_NOTNULL      = CONSTRAINT | (5 << 8) | ||||||
|  | 	CONSTRAINT_PRIMARYKEY   = CONSTRAINT | (6 << 8) | ||||||
|  | 	CONSTRAINT_TRIGGER      = CONSTRAINT | (7 << 8) | ||||||
|  | 	CONSTRAINT_UNIQUE       = CONSTRAINT | (8 << 8) | ||||||
|  | 	CONSTRAINT_VTAB         = CONSTRAINT | (9 << 8) | ||||||
|  | 	CONSTRAINT_ROWID        = CONSTRAINT | (10 << 8) | ||||||
|  | 	CONSTRAINT_PINNED       = CONSTRAINT | (11 << 8) | ||||||
|  | 	CONSTRAINT_DATATYPE     = CONSTRAINT | (12 << 8) | ||||||
|  | 	NOTICE_RECOVER_WAL      = NOTICE | (1 << 8) | ||||||
|  | 	NOTICE_RECOVER_ROLLBACK = NOTICE | (2 << 8) | ||||||
|  | 	NOTICE_RBU              = NOTICE | (3 << 8) | ||||||
|  | 	WARNING_AUTOINDEX       = WARNING | (1 << 8) | ||||||
|  | 	AUTH_USER               = AUTH | (1 << 8) | ||||||
|  | 
 | ||||||
|  | 	OK_LOAD_PERMANENTLY = OK | (1 << 8) | ||||||
|  | 	OK_SYMLINK          = OK | (2 << 8) /* internal use only */ | ||||||
|  | ) | ||||||
							
								
								
									
										106
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/error.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/error.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,106 @@ | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"runtime" | ||||||
|  | 	"strconv" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type ErrorString string | ||||||
|  | 
 | ||||||
|  | func (e ErrorString) Error() string { return string(e) } | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	NilErr       = ErrorString("sqlite3: invalid memory address or null pointer dereference") | ||||||
|  | 	OOMErr       = ErrorString("sqlite3: out of memory") | ||||||
|  | 	RangeErr     = ErrorString("sqlite3: index out of range") | ||||||
|  | 	NoNulErr     = ErrorString("sqlite3: missing NUL terminator") | ||||||
|  | 	NoBinaryErr  = ErrorString("sqlite3: no SQLite binary embed/set/loaded") | ||||||
|  | 	BadBinaryErr = ErrorString("sqlite3: invalid SQLite binary embed/set/loaded") | ||||||
|  | 	TimeErr      = ErrorString("sqlite3: invalid time value") | ||||||
|  | 	WhenceErr    = ErrorString("sqlite3: invalid whence") | ||||||
|  | 	OffsetErr    = ErrorString("sqlite3: invalid offset") | ||||||
|  | 	TailErr      = ErrorString("sqlite3: multiple statements") | ||||||
|  | 	IsolationErr = ErrorString("sqlite3: unsupported isolation level") | ||||||
|  | 	ValueErr     = ErrorString("sqlite3: unsupported value") | ||||||
|  | 	NoVFSErr     = ErrorString("sqlite3: no such vfs: ") | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func AssertErr() ErrorString { | ||||||
|  | 	msg := "sqlite3: assertion failed" | ||||||
|  | 	if _, file, line, ok := runtime.Caller(1); ok { | ||||||
|  | 		msg += " (" + file + ":" + strconv.Itoa(line) + ")" | ||||||
|  | 	} | ||||||
|  | 	return ErrorString(msg) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ErrorCodeString(rc uint32) string { | ||||||
|  | 	switch rc { | ||||||
|  | 	case ABORT_ROLLBACK: | ||||||
|  | 		return "sqlite3: abort due to ROLLBACK" | ||||||
|  | 	case ROW: | ||||||
|  | 		return "sqlite3: another row available" | ||||||
|  | 	case DONE: | ||||||
|  | 		return "sqlite3: no more rows available" | ||||||
|  | 	} | ||||||
|  | 	switch rc & 0xff { | ||||||
|  | 	case OK: | ||||||
|  | 		return "sqlite3: not an error" | ||||||
|  | 	case ERROR: | ||||||
|  | 		return "sqlite3: SQL logic error" | ||||||
|  | 	case INTERNAL: | ||||||
|  | 		break | ||||||
|  | 	case PERM: | ||||||
|  | 		return "sqlite3: access permission denied" | ||||||
|  | 	case ABORT: | ||||||
|  | 		return "sqlite3: query aborted" | ||||||
|  | 	case BUSY: | ||||||
|  | 		return "sqlite3: database is locked" | ||||||
|  | 	case LOCKED: | ||||||
|  | 		return "sqlite3: database table is locked" | ||||||
|  | 	case NOMEM: | ||||||
|  | 		return "sqlite3: out of memory" | ||||||
|  | 	case READONLY: | ||||||
|  | 		return "sqlite3: attempt to write a readonly database" | ||||||
|  | 	case INTERRUPT: | ||||||
|  | 		return "sqlite3: interrupted" | ||||||
|  | 	case IOERR: | ||||||
|  | 		return "sqlite3: disk I/O error" | ||||||
|  | 	case CORRUPT: | ||||||
|  | 		return "sqlite3: database disk image is malformed" | ||||||
|  | 	case NOTFOUND: | ||||||
|  | 		return "sqlite3: unknown operation" | ||||||
|  | 	case FULL: | ||||||
|  | 		return "sqlite3: database or disk is full" | ||||||
|  | 	case CANTOPEN: | ||||||
|  | 		return "sqlite3: unable to open database file" | ||||||
|  | 	case PROTOCOL: | ||||||
|  | 		return "sqlite3: locking protocol" | ||||||
|  | 	case FORMAT: | ||||||
|  | 		break | ||||||
|  | 	case SCHEMA: | ||||||
|  | 		return "sqlite3: database schema has changed" | ||||||
|  | 	case TOOBIG: | ||||||
|  | 		return "sqlite3: string or blob too big" | ||||||
|  | 	case CONSTRAINT: | ||||||
|  | 		return "sqlite3: constraint failed" | ||||||
|  | 	case MISMATCH: | ||||||
|  | 		return "sqlite3: datatype mismatch" | ||||||
|  | 	case MISUSE: | ||||||
|  | 		return "sqlite3: bad parameter or other API misuse" | ||||||
|  | 	case NOLFS: | ||||||
|  | 		break | ||||||
|  | 	case AUTH: | ||||||
|  | 		return "sqlite3: authorization denied" | ||||||
|  | 	case EMPTY: | ||||||
|  | 		break | ||||||
|  | 	case RANGE: | ||||||
|  | 		return "sqlite3: column index out of range" | ||||||
|  | 	case NOTADB: | ||||||
|  | 		return "sqlite3: file is not a database" | ||||||
|  | 	case NOTICE: | ||||||
|  | 		return "sqlite3: notification message" | ||||||
|  | 	case WARNING: | ||||||
|  | 		return "sqlite3: warning message" | ||||||
|  | 	} | ||||||
|  | 	return "sqlite3: unknown error" | ||||||
|  | } | ||||||
							
								
								
									
										193
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/func.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/func.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,193 @@ | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 
 | ||||||
|  | 	"github.com/tetratelabs/wazero" | ||||||
|  | 	"github.com/tetratelabs/wazero/api" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type i32 interface{ ~int32 | ~uint32 } | ||||||
|  | type i64 interface{ ~int64 | ~uint64 } | ||||||
|  | 
 | ||||||
|  | type funcVI[T0 i32] func(context.Context, api.Module, T0) | ||||||
|  | 
 | ||||||
|  | func (fn funcVI[T0]) Call(ctx context.Context, mod api.Module, stack []uint64) { | ||||||
|  | 	fn(ctx, mod, T0(stack[0])) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ExportFuncVI[T0 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0)) { | ||||||
|  | 	mod.NewFunctionBuilder(). | ||||||
|  | 		WithGoModuleFunction(funcVI[T0](fn), | ||||||
|  | 			[]api.ValueType{api.ValueTypeI32}, nil). | ||||||
|  | 		Export(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type funcVII[T0, T1 i32] func(context.Context, api.Module, T0, T1) | ||||||
|  | 
 | ||||||
|  | func (fn funcVII[T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) { | ||||||
|  | 	fn(ctx, mod, T0(stack[0]), T1(stack[1])) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ExportFuncVII[T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1)) { | ||||||
|  | 	mod.NewFunctionBuilder(). | ||||||
|  | 		WithGoModuleFunction(funcVII[T0, T1](fn), | ||||||
|  | 			[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, nil). | ||||||
|  | 		Export(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type funcVIII[T0, T1, T2 i32] func(context.Context, api.Module, T0, T1, T2) | ||||||
|  | 
 | ||||||
|  | func (fn funcVIII[T0, T1, T2]) Call(ctx context.Context, mod api.Module, stack []uint64) { | ||||||
|  | 	fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2])) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ExportFuncVIII[T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2)) { | ||||||
|  | 	mod.NewFunctionBuilder(). | ||||||
|  | 		WithGoModuleFunction(funcVIII[T0, T1, T2](fn), | ||||||
|  | 			[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil). | ||||||
|  | 		Export(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type funcVIIII[T0, T1, T2, T3 i32] func(context.Context, api.Module, T0, T1, T2, T3) | ||||||
|  | 
 | ||||||
|  | func (fn funcVIIII[T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) { | ||||||
|  | 	fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3])) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ExportFuncVIIII[T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3)) { | ||||||
|  | 	mod.NewFunctionBuilder(). | ||||||
|  | 		WithGoModuleFunction(funcVIIII[T0, T1, T2, T3](fn), | ||||||
|  | 			[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil). | ||||||
|  | 		Export(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type funcVIIIII[T0, T1, T2, T3, T4 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4) | ||||||
|  | 
 | ||||||
|  | func (fn funcVIIIII[T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) { | ||||||
|  | 	fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4])) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ExportFuncVIIIII[T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4)) { | ||||||
|  | 	mod.NewFunctionBuilder(). | ||||||
|  | 		WithGoModuleFunction(funcVIIIII[T0, T1, T2, T3, T4](fn), | ||||||
|  | 			[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil). | ||||||
|  | 		Export(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type funcVIIIIJ[T0, T1, T2, T3 i32, T4 i64] func(context.Context, api.Module, T0, T1, T2, T3, T4) | ||||||
|  | 
 | ||||||
|  | func (fn funcVIIIIJ[T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) { | ||||||
|  | 	fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4])) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ExportFuncVIIIIJ[T0, T1, T2, T3 i32, T4 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4)) { | ||||||
|  | 	mod.NewFunctionBuilder(). | ||||||
|  | 		WithGoModuleFunction(funcVIIIIJ[T0, T1, T2, T3, T4](fn), | ||||||
|  | 			[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI64}, nil). | ||||||
|  | 		Export(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type funcII[TR, T0 i32] func(context.Context, api.Module, T0) TR | ||||||
|  | 
 | ||||||
|  | func (fn funcII[TR, T0]) Call(ctx context.Context, mod api.Module, stack []uint64) { | ||||||
|  | 	stack[0] = uint64(fn(ctx, mod, T0(stack[0]))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ExportFuncII[TR, T0 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0) TR) { | ||||||
|  | 	mod.NewFunctionBuilder(). | ||||||
|  | 		WithGoModuleFunction(funcII[TR, T0](fn), | ||||||
|  | 			[]api.ValueType{api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). | ||||||
|  | 		Export(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type funcIII[TR, T0, T1 i32] func(context.Context, api.Module, T0, T1) TR | ||||||
|  | 
 | ||||||
|  | func (fn funcIII[TR, T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) { | ||||||
|  | 	stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ExportFuncIII[TR, T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1) TR) { | ||||||
|  | 	mod.NewFunctionBuilder(). | ||||||
|  | 		WithGoModuleFunction(funcIII[TR, T0, T1](fn), | ||||||
|  | 			[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). | ||||||
|  | 		Export(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type funcIIII[TR, T0, T1, T2 i32] func(context.Context, api.Module, T0, T1, T2) TR | ||||||
|  | 
 | ||||||
|  | func (fn funcIIII[TR, T0, T1, T2]) Call(ctx context.Context, mod api.Module, stack []uint64) { | ||||||
|  | 	stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ExportFuncIIII[TR, T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2) TR) { | ||||||
|  | 	mod.NewFunctionBuilder(). | ||||||
|  | 		WithGoModuleFunction(funcIIII[TR, T0, T1, T2](fn), | ||||||
|  | 			[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). | ||||||
|  | 		Export(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type funcIIIII[TR, T0, T1, T2, T3 i32] func(context.Context, api.Module, T0, T1, T2, T3) TR | ||||||
|  | 
 | ||||||
|  | func (fn funcIIIII[TR, T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) { | ||||||
|  | 	stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ExportFuncIIIII[TR, T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3) TR) { | ||||||
|  | 	mod.NewFunctionBuilder(). | ||||||
|  | 		WithGoModuleFunction(funcIIIII[TR, T0, T1, T2, T3](fn), | ||||||
|  | 			[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). | ||||||
|  | 		Export(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type funcIIIIII[TR, T0, T1, T2, T3, T4 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4) TR | ||||||
|  | 
 | ||||||
|  | func (fn funcIIIIII[TR, T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) { | ||||||
|  | 	stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ExportFuncIIIIII[TR, T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4) TR) { | ||||||
|  | 	mod.NewFunctionBuilder(). | ||||||
|  | 		WithGoModuleFunction(funcIIIIII[TR, T0, T1, T2, T3, T4](fn), | ||||||
|  | 			[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). | ||||||
|  | 		Export(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type funcIIIIIII[TR, T0, T1, T2, T3, T4, T5 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4, T5) TR | ||||||
|  | 
 | ||||||
|  | func (fn funcIIIIIII[TR, T0, T1, T2, T3, T4, T5]) Call(ctx context.Context, mod api.Module, stack []uint64) { | ||||||
|  | 	stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]), T5(stack[5]))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ExportFuncIIIIIII[TR, T0, T1, T2, T3, T4, T5 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4, T5) TR) { | ||||||
|  | 	mod.NewFunctionBuilder(). | ||||||
|  | 		WithGoModuleFunction(funcIIIIIII[TR, T0, T1, T2, T3, T4, T5](fn), | ||||||
|  | 			[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). | ||||||
|  | 		Export(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type funcIIIIJ[TR, T0, T1, T2 i32, T3 i64] func(context.Context, api.Module, T0, T1, T2, T3) TR | ||||||
|  | 
 | ||||||
|  | func (fn funcIIIIJ[TR, T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) { | ||||||
|  | 	stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ExportFuncIIIIJ[TR, T0, T1, T2 i32, T3 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3) TR) { | ||||||
|  | 	mod.NewFunctionBuilder(). | ||||||
|  | 		WithGoModuleFunction(funcIIIIJ[TR, T0, T1, T2, T3](fn), | ||||||
|  | 			[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}). | ||||||
|  | 		Export(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type funcIIJ[TR, T0 i32, T1 i64] func(context.Context, api.Module, T0, T1) TR | ||||||
|  | 
 | ||||||
|  | func (fn funcIIJ[TR, T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) { | ||||||
|  | 	stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]))) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ExportFuncIIJ[TR, T0 i32, T1 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1) TR) { | ||||||
|  | 	mod.NewFunctionBuilder(). | ||||||
|  | 		WithGoModuleFunction(funcIIJ[TR, T0, T1](fn), | ||||||
|  | 			[]api.ValueType{api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}). | ||||||
|  | 		Export(name) | ||||||
|  | } | ||||||
							
								
								
									
										65
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/handle.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/handle.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"io" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type handleState struct { | ||||||
|  | 	handles []any | ||||||
|  | 	holes   int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *handleState) CloseNotify(ctx context.Context, exitCode uint32) { | ||||||
|  | 	for _, h := range s.handles { | ||||||
|  | 		if c, ok := h.(io.Closer); ok { | ||||||
|  | 			c.Close() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	s.handles = nil | ||||||
|  | 	s.holes = 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func GetHandle(ctx context.Context, id uint32) any { | ||||||
|  | 	if id == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	s := ctx.Value(moduleKey{}).(*moduleState) | ||||||
|  | 	return s.handles[^id] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func DelHandle(ctx context.Context, id uint32) error { | ||||||
|  | 	if id == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	s := ctx.Value(moduleKey{}).(*moduleState) | ||||||
|  | 	a := s.handles[^id] | ||||||
|  | 	s.handles[^id] = nil | ||||||
|  | 	s.holes++ | ||||||
|  | 	if c, ok := a.(io.Closer); ok { | ||||||
|  | 		return c.Close() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func AddHandle(ctx context.Context, a any) (id uint32) { | ||||||
|  | 	if a == nil { | ||||||
|  | 		panic(NilErr) | ||||||
|  | 	} | ||||||
|  | 	s := ctx.Value(moduleKey{}).(*moduleState) | ||||||
|  | 
 | ||||||
|  | 	// Find an empty slot. | ||||||
|  | 	if s.holes > cap(s.handles)-len(s.handles) { | ||||||
|  | 		for id, h := range s.handles { | ||||||
|  | 			if h == nil { | ||||||
|  | 				s.holes-- | ||||||
|  | 				s.handles[id] = a | ||||||
|  | 				return ^uint32(id) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Add a new slot. | ||||||
|  | 	s.handles = append(s.handles, a) | ||||||
|  | 	return -uint32(len(s.handles)) | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/json.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/json.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"strconv" | ||||||
|  | 	"time" | ||||||
|  | 	"unsafe" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type JSON struct{ Value any } | ||||||
|  | 
 | ||||||
|  | func (j JSON) Scan(value any) error { | ||||||
|  | 	var buf []byte | ||||||
|  | 
 | ||||||
|  | 	switch v := value.(type) { | ||||||
|  | 	case []byte: | ||||||
|  | 		buf = v | ||||||
|  | 	case string: | ||||||
|  | 		buf = unsafe.Slice(unsafe.StringData(v), len(v)) | ||||||
|  | 	case int64: | ||||||
|  | 		buf = strconv.AppendInt(nil, v, 10) | ||||||
|  | 	case float64: | ||||||
|  | 		buf = strconv.AppendFloat(nil, v, 'g', -1, 64) | ||||||
|  | 	case time.Time: | ||||||
|  | 		buf = append(buf, '"') | ||||||
|  | 		buf = v.AppendFormat(buf, time.RFC3339Nano) | ||||||
|  | 		buf = append(buf, '"') | ||||||
|  | 	case nil: | ||||||
|  | 		buf = append(buf, "null"...) | ||||||
|  | 	default: | ||||||
|  | 		panic(AssertErr()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return json.Unmarshal(buf, j.Value) | ||||||
|  | } | ||||||
							
								
								
									
										134
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/mem.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/mem.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,134 @@ | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"math" | ||||||
|  | 
 | ||||||
|  | 	"github.com/tetratelabs/wazero/api" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func View(mod api.Module, ptr uint32, size uint64) []byte { | ||||||
|  | 	if ptr == 0 { | ||||||
|  | 		panic(NilErr) | ||||||
|  | 	} | ||||||
|  | 	if size > math.MaxUint32 { | ||||||
|  | 		panic(RangeErr) | ||||||
|  | 	} | ||||||
|  | 	if size == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	buf, ok := mod.Memory().Read(ptr, uint32(size)) | ||||||
|  | 	if !ok { | ||||||
|  | 		panic(RangeErr) | ||||||
|  | 	} | ||||||
|  | 	return buf | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ReadUint8(mod api.Module, ptr uint32) uint8 { | ||||||
|  | 	if ptr == 0 { | ||||||
|  | 		panic(NilErr) | ||||||
|  | 	} | ||||||
|  | 	v, ok := mod.Memory().ReadByte(ptr) | ||||||
|  | 	if !ok { | ||||||
|  | 		panic(RangeErr) | ||||||
|  | 	} | ||||||
|  | 	return v | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ReadUint32(mod api.Module, ptr uint32) uint32 { | ||||||
|  | 	if ptr == 0 { | ||||||
|  | 		panic(NilErr) | ||||||
|  | 	} | ||||||
|  | 	v, ok := mod.Memory().ReadUint32Le(ptr) | ||||||
|  | 	if !ok { | ||||||
|  | 		panic(RangeErr) | ||||||
|  | 	} | ||||||
|  | 	return v | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func WriteUint8(mod api.Module, ptr uint32, v uint8) { | ||||||
|  | 	if ptr == 0 { | ||||||
|  | 		panic(NilErr) | ||||||
|  | 	} | ||||||
|  | 	ok := mod.Memory().WriteByte(ptr, v) | ||||||
|  | 	if !ok { | ||||||
|  | 		panic(RangeErr) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func WriteUint32(mod api.Module, ptr uint32, v uint32) { | ||||||
|  | 	if ptr == 0 { | ||||||
|  | 		panic(NilErr) | ||||||
|  | 	} | ||||||
|  | 	ok := mod.Memory().WriteUint32Le(ptr, v) | ||||||
|  | 	if !ok { | ||||||
|  | 		panic(RangeErr) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ReadUint64(mod api.Module, ptr uint32) uint64 { | ||||||
|  | 	if ptr == 0 { | ||||||
|  | 		panic(NilErr) | ||||||
|  | 	} | ||||||
|  | 	v, ok := mod.Memory().ReadUint64Le(ptr) | ||||||
|  | 	if !ok { | ||||||
|  | 		panic(RangeErr) | ||||||
|  | 	} | ||||||
|  | 	return v | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func WriteUint64(mod api.Module, ptr uint32, v uint64) { | ||||||
|  | 	if ptr == 0 { | ||||||
|  | 		panic(NilErr) | ||||||
|  | 	} | ||||||
|  | 	ok := mod.Memory().WriteUint64Le(ptr, v) | ||||||
|  | 	if !ok { | ||||||
|  | 		panic(RangeErr) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ReadFloat64(mod api.Module, ptr uint32) float64 { | ||||||
|  | 	return math.Float64frombits(ReadUint64(mod, ptr)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func WriteFloat64(mod api.Module, ptr uint32, v float64) { | ||||||
|  | 	WriteUint64(mod, ptr, math.Float64bits(v)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ReadString(mod api.Module, ptr, maxlen uint32) string { | ||||||
|  | 	if ptr == 0 { | ||||||
|  | 		panic(NilErr) | ||||||
|  | 	} | ||||||
|  | 	switch maxlen { | ||||||
|  | 	case 0: | ||||||
|  | 		return "" | ||||||
|  | 	case math.MaxUint32: | ||||||
|  | 		// avoid overflow | ||||||
|  | 	default: | ||||||
|  | 		maxlen = maxlen + 1 | ||||||
|  | 	} | ||||||
|  | 	mem := mod.Memory() | ||||||
|  | 	buf, ok := mem.Read(ptr, maxlen) | ||||||
|  | 	if !ok { | ||||||
|  | 		buf, ok = mem.Read(ptr, mem.Size()-ptr) | ||||||
|  | 		if !ok { | ||||||
|  | 			panic(RangeErr) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if i := bytes.IndexByte(buf, 0); i < 0 { | ||||||
|  | 		panic(NoNulErr) | ||||||
|  | 	} else { | ||||||
|  | 		return string(buf[:i]) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func WriteBytes(mod api.Module, ptr uint32, b []byte) { | ||||||
|  | 	buf := View(mod, ptr, uint64(len(b))) | ||||||
|  | 	copy(buf, b) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func WriteString(mod api.Module, ptr uint32, s string) { | ||||||
|  | 	buf := View(mod, ptr, uint64(len(s)+1)) | ||||||
|  | 	buf[len(s)] = 0 | ||||||
|  | 	copy(buf, s) | ||||||
|  | } | ||||||
							
								
								
									
										97
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/mmap.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/mmap.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,97 @@ | ||||||
|  | //go:build (darwin || linux) && (amd64 || arm64 || riscv64) && !(sqlite3_noshm || sqlite3_nosys) | ||||||
|  | 
 | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"os" | ||||||
|  | 	"unsafe" | ||||||
|  | 
 | ||||||
|  | 	"github.com/tetratelabs/wazero/api" | ||||||
|  | 	"github.com/tetratelabs/wazero/experimental" | ||||||
|  | 	"golang.org/x/sys/unix" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func withAllocator(ctx context.Context) context.Context { | ||||||
|  | 	return experimental.WithMemoryAllocator(ctx, | ||||||
|  | 		experimental.MemoryAllocatorFunc(virtualAlloc)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type mmapState struct { | ||||||
|  | 	regions []*MappedRegion | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *mmapState) new(ctx context.Context, mod api.Module, size int32) *MappedRegion { | ||||||
|  | 	// Find unused region. | ||||||
|  | 	for _, r := range s.regions { | ||||||
|  | 		if !r.used && r.size == size { | ||||||
|  | 			return r | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Allocate page aligned memmory. | ||||||
|  | 	alloc := mod.ExportedFunction("aligned_alloc") | ||||||
|  | 	stack := [2]uint64{ | ||||||
|  | 		uint64(unix.Getpagesize()), | ||||||
|  | 		uint64(size), | ||||||
|  | 	} | ||||||
|  | 	if err := alloc.CallWithStack(ctx, stack[:]); err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	if stack[0] == 0 { | ||||||
|  | 		panic(OOMErr) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Save the newly allocated region. | ||||||
|  | 	ptr := uint32(stack[0]) | ||||||
|  | 	buf := View(mod, ptr, uint64(size)) | ||||||
|  | 	addr := uintptr(unsafe.Pointer(&buf[0])) | ||||||
|  | 	s.regions = append(s.regions, &MappedRegion{ | ||||||
|  | 		Ptr:  ptr, | ||||||
|  | 		addr: addr, | ||||||
|  | 		size: size, | ||||||
|  | 	}) | ||||||
|  | 	return s.regions[len(s.regions)-1] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type MappedRegion struct { | ||||||
|  | 	addr uintptr | ||||||
|  | 	Ptr  uint32 | ||||||
|  | 	size int32 | ||||||
|  | 	used bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func MapRegion(ctx context.Context, mod api.Module, f *os.File, offset int64, size int32, prot int) (*MappedRegion, error) { | ||||||
|  | 	s := ctx.Value(moduleKey{}).(*moduleState) | ||||||
|  | 	r := s.new(ctx, mod, size) | ||||||
|  | 	err := r.mmap(f, offset, prot) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return r, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *MappedRegion) Unmap() error { | ||||||
|  | 	// We can't munmap the region, otherwise it could be remaped. | ||||||
|  | 	// Instead, convert it to a protected, private, anonymous mapping. | ||||||
|  | 	// If successful, it can be reused for a subsequent mmap. | ||||||
|  | 	_, err := mmap(r.addr, uintptr(r.size), | ||||||
|  | 		unix.PROT_NONE, unix.MAP_PRIVATE|unix.MAP_ANON|unix.MAP_FIXED, | ||||||
|  | 		-1, 0) | ||||||
|  | 	r.used = err != nil | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *MappedRegion) mmap(f *os.File, offset int64, prot int) error { | ||||||
|  | 	_, err := mmap(r.addr, uintptr(r.size), | ||||||
|  | 		prot, unix.MAP_SHARED|unix.MAP_FIXED, | ||||||
|  | 		int(f.Fd()), offset) | ||||||
|  | 	r.used = err == nil | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // We need the low level mmap for MAP_FIXED to work. | ||||||
|  | // Bind the syscall version hoping that it is more stable. | ||||||
|  | 
 | ||||||
|  | //go:linkname mmap syscall.mmap | ||||||
|  | func mmap(addr, length uintptr, prot, flag, fd int, pos int64) (*byte, error) | ||||||
							
								
								
									
										21
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_other.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_other.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | //go:build !(darwin || linux) || !(amd64 || arm64 || riscv64) || sqlite3_noshm || sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 
 | ||||||
|  | 	"github.com/tetratelabs/wazero/experimental" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type mmapState struct{} | ||||||
|  | 
 | ||||||
|  | func withAllocator(ctx context.Context) context.Context { | ||||||
|  | 	return experimental.WithMemoryAllocator(ctx, | ||||||
|  | 		experimental.MemoryAllocatorFunc(func(cap, max uint64) experimental.LinearMemory { | ||||||
|  | 			if cap == max { | ||||||
|  | 				return virtualAlloc(cap, max) | ||||||
|  | 			} | ||||||
|  | 			return sliceAlloc(cap, max) | ||||||
|  | 		})) | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/module.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/module.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 
 | ||||||
|  | 	"github.com/tetratelabs/wazero/experimental" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type moduleKey struct{} | ||||||
|  | type moduleState struct { | ||||||
|  | 	mmapState | ||||||
|  | 	handleState | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewContext(ctx context.Context) context.Context { | ||||||
|  | 	state := new(moduleState) | ||||||
|  | 	ctx = withAllocator(ctx) | ||||||
|  | 	ctx = experimental.WithCloseNotifier(ctx, state) | ||||||
|  | 	ctx = context.WithValue(ctx, moduleKey{}, state) | ||||||
|  | 	return ctx | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/pointer.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/pointer.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | type Pointer[T any] struct{ Value T } | ||||||
|  | 
 | ||||||
|  | func (p Pointer[T]) unwrap() any { return p.Value } | ||||||
|  | 
 | ||||||
|  | type PointerUnwrap interface{ unwrap() any } | ||||||
|  | 
 | ||||||
|  | func UnwrapPointer(p PointerUnwrap) any { | ||||||
|  | 	return p.unwrap() | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/reflect.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/ncruces/go-sqlite3/internal/util/reflect.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | ||||||
|  | package util | ||||||
|  | 
 | ||||||
|  | import "reflect" | ||||||
|  | 
 | ||||||
|  | func ReflectType(v reflect.Value) reflect.Type { | ||||||
|  | 	if v.Kind() != reflect.Invalid { | ||||||
|  | 		return v.Type() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								vendor/github.com/ncruces/go-sqlite3/json.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								vendor/github.com/ncruces/go-sqlite3/json.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | import "github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | 
 | ||||||
|  | // JSON returns a value that can be used as an argument to | ||||||
|  | // [database/sql.DB.Exec], [database/sql.Row.Scan] and similar methods to | ||||||
|  | // store value as JSON, or decode JSON into value. | ||||||
|  | // JSON should NOT be used with [BindJSON] or [ResultJSON]. | ||||||
|  | func JSON(value any) any { | ||||||
|  | 	return util.JSON{Value: value} | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								vendor/github.com/ncruces/go-sqlite3/pointer.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								vendor/github.com/ncruces/go-sqlite3/pointer.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | import "github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | 
 | ||||||
|  | // Pointer returns a pointer to a value that can be used as an argument to | ||||||
|  | // [database/sql.DB.Exec] and similar methods. | ||||||
|  | // Pointer should NOT be used with [BindPointer] or [ResultPointer]. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/bindptr.html | ||||||
|  | func Pointer[T any](value T) any { | ||||||
|  | 	return util.Pointer[T]{Value: value} | ||||||
|  | } | ||||||
							
								
								
									
										112
									
								
								vendor/github.com/ncruces/go-sqlite3/quote.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								vendor/github.com/ncruces/go-sqlite3/quote.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,112 @@ | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"math" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | 	"unsafe" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Quote escapes and quotes a value | ||||||
|  | // making it safe to embed in SQL text. | ||||||
|  | func Quote(value any) string { | ||||||
|  | 	switch v := value.(type) { | ||||||
|  | 	case nil: | ||||||
|  | 		return "NULL" | ||||||
|  | 	case bool: | ||||||
|  | 		if v { | ||||||
|  | 			return "1" | ||||||
|  | 		} else { | ||||||
|  | 			return "0" | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	case int: | ||||||
|  | 		return strconv.Itoa(v) | ||||||
|  | 	case int64: | ||||||
|  | 		return strconv.FormatInt(v, 10) | ||||||
|  | 	case float64: | ||||||
|  | 		switch { | ||||||
|  | 		case math.IsNaN(v): | ||||||
|  | 			return "NULL" | ||||||
|  | 		case math.IsInf(v, 1): | ||||||
|  | 			return "9.0e999" | ||||||
|  | 		case math.IsInf(v, -1): | ||||||
|  | 			return "-9.0e999" | ||||||
|  | 		} | ||||||
|  | 		return strconv.FormatFloat(v, 'g', -1, 64) | ||||||
|  | 	case time.Time: | ||||||
|  | 		return "'" + v.Format(time.RFC3339Nano) + "'" | ||||||
|  | 
 | ||||||
|  | 	case string: | ||||||
|  | 		if strings.IndexByte(v, 0) >= 0 { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		buf := make([]byte, 2+len(v)+strings.Count(v, "'")) | ||||||
|  | 		buf[0] = '\'' | ||||||
|  | 		i := 1 | ||||||
|  | 		for _, b := range []byte(v) { | ||||||
|  | 			if b == '\'' { | ||||||
|  | 				buf[i] = b | ||||||
|  | 				i += 1 | ||||||
|  | 			} | ||||||
|  | 			buf[i] = b | ||||||
|  | 			i += 1 | ||||||
|  | 		} | ||||||
|  | 		buf[i] = '\'' | ||||||
|  | 		return unsafe.String(&buf[0], len(buf)) | ||||||
|  | 
 | ||||||
|  | 	case []byte: | ||||||
|  | 		buf := make([]byte, 3+2*len(v)) | ||||||
|  | 		buf[0] = 'x' | ||||||
|  | 		buf[1] = '\'' | ||||||
|  | 		i := 2 | ||||||
|  | 		for _, b := range v { | ||||||
|  | 			const hex = "0123456789ABCDEF" | ||||||
|  | 			buf[i+0] = hex[b/16] | ||||||
|  | 			buf[i+1] = hex[b%16] | ||||||
|  | 			i += 2 | ||||||
|  | 		} | ||||||
|  | 		buf[i] = '\'' | ||||||
|  | 		return unsafe.String(&buf[0], len(buf)) | ||||||
|  | 
 | ||||||
|  | 	case ZeroBlob: | ||||||
|  | 		if v > ZeroBlob(1e9-3)/2 { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		buf := bytes.Repeat([]byte("0"), int(3+2*int64(v))) | ||||||
|  | 		buf[0] = 'x' | ||||||
|  | 		buf[1] = '\'' | ||||||
|  | 		buf[len(buf)-1] = '\'' | ||||||
|  | 		return unsafe.String(&buf[0], len(buf)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	panic(util.ValueErr) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // QuoteIdentifier escapes and quotes an identifier | ||||||
|  | // making it safe to embed in SQL text. | ||||||
|  | func QuoteIdentifier(id string) string { | ||||||
|  | 	if strings.IndexByte(id, 0) >= 0 { | ||||||
|  | 		panic(util.ValueErr) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	buf := make([]byte, 2+len(id)+strings.Count(id, `"`)) | ||||||
|  | 	buf[0] = '"' | ||||||
|  | 	i := 1 | ||||||
|  | 	for _, b := range []byte(id) { | ||||||
|  | 		if b == '"' { | ||||||
|  | 			buf[i] = b | ||||||
|  | 			i += 1 | ||||||
|  | 		} | ||||||
|  | 		buf[i] = b | ||||||
|  | 		i += 1 | ||||||
|  | 	} | ||||||
|  | 	buf[i] = '"' | ||||||
|  | 	return unsafe.String(&buf[0], len(buf)) | ||||||
|  | } | ||||||
							
								
								
									
										341
									
								
								vendor/github.com/ncruces/go-sqlite3/sqlite.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								vendor/github.com/ncruces/go-sqlite3/sqlite.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,341 @@ | ||||||
|  | // Package sqlite3 wraps the C SQLite API. | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"math" | ||||||
|  | 	"math/bits" | ||||||
|  | 	"os" | ||||||
|  | 	"sync" | ||||||
|  | 	"unsafe" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | 	"github.com/ncruces/go-sqlite3/vfs" | ||||||
|  | 	"github.com/tetratelabs/wazero" | ||||||
|  | 	"github.com/tetratelabs/wazero/api" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Configure SQLite Wasm. | ||||||
|  | // | ||||||
|  | // Importing package embed initializes [Binary] | ||||||
|  | // with an appropriate build of SQLite: | ||||||
|  | // | ||||||
|  | //	import _ "github.com/ncruces/go-sqlite3/embed" | ||||||
|  | var ( | ||||||
|  | 	Binary []byte // Wasm binary to load. | ||||||
|  | 	Path   string // Path to load the binary from. | ||||||
|  | 
 | ||||||
|  | 	RuntimeConfig wazero.RuntimeConfig | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Initialize decodes and compiles the SQLite Wasm binary. | ||||||
|  | // This is called implicitly when the first connection is openned, | ||||||
|  | // but is potentially slow, so you may want to call it at a more convenient time. | ||||||
|  | func Initialize() error { | ||||||
|  | 	instance.once.Do(compileSQLite) | ||||||
|  | 	return instance.err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var instance struct { | ||||||
|  | 	runtime  wazero.Runtime | ||||||
|  | 	compiled wazero.CompiledModule | ||||||
|  | 	err      error | ||||||
|  | 	once     sync.Once | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func compileSQLite() { | ||||||
|  | 	if RuntimeConfig == nil { | ||||||
|  | 		RuntimeConfig = wazero.NewRuntimeConfig() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 	instance.runtime = wazero.NewRuntimeWithConfig(ctx, RuntimeConfig) | ||||||
|  | 
 | ||||||
|  | 	env := instance.runtime.NewHostModuleBuilder("env") | ||||||
|  | 	env = vfs.ExportHostFunctions(env) | ||||||
|  | 	env = exportCallbacks(env) | ||||||
|  | 	_, instance.err = env.Instantiate(ctx) | ||||||
|  | 	if instance.err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	bin := Binary | ||||||
|  | 	if bin == nil && Path != "" { | ||||||
|  | 		bin, instance.err = os.ReadFile(Path) | ||||||
|  | 		if instance.err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if bin == nil { | ||||||
|  | 		instance.err = util.NoBinaryErr | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	instance.compiled, instance.err = instance.runtime.CompileModule(ctx, bin) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type sqlite struct { | ||||||
|  | 	ctx   context.Context | ||||||
|  | 	mod   api.Module | ||||||
|  | 	funcs struct { | ||||||
|  | 		fn   [32]api.Function | ||||||
|  | 		id   [32]*byte | ||||||
|  | 		mask uint32 | ||||||
|  | 	} | ||||||
|  | 	stack [8]uint64 | ||||||
|  | 	freer uint32 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func instantiateSQLite() (sqlt *sqlite, err error) { | ||||||
|  | 	if err := Initialize(); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sqlt = new(sqlite) | ||||||
|  | 	sqlt.ctx = util.NewContext(context.Background()) | ||||||
|  | 
 | ||||||
|  | 	sqlt.mod, err = instance.runtime.InstantiateModule(sqlt.ctx, | ||||||
|  | 		instance.compiled, wazero.NewModuleConfig().WithName("")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	global := sqlt.mod.ExportedGlobal("malloc_destructor") | ||||||
|  | 	if global == nil { | ||||||
|  | 		return nil, util.BadBinaryErr | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sqlt.freer = util.ReadUint32(sqlt.mod, uint32(global.Get())) | ||||||
|  | 	if sqlt.freer == 0 { | ||||||
|  | 		return nil, util.BadBinaryErr | ||||||
|  | 	} | ||||||
|  | 	return sqlt, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sqlt *sqlite) close() error { | ||||||
|  | 	return sqlt.mod.Close(sqlt.ctx) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sqlt *sqlite) error(rc uint64, handle uint32, sql ...string) error { | ||||||
|  | 	if rc == _OK { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err := Error{code: rc} | ||||||
|  | 
 | ||||||
|  | 	if err.Code() == NOMEM || err.ExtendedCode() == IOERR_NOMEM { | ||||||
|  | 		panic(util.OOMErr) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if r := sqlt.call("sqlite3_errstr", rc); r != 0 { | ||||||
|  | 		err.str = util.ReadString(sqlt.mod, uint32(r), _MAX_NAME) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if handle != 0 { | ||||||
|  | 		if r := sqlt.call("sqlite3_errmsg", uint64(handle)); r != 0 { | ||||||
|  | 			err.msg = util.ReadString(sqlt.mod, uint32(r), _MAX_LENGTH) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if sql != nil { | ||||||
|  | 			if r := sqlt.call("sqlite3_error_offset", uint64(handle)); r != math.MaxUint32 { | ||||||
|  | 				err.sql = sql[0][r:] | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch err.msg { | ||||||
|  | 	case err.str, "not an error": | ||||||
|  | 		err.msg = "" | ||||||
|  | 	} | ||||||
|  | 	return &err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sqlt *sqlite) getfn(name string) api.Function { | ||||||
|  | 	c := &sqlt.funcs | ||||||
|  | 	p := unsafe.StringData(name) | ||||||
|  | 	for i := range c.id { | ||||||
|  | 		if c.id[i] == p { | ||||||
|  | 			c.id[i] = nil | ||||||
|  | 			c.mask &^= uint32(1) << i | ||||||
|  | 			return c.fn[i] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return sqlt.mod.ExportedFunction(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sqlt *sqlite) putfn(name string, fn api.Function) { | ||||||
|  | 	c := &sqlt.funcs | ||||||
|  | 	p := unsafe.StringData(name) | ||||||
|  | 	i := bits.TrailingZeros32(^c.mask) | ||||||
|  | 	if i < 32 { | ||||||
|  | 		c.id[i] = p | ||||||
|  | 		c.fn[i] = fn | ||||||
|  | 		c.mask |= uint32(1) << i | ||||||
|  | 	} else { | ||||||
|  | 		c.id[0] = p | ||||||
|  | 		c.fn[0] = fn | ||||||
|  | 		c.mask = uint32(1) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sqlt *sqlite) call(name string, params ...uint64) uint64 { | ||||||
|  | 	copy(sqlt.stack[:], params) | ||||||
|  | 	fn := sqlt.getfn(name) | ||||||
|  | 	err := fn.CallWithStack(sqlt.ctx, sqlt.stack[:]) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	sqlt.putfn(name, fn) | ||||||
|  | 	return sqlt.stack[0] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sqlt *sqlite) free(ptr uint32) { | ||||||
|  | 	if ptr == 0 { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	sqlt.call("free", uint64(ptr)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sqlt *sqlite) new(size uint64) uint32 { | ||||||
|  | 	if size > _MAX_ALLOCATION_SIZE { | ||||||
|  | 		panic(util.OOMErr) | ||||||
|  | 	} | ||||||
|  | 	ptr := uint32(sqlt.call("malloc", size)) | ||||||
|  | 	if ptr == 0 && size != 0 { | ||||||
|  | 		panic(util.OOMErr) | ||||||
|  | 	} | ||||||
|  | 	return ptr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sqlt *sqlite) newBytes(b []byte) uint32 { | ||||||
|  | 	if (*[0]byte)(b) == nil { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 	ptr := sqlt.new(uint64(len(b))) | ||||||
|  | 	util.WriteBytes(sqlt.mod, ptr, b) | ||||||
|  | 	return ptr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sqlt *sqlite) newString(s string) uint32 { | ||||||
|  | 	ptr := sqlt.new(uint64(len(s) + 1)) | ||||||
|  | 	util.WriteString(sqlt.mod, ptr, s) | ||||||
|  | 	return ptr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sqlt *sqlite) newArena(size uint64) arena { | ||||||
|  | 	// Ensure the arena's size is a multiple of 8. | ||||||
|  | 	size = (size + 7) &^ 7 | ||||||
|  | 	return arena{ | ||||||
|  | 		sqlt: sqlt, | ||||||
|  | 		size: uint32(size), | ||||||
|  | 		base: sqlt.new(size), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type arena struct { | ||||||
|  | 	sqlt *sqlite | ||||||
|  | 	ptrs []uint32 | ||||||
|  | 	base uint32 | ||||||
|  | 	next uint32 | ||||||
|  | 	size uint32 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *arena) free() { | ||||||
|  | 	if a.sqlt == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	for _, ptr := range a.ptrs { | ||||||
|  | 		a.sqlt.free(ptr) | ||||||
|  | 	} | ||||||
|  | 	a.sqlt.free(a.base) | ||||||
|  | 	a.sqlt = nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *arena) mark() (reset func()) { | ||||||
|  | 	ptrs := len(a.ptrs) | ||||||
|  | 	next := a.next | ||||||
|  | 	return func() { | ||||||
|  | 		for _, ptr := range a.ptrs[ptrs:] { | ||||||
|  | 			a.sqlt.free(ptr) | ||||||
|  | 		} | ||||||
|  | 		a.ptrs = a.ptrs[:ptrs] | ||||||
|  | 		a.next = next | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *arena) new(size uint64) uint32 { | ||||||
|  | 	// Align the next address, to 4 or 8 bytes. | ||||||
|  | 	if size&7 != 0 { | ||||||
|  | 		a.next = (a.next + 3) &^ 3 | ||||||
|  | 	} else { | ||||||
|  | 		a.next = (a.next + 7) &^ 7 | ||||||
|  | 	} | ||||||
|  | 	if size <= uint64(a.size-a.next) { | ||||||
|  | 		ptr := a.base + a.next | ||||||
|  | 		a.next += uint32(size) | ||||||
|  | 		return ptr | ||||||
|  | 	} | ||||||
|  | 	ptr := a.sqlt.new(size) | ||||||
|  | 	a.ptrs = append(a.ptrs, ptr) | ||||||
|  | 	return ptr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *arena) bytes(b []byte) uint32 { | ||||||
|  | 	if (*[0]byte)(b) == nil { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 	ptr := a.new(uint64(len(b))) | ||||||
|  | 	util.WriteBytes(a.sqlt.mod, ptr, b) | ||||||
|  | 	return ptr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (a *arena) string(s string) uint32 { | ||||||
|  | 	ptr := a.new(uint64(len(s) + 1)) | ||||||
|  | 	util.WriteString(a.sqlt.mod, ptr, s) | ||||||
|  | 	return ptr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder { | ||||||
|  | 	util.ExportFuncII(env, "go_progress_handler", progressCallback) | ||||||
|  | 	util.ExportFuncIIII(env, "go_busy_timeout", timeoutCallback) | ||||||
|  | 	util.ExportFuncIII(env, "go_busy_handler", busyCallback) | ||||||
|  | 	util.ExportFuncII(env, "go_commit_hook", commitCallback) | ||||||
|  | 	util.ExportFuncVI(env, "go_rollback_hook", rollbackCallback) | ||||||
|  | 	util.ExportFuncVIIIIJ(env, "go_update_hook", updateCallback) | ||||||
|  | 	util.ExportFuncIIIII(env, "go_wal_hook", walCallback) | ||||||
|  | 	util.ExportFuncIIIIII(env, "go_autovacuum_pages", autoVacuumCallback) | ||||||
|  | 	util.ExportFuncIIIIIII(env, "go_authorizer", authorizerCallback) | ||||||
|  | 	util.ExportFuncVIII(env, "go_log", logCallback) | ||||||
|  | 	util.ExportFuncVI(env, "go_destroy", destroyCallback) | ||||||
|  | 	util.ExportFuncVIIII(env, "go_func", funcCallback) | ||||||
|  | 	util.ExportFuncVIIIII(env, "go_step", stepCallback) | ||||||
|  | 	util.ExportFuncVIII(env, "go_final", finalCallback) | ||||||
|  | 	util.ExportFuncVII(env, "go_value", valueCallback) | ||||||
|  | 	util.ExportFuncVIIII(env, "go_inverse", inverseCallback) | ||||||
|  | 	util.ExportFuncVIIII(env, "go_collation_needed", collationCallback) | ||||||
|  | 	util.ExportFuncIIIIII(env, "go_compare", compareCallback) | ||||||
|  | 	util.ExportFuncIIIIII(env, "go_vtab_create", vtabModuleCallback(xCreate)) | ||||||
|  | 	util.ExportFuncIIIIII(env, "go_vtab_connect", vtabModuleCallback(xConnect)) | ||||||
|  | 	util.ExportFuncII(env, "go_vtab_disconnect", vtabDisconnectCallback) | ||||||
|  | 	util.ExportFuncII(env, "go_vtab_destroy", vtabDestroyCallback) | ||||||
|  | 	util.ExportFuncIII(env, "go_vtab_best_index", vtabBestIndexCallback) | ||||||
|  | 	util.ExportFuncIIIII(env, "go_vtab_update", vtabUpdateCallback) | ||||||
|  | 	util.ExportFuncIII(env, "go_vtab_rename", vtabRenameCallback) | ||||||
|  | 	util.ExportFuncIIIII(env, "go_vtab_find_function", vtabFindFuncCallback) | ||||||
|  | 	util.ExportFuncII(env, "go_vtab_begin", vtabBeginCallback) | ||||||
|  | 	util.ExportFuncII(env, "go_vtab_sync", vtabSyncCallback) | ||||||
|  | 	util.ExportFuncII(env, "go_vtab_commit", vtabCommitCallback) | ||||||
|  | 	util.ExportFuncII(env, "go_vtab_rollback", vtabRollbackCallback) | ||||||
|  | 	util.ExportFuncIII(env, "go_vtab_savepoint", vtabSavepointCallback) | ||||||
|  | 	util.ExportFuncIII(env, "go_vtab_release", vtabReleaseCallback) | ||||||
|  | 	util.ExportFuncIII(env, "go_vtab_rollback_to", vtabRollbackToCallback) | ||||||
|  | 	util.ExportFuncIIIIII(env, "go_vtab_integrity", vtabIntegrityCallback) | ||||||
|  | 	util.ExportFuncIII(env, "go_cur_open", cursorOpenCallback) | ||||||
|  | 	util.ExportFuncII(env, "go_cur_close", cursorCloseCallback) | ||||||
|  | 	util.ExportFuncIIIIII(env, "go_cur_filter", cursorFilterCallback) | ||||||
|  | 	util.ExportFuncII(env, "go_cur_next", cursorNextCallback) | ||||||
|  | 	util.ExportFuncII(env, "go_cur_eof", cursorEOFCallback) | ||||||
|  | 	util.ExportFuncIIII(env, "go_cur_column", cursorColumnCallback) | ||||||
|  | 	util.ExportFuncIII(env, "go_cur_rowid", cursorRowIDCallback) | ||||||
|  | 	return env | ||||||
|  | } | ||||||
							
								
								
									
										639
									
								
								vendor/github.com/ncruces/go-sqlite3/stmt.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										639
									
								
								vendor/github.com/ncruces/go-sqlite3/stmt.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,639 @@ | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"math" | ||||||
|  | 	"strconv" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Stmt is a prepared statement object. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/stmt.html | ||||||
|  | type Stmt struct { | ||||||
|  | 	c      *Conn | ||||||
|  | 	err    error | ||||||
|  | 	handle uint32 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Close destroys the prepared statement object. | ||||||
|  | // | ||||||
|  | // It is safe to close a nil, zero or closed Stmt. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/finalize.html | ||||||
|  | func (s *Stmt) Close() error { | ||||||
|  | 	if s == nil || s.handle == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r := s.c.call("sqlite3_finalize", uint64(s.handle)) | ||||||
|  | 
 | ||||||
|  | 	s.handle = 0 | ||||||
|  | 	return s.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Conn returns the database connection to which the prepared statement belongs. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/db_handle.html | ||||||
|  | func (s *Stmt) Conn() *Conn { | ||||||
|  | 	return s.c | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ReadOnly returns true if and only if the statement | ||||||
|  | // makes no direct changes to the content of the database file. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/stmt_readonly.html | ||||||
|  | func (s *Stmt) ReadOnly() bool { | ||||||
|  | 	r := s.c.call("sqlite3_stmt_readonly", uint64(s.handle)) | ||||||
|  | 	return r != 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Reset resets the prepared statement object. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/reset.html | ||||||
|  | func (s *Stmt) Reset() error { | ||||||
|  | 	r := s.c.call("sqlite3_reset", uint64(s.handle)) | ||||||
|  | 	s.err = nil | ||||||
|  | 	return s.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Busy determines if a prepared statement has been reset. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/stmt_busy.html | ||||||
|  | func (s *Stmt) Busy() bool { | ||||||
|  | 	r := s.c.call("sqlite3_stmt_busy", uint64(s.handle)) | ||||||
|  | 	return r != 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Step evaluates the SQL statement. | ||||||
|  | // If the SQL statement being executed returns any data, | ||||||
|  | // then true is returned each time a new row of data is ready for processing by the caller. | ||||||
|  | // The values may be accessed using the Column access functions. | ||||||
|  | // Step is called again to retrieve the next row of data. | ||||||
|  | // If an error has occurred, Step returns false; | ||||||
|  | // call [Stmt.Err] or [Stmt.Reset] to get the error. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/step.html | ||||||
|  | func (s *Stmt) Step() bool { | ||||||
|  | 	s.c.checkInterrupt() | ||||||
|  | 	r := s.c.call("sqlite3_step", uint64(s.handle)) | ||||||
|  | 	switch r { | ||||||
|  | 	case _ROW: | ||||||
|  | 		s.err = nil | ||||||
|  | 		return true | ||||||
|  | 	case _DONE: | ||||||
|  | 		s.err = nil | ||||||
|  | 	default: | ||||||
|  | 		s.err = s.c.error(r) | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Err gets the last error occurred during [Stmt.Step]. | ||||||
|  | // Err returns nil after [Stmt.Reset] is called. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/step.html | ||||||
|  | func (s *Stmt) Err() error { | ||||||
|  | 	return s.err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Exec is a convenience function that repeatedly calls [Stmt.Step] until it returns false, | ||||||
|  | // then calls [Stmt.Reset] to reset the statement and get any error that occurred. | ||||||
|  | func (s *Stmt) Exec() error { | ||||||
|  | 	for s.Step() { | ||||||
|  | 	} | ||||||
|  | 	return s.Reset() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Status monitors the performance characteristics of prepared statements. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/stmt_status.html | ||||||
|  | func (s *Stmt) Status(op StmtStatus, reset bool) int { | ||||||
|  | 	if op > STMTSTATUS_FILTER_HIT && op != STMTSTATUS_MEMUSED { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  | 	var i uint64 | ||||||
|  | 	if reset { | ||||||
|  | 		i = 1 | ||||||
|  | 	} | ||||||
|  | 	r := s.c.call("sqlite3_stmt_status", uint64(s.handle), | ||||||
|  | 		uint64(op), i) | ||||||
|  | 	return int(int32(r)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ClearBindings resets all bindings on the prepared statement. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/clear_bindings.html | ||||||
|  | func (s *Stmt) ClearBindings() error { | ||||||
|  | 	r := s.c.call("sqlite3_clear_bindings", uint64(s.handle)) | ||||||
|  | 	return s.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindCount returns the number of SQL parameters in the prepared statement. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_parameter_count.html | ||||||
|  | func (s *Stmt) BindCount() int { | ||||||
|  | 	r := s.c.call("sqlite3_bind_parameter_count", | ||||||
|  | 		uint64(s.handle)) | ||||||
|  | 	return int(int32(r)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindIndex returns the index of a parameter in the prepared statement | ||||||
|  | // given its name. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_parameter_index.html | ||||||
|  | func (s *Stmt) BindIndex(name string) int { | ||||||
|  | 	defer s.c.arena.mark()() | ||||||
|  | 	namePtr := s.c.arena.string(name) | ||||||
|  | 	r := s.c.call("sqlite3_bind_parameter_index", | ||||||
|  | 		uint64(s.handle), uint64(namePtr)) | ||||||
|  | 	return int(int32(r)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindName returns the name of a parameter in the prepared statement. | ||||||
|  | // The leftmost SQL parameter has an index of 1. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_parameter_name.html | ||||||
|  | func (s *Stmt) BindName(param int) string { | ||||||
|  | 	r := s.c.call("sqlite3_bind_parameter_name", | ||||||
|  | 		uint64(s.handle), uint64(param)) | ||||||
|  | 
 | ||||||
|  | 	ptr := uint32(r) | ||||||
|  | 	if ptr == 0 { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return util.ReadString(s.c.mod, ptr, _MAX_NAME) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindBool binds a bool to the prepared statement. | ||||||
|  | // The leftmost SQL parameter has an index of 1. | ||||||
|  | // SQLite does not have a separate boolean storage class. | ||||||
|  | // Instead, boolean values are stored as integers 0 (false) and 1 (true). | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_blob.html | ||||||
|  | func (s *Stmt) BindBool(param int, value bool) error { | ||||||
|  | 	var i int64 | ||||||
|  | 	if value { | ||||||
|  | 		i = 1 | ||||||
|  | 	} | ||||||
|  | 	return s.BindInt64(param, i) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindInt binds an int to the prepared statement. | ||||||
|  | // The leftmost SQL parameter has an index of 1. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_blob.html | ||||||
|  | func (s *Stmt) BindInt(param int, value int) error { | ||||||
|  | 	return s.BindInt64(param, int64(value)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindInt64 binds an int64 to the prepared statement. | ||||||
|  | // The leftmost SQL parameter has an index of 1. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_blob.html | ||||||
|  | func (s *Stmt) BindInt64(param int, value int64) error { | ||||||
|  | 	r := s.c.call("sqlite3_bind_int64", | ||||||
|  | 		uint64(s.handle), uint64(param), uint64(value)) | ||||||
|  | 	return s.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindFloat binds a float64 to the prepared statement. | ||||||
|  | // The leftmost SQL parameter has an index of 1. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_blob.html | ||||||
|  | func (s *Stmt) BindFloat(param int, value float64) error { | ||||||
|  | 	r := s.c.call("sqlite3_bind_double", | ||||||
|  | 		uint64(s.handle), uint64(param), math.Float64bits(value)) | ||||||
|  | 	return s.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindText binds a string to the prepared statement. | ||||||
|  | // The leftmost SQL parameter has an index of 1. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_blob.html | ||||||
|  | func (s *Stmt) BindText(param int, value string) error { | ||||||
|  | 	if len(value) > _MAX_LENGTH { | ||||||
|  | 		return TOOBIG | ||||||
|  | 	} | ||||||
|  | 	ptr := s.c.newString(value) | ||||||
|  | 	r := s.c.call("sqlite3_bind_text64", | ||||||
|  | 		uint64(s.handle), uint64(param), | ||||||
|  | 		uint64(ptr), uint64(len(value)), | ||||||
|  | 		uint64(s.c.freer), _UTF8) | ||||||
|  | 	return s.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindRawText binds a []byte to the prepared statement as text. | ||||||
|  | // The leftmost SQL parameter has an index of 1. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_blob.html | ||||||
|  | func (s *Stmt) BindRawText(param int, value []byte) error { | ||||||
|  | 	if len(value) > _MAX_LENGTH { | ||||||
|  | 		return TOOBIG | ||||||
|  | 	} | ||||||
|  | 	ptr := s.c.newBytes(value) | ||||||
|  | 	r := s.c.call("sqlite3_bind_text64", | ||||||
|  | 		uint64(s.handle), uint64(param), | ||||||
|  | 		uint64(ptr), uint64(len(value)), | ||||||
|  | 		uint64(s.c.freer), _UTF8) | ||||||
|  | 	return s.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindBlob binds a []byte to the prepared statement. | ||||||
|  | // The leftmost SQL parameter has an index of 1. | ||||||
|  | // Binding a nil slice is the same as calling [Stmt.BindNull]. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_blob.html | ||||||
|  | func (s *Stmt) BindBlob(param int, value []byte) error { | ||||||
|  | 	if len(value) > _MAX_LENGTH { | ||||||
|  | 		return TOOBIG | ||||||
|  | 	} | ||||||
|  | 	ptr := s.c.newBytes(value) | ||||||
|  | 	r := s.c.call("sqlite3_bind_blob64", | ||||||
|  | 		uint64(s.handle), uint64(param), | ||||||
|  | 		uint64(ptr), uint64(len(value)), | ||||||
|  | 		uint64(s.c.freer)) | ||||||
|  | 	return s.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindZeroBlob binds a zero-filled, length n BLOB to the prepared statement. | ||||||
|  | // The leftmost SQL parameter has an index of 1. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_blob.html | ||||||
|  | func (s *Stmt) BindZeroBlob(param int, n int64) error { | ||||||
|  | 	r := s.c.call("sqlite3_bind_zeroblob64", | ||||||
|  | 		uint64(s.handle), uint64(param), uint64(n)) | ||||||
|  | 	return s.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindNull binds a NULL to the prepared statement. | ||||||
|  | // The leftmost SQL parameter has an index of 1. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_blob.html | ||||||
|  | func (s *Stmt) BindNull(param int) error { | ||||||
|  | 	r := s.c.call("sqlite3_bind_null", | ||||||
|  | 		uint64(s.handle), uint64(param)) | ||||||
|  | 	return s.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindTime binds a [time.Time] to the prepared statement. | ||||||
|  | // The leftmost SQL parameter has an index of 1. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_blob.html | ||||||
|  | func (s *Stmt) BindTime(param int, value time.Time, format TimeFormat) error { | ||||||
|  | 	if format == TimeFormatDefault { | ||||||
|  | 		return s.bindRFC3339Nano(param, value) | ||||||
|  | 	} | ||||||
|  | 	switch v := format.Encode(value).(type) { | ||||||
|  | 	case string: | ||||||
|  | 		s.BindText(param, v) | ||||||
|  | 	case int64: | ||||||
|  | 		s.BindInt64(param, v) | ||||||
|  | 	case float64: | ||||||
|  | 		s.BindFloat(param, v) | ||||||
|  | 	default: | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Stmt) bindRFC3339Nano(param int, value time.Time) error { | ||||||
|  | 	const maxlen = uint64(len(time.RFC3339Nano)) + 5 | ||||||
|  | 
 | ||||||
|  | 	ptr := s.c.new(maxlen) | ||||||
|  | 	buf := util.View(s.c.mod, ptr, maxlen) | ||||||
|  | 	buf = value.AppendFormat(buf[:0], time.RFC3339Nano) | ||||||
|  | 
 | ||||||
|  | 	r := s.c.call("sqlite3_bind_text64", | ||||||
|  | 		uint64(s.handle), uint64(param), | ||||||
|  | 		uint64(ptr), uint64(len(buf)), | ||||||
|  | 		uint64(s.c.freer), _UTF8) | ||||||
|  | 	return s.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindPointer binds a NULL to the prepared statement, just like [Stmt.BindNull], | ||||||
|  | // but it also associates ptr with that NULL value such that it can be retrieved | ||||||
|  | // within an application-defined SQL function using [Value.Pointer]. | ||||||
|  | // The leftmost SQL parameter has an index of 1. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_blob.html | ||||||
|  | func (s *Stmt) BindPointer(param int, ptr any) error { | ||||||
|  | 	valPtr := util.AddHandle(s.c.ctx, ptr) | ||||||
|  | 	r := s.c.call("sqlite3_bind_pointer_go", | ||||||
|  | 		uint64(s.handle), uint64(param), uint64(valPtr)) | ||||||
|  | 	return s.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindJSON binds the JSON encoding of value to the prepared statement. | ||||||
|  | // The leftmost SQL parameter has an index of 1. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_blob.html | ||||||
|  | func (s *Stmt) BindJSON(param int, value any) error { | ||||||
|  | 	data, err := json.Marshal(value) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return s.BindRawText(param, data) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BindValue binds a copy of value to the prepared statement. | ||||||
|  | // The leftmost SQL parameter has an index of 1. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/bind_blob.html | ||||||
|  | func (s *Stmt) BindValue(param int, value Value) error { | ||||||
|  | 	if value.c != s.c { | ||||||
|  | 		return MISUSE | ||||||
|  | 	} | ||||||
|  | 	r := s.c.call("sqlite3_bind_value", | ||||||
|  | 		uint64(s.handle), uint64(param), uint64(value.handle)) | ||||||
|  | 	return s.c.error(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnCount returns the number of columns in a result set. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_count.html | ||||||
|  | func (s *Stmt) ColumnCount() int { | ||||||
|  | 	r := s.c.call("sqlite3_column_count", | ||||||
|  | 		uint64(s.handle)) | ||||||
|  | 	return int(int32(r)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnName returns the name of the result column. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_name.html | ||||||
|  | func (s *Stmt) ColumnName(col int) string { | ||||||
|  | 	r := s.c.call("sqlite3_column_name", | ||||||
|  | 		uint64(s.handle), uint64(col)) | ||||||
|  | 	if r == 0 { | ||||||
|  | 		panic(util.OOMErr) | ||||||
|  | 	} | ||||||
|  | 	return util.ReadString(s.c.mod, uint32(r), _MAX_NAME) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnType returns the initial [Datatype] of the result column. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_blob.html | ||||||
|  | func (s *Stmt) ColumnType(col int) Datatype { | ||||||
|  | 	r := s.c.call("sqlite3_column_type", | ||||||
|  | 		uint64(s.handle), uint64(col)) | ||||||
|  | 	return Datatype(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnDeclType returns the declared datatype of the result column. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_decltype.html | ||||||
|  | func (s *Stmt) ColumnDeclType(col int) string { | ||||||
|  | 	r := s.c.call("sqlite3_column_decltype", | ||||||
|  | 		uint64(s.handle), uint64(col)) | ||||||
|  | 	if r == 0 { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return util.ReadString(s.c.mod, uint32(r), _MAX_NAME) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnDatabaseName returns the name of the database | ||||||
|  | // that is the origin of a particular result column. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_database_name.html | ||||||
|  | func (s *Stmt) ColumnDatabaseName(col int) string { | ||||||
|  | 	r := s.c.call("sqlite3_column_database_name", | ||||||
|  | 		uint64(s.handle), uint64(col)) | ||||||
|  | 	if r == 0 { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return util.ReadString(s.c.mod, uint32(r), _MAX_NAME) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnTableName returns the name of the table | ||||||
|  | // that is the origin of a particular result column. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_database_name.html | ||||||
|  | func (s *Stmt) ColumnTableName(col int) string { | ||||||
|  | 	r := s.c.call("sqlite3_column_table_name", | ||||||
|  | 		uint64(s.handle), uint64(col)) | ||||||
|  | 	if r == 0 { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return util.ReadString(s.c.mod, uint32(r), _MAX_NAME) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnOriginName returns the name of the table column | ||||||
|  | // that is the origin of a particular result column. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_database_name.html | ||||||
|  | func (s *Stmt) ColumnOriginName(col int) string { | ||||||
|  | 	r := s.c.call("sqlite3_column_origin_name", | ||||||
|  | 		uint64(s.handle), uint64(col)) | ||||||
|  | 	if r == 0 { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return util.ReadString(s.c.mod, uint32(r), _MAX_NAME) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnBool returns the value of the result column as a bool. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // SQLite does not have a separate boolean storage class. | ||||||
|  | // Instead, boolean values are retrieved as integers, | ||||||
|  | // with 0 converted to false and any other value to true. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_blob.html | ||||||
|  | func (s *Stmt) ColumnBool(col int) bool { | ||||||
|  | 	return s.ColumnInt64(col) != 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnInt returns the value of the result column as an int. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_blob.html | ||||||
|  | func (s *Stmt) ColumnInt(col int) int { | ||||||
|  | 	return int(s.ColumnInt64(col)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnInt64 returns the value of the result column as an int64. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_blob.html | ||||||
|  | func (s *Stmt) ColumnInt64(col int) int64 { | ||||||
|  | 	r := s.c.call("sqlite3_column_int64", | ||||||
|  | 		uint64(s.handle), uint64(col)) | ||||||
|  | 	return int64(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnFloat returns the value of the result column as a float64. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_blob.html | ||||||
|  | func (s *Stmt) ColumnFloat(col int) float64 { | ||||||
|  | 	r := s.c.call("sqlite3_column_double", | ||||||
|  | 		uint64(s.handle), uint64(col)) | ||||||
|  | 	return math.Float64frombits(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnTime returns the value of the result column as a [time.Time]. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_blob.html | ||||||
|  | func (s *Stmt) ColumnTime(col int, format TimeFormat) time.Time { | ||||||
|  | 	var v any | ||||||
|  | 	switch s.ColumnType(col) { | ||||||
|  | 	case INTEGER: | ||||||
|  | 		v = s.ColumnInt64(col) | ||||||
|  | 	case FLOAT: | ||||||
|  | 		v = s.ColumnFloat(col) | ||||||
|  | 	case TEXT, BLOB: | ||||||
|  | 		v = s.ColumnText(col) | ||||||
|  | 	case NULL: | ||||||
|  | 		return time.Time{} | ||||||
|  | 	default: | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | 	t, err := format.Decode(v) | ||||||
|  | 	if err != nil { | ||||||
|  | 		s.err = err | ||||||
|  | 	} | ||||||
|  | 	return t | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnText returns the value of the result column as a string. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_blob.html | ||||||
|  | func (s *Stmt) ColumnText(col int) string { | ||||||
|  | 	return string(s.ColumnRawText(col)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnBlob appends to buf and returns | ||||||
|  | // the value of the result column as a []byte. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_blob.html | ||||||
|  | func (s *Stmt) ColumnBlob(col int, buf []byte) []byte { | ||||||
|  | 	return append(buf, s.ColumnRawBlob(col)...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnRawText returns the value of the result column as a []byte. | ||||||
|  | // The []byte is owned by SQLite and may be invalidated by | ||||||
|  | // subsequent calls to [Stmt] methods. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_blob.html | ||||||
|  | func (s *Stmt) ColumnRawText(col int) []byte { | ||||||
|  | 	r := s.c.call("sqlite3_column_text", | ||||||
|  | 		uint64(s.handle), uint64(col)) | ||||||
|  | 	return s.columnRawBytes(col, uint32(r)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnRawBlob returns the value of the result column as a []byte. | ||||||
|  | // The []byte is owned by SQLite and may be invalidated by | ||||||
|  | // subsequent calls to [Stmt] methods. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_blob.html | ||||||
|  | func (s *Stmt) ColumnRawBlob(col int) []byte { | ||||||
|  | 	r := s.c.call("sqlite3_column_blob", | ||||||
|  | 		uint64(s.handle), uint64(col)) | ||||||
|  | 	return s.columnRawBytes(col, uint32(r)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Stmt) columnRawBytes(col int, ptr uint32) []byte { | ||||||
|  | 	if ptr == 0 { | ||||||
|  | 		r := s.c.call("sqlite3_errcode", uint64(s.c.handle)) | ||||||
|  | 		s.err = s.c.error(r) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r := s.c.call("sqlite3_column_bytes", | ||||||
|  | 		uint64(s.handle), uint64(col)) | ||||||
|  | 	return util.View(s.c.mod, ptr, r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnJSON parses the JSON-encoded value of the result column | ||||||
|  | // and stores it in the value pointed to by ptr. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_blob.html | ||||||
|  | func (s *Stmt) ColumnJSON(col int, ptr any) error { | ||||||
|  | 	var data []byte | ||||||
|  | 	switch s.ColumnType(col) { | ||||||
|  | 	case NULL: | ||||||
|  | 		data = append(data, "null"...) | ||||||
|  | 	case TEXT: | ||||||
|  | 		data = s.ColumnRawText(col) | ||||||
|  | 	case BLOB: | ||||||
|  | 		data = s.ColumnRawBlob(col) | ||||||
|  | 	case INTEGER: | ||||||
|  | 		data = strconv.AppendInt(nil, s.ColumnInt64(col), 10) | ||||||
|  | 	case FLOAT: | ||||||
|  | 		data = strconv.AppendFloat(nil, s.ColumnFloat(col), 'g', -1, 64) | ||||||
|  | 	default: | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | 	return json.Unmarshal(data, ptr) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ColumnValue returns the unprotected value of the result column. | ||||||
|  | // The leftmost column of the result set has the index 0. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/column_blob.html | ||||||
|  | func (s *Stmt) ColumnValue(col int) Value { | ||||||
|  | 	r := s.c.call("sqlite3_column_value", | ||||||
|  | 		uint64(s.handle), uint64(col)) | ||||||
|  | 	return Value{ | ||||||
|  | 		c:      s.c, | ||||||
|  | 		unprot: true, | ||||||
|  | 		handle: uint32(r), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Columns populates result columns into the provided slice. | ||||||
|  | // The slice must have [Stmt.ColumnCount] length. | ||||||
|  | // | ||||||
|  | // [INTEGER] columns will be retrieved as int64 values, | ||||||
|  | // [FLOAT] as float64, [NULL] as nil, | ||||||
|  | // [TEXT] as string, and [BLOB] as []byte. | ||||||
|  | // Any []byte are owned by SQLite and may be invalidated by | ||||||
|  | // subsequent calls to [Stmt] methods. | ||||||
|  | func (s *Stmt) Columns(dest []any) error { | ||||||
|  | 	defer s.c.arena.mark()() | ||||||
|  | 	count := uint64(len(dest)) | ||||||
|  | 	typePtr := s.c.arena.new(count) | ||||||
|  | 	dataPtr := s.c.arena.new(8 * count) | ||||||
|  | 
 | ||||||
|  | 	r := s.c.call("sqlite3_columns_go", | ||||||
|  | 		uint64(s.handle), count, uint64(typePtr), uint64(dataPtr)) | ||||||
|  | 	if err := s.c.error(r); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	types := util.View(s.c.mod, typePtr, count) | ||||||
|  | 	for i := range dest { | ||||||
|  | 		switch types[i] { | ||||||
|  | 		case byte(INTEGER): | ||||||
|  | 			dest[i] = int64(util.ReadUint64(s.c.mod, dataPtr+8*uint32(i))) | ||||||
|  | 			continue | ||||||
|  | 		case byte(FLOAT): | ||||||
|  | 			dest[i] = util.ReadFloat64(s.c.mod, dataPtr+8*uint32(i)) | ||||||
|  | 			continue | ||||||
|  | 		case byte(NULL): | ||||||
|  | 			dest[i] = nil | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		ptr := util.ReadUint32(s.c.mod, dataPtr+8*uint32(i)+0) | ||||||
|  | 		len := util.ReadUint32(s.c.mod, dataPtr+8*uint32(i)+4) | ||||||
|  | 		buf := util.View(s.c.mod, ptr, uint64(len)) | ||||||
|  | 		if types[i] == byte(TEXT) { | ||||||
|  | 			dest[i] = string(buf) | ||||||
|  | 		} else { | ||||||
|  | 			dest[i] = buf | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										354
									
								
								vendor/github.com/ncruces/go-sqlite3/time.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										354
									
								
								vendor/github.com/ncruces/go-sqlite3/time.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,354 @@ | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"math" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | 	"github.com/ncruces/julianday" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // TimeFormat specifies how to encode/decode time values. | ||||||
|  | // | ||||||
|  | // See the documentation for the [TimeFormatDefault] constant | ||||||
|  | // for formats recognized by SQLite. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/lang_datefunc.html | ||||||
|  | type TimeFormat string | ||||||
|  | 
 | ||||||
|  | // TimeFormats recognized by SQLite to encode/decode time values. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/lang_datefunc.html#time_values | ||||||
|  | const ( | ||||||
|  | 	TimeFormatDefault TimeFormat = "" // time.RFC3339Nano | ||||||
|  | 
 | ||||||
|  | 	// Text formats | ||||||
|  | 	TimeFormat1  TimeFormat = "2006-01-02" | ||||||
|  | 	TimeFormat2  TimeFormat = "2006-01-02 15:04" | ||||||
|  | 	TimeFormat3  TimeFormat = "2006-01-02 15:04:05" | ||||||
|  | 	TimeFormat4  TimeFormat = "2006-01-02 15:04:05.000" | ||||||
|  | 	TimeFormat5  TimeFormat = "2006-01-02T15:04" | ||||||
|  | 	TimeFormat6  TimeFormat = "2006-01-02T15:04:05" | ||||||
|  | 	TimeFormat7  TimeFormat = "2006-01-02T15:04:05.000" | ||||||
|  | 	TimeFormat8  TimeFormat = "15:04" | ||||||
|  | 	TimeFormat9  TimeFormat = "15:04:05" | ||||||
|  | 	TimeFormat10 TimeFormat = "15:04:05.000" | ||||||
|  | 
 | ||||||
|  | 	TimeFormat2TZ  = TimeFormat2 + "Z07:00" | ||||||
|  | 	TimeFormat3TZ  = TimeFormat3 + "Z07:00" | ||||||
|  | 	TimeFormat4TZ  = TimeFormat4 + "Z07:00" | ||||||
|  | 	TimeFormat5TZ  = TimeFormat5 + "Z07:00" | ||||||
|  | 	TimeFormat6TZ  = TimeFormat6 + "Z07:00" | ||||||
|  | 	TimeFormat7TZ  = TimeFormat7 + "Z07:00" | ||||||
|  | 	TimeFormat8TZ  = TimeFormat8 + "Z07:00" | ||||||
|  | 	TimeFormat9TZ  = TimeFormat9 + "Z07:00" | ||||||
|  | 	TimeFormat10TZ = TimeFormat10 + "Z07:00" | ||||||
|  | 
 | ||||||
|  | 	// Numeric formats | ||||||
|  | 	TimeFormatJulianDay TimeFormat = "julianday" | ||||||
|  | 	TimeFormatUnix      TimeFormat = "unixepoch" | ||||||
|  | 	TimeFormatUnixFrac  TimeFormat = "unixepoch_frac" | ||||||
|  | 	TimeFormatUnixMilli TimeFormat = "unixepoch_milli" // not an SQLite format | ||||||
|  | 	TimeFormatUnixMicro TimeFormat = "unixepoch_micro" // not an SQLite format | ||||||
|  | 	TimeFormatUnixNano  TimeFormat = "unixepoch_nano"  // not an SQLite format | ||||||
|  | 
 | ||||||
|  | 	// Auto | ||||||
|  | 	TimeFormatAuto TimeFormat = "auto" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Encode encodes a time value using this format. | ||||||
|  | // | ||||||
|  | // [TimeFormatDefault] and [TimeFormatAuto] encode using [time.RFC3339Nano], | ||||||
|  | // with nanosecond accuracy, and preserving any timezone offset. | ||||||
|  | // | ||||||
|  | // This is the format used by the [database/sql] driver: | ||||||
|  | // [database/sql.Row.Scan] will decode as [time.Time] | ||||||
|  | // values encoded with [time.RFC3339Nano]. | ||||||
|  | // | ||||||
|  | // Time values encoded with [time.RFC3339Nano] cannot be sorted as strings | ||||||
|  | // to produce a time-ordered sequence. | ||||||
|  | // | ||||||
|  | // Assuming that the time zones of the time values are the same (e.g., all in UTC), | ||||||
|  | // and expressed using the same string (e.g., all "Z" or all "+00:00"), | ||||||
|  | // use the TIME [collating sequence] to produce a time-ordered sequence. | ||||||
|  | // | ||||||
|  | // Otherwise, use [TimeFormat7] for time-ordered encoding. | ||||||
|  | // | ||||||
|  | // Formats [TimeFormat1] through [TimeFormat10] | ||||||
|  | // convert time values to UTC before encoding. | ||||||
|  | // | ||||||
|  | // Returns a string for the text formats, | ||||||
|  | // a float64 for [TimeFormatJulianDay] and [TimeFormatUnixFrac], | ||||||
|  | // or an int64 for the other numeric formats. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/lang_datefunc.html | ||||||
|  | // | ||||||
|  | // [collating sequence]: https://sqlite.org/datatype3.html#collating_sequences | ||||||
|  | func (f TimeFormat) Encode(t time.Time) any { | ||||||
|  | 	switch f { | ||||||
|  | 	// Numeric formats | ||||||
|  | 	case TimeFormatJulianDay: | ||||||
|  | 		return julianday.Float(t) | ||||||
|  | 	case TimeFormatUnix: | ||||||
|  | 		return t.Unix() | ||||||
|  | 	case TimeFormatUnixFrac: | ||||||
|  | 		return float64(t.Unix()) + float64(t.Nanosecond())*1e-9 | ||||||
|  | 	case TimeFormatUnixMilli: | ||||||
|  | 		return t.UnixMilli() | ||||||
|  | 	case TimeFormatUnixMicro: | ||||||
|  | 		return t.UnixMicro() | ||||||
|  | 	case TimeFormatUnixNano: | ||||||
|  | 		return t.UnixNano() | ||||||
|  | 	// Special formats. | ||||||
|  | 	case TimeFormatDefault, TimeFormatAuto: | ||||||
|  | 		f = time.RFC3339Nano | ||||||
|  | 	// SQLite assumes UTC if unspecified. | ||||||
|  | 	case | ||||||
|  | 		TimeFormat1, TimeFormat2, | ||||||
|  | 		TimeFormat3, TimeFormat4, | ||||||
|  | 		TimeFormat5, TimeFormat6, | ||||||
|  | 		TimeFormat7, TimeFormat8, | ||||||
|  | 		TimeFormat9, TimeFormat10: | ||||||
|  | 		t = t.UTC() | ||||||
|  | 	} | ||||||
|  | 	return t.Format(string(f)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Decode decodes a time value using this format. | ||||||
|  | // | ||||||
|  | // The time value can be a string, an int64, or a float64. | ||||||
|  | // | ||||||
|  | // Formats [TimeFormat8] through [TimeFormat10] | ||||||
|  | // (and [TimeFormat8TZ] through [TimeFormat10TZ]) | ||||||
|  | // assume a date of 2000-01-01. | ||||||
|  | // | ||||||
|  | // The timezone indicator and fractional seconds are always optional | ||||||
|  | // for formats [TimeFormat2] through [TimeFormat10] | ||||||
|  | // (and [TimeFormat2TZ] through [TimeFormat10TZ]). | ||||||
|  | // | ||||||
|  | // [TimeFormatAuto] implements (and extends) the SQLite auto modifier. | ||||||
|  | // Julian day numbers are safe to use for historical dates, | ||||||
|  | // from 4712BC through 9999AD. | ||||||
|  | // Unix timestamps (expressed in seconds, milliseconds, microseconds, or nanoseconds) | ||||||
|  | // are safe to use for current events, from at least 1980 through at least 2260. | ||||||
|  | // Unix timestamps before 1980 and after 9999 may be misinterpreted as julian day numbers, | ||||||
|  | // or have the wrong time unit. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/lang_datefunc.html | ||||||
|  | func (f TimeFormat) Decode(v any) (time.Time, error) { | ||||||
|  | 	switch f { | ||||||
|  | 	// Numeric formats. | ||||||
|  | 	case TimeFormatJulianDay: | ||||||
|  | 		switch v := v.(type) { | ||||||
|  | 		case string: | ||||||
|  | 			return julianday.Parse(v) | ||||||
|  | 		case float64: | ||||||
|  | 			return julianday.FloatTime(v), nil | ||||||
|  | 		case int64: | ||||||
|  | 			return julianday.Time(v, 0), nil | ||||||
|  | 		default: | ||||||
|  | 			return time.Time{}, util.TimeErr | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	case TimeFormatUnix, TimeFormatUnixFrac: | ||||||
|  | 		if s, ok := v.(string); ok { | ||||||
|  | 			f, err := strconv.ParseFloat(s, 64) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return time.Time{}, err | ||||||
|  | 			} | ||||||
|  | 			v = f | ||||||
|  | 		} | ||||||
|  | 		switch v := v.(type) { | ||||||
|  | 		case float64: | ||||||
|  | 			sec, frac := math.Modf(v) | ||||||
|  | 			nsec := math.Floor(frac * 1e9) | ||||||
|  | 			return time.Unix(int64(sec), int64(nsec)).UTC(), nil | ||||||
|  | 		case int64: | ||||||
|  | 			return time.Unix(v, 0).UTC(), nil | ||||||
|  | 		default: | ||||||
|  | 			return time.Time{}, util.TimeErr | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	case TimeFormatUnixMilli: | ||||||
|  | 		if s, ok := v.(string); ok { | ||||||
|  | 			i, err := strconv.ParseInt(s, 10, 64) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return time.Time{}, err | ||||||
|  | 			} | ||||||
|  | 			v = i | ||||||
|  | 		} | ||||||
|  | 		switch v := v.(type) { | ||||||
|  | 		case float64: | ||||||
|  | 			return time.UnixMilli(int64(math.Floor(v))).UTC(), nil | ||||||
|  | 		case int64: | ||||||
|  | 			return time.UnixMilli(v).UTC(), nil | ||||||
|  | 		default: | ||||||
|  | 			return time.Time{}, util.TimeErr | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	case TimeFormatUnixMicro: | ||||||
|  | 		if s, ok := v.(string); ok { | ||||||
|  | 			i, err := strconv.ParseInt(s, 10, 64) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return time.Time{}, err | ||||||
|  | 			} | ||||||
|  | 			v = i | ||||||
|  | 		} | ||||||
|  | 		switch v := v.(type) { | ||||||
|  | 		case float64: | ||||||
|  | 			return time.UnixMicro(int64(math.Floor(v))).UTC(), nil | ||||||
|  | 		case int64: | ||||||
|  | 			return time.UnixMicro(v).UTC(), nil | ||||||
|  | 		default: | ||||||
|  | 			return time.Time{}, util.TimeErr | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	case TimeFormatUnixNano: | ||||||
|  | 		if s, ok := v.(string); ok { | ||||||
|  | 			i, err := strconv.ParseInt(s, 10, 64) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return time.Time{}, util.TimeErr | ||||||
|  | 			} | ||||||
|  | 			v = i | ||||||
|  | 		} | ||||||
|  | 		switch v := v.(type) { | ||||||
|  | 		case float64: | ||||||
|  | 			return time.Unix(0, int64(math.Floor(v))).UTC(), nil | ||||||
|  | 		case int64: | ||||||
|  | 			return time.Unix(0, v).UTC(), nil | ||||||
|  | 		default: | ||||||
|  | 			return time.Time{}, util.TimeErr | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	// Special formats. | ||||||
|  | 	case TimeFormatAuto: | ||||||
|  | 		switch s := v.(type) { | ||||||
|  | 		case string: | ||||||
|  | 			i, err := strconv.ParseInt(s, 10, 64) | ||||||
|  | 			if err == nil { | ||||||
|  | 				v = i | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			f, err := strconv.ParseFloat(s, 64) | ||||||
|  | 			if err == nil { | ||||||
|  | 				v = f | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			dates := []TimeFormat{ | ||||||
|  | 				TimeFormat9, TimeFormat8, | ||||||
|  | 				TimeFormat6, TimeFormat5, | ||||||
|  | 				TimeFormat3, TimeFormat2, TimeFormat1, | ||||||
|  | 			} | ||||||
|  | 			for _, f := range dates { | ||||||
|  | 				t, err := f.Decode(s) | ||||||
|  | 				if err == nil { | ||||||
|  | 					return t, nil | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		switch v := v.(type) { | ||||||
|  | 		case float64: | ||||||
|  | 			if 0 <= v && v < 5373484.5 { | ||||||
|  | 				return TimeFormatJulianDay.Decode(v) | ||||||
|  | 			} | ||||||
|  | 			if v < 253402300800 { | ||||||
|  | 				return TimeFormatUnixFrac.Decode(v) | ||||||
|  | 			} | ||||||
|  | 			if v < 253402300800_000 { | ||||||
|  | 				return TimeFormatUnixMilli.Decode(v) | ||||||
|  | 			} | ||||||
|  | 			if v < 253402300800_000000 { | ||||||
|  | 				return TimeFormatUnixMicro.Decode(v) | ||||||
|  | 			} | ||||||
|  | 			return TimeFormatUnixNano.Decode(v) | ||||||
|  | 		case int64: | ||||||
|  | 			if 0 <= v && v < 5373485 { | ||||||
|  | 				return TimeFormatJulianDay.Decode(v) | ||||||
|  | 			} | ||||||
|  | 			if v < 253402300800 { | ||||||
|  | 				return TimeFormatUnixFrac.Decode(v) | ||||||
|  | 			} | ||||||
|  | 			if v < 253402300800_000 { | ||||||
|  | 				return TimeFormatUnixMilli.Decode(v) | ||||||
|  | 			} | ||||||
|  | 			if v < 253402300800_000000 { | ||||||
|  | 				return TimeFormatUnixMicro.Decode(v) | ||||||
|  | 			} | ||||||
|  | 			return TimeFormatUnixNano.Decode(v) | ||||||
|  | 		default: | ||||||
|  | 			return time.Time{}, util.TimeErr | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	case | ||||||
|  | 		TimeFormat2, TimeFormat2TZ, | ||||||
|  | 		TimeFormat3, TimeFormat3TZ, | ||||||
|  | 		TimeFormat4, TimeFormat4TZ, | ||||||
|  | 		TimeFormat5, TimeFormat5TZ, | ||||||
|  | 		TimeFormat6, TimeFormat6TZ, | ||||||
|  | 		TimeFormat7, TimeFormat7TZ: | ||||||
|  | 		s, ok := v.(string) | ||||||
|  | 		if !ok { | ||||||
|  | 			return time.Time{}, util.TimeErr | ||||||
|  | 		} | ||||||
|  | 		return f.parseRelaxed(s) | ||||||
|  | 
 | ||||||
|  | 	case | ||||||
|  | 		TimeFormat8, TimeFormat8TZ, | ||||||
|  | 		TimeFormat9, TimeFormat9TZ, | ||||||
|  | 		TimeFormat10, TimeFormat10TZ: | ||||||
|  | 		s, ok := v.(string) | ||||||
|  | 		if !ok { | ||||||
|  | 			return time.Time{}, util.TimeErr | ||||||
|  | 		} | ||||||
|  | 		t, err := f.parseRelaxed(s) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return time.Time{}, err | ||||||
|  | 		} | ||||||
|  | 		return t.AddDate(2000, 0, 0), nil | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		s, ok := v.(string) | ||||||
|  | 		if !ok { | ||||||
|  | 			return time.Time{}, util.TimeErr | ||||||
|  | 		} | ||||||
|  | 		if f == "" { | ||||||
|  | 			f = time.RFC3339Nano | ||||||
|  | 		} | ||||||
|  | 		return time.Parse(string(f), s) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f TimeFormat) parseRelaxed(s string) (time.Time, error) { | ||||||
|  | 	fs := string(f) | ||||||
|  | 	fs = strings.TrimSuffix(fs, "Z07:00") | ||||||
|  | 	fs = strings.TrimSuffix(fs, ".000") | ||||||
|  | 	t, err := time.Parse(fs+"Z07:00", s) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return time.Parse(fs, s) | ||||||
|  | 	} | ||||||
|  | 	return t, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Scanner returns a [database/sql.Scanner] that can be used as an argument to | ||||||
|  | // [database/sql.Row.Scan] and similar methods to | ||||||
|  | // decode a time value into dest using this format. | ||||||
|  | func (f TimeFormat) Scanner(dest *time.Time) interface{ Scan(any) error } { | ||||||
|  | 	return timeScanner{dest, f} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type timeScanner struct { | ||||||
|  | 	*time.Time | ||||||
|  | 	TimeFormat | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s timeScanner) Scan(src any) error { | ||||||
|  | 	var ok bool | ||||||
|  | 	var err error | ||||||
|  | 	if *s.Time, ok = src.(time.Time); !ok { | ||||||
|  | 		*s.Time, err = s.Decode(src) | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
							
								
								
									
										294
									
								
								vendor/github.com/ncruces/go-sqlite3/txn.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										294
									
								
								vendor/github.com/ncruces/go-sqlite3/txn.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,294 @@ | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"math/rand" | ||||||
|  | 	"runtime" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | 	"github.com/tetratelabs/wazero/api" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Txn is an in-progress database transaction. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/lang_transaction.html | ||||||
|  | type Txn struct { | ||||||
|  | 	c *Conn | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Begin starts a deferred transaction. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/lang_transaction.html | ||||||
|  | func (c *Conn) Begin() Txn { | ||||||
|  | 	// BEGIN even if interrupted. | ||||||
|  | 	err := c.txnExecInterrupted(`BEGIN DEFERRED`) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	return Txn{c} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BeginImmediate starts an immediate transaction. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/lang_transaction.html | ||||||
|  | func (c *Conn) BeginImmediate() (Txn, error) { | ||||||
|  | 	err := c.Exec(`BEGIN IMMEDIATE`) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return Txn{}, err | ||||||
|  | 	} | ||||||
|  | 	return Txn{c}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // BeginExclusive starts an exclusive transaction. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/lang_transaction.html | ||||||
|  | func (c *Conn) BeginExclusive() (Txn, error) { | ||||||
|  | 	err := c.Exec(`BEGIN EXCLUSIVE`) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return Txn{}, err | ||||||
|  | 	} | ||||||
|  | 	return Txn{c}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // End calls either [Txn.Commit] or [Txn.Rollback] | ||||||
|  | // depending on whether *error points to a nil or non-nil error. | ||||||
|  | // | ||||||
|  | // This is meant to be deferred: | ||||||
|  | // | ||||||
|  | //	func doWork(db *sqlite3.Conn) (err error) { | ||||||
|  | //		tx := db.Begin() | ||||||
|  | //		defer tx.End(&err) | ||||||
|  | // | ||||||
|  | //		// ... do work in the transaction | ||||||
|  | //	} | ||||||
|  | // | ||||||
|  | // https://sqlite.org/lang_transaction.html | ||||||
|  | func (tx Txn) End(errp *error) { | ||||||
|  | 	recovered := recover() | ||||||
|  | 	if recovered != nil { | ||||||
|  | 		defer panic(recovered) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if *errp == nil && recovered == nil { | ||||||
|  | 		// Success path. | ||||||
|  | 		if tx.c.GetAutocommit() { // There is nothing to commit. | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		*errp = tx.Commit() | ||||||
|  | 		if *errp == nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		// Fall through to the error path. | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Error path. | ||||||
|  | 	if tx.c.GetAutocommit() { // There is nothing to rollback. | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	err := tx.Rollback() | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Commit commits the transaction. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/lang_transaction.html | ||||||
|  | func (tx Txn) Commit() error { | ||||||
|  | 	return tx.c.Exec(`COMMIT`) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Rollback rolls back the transaction, | ||||||
|  | // even if the connection has been interrupted. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/lang_transaction.html | ||||||
|  | func (tx Txn) Rollback() error { | ||||||
|  | 	return tx.c.txnExecInterrupted(`ROLLBACK`) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Savepoint is a marker within a transaction | ||||||
|  | // that allows for partial rollback. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/lang_savepoint.html | ||||||
|  | type Savepoint struct { | ||||||
|  | 	c    *Conn | ||||||
|  | 	name string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Savepoint establishes a new transaction savepoint. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/lang_savepoint.html | ||||||
|  | func (c *Conn) Savepoint() Savepoint { | ||||||
|  | 	// Names can be reused; this makes catching bugs more likely. | ||||||
|  | 	name := saveptName() + "_" + strconv.Itoa(int(rand.Int31())) | ||||||
|  | 
 | ||||||
|  | 	err := c.txnExecInterrupted(fmt.Sprintf("SAVEPOINT %q;", name)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	return Savepoint{c: c, name: name} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func saveptName() (name string) { | ||||||
|  | 	defer func() { | ||||||
|  | 		if name == "" { | ||||||
|  | 			name = "sqlite3.Savepoint" | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	var pc [8]uintptr | ||||||
|  | 	n := runtime.Callers(3, pc[:]) | ||||||
|  | 	if n <= 0 { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	frames := runtime.CallersFrames(pc[:n]) | ||||||
|  | 	frame, more := frames.Next() | ||||||
|  | 	for more && (strings.HasPrefix(frame.Function, "database/sql.") || | ||||||
|  | 		strings.HasPrefix(frame.Function, "github.com/ncruces/go-sqlite3/driver.")) { | ||||||
|  | 		frame, more = frames.Next() | ||||||
|  | 	} | ||||||
|  | 	return frame.Function | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Release releases the savepoint rolling back any changes | ||||||
|  | // if *error points to a non-nil error. | ||||||
|  | // | ||||||
|  | // This is meant to be deferred: | ||||||
|  | // | ||||||
|  | //	func doWork(db *sqlite3.Conn) (err error) { | ||||||
|  | //		savept := db.Savepoint() | ||||||
|  | //		defer savept.Release(&err) | ||||||
|  | // | ||||||
|  | //		// ... do work in the transaction | ||||||
|  | //	} | ||||||
|  | func (s Savepoint) Release(errp *error) { | ||||||
|  | 	recovered := recover() | ||||||
|  | 	if recovered != nil { | ||||||
|  | 		defer panic(recovered) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if *errp == nil && recovered == nil { | ||||||
|  | 		// Success path. | ||||||
|  | 		if s.c.GetAutocommit() { // There is nothing to commit. | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		*errp = s.c.Exec(fmt.Sprintf("RELEASE %q;", s.name)) | ||||||
|  | 		if *errp == nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		// Fall through to the error path. | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Error path. | ||||||
|  | 	if s.c.GetAutocommit() { // There is nothing to rollback. | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	// ROLLBACK and RELEASE even if interrupted. | ||||||
|  | 	err := s.c.txnExecInterrupted(fmt.Sprintf(` | ||||||
|  | 		ROLLBACK TO %[1]q; | ||||||
|  | 		RELEASE %[1]q; | ||||||
|  | 	`, s.name)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Rollback rolls the transaction back to the savepoint, | ||||||
|  | // even if the connection has been interrupted. | ||||||
|  | // Rollback does not release the savepoint. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/lang_transaction.html | ||||||
|  | func (s Savepoint) Rollback() error { | ||||||
|  | 	// ROLLBACK even if interrupted. | ||||||
|  | 	return s.c.txnExecInterrupted(fmt.Sprintf("ROLLBACK TO %q;", s.name)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *Conn) txnExecInterrupted(sql string) error { | ||||||
|  | 	err := c.Exec(sql) | ||||||
|  | 	if errors.Is(err, INTERRUPT) { | ||||||
|  | 		old := c.SetInterrupt(context.Background()) | ||||||
|  | 		defer c.SetInterrupt(old) | ||||||
|  | 		err = c.Exec(sql) | ||||||
|  | 	} | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TxnState starts a deferred transaction. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/txn_state.html | ||||||
|  | func (c *Conn) TxnState(schema string) TxnState { | ||||||
|  | 	var ptr uint32 | ||||||
|  | 	if schema != "" { | ||||||
|  | 		defer c.arena.mark()() | ||||||
|  | 		ptr = c.arena.string(schema) | ||||||
|  | 	} | ||||||
|  | 	r := c.call("sqlite3_txn_state", uint64(c.handle), uint64(ptr)) | ||||||
|  | 	return TxnState(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CommitHook registers a callback function to be invoked | ||||||
|  | // whenever a transaction is committed. | ||||||
|  | // Return true to allow the commit operation to continue normally. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/commit_hook.html | ||||||
|  | func (c *Conn) CommitHook(cb func() (ok bool)) { | ||||||
|  | 	var enable uint64 | ||||||
|  | 	if cb != nil { | ||||||
|  | 		enable = 1 | ||||||
|  | 	} | ||||||
|  | 	c.call("sqlite3_commit_hook_go", uint64(c.handle), enable) | ||||||
|  | 	c.commit = cb | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RollbackHook registers a callback function to be invoked | ||||||
|  | // whenever a transaction is rolled back. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/commit_hook.html | ||||||
|  | func (c *Conn) RollbackHook(cb func()) { | ||||||
|  | 	var enable uint64 | ||||||
|  | 	if cb != nil { | ||||||
|  | 		enable = 1 | ||||||
|  | 	} | ||||||
|  | 	c.call("sqlite3_rollback_hook_go", uint64(c.handle), enable) | ||||||
|  | 	c.rollback = cb | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // UpdateHook registers a callback function to be invoked | ||||||
|  | // whenever a row is updated, inserted or deleted in a rowid table. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/update_hook.html | ||||||
|  | func (c *Conn) UpdateHook(cb func(action AuthorizerActionCode, schema, table string, rowid int64)) { | ||||||
|  | 	var enable uint64 | ||||||
|  | 	if cb != nil { | ||||||
|  | 		enable = 1 | ||||||
|  | 	} | ||||||
|  | 	c.call("sqlite3_update_hook_go", uint64(c.handle), enable) | ||||||
|  | 	c.update = cb | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func commitCallback(ctx context.Context, mod api.Module, pDB uint32) (rollback uint32) { | ||||||
|  | 	if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.commit != nil { | ||||||
|  | 		if !c.commit() { | ||||||
|  | 			rollback = 1 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return rollback | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func rollbackCallback(ctx context.Context, mod api.Module, pDB uint32) { | ||||||
|  | 	if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.rollback != nil { | ||||||
|  | 		c.rollback() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func updateCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zSchema, zTabName uint32, rowid uint64) { | ||||||
|  | 	if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.update != nil { | ||||||
|  | 		schema := util.ReadString(mod, zSchema, _MAX_NAME) | ||||||
|  | 		table := util.ReadString(mod, zTabName, _MAX_NAME) | ||||||
|  | 		c.update(action, schema, table, int64(rowid)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								vendor/github.com/ncruces/go-sqlite3/util/osutil/open.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								vendor/github.com/ncruces/go-sqlite3/util/osutil/open.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | ||||||
|  | //go:build !windows | ||||||
|  | 
 | ||||||
|  | package osutil | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"io/fs" | ||||||
|  | 	"os" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // OpenFile behaves the same as [os.OpenFile], | ||||||
|  | // except on Windows it sets [syscall.FILE_SHARE_DELETE]. | ||||||
|  | // | ||||||
|  | // See: https://go.dev/issue/32088#issuecomment-502850674 | ||||||
|  | func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) { | ||||||
|  | 	return os.OpenFile(name, flag, perm) | ||||||
|  | } | ||||||
							
								
								
									
										112
									
								
								vendor/github.com/ncruces/go-sqlite3/util/osutil/open_windows.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								vendor/github.com/ncruces/go-sqlite3/util/osutil/open_windows.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,112 @@ | ||||||
|  | package osutil | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"io/fs" | ||||||
|  | 	"os" | ||||||
|  | 	. "syscall" | ||||||
|  | 	"unsafe" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // OpenFile behaves the same as [os.OpenFile], | ||||||
|  | // except on Windows it sets [syscall.FILE_SHARE_DELETE]. | ||||||
|  | // | ||||||
|  | // See: https://go.dev/issue/32088#issuecomment-502850674 | ||||||
|  | func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) { | ||||||
|  | 	if name == "" { | ||||||
|  | 		return nil, &os.PathError{Op: "open", Path: name, Err: ENOENT} | ||||||
|  | 	} | ||||||
|  | 	r, e := syscallOpen(name, flag, uint32(perm.Perm())) | ||||||
|  | 	if e != nil { | ||||||
|  | 		return nil, &os.PathError{Op: "open", Path: name, Err: e} | ||||||
|  | 	} | ||||||
|  | 	return os.NewFile(uintptr(r), name), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // syscallOpen is a copy of [syscall.Open] | ||||||
|  | // that uses [syscall.FILE_SHARE_DELETE]. | ||||||
|  | // | ||||||
|  | // https://go.dev/src/syscall/syscall_windows.go | ||||||
|  | func syscallOpen(path string, mode int, perm uint32) (fd Handle, err error) { | ||||||
|  | 	if len(path) == 0 { | ||||||
|  | 		return InvalidHandle, ERROR_FILE_NOT_FOUND | ||||||
|  | 	} | ||||||
|  | 	pathp, err := UTF16PtrFromString(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return InvalidHandle, err | ||||||
|  | 	} | ||||||
|  | 	var access uint32 | ||||||
|  | 	switch mode & (O_RDONLY | O_WRONLY | O_RDWR) { | ||||||
|  | 	case O_RDONLY: | ||||||
|  | 		access = GENERIC_READ | ||||||
|  | 	case O_WRONLY: | ||||||
|  | 		access = GENERIC_WRITE | ||||||
|  | 	case O_RDWR: | ||||||
|  | 		access = GENERIC_READ | GENERIC_WRITE | ||||||
|  | 	} | ||||||
|  | 	if mode&O_CREAT != 0 { | ||||||
|  | 		access |= GENERIC_WRITE | ||||||
|  | 	} | ||||||
|  | 	if mode&O_APPEND != 0 { | ||||||
|  | 		access &^= GENERIC_WRITE | ||||||
|  | 		access |= FILE_APPEND_DATA | ||||||
|  | 	} | ||||||
|  | 	sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE) | ||||||
|  | 	var sa *SecurityAttributes | ||||||
|  | 	if mode&O_CLOEXEC == 0 { | ||||||
|  | 		sa = makeInheritSa() | ||||||
|  | 	} | ||||||
|  | 	var createmode uint32 | ||||||
|  | 	switch { | ||||||
|  | 	case mode&(O_CREAT|O_EXCL) == (O_CREAT | O_EXCL): | ||||||
|  | 		createmode = CREATE_NEW | ||||||
|  | 	case mode&(O_CREAT|O_TRUNC) == (O_CREAT | O_TRUNC): | ||||||
|  | 		createmode = CREATE_ALWAYS | ||||||
|  | 	case mode&O_CREAT == O_CREAT: | ||||||
|  | 		createmode = OPEN_ALWAYS | ||||||
|  | 	case mode&O_TRUNC == O_TRUNC: | ||||||
|  | 		createmode = TRUNCATE_EXISTING | ||||||
|  | 	default: | ||||||
|  | 		createmode = OPEN_EXISTING | ||||||
|  | 	} | ||||||
|  | 	var attrs uint32 = FILE_ATTRIBUTE_NORMAL | ||||||
|  | 	if perm&S_IWRITE == 0 { | ||||||
|  | 		attrs = FILE_ATTRIBUTE_READONLY | ||||||
|  | 		if createmode == CREATE_ALWAYS { | ||||||
|  | 			const _ERROR_BAD_NETPATH = Errno(53) | ||||||
|  | 			// We have been asked to create a read-only file. | ||||||
|  | 			// If the file already exists, the semantics of | ||||||
|  | 			// the Unix open system call is to preserve the | ||||||
|  | 			// existing permissions. If we pass CREATE_ALWAYS | ||||||
|  | 			// and FILE_ATTRIBUTE_READONLY to CreateFile, | ||||||
|  | 			// and the file already exists, CreateFile will | ||||||
|  | 			// change the file permissions. | ||||||
|  | 			// Avoid that to preserve the Unix semantics. | ||||||
|  | 			h, e := CreateFile(pathp, access, sharemode, sa, TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) | ||||||
|  | 			switch e { | ||||||
|  | 			case ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, ERROR_PATH_NOT_FOUND: | ||||||
|  | 				// File does not exist. These are the same | ||||||
|  | 				// errors as Errno.Is checks for ErrNotExist. | ||||||
|  | 				// Carry on to create the file. | ||||||
|  | 			default: | ||||||
|  | 				// Success or some different error. | ||||||
|  | 				return h, e | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if createmode == OPEN_EXISTING && access == GENERIC_READ { | ||||||
|  | 		// Necessary for opening directory handles. | ||||||
|  | 		attrs |= FILE_FLAG_BACKUP_SEMANTICS | ||||||
|  | 	} | ||||||
|  | 	if mode&O_SYNC != 0 { | ||||||
|  | 		const _FILE_FLAG_WRITE_THROUGH = 0x80000000 | ||||||
|  | 		attrs |= _FILE_FLAG_WRITE_THROUGH | ||||||
|  | 	} | ||||||
|  | 	return CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func makeInheritSa() *SecurityAttributes { | ||||||
|  | 	var sa SecurityAttributes | ||||||
|  | 	sa.Length = uint32(unsafe.Sizeof(sa)) | ||||||
|  | 	sa.InheritHandle = 1 | ||||||
|  | 	return &sa | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								vendor/github.com/ncruces/go-sqlite3/util/osutil/osfs.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								vendor/github.com/ncruces/go-sqlite3/util/osutil/osfs.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | ||||||
|  | package osutil | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"io/fs" | ||||||
|  | 	"os" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // FS implements [fs.FS], [fs.StatFS], and [fs.ReadFileFS] | ||||||
|  | // using package [os]. | ||||||
|  | // | ||||||
|  | // This filesystem does not respect [fs.ValidPath] rules, | ||||||
|  | // and fails [testing/fstest.TestFS]! | ||||||
|  | // | ||||||
|  | // Still, it can be a useful tool to unify implementations | ||||||
|  | // that can access either the [os] filesystem or an [fs.FS]. | ||||||
|  | // It's OK to use this to open files, but you should avoid | ||||||
|  | // opening directories, resolving paths, or walking the file system. | ||||||
|  | type FS struct{} | ||||||
|  | 
 | ||||||
|  | // Open implements [fs.FS]. | ||||||
|  | func (FS) Open(name string) (fs.File, error) { | ||||||
|  | 	return OpenFile(name, os.O_RDONLY, 0) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ReadFileFS implements [fs.StatFS]. | ||||||
|  | func (FS) Stat(name string) (fs.FileInfo, error) { | ||||||
|  | 	return os.Stat(name) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ReadFile implements [fs.ReadFileFS]. | ||||||
|  | func (FS) ReadFile(name string) ([]byte, error) { | ||||||
|  | 	return os.ReadFile(name) | ||||||
|  | } | ||||||
							
								
								
									
										2
									
								
								vendor/github.com/ncruces/go-sqlite3/util/osutil/osutil.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/ncruces/go-sqlite3/util/osutil/osutil.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | ||||||
|  | // Package osutil implements operating system utility functions. | ||||||
|  | package osutil | ||||||
							
								
								
									
										236
									
								
								vendor/github.com/ncruces/go-sqlite3/value.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								vendor/github.com/ncruces/go-sqlite3/value.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,236 @@ | ||||||
|  | package sqlite3 | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"math" | ||||||
|  | 	"strconv" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Value is any value that can be stored in a database table. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/value.html | ||||||
|  | type Value struct { | ||||||
|  | 	c      *Conn | ||||||
|  | 	handle uint32 | ||||||
|  | 	unprot bool | ||||||
|  | 	copied bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v Value) protected() uint64 { | ||||||
|  | 	if v.unprot { | ||||||
|  | 		panic(util.ValueErr) | ||||||
|  | 	} | ||||||
|  | 	return uint64(v.handle) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Dup makes a copy of the SQL value and returns a pointer to that copy. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/value_dup.html | ||||||
|  | func (v Value) Dup() *Value { | ||||||
|  | 	r := v.c.call("sqlite3_value_dup", uint64(v.handle)) | ||||||
|  | 	return &Value{ | ||||||
|  | 		c:      v.c, | ||||||
|  | 		copied: true, | ||||||
|  | 		handle: uint32(r), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Close frees an SQL value previously obtained by [Value.Dup]. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/value_dup.html | ||||||
|  | func (dup *Value) Close() error { | ||||||
|  | 	if !dup.copied { | ||||||
|  | 		panic(util.ValueErr) | ||||||
|  | 	} | ||||||
|  | 	dup.c.call("sqlite3_value_free", uint64(dup.handle)) | ||||||
|  | 	dup.handle = 0 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Type returns the initial datatype of the value. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/value_blob.html | ||||||
|  | func (v Value) Type() Datatype { | ||||||
|  | 	r := v.c.call("sqlite3_value_type", v.protected()) | ||||||
|  | 	return Datatype(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Type returns the numeric datatype of the value. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/value_blob.html | ||||||
|  | func (v Value) NumericType() Datatype { | ||||||
|  | 	r := v.c.call("sqlite3_value_numeric_type", v.protected()) | ||||||
|  | 	return Datatype(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Bool returns the value as a bool. | ||||||
|  | // SQLite does not have a separate boolean storage class. | ||||||
|  | // Instead, boolean values are retrieved as integers, | ||||||
|  | // with 0 converted to false and any other value to true. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/value_blob.html | ||||||
|  | func (v Value) Bool() bool { | ||||||
|  | 	return v.Int64() != 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Int returns the value as an int. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/value_blob.html | ||||||
|  | func (v Value) Int() int { | ||||||
|  | 	return int(v.Int64()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Int64 returns the value as an int64. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/value_blob.html | ||||||
|  | func (v Value) Int64() int64 { | ||||||
|  | 	r := v.c.call("sqlite3_value_int64", v.protected()) | ||||||
|  | 	return int64(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Float returns the value as a float64. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/value_blob.html | ||||||
|  | func (v Value) Float() float64 { | ||||||
|  | 	r := v.c.call("sqlite3_value_double", v.protected()) | ||||||
|  | 	return math.Float64frombits(r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Time returns the value as a [time.Time]. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/value_blob.html | ||||||
|  | func (v Value) Time(format TimeFormat) time.Time { | ||||||
|  | 	var a any | ||||||
|  | 	switch v.Type() { | ||||||
|  | 	case INTEGER: | ||||||
|  | 		a = v.Int64() | ||||||
|  | 	case FLOAT: | ||||||
|  | 		a = v.Float() | ||||||
|  | 	case TEXT, BLOB: | ||||||
|  | 		a = v.Text() | ||||||
|  | 	case NULL: | ||||||
|  | 		return time.Time{} | ||||||
|  | 	default: | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | 	t, _ := format.Decode(a) | ||||||
|  | 	return t | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Text returns the value as a string. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/value_blob.html | ||||||
|  | func (v Value) Text() string { | ||||||
|  | 	return string(v.RawText()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Blob appends to buf and returns | ||||||
|  | // the value as a []byte. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/value_blob.html | ||||||
|  | func (v Value) Blob(buf []byte) []byte { | ||||||
|  | 	return append(buf, v.RawBlob()...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RawText returns the value as a []byte. | ||||||
|  | // The []byte is owned by SQLite and may be invalidated by | ||||||
|  | // subsequent calls to [Value] methods. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/value_blob.html | ||||||
|  | func (v Value) RawText() []byte { | ||||||
|  | 	r := v.c.call("sqlite3_value_text", v.protected()) | ||||||
|  | 	return v.rawBytes(uint32(r)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // RawBlob returns the value as a []byte. | ||||||
|  | // The []byte is owned by SQLite and may be invalidated by | ||||||
|  | // subsequent calls to [Value] methods. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/value_blob.html | ||||||
|  | func (v Value) RawBlob() []byte { | ||||||
|  | 	r := v.c.call("sqlite3_value_blob", v.protected()) | ||||||
|  | 	return v.rawBytes(uint32(r)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (v Value) rawBytes(ptr uint32) []byte { | ||||||
|  | 	if ptr == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	r := v.c.call("sqlite3_value_bytes", v.protected()) | ||||||
|  | 	return util.View(v.c.mod, ptr, r) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Pointer gets the pointer associated with this value, | ||||||
|  | // or nil if it has no associated pointer. | ||||||
|  | func (v Value) Pointer() any { | ||||||
|  | 	r := v.c.call("sqlite3_value_pointer_go", v.protected()) | ||||||
|  | 	return util.GetHandle(v.c.ctx, uint32(r)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // JSON parses a JSON-encoded value | ||||||
|  | // and stores the result in the value pointed to by ptr. | ||||||
|  | func (v Value) JSON(ptr any) error { | ||||||
|  | 	var data []byte | ||||||
|  | 	switch v.Type() { | ||||||
|  | 	case NULL: | ||||||
|  | 		data = append(data, "null"...) | ||||||
|  | 	case TEXT: | ||||||
|  | 		data = v.RawText() | ||||||
|  | 	case BLOB: | ||||||
|  | 		data = v.RawBlob() | ||||||
|  | 	case INTEGER: | ||||||
|  | 		data = strconv.AppendInt(nil, v.Int64(), 10) | ||||||
|  | 	case FLOAT: | ||||||
|  | 		data = strconv.AppendFloat(nil, v.Float(), 'g', -1, 64) | ||||||
|  | 	default: | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | 	return json.Unmarshal(data, ptr) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NoChange returns true if and only if the value is unchanged | ||||||
|  | // in a virtual table update operatiom. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/value_blob.html | ||||||
|  | func (v Value) NoChange() bool { | ||||||
|  | 	r := v.c.call("sqlite3_value_nochange", v.protected()) | ||||||
|  | 	return r != 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // InFirst returns the first element | ||||||
|  | // on the right-hand side of an IN constraint. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/vtab_in_first.html | ||||||
|  | func (v Value) InFirst() (Value, error) { | ||||||
|  | 	defer v.c.arena.mark()() | ||||||
|  | 	valPtr := v.c.arena.new(ptrlen) | ||||||
|  | 	r := v.c.call("sqlite3_vtab_in_first", uint64(v.handle), uint64(valPtr)) | ||||||
|  | 	if err := v.c.error(r); err != nil { | ||||||
|  | 		return Value{}, err | ||||||
|  | 	} | ||||||
|  | 	return Value{ | ||||||
|  | 		c:      v.c, | ||||||
|  | 		handle: util.ReadUint32(v.c.mod, valPtr), | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // InNext returns the next element | ||||||
|  | // on the right-hand side of an IN constraint. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/vtab_in_first.html | ||||||
|  | func (v Value) InNext() (Value, error) { | ||||||
|  | 	defer v.c.arena.mark()() | ||||||
|  | 	valPtr := v.c.arena.new(ptrlen) | ||||||
|  | 	r := v.c.call("sqlite3_vtab_in_next", uint64(v.handle), uint64(valPtr)) | ||||||
|  | 	if err := v.c.error(r); err != nil { | ||||||
|  | 		return Value{}, err | ||||||
|  | 	} | ||||||
|  | 	return Value{ | ||||||
|  | 		c:      v.c, | ||||||
|  | 		handle: util.ReadUint32(v.c.mod, valPtr), | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
							
								
								
									
										86
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/README.md
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/README.md
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,86 @@ | ||||||
|  | # Go SQLite VFS API | ||||||
|  | 
 | ||||||
|  | This package implements the SQLite [OS Interface](https://sqlite.org/vfs.html) (aka VFS). | ||||||
|  | 
 | ||||||
|  | It replaces the default SQLite VFS with a **pure Go** implementation, | ||||||
|  | and exposes [interfaces](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#VFS) | ||||||
|  | that should allow you to implement your own custom VFSes. | ||||||
|  | 
 | ||||||
|  | Since it is a from scratch reimplementation, | ||||||
|  | there are naturally some ways it deviates from the original. | ||||||
|  | 
 | ||||||
|  | The main differences are [file locking](#file-locking) and [WAL mode](#write-ahead-logging) support. | ||||||
|  | 
 | ||||||
|  | ### File Locking | ||||||
|  | 
 | ||||||
|  | POSIX advisory locks, which SQLite uses on Unix, are | ||||||
|  | [broken by design](https://github.com/sqlite/sqlite/blob/b74eb0/src/os_unix.c#L1073-L1161). | ||||||
|  | 
 | ||||||
|  | On Linux and macOS, this module uses | ||||||
|  | [OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html) | ||||||
|  | to synchronize access to database files. | ||||||
|  | OFD locks are fully compatible with POSIX advisory locks. | ||||||
|  | 
 | ||||||
|  | This module can also use | ||||||
|  | [BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2), | ||||||
|  | albeit with reduced concurrency (`BEGIN IMMEDIATE` behaves like `BEGIN EXCLUSIVE`). | ||||||
|  | On BSD, macOS, and illumos, BSD locks are fully compatible with POSIX advisory locks; | ||||||
|  | on Linux and z/OS, they are fully functional, but incompatible; | ||||||
|  | elsewhere, they are very likely broken. | ||||||
|  | BSD locks are the default on BSD and illumos, | ||||||
|  | but you can opt into them with the `sqlite3_flock` build tag. | ||||||
|  | 
 | ||||||
|  | On Windows, this module uses `LockFileEx` and `UnlockFileEx`, | ||||||
|  | like SQLite. | ||||||
|  | 
 | ||||||
|  | Otherwise, file locking is not supported, and you must use | ||||||
|  | [`nolock=1`](https://sqlite.org/uri.html#urinolock) | ||||||
|  | (or [`immutable=1`](https://sqlite.org/uri.html#uriimmutable)) | ||||||
|  | to open database files. | ||||||
|  | To use the [`database/sql`](https://pkg.go.dev/database/sql) driver | ||||||
|  | with `nolock=1` you must disable connection pooling by calling | ||||||
|  | [`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns). | ||||||
|  | 
 | ||||||
|  | You can use [`vfs.SupportsFileLocking`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#SupportsFileLocking) | ||||||
|  | to check if your build supports file locking. | ||||||
|  | 
 | ||||||
|  | ### Write-Ahead Logging | ||||||
|  | 
 | ||||||
|  | On 64-bit Linux and macOS, this module uses `mmap` to implement | ||||||
|  | [shared-memory for the WAL-index](https://sqlite.org/wal.html#implementation_of_shared_memory_for_the_wal_index), | ||||||
|  | like SQLite. | ||||||
|  | 
 | ||||||
|  | To allow `mmap` to work, each connection needs to reserve up to 4GB of address space. | ||||||
|  | To limit the address space each connection reserves, | ||||||
|  | use [`WithMemoryLimitPages`](../tests/testcfg/testcfg.go). | ||||||
|  | 
 | ||||||
|  | Otherwise, [WAL support is limited](https://sqlite.org/wal.html#noshm), | ||||||
|  | and `EXCLUSIVE` locking mode must be set to create, read, and write WAL databases. | ||||||
|  | To use `EXCLUSIVE` locking mode with the | ||||||
|  | [`database/sql`](https://pkg.go.dev/database/sql) driver | ||||||
|  | you must disable connection pooling by calling | ||||||
|  | [`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns). | ||||||
|  | 
 | ||||||
|  | You can use [`vfs.SupportsSharedMemory`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#SupportsSharedMemory) | ||||||
|  | to check if your build supports shared memory. | ||||||
|  | 
 | ||||||
|  | ### Batch-Atomic Write | ||||||
|  | 
 | ||||||
|  | On 64-bit Linux, this module supports [batch-atomic writes](https://sqlite.org/cgi/src/technote/714) | ||||||
|  | on the F2FS filesystem. | ||||||
|  | 
 | ||||||
|  | ### Build Tags | ||||||
|  | 
 | ||||||
|  | The VFS can be customized with a few build tags: | ||||||
|  | - `sqlite3_flock` forces the use of BSD locks; it can be used on z/OS to enable locking, | ||||||
|  |   and elsewhere to test BSD locks. | ||||||
|  | - `sqlite3_nosys` prevents importing [`x/sys`](https://pkg.go.dev/golang.org/x/sys); | ||||||
|  |   disables locking _and_ shared memory on all platforms. | ||||||
|  | - `sqlite3_noshm` disables shared memory on all platforms. | ||||||
|  | 
 | ||||||
|  | > [!IMPORTANT] | ||||||
|  | > The default configuration of this package is compatible with | ||||||
|  | > the standard [Unix and Windows SQLite VFSes](https://sqlite.org/vfs.html#multiple_vfses); | ||||||
|  | > `sqlite3_flock` is compatible with the [`unix-flock` VFS](https://sqlite.org/compile.html#enable_locking_style). | ||||||
|  | > If incompatible file locking is used, accessing databases concurrently with _other_ SQLite libraries | ||||||
|  | > will eventually corrupt data. | ||||||
							
								
								
									
										175
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/api.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/api.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,175 @@ | ||||||
|  | // Package vfs wraps the C SQLite VFS API. | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"io" | ||||||
|  | 
 | ||||||
|  | 	"github.com/tetratelabs/wazero/api" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // A VFS defines the interface between the SQLite core and the underlying operating system. | ||||||
|  | // | ||||||
|  | // Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/vfs.html | ||||||
|  | type VFS interface { | ||||||
|  | 	Open(name string, flags OpenFlag) (File, OpenFlag, error) | ||||||
|  | 	Delete(name string, syncDir bool) error | ||||||
|  | 	Access(name string, flags AccessFlag) (bool, error) | ||||||
|  | 	FullPathname(name string) (string, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // VFSFilename extends VFS with the ability to use Filename | ||||||
|  | // objects for opening files. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/filename.html | ||||||
|  | type VFSFilename interface { | ||||||
|  | 	VFS | ||||||
|  | 	OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A File represents an open file in the OS interface layer. | ||||||
|  | // | ||||||
|  | // Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite. | ||||||
|  | // In particular, sqlite3.BUSY is necessary to correctly implement lock methods. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/io_methods.html | ||||||
|  | type File interface { | ||||||
|  | 	Close() error | ||||||
|  | 	ReadAt(p []byte, off int64) (n int, err error) | ||||||
|  | 	WriteAt(p []byte, off int64) (n int, err error) | ||||||
|  | 	Truncate(size int64) error | ||||||
|  | 	Sync(flags SyncFlag) error | ||||||
|  | 	Size() (int64, error) | ||||||
|  | 	Lock(lock LockLevel) error | ||||||
|  | 	Unlock(lock LockLevel) error | ||||||
|  | 	CheckReservedLock() (bool, error) | ||||||
|  | 	SectorSize() int | ||||||
|  | 	DeviceCharacteristics() DeviceCharacteristic | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FileLockState extends File to implement the | ||||||
|  | // SQLITE_FCNTL_LOCKSTATE file control opcode. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntllockstate | ||||||
|  | type FileLockState interface { | ||||||
|  | 	File | ||||||
|  | 	LockState() LockLevel | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FileChunkSize extends File to implement the | ||||||
|  | // SQLITE_FCNTL_CHUNK_SIZE file control opcode. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlchunksize | ||||||
|  | type FileChunkSize interface { | ||||||
|  | 	File | ||||||
|  | 	ChunkSize(size int) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FileSizeHint extends File to implement the | ||||||
|  | // SQLITE_FCNTL_SIZE_HINT file control opcode. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlsizehint | ||||||
|  | type FileSizeHint interface { | ||||||
|  | 	File | ||||||
|  | 	SizeHint(size int64) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FileHasMoved extends File to implement the | ||||||
|  | // SQLITE_FCNTL_HAS_MOVED file control opcode. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlhasmoved | ||||||
|  | type FileHasMoved interface { | ||||||
|  | 	File | ||||||
|  | 	HasMoved() (bool, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FileOverwrite extends File to implement the | ||||||
|  | // SQLITE_FCNTL_OVERWRITE file control opcode. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntloverwrite | ||||||
|  | type FileOverwrite interface { | ||||||
|  | 	File | ||||||
|  | 	Overwrite() error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FilePersistentWAL extends File to implement the | ||||||
|  | // SQLITE_FCNTL_PERSIST_WAL file control opcode. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpersistwal | ||||||
|  | type FilePersistentWAL interface { | ||||||
|  | 	File | ||||||
|  | 	PersistentWAL() bool | ||||||
|  | 	SetPersistentWAL(bool) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FilePowersafeOverwrite extends File to implement the | ||||||
|  | // SQLITE_FCNTL_POWERSAFE_OVERWRITE file control opcode. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpowersafeoverwrite | ||||||
|  | type FilePowersafeOverwrite interface { | ||||||
|  | 	File | ||||||
|  | 	PowersafeOverwrite() bool | ||||||
|  | 	SetPowersafeOverwrite(bool) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FileCommitPhaseTwo extends File to implement the | ||||||
|  | // SQLITE_FCNTL_COMMIT_PHASETWO file control opcode. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlcommitphasetwo | ||||||
|  | type FileCommitPhaseTwo interface { | ||||||
|  | 	File | ||||||
|  | 	CommitPhaseTwo() error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FileBatchAtomicWrite extends File to implement the | ||||||
|  | // SQLITE_FCNTL_BEGIN_ATOMIC_WRITE, SQLITE_FCNTL_COMMIT_ATOMIC_WRITE | ||||||
|  | // and SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE file control opcodes. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlbeginatomicwrite | ||||||
|  | type FileBatchAtomicWrite interface { | ||||||
|  | 	File | ||||||
|  | 	BeginAtomicWrite() error | ||||||
|  | 	CommitAtomicWrite() error | ||||||
|  | 	RollbackAtomicWrite() error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FilePragma extends File to implement the | ||||||
|  | // SQLITE_FCNTL_PRAGMA file control opcode. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpragma | ||||||
|  | type FilePragma interface { | ||||||
|  | 	File | ||||||
|  | 	Pragma(name, value string) (string, error) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FileCheckpoint extends File to implement the | ||||||
|  | // SQLITE_FCNTL_CKPT_START and SQLITE_FCNTL_CKPT_DONE | ||||||
|  | // file control opcodes. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlckptstart | ||||||
|  | type FileCheckpoint interface { | ||||||
|  | 	File | ||||||
|  | 	CheckpointDone() error | ||||||
|  | 	CheckpointStart() error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // FileSharedMemory extends File to possibly implement | ||||||
|  | // shared-memory for the WAL-index. | ||||||
|  | // The same shared-memory instance must be returned | ||||||
|  | // for the entire life of the file. | ||||||
|  | // It's OK for SharedMemory to return nil. | ||||||
|  | type FileSharedMemory interface { | ||||||
|  | 	File | ||||||
|  | 	SharedMemory() SharedMemory | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SharedMemory is a shared-memory WAL-index implementation. | ||||||
|  | // Use [NewSharedMemory] to create a shared-memory. | ||||||
|  | type SharedMemory interface { | ||||||
|  | 	shmMap(context.Context, api.Module, int32, int32, bool) (uint32, error) | ||||||
|  | 	shmLock(int32, int32, _ShmFlag) error | ||||||
|  | 	shmUnmap(bool) | ||||||
|  | 	io.Closer | ||||||
|  | } | ||||||
							
								
								
									
										234
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/const.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/const.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,234 @@ | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import "github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	_MAX_NAME            = 1e6 // Self-imposed limit for most NUL terminated strings. | ||||||
|  | 	_MAX_SQL_LENGTH      = 1e9 | ||||||
|  | 	_MAX_PATHNAME        = 1024 | ||||||
|  | 	_DEFAULT_SECTOR_SIZE = 4096 | ||||||
|  | 
 | ||||||
|  | 	ptrlen = 4 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // https://sqlite.org/rescode.html | ||||||
|  | type _ErrorCode uint32 | ||||||
|  | 
 | ||||||
|  | func (e _ErrorCode) Error() string { | ||||||
|  | 	return util.ErrorCodeString(uint32(e)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	_OK                      _ErrorCode = util.OK | ||||||
|  | 	_ERROR                   _ErrorCode = util.ERROR | ||||||
|  | 	_PERM                    _ErrorCode = util.PERM | ||||||
|  | 	_BUSY                    _ErrorCode = util.BUSY | ||||||
|  | 	_READONLY                _ErrorCode = util.READONLY | ||||||
|  | 	_IOERR                   _ErrorCode = util.IOERR | ||||||
|  | 	_NOTFOUND                _ErrorCode = util.NOTFOUND | ||||||
|  | 	_CANTOPEN                _ErrorCode = util.CANTOPEN | ||||||
|  | 	_IOERR_READ              _ErrorCode = util.IOERR_READ | ||||||
|  | 	_IOERR_SHORT_READ        _ErrorCode = util.IOERR_SHORT_READ | ||||||
|  | 	_IOERR_WRITE             _ErrorCode = util.IOERR_WRITE | ||||||
|  | 	_IOERR_FSYNC             _ErrorCode = util.IOERR_FSYNC | ||||||
|  | 	_IOERR_DIR_FSYNC         _ErrorCode = util.IOERR_DIR_FSYNC | ||||||
|  | 	_IOERR_TRUNCATE          _ErrorCode = util.IOERR_TRUNCATE | ||||||
|  | 	_IOERR_FSTAT             _ErrorCode = util.IOERR_FSTAT | ||||||
|  | 	_IOERR_UNLOCK            _ErrorCode = util.IOERR_UNLOCK | ||||||
|  | 	_IOERR_RDLOCK            _ErrorCode = util.IOERR_RDLOCK | ||||||
|  | 	_IOERR_DELETE            _ErrorCode = util.IOERR_DELETE | ||||||
|  | 	_IOERR_ACCESS            _ErrorCode = util.IOERR_ACCESS | ||||||
|  | 	_IOERR_CHECKRESERVEDLOCK _ErrorCode = util.IOERR_CHECKRESERVEDLOCK | ||||||
|  | 	_IOERR_LOCK              _ErrorCode = util.IOERR_LOCK | ||||||
|  | 	_IOERR_CLOSE             _ErrorCode = util.IOERR_CLOSE | ||||||
|  | 	_IOERR_SHMOPEN           _ErrorCode = util.IOERR_SHMOPEN | ||||||
|  | 	_IOERR_SHMSIZE           _ErrorCode = util.IOERR_SHMSIZE | ||||||
|  | 	_IOERR_SHMLOCK           _ErrorCode = util.IOERR_SHMLOCK | ||||||
|  | 	_IOERR_SHMMAP            _ErrorCode = util.IOERR_SHMMAP | ||||||
|  | 	_IOERR_SEEK              _ErrorCode = util.IOERR_SEEK | ||||||
|  | 	_IOERR_DELETE_NOENT      _ErrorCode = util.IOERR_DELETE_NOENT | ||||||
|  | 	_IOERR_BEGIN_ATOMIC      _ErrorCode = util.IOERR_BEGIN_ATOMIC | ||||||
|  | 	_IOERR_COMMIT_ATOMIC     _ErrorCode = util.IOERR_COMMIT_ATOMIC | ||||||
|  | 	_IOERR_ROLLBACK_ATOMIC   _ErrorCode = util.IOERR_ROLLBACK_ATOMIC | ||||||
|  | 	_CANTOPEN_FULLPATH       _ErrorCode = util.CANTOPEN_FULLPATH | ||||||
|  | 	_CANTOPEN_ISDIR          _ErrorCode = util.CANTOPEN_ISDIR | ||||||
|  | 	_READONLY_CANTINIT       _ErrorCode = util.READONLY_CANTINIT | ||||||
|  | 	_OK_SYMLINK              _ErrorCode = util.OK_SYMLINK | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // OpenFlag is a flag for the [VFS] Open method. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_open_autoproxy.html | ||||||
|  | type OpenFlag uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	OPEN_READONLY      OpenFlag = 0x00000001 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_READWRITE     OpenFlag = 0x00000002 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_CREATE        OpenFlag = 0x00000004 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_DELETEONCLOSE OpenFlag = 0x00000008 /* VFS only */ | ||||||
|  | 	OPEN_EXCLUSIVE     OpenFlag = 0x00000010 /* VFS only */ | ||||||
|  | 	OPEN_AUTOPROXY     OpenFlag = 0x00000020 /* VFS only */ | ||||||
|  | 	OPEN_URI           OpenFlag = 0x00000040 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_MEMORY        OpenFlag = 0x00000080 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_MAIN_DB       OpenFlag = 0x00000100 /* VFS only */ | ||||||
|  | 	OPEN_TEMP_DB       OpenFlag = 0x00000200 /* VFS only */ | ||||||
|  | 	OPEN_TRANSIENT_DB  OpenFlag = 0x00000400 /* VFS only */ | ||||||
|  | 	OPEN_MAIN_JOURNAL  OpenFlag = 0x00000800 /* VFS only */ | ||||||
|  | 	OPEN_TEMP_JOURNAL  OpenFlag = 0x00001000 /* VFS only */ | ||||||
|  | 	OPEN_SUBJOURNAL    OpenFlag = 0x00002000 /* VFS only */ | ||||||
|  | 	OPEN_SUPER_JOURNAL OpenFlag = 0x00004000 /* VFS only */ | ||||||
|  | 	OPEN_NOMUTEX       OpenFlag = 0x00008000 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_FULLMUTEX     OpenFlag = 0x00010000 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_SHAREDCACHE   OpenFlag = 0x00020000 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_PRIVATECACHE  OpenFlag = 0x00040000 /* Ok for sqlite3_open_v2() */ | ||||||
|  | 	OPEN_WAL           OpenFlag = 0x00080000 /* VFS only */ | ||||||
|  | 	OPEN_NOFOLLOW      OpenFlag = 0x01000000 /* Ok for sqlite3_open_v2() */ | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // AccessFlag is a flag for the [VFS] Access method. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_access_exists.html | ||||||
|  | type AccessFlag uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	ACCESS_EXISTS    AccessFlag = 0 | ||||||
|  | 	ACCESS_READWRITE AccessFlag = 1 /* Used by PRAGMA temp_store_directory */ | ||||||
|  | 	ACCESS_READ      AccessFlag = 2 /* Unused */ | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // SyncFlag is a flag for the [File] Sync method. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_sync_dataonly.html | ||||||
|  | type SyncFlag uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	SYNC_NORMAL   SyncFlag = 0x00002 | ||||||
|  | 	SYNC_FULL     SyncFlag = 0x00003 | ||||||
|  | 	SYNC_DATAONLY SyncFlag = 0x00010 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // LockLevel is a value used with [File] Lock and Unlock methods. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_lock_exclusive.html | ||||||
|  | type LockLevel uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// No locks are held on the database. | ||||||
|  | 	// The database may be neither read nor written. | ||||||
|  | 	// Any internally cached data is considered suspect and subject to | ||||||
|  | 	// verification against the database file before being used. | ||||||
|  | 	// Other processes can read or write the database as their own locking | ||||||
|  | 	// states permit. | ||||||
|  | 	// This is the default state. | ||||||
|  | 	LOCK_NONE LockLevel = 0 /* xUnlock() only */ | ||||||
|  | 
 | ||||||
|  | 	// The database may be read but not written. | ||||||
|  | 	// Any number of processes can hold SHARED locks at the same time, | ||||||
|  | 	// hence there can be many simultaneous readers. | ||||||
|  | 	// But no other thread or process is allowed to write to the database file | ||||||
|  | 	// while one or more SHARED locks are active. | ||||||
|  | 	LOCK_SHARED LockLevel = 1 /* xLock() or xUnlock() */ | ||||||
|  | 
 | ||||||
|  | 	// A RESERVED lock means that the process is planning on writing to the | ||||||
|  | 	// database file at some point in the future but that it is currently just | ||||||
|  | 	// reading from the file. | ||||||
|  | 	// Only a single RESERVED lock may be active at one time, | ||||||
|  | 	// though multiple SHARED locks can coexist with a single RESERVED lock. | ||||||
|  | 	// RESERVED differs from PENDING in that new SHARED locks can be acquired | ||||||
|  | 	// while there is a RESERVED lock. | ||||||
|  | 	LOCK_RESERVED LockLevel = 2 /* xLock() only */ | ||||||
|  | 
 | ||||||
|  | 	// A PENDING lock means that the process holding the lock wants to write to | ||||||
|  | 	// the database as soon as possible and is just waiting on all current | ||||||
|  | 	// SHARED locks to clear so that it can get an EXCLUSIVE lock. | ||||||
|  | 	// No new SHARED locks are permitted against the database if a PENDING lock | ||||||
|  | 	// is active, though existing SHARED locks are allowed to continue. | ||||||
|  | 	LOCK_PENDING LockLevel = 3 /* internal use only */ | ||||||
|  | 
 | ||||||
|  | 	// An EXCLUSIVE lock is needed in order to write to the database file. | ||||||
|  | 	// Only one EXCLUSIVE lock is allowed on the file and no other locks of any | ||||||
|  | 	// kind are allowed to coexist with an EXCLUSIVE lock. | ||||||
|  | 	// In order to maximize concurrency, SQLite works to minimize the amount of | ||||||
|  | 	// time that EXCLUSIVE locks are held. | ||||||
|  | 	LOCK_EXCLUSIVE LockLevel = 4 /* xLock() only */ | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // DeviceCharacteristic is a flag retuned by the [File] DeviceCharacteristics method. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/c_iocap_atomic.html | ||||||
|  | type DeviceCharacteristic uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	IOCAP_ATOMIC                DeviceCharacteristic = 0x00000001 | ||||||
|  | 	IOCAP_ATOMIC512             DeviceCharacteristic = 0x00000002 | ||||||
|  | 	IOCAP_ATOMIC1K              DeviceCharacteristic = 0x00000004 | ||||||
|  | 	IOCAP_ATOMIC2K              DeviceCharacteristic = 0x00000008 | ||||||
|  | 	IOCAP_ATOMIC4K              DeviceCharacteristic = 0x00000010 | ||||||
|  | 	IOCAP_ATOMIC8K              DeviceCharacteristic = 0x00000020 | ||||||
|  | 	IOCAP_ATOMIC16K             DeviceCharacteristic = 0x00000040 | ||||||
|  | 	IOCAP_ATOMIC32K             DeviceCharacteristic = 0x00000080 | ||||||
|  | 	IOCAP_ATOMIC64K             DeviceCharacteristic = 0x00000100 | ||||||
|  | 	IOCAP_SAFE_APPEND           DeviceCharacteristic = 0x00000200 | ||||||
|  | 	IOCAP_SEQUENTIAL            DeviceCharacteristic = 0x00000400 | ||||||
|  | 	IOCAP_UNDELETABLE_WHEN_OPEN DeviceCharacteristic = 0x00000800 | ||||||
|  | 	IOCAP_POWERSAFE_OVERWRITE   DeviceCharacteristic = 0x00001000 | ||||||
|  | 	IOCAP_IMMUTABLE             DeviceCharacteristic = 0x00002000 | ||||||
|  | 	IOCAP_BATCH_ATOMIC          DeviceCharacteristic = 0x00004000 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html | ||||||
|  | type _FcntlOpcode uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	_FCNTL_LOCKSTATE             _FcntlOpcode = 1 | ||||||
|  | 	_FCNTL_GET_LOCKPROXYFILE     _FcntlOpcode = 2 | ||||||
|  | 	_FCNTL_SET_LOCKPROXYFILE     _FcntlOpcode = 3 | ||||||
|  | 	_FCNTL_LAST_ERRNO            _FcntlOpcode = 4 | ||||||
|  | 	_FCNTL_SIZE_HINT             _FcntlOpcode = 5 | ||||||
|  | 	_FCNTL_CHUNK_SIZE            _FcntlOpcode = 6 | ||||||
|  | 	_FCNTL_FILE_POINTER          _FcntlOpcode = 7 | ||||||
|  | 	_FCNTL_SYNC_OMITTED          _FcntlOpcode = 8 | ||||||
|  | 	_FCNTL_WIN32_AV_RETRY        _FcntlOpcode = 9 | ||||||
|  | 	_FCNTL_PERSIST_WAL           _FcntlOpcode = 10 | ||||||
|  | 	_FCNTL_OVERWRITE             _FcntlOpcode = 11 | ||||||
|  | 	_FCNTL_VFSNAME               _FcntlOpcode = 12 | ||||||
|  | 	_FCNTL_POWERSAFE_OVERWRITE   _FcntlOpcode = 13 | ||||||
|  | 	_FCNTL_PRAGMA                _FcntlOpcode = 14 | ||||||
|  | 	_FCNTL_BUSYHANDLER           _FcntlOpcode = 15 | ||||||
|  | 	_FCNTL_TEMPFILENAME          _FcntlOpcode = 16 | ||||||
|  | 	_FCNTL_MMAP_SIZE             _FcntlOpcode = 18 | ||||||
|  | 	_FCNTL_TRACE                 _FcntlOpcode = 19 | ||||||
|  | 	_FCNTL_HAS_MOVED             _FcntlOpcode = 20 | ||||||
|  | 	_FCNTL_SYNC                  _FcntlOpcode = 21 | ||||||
|  | 	_FCNTL_COMMIT_PHASETWO       _FcntlOpcode = 22 | ||||||
|  | 	_FCNTL_WIN32_SET_HANDLE      _FcntlOpcode = 23 | ||||||
|  | 	_FCNTL_WAL_BLOCK             _FcntlOpcode = 24 | ||||||
|  | 	_FCNTL_ZIPVFS                _FcntlOpcode = 25 | ||||||
|  | 	_FCNTL_RBU                   _FcntlOpcode = 26 | ||||||
|  | 	_FCNTL_VFS_POINTER           _FcntlOpcode = 27 | ||||||
|  | 	_FCNTL_JOURNAL_POINTER       _FcntlOpcode = 28 | ||||||
|  | 	_FCNTL_WIN32_GET_HANDLE      _FcntlOpcode = 29 | ||||||
|  | 	_FCNTL_PDB                   _FcntlOpcode = 30 | ||||||
|  | 	_FCNTL_BEGIN_ATOMIC_WRITE    _FcntlOpcode = 31 | ||||||
|  | 	_FCNTL_COMMIT_ATOMIC_WRITE   _FcntlOpcode = 32 | ||||||
|  | 	_FCNTL_ROLLBACK_ATOMIC_WRITE _FcntlOpcode = 33 | ||||||
|  | 	_FCNTL_LOCK_TIMEOUT          _FcntlOpcode = 34 | ||||||
|  | 	_FCNTL_DATA_VERSION          _FcntlOpcode = 35 | ||||||
|  | 	_FCNTL_SIZE_LIMIT            _FcntlOpcode = 36 | ||||||
|  | 	_FCNTL_CKPT_DONE             _FcntlOpcode = 37 | ||||||
|  | 	_FCNTL_RESERVE_BYTES         _FcntlOpcode = 38 | ||||||
|  | 	_FCNTL_CKPT_START            _FcntlOpcode = 39 | ||||||
|  | 	_FCNTL_EXTERNAL_READER       _FcntlOpcode = 40 | ||||||
|  | 	_FCNTL_CKSM_FILE             _FcntlOpcode = 41 | ||||||
|  | 	_FCNTL_RESET_CACHE           _FcntlOpcode = 42 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // https://sqlite.org/c3ref/c_shm_exclusive.html | ||||||
|  | type _ShmFlag uint32 | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	_SHM_UNLOCK    _ShmFlag = 1 | ||||||
|  | 	_SHM_LOCK      _ShmFlag = 2 | ||||||
|  | 	_SHM_SHARED    _ShmFlag = 4 | ||||||
|  | 	_SHM_EXCLUSIVE _ShmFlag = 8 | ||||||
|  | ) | ||||||
							
								
								
									
										217
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/file.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/file.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,217 @@ | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"io/fs" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"runtime" | ||||||
|  | 	"syscall" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/util/osutil" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type vfsOS struct{} | ||||||
|  | 
 | ||||||
|  | func (vfsOS) FullPathname(path string) (string, error) { | ||||||
|  | 	path, err := filepath.Abs(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	fi, err := os.Lstat(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if errors.Is(err, fs.ErrNotExist) { | ||||||
|  | 			return path, nil | ||||||
|  | 		} | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	if fi.Mode()&fs.ModeSymlink != 0 { | ||||||
|  | 		err = _OK_SYMLINK | ||||||
|  | 	} | ||||||
|  | 	return path, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (vfsOS) Delete(path string, syncDir bool) error { | ||||||
|  | 	err := os.Remove(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if errors.Is(err, fs.ErrNotExist) { | ||||||
|  | 			return _IOERR_DELETE_NOENT | ||||||
|  | 		} | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if runtime.GOOS != "windows" && syncDir { | ||||||
|  | 		f, err := os.Open(filepath.Dir(path)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return _OK | ||||||
|  | 		} | ||||||
|  | 		defer f.Close() | ||||||
|  | 		err = osSync(f, false, false) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return _IOERR_DIR_FSYNC | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (vfsOS) Access(name string, flags AccessFlag) (bool, error) { | ||||||
|  | 	err := osAccess(name, flags) | ||||||
|  | 	if flags == ACCESS_EXISTS { | ||||||
|  | 		if errors.Is(err, fs.ErrNotExist) { | ||||||
|  | 			return false, nil | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		if errors.Is(err, fs.ErrPermission) { | ||||||
|  | 			return false, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return err == nil, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (vfsOS) Open(name string, flags OpenFlag) (File, OpenFlag, error) { | ||||||
|  | 	return nil, 0, _CANTOPEN | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (vfsOS) OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error) { | ||||||
|  | 	var oflags int | ||||||
|  | 	if flags&OPEN_EXCLUSIVE != 0 { | ||||||
|  | 		oflags |= os.O_EXCL | ||||||
|  | 	} | ||||||
|  | 	if flags&OPEN_CREATE != 0 { | ||||||
|  | 		oflags |= os.O_CREATE | ||||||
|  | 	} | ||||||
|  | 	if flags&OPEN_READONLY != 0 { | ||||||
|  | 		oflags |= os.O_RDONLY | ||||||
|  | 	} | ||||||
|  | 	if flags&OPEN_READWRITE != 0 { | ||||||
|  | 		oflags |= os.O_RDWR | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var err error | ||||||
|  | 	var f *os.File | ||||||
|  | 	if name == nil { | ||||||
|  | 		f, err = os.CreateTemp("", "*.db") | ||||||
|  | 	} else { | ||||||
|  | 		f, err = osutil.OpenFile(name.String(), oflags, 0666) | ||||||
|  | 	} | ||||||
|  | 	if err != nil { | ||||||
|  | 		if errors.Is(err, syscall.EISDIR) { | ||||||
|  | 			return nil, flags, _CANTOPEN_ISDIR | ||||||
|  | 		} | ||||||
|  | 		return nil, flags, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if modeof := name.URIParameter("modeof"); modeof != "" { | ||||||
|  | 		if err = osSetMode(f, modeof); err != nil { | ||||||
|  | 			f.Close() | ||||||
|  | 			return nil, flags, _IOERR_FSTAT | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if flags&OPEN_DELETEONCLOSE != 0 { | ||||||
|  | 		os.Remove(f.Name()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	file := vfsFile{ | ||||||
|  | 		File:     f, | ||||||
|  | 		psow:     true, | ||||||
|  | 		readOnly: flags&OPEN_READONLY != 0, | ||||||
|  | 		syncDir: runtime.GOOS != "windows" && | ||||||
|  | 			flags&(OPEN_CREATE) != 0 && | ||||||
|  | 			flags&(OPEN_MAIN_JOURNAL|OPEN_SUPER_JOURNAL|OPEN_WAL) != 0, | ||||||
|  | 		shm: NewSharedMemory(name.String()+"-shm", flags), | ||||||
|  | 	} | ||||||
|  | 	return &file, flags, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type vfsFile struct { | ||||||
|  | 	*os.File | ||||||
|  | 	shm      SharedMemory | ||||||
|  | 	lock     LockLevel | ||||||
|  | 	readOnly bool | ||||||
|  | 	keepWAL  bool | ||||||
|  | 	syncDir  bool | ||||||
|  | 	psow     bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	// Ensure these interfaces are implemented: | ||||||
|  | 	_ FileLockState          = &vfsFile{} | ||||||
|  | 	_ FileHasMoved           = &vfsFile{} | ||||||
|  | 	_ FileSizeHint           = &vfsFile{} | ||||||
|  | 	_ FilePersistentWAL      = &vfsFile{} | ||||||
|  | 	_ FilePowersafeOverwrite = &vfsFile{} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) Close() error { | ||||||
|  | 	if f.shm != nil { | ||||||
|  | 		f.shm.Close() | ||||||
|  | 	} | ||||||
|  | 	return f.File.Close() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) Sync(flags SyncFlag) error { | ||||||
|  | 	dataonly := (flags & SYNC_DATAONLY) != 0 | ||||||
|  | 	fullsync := (flags & 0x0f) == SYNC_FULL | ||||||
|  | 
 | ||||||
|  | 	err := osSync(f.File, fullsync, dataonly) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if runtime.GOOS != "windows" && f.syncDir { | ||||||
|  | 		f.syncDir = false | ||||||
|  | 		d, err := os.Open(filepath.Dir(f.File.Name())) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		defer d.Close() | ||||||
|  | 		err = osSync(d, false, false) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return _IOERR_DIR_FSYNC | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) Size() (int64, error) { | ||||||
|  | 	return f.Seek(0, io.SeekEnd) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) SectorSize() int { | ||||||
|  | 	return _DEFAULT_SECTOR_SIZE | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) DeviceCharacteristics() DeviceCharacteristic { | ||||||
|  | 	var res DeviceCharacteristic | ||||||
|  | 	if osBatchAtomic(f.File) { | ||||||
|  | 		res |= IOCAP_BATCH_ATOMIC | ||||||
|  | 	} | ||||||
|  | 	if f.psow { | ||||||
|  | 		res |= IOCAP_POWERSAFE_OVERWRITE | ||||||
|  | 	} | ||||||
|  | 	return res | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) SizeHint(size int64) error { | ||||||
|  | 	return osAllocate(f.File, size) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) HasMoved() (bool, error) { | ||||||
|  | 	fi, err := f.Stat() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 	pi, err := os.Stat(f.Name()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if errors.Is(err, fs.ErrNotExist) { | ||||||
|  | 			return true, nil | ||||||
|  | 		} | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 	return !os.SameFile(fi, pi), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) LockState() LockLevel            { return f.lock } | ||||||
|  | func (f *vfsFile) PowersafeOverwrite() bool        { return f.psow } | ||||||
|  | func (f *vfsFile) PersistentWAL() bool             { return f.keepWAL } | ||||||
|  | func (f *vfsFile) SetPowersafeOverwrite(psow bool) { f.psow = psow } | ||||||
|  | func (f *vfsFile) SetPersistentWAL(keepWAL bool)   { f.keepWAL = keepWAL } | ||||||
							
								
								
									
										174
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/filename.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/filename.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,174 @@ | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"net/url" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | 	"github.com/tetratelabs/wazero/api" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Filename is used by SQLite to pass filenames | ||||||
|  | // to the Open method of a VFS. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/filename.html | ||||||
|  | type Filename struct { | ||||||
|  | 	ctx   context.Context | ||||||
|  | 	mod   api.Module | ||||||
|  | 	zPath uint32 | ||||||
|  | 	flags OpenFlag | ||||||
|  | 	stack [2]uint64 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // OpenFilename is an internal API users should not call directly. | ||||||
|  | func OpenFilename(ctx context.Context, mod api.Module, id uint32, flags OpenFlag) *Filename { | ||||||
|  | 	if id == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return &Filename{ | ||||||
|  | 		ctx:   ctx, | ||||||
|  | 		mod:   mod, | ||||||
|  | 		zPath: id, | ||||||
|  | 		flags: flags, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // String returns this filename as a string. | ||||||
|  | func (n *Filename) String() string { | ||||||
|  | 	if n == nil || n.zPath == 0 { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return util.ReadString(n.mod, n.zPath, _MAX_PATHNAME) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Database returns the name of the corresponding database file. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/filename_database.html | ||||||
|  | func (n *Filename) Database() string { | ||||||
|  | 	return n.path("sqlite3_filename_database") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Journal returns the name of the corresponding rollback journal file. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/filename_database.html | ||||||
|  | func (n *Filename) Journal() string { | ||||||
|  | 	return n.path("sqlite3_filename_journal") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Journal returns the name of the corresponding WAL file. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/filename_database.html | ||||||
|  | func (n *Filename) WAL() string { | ||||||
|  | 	return n.path("sqlite3_filename_wal") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (n *Filename) path(method string) string { | ||||||
|  | 	if n == nil || n.zPath == 0 { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	n.stack[0] = uint64(n.zPath) | ||||||
|  | 	fn := n.mod.ExportedFunction(method) | ||||||
|  | 	if err := fn.CallWithStack(n.ctx, n.stack[:]); err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	return util.ReadString(n.mod, uint32(n.stack[0]), _MAX_PATHNAME) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DatabaseFile returns the main database [File] corresponding to a journal. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/database_file_object.html | ||||||
|  | func (n *Filename) DatabaseFile() File { | ||||||
|  | 	if n == nil || n.zPath == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	if n.flags&(OPEN_MAIN_DB|OPEN_MAIN_JOURNAL|OPEN_WAL) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	n.stack[0] = uint64(n.zPath) | ||||||
|  | 	fn := n.mod.ExportedFunction("sqlite3_database_file_object") | ||||||
|  | 	if err := fn.CallWithStack(n.ctx, n.stack[:]); err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	file, _ := vfsFileGet(n.ctx, n.mod, uint32(n.stack[0])).(File) | ||||||
|  | 	return file | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // URIParameter returns the value of a URI parameter. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/uri_boolean.html | ||||||
|  | func (n *Filename) URIParameter(key string) string { | ||||||
|  | 	if n == nil || n.zPath == 0 { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	uriKey := n.mod.ExportedFunction("sqlite3_uri_key") | ||||||
|  | 	n.stack[0] = uint64(n.zPath) | ||||||
|  | 	n.stack[1] = uint64(0) | ||||||
|  | 	if err := uriKey.CallWithStack(n.ctx, n.stack[:]); err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ptr := uint32(n.stack[0]) | ||||||
|  | 	if ptr == 0 { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Parse the format from: | ||||||
|  | 	// https://github.com/sqlite/sqlite/blob/b74eb0/src/pager.c#L4797-L4840 | ||||||
|  | 	// This avoids having to alloc/free the key just to find a value. | ||||||
|  | 	for { | ||||||
|  | 		k := util.ReadString(n.mod, ptr, _MAX_NAME) | ||||||
|  | 		if k == "" { | ||||||
|  | 			return "" | ||||||
|  | 		} | ||||||
|  | 		ptr += uint32(len(k)) + 1 | ||||||
|  | 
 | ||||||
|  | 		v := util.ReadString(n.mod, ptr, _MAX_NAME) | ||||||
|  | 		if k == key { | ||||||
|  | 			return v | ||||||
|  | 		} | ||||||
|  | 		ptr += uint32(len(v)) + 1 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // URIParameters obtains values for URI parameters. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/uri_boolean.html | ||||||
|  | func (n *Filename) URIParameters() url.Values { | ||||||
|  | 	if n == nil || n.zPath == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	uriKey := n.mod.ExportedFunction("sqlite3_uri_key") | ||||||
|  | 	n.stack[0] = uint64(n.zPath) | ||||||
|  | 	n.stack[1] = uint64(0) | ||||||
|  | 	if err := uriKey.CallWithStack(n.ctx, n.stack[:]); err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ptr := uint32(n.stack[0]) | ||||||
|  | 	if ptr == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var params url.Values | ||||||
|  | 
 | ||||||
|  | 	// Parse the format from: | ||||||
|  | 	// https://github.com/sqlite/sqlite/blob/b74eb0/src/pager.c#L4797-L4840 | ||||||
|  | 	// This is the only way to support multiple valued keys. | ||||||
|  | 	for { | ||||||
|  | 		k := util.ReadString(n.mod, ptr, _MAX_NAME) | ||||||
|  | 		if k == "" { | ||||||
|  | 			return params | ||||||
|  | 		} | ||||||
|  | 		ptr += uint32(len(k)) + 1 | ||||||
|  | 
 | ||||||
|  | 		v := util.ReadString(n.mod, ptr, _MAX_NAME) | ||||||
|  | 		if params == nil { | ||||||
|  | 			params = url.Values{} | ||||||
|  | 		} | ||||||
|  | 		params.Add(k, v) | ||||||
|  | 		ptr += uint32(len(v)) + 1 | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										144
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/lock.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/lock.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,144 @@ | ||||||
|  | //go:build (linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import "github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | 
 | ||||||
|  | // SupportsFileLocking is false on platforms that do not support file locking. | ||||||
|  | // To open a database file on those platforms, | ||||||
|  | // you need to use the [nolock] or [immutable] URI parameters. | ||||||
|  | // | ||||||
|  | // [nolock]: https://sqlite.org/uri.html#urinolock | ||||||
|  | // [immutable]: https://sqlite.org/uri.html#uriimmutable | ||||||
|  | const SupportsFileLocking = true | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	_PENDING_BYTE  = 0x40000000 | ||||||
|  | 	_RESERVED_BYTE = (_PENDING_BYTE + 1) | ||||||
|  | 	_SHARED_FIRST  = (_PENDING_BYTE + 2) | ||||||
|  | 	_SHARED_SIZE   = 510 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) Lock(lock LockLevel) error { | ||||||
|  | 	// Argument check. SQLite never explicitly requests a pending lock. | ||||||
|  | 	if lock != LOCK_SHARED && lock != LOCK_RESERVED && lock != LOCK_EXCLUSIVE { | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch { | ||||||
|  | 	case f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE: | ||||||
|  | 		// Connection state check. | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	case f.lock == LOCK_NONE && lock > LOCK_SHARED: | ||||||
|  | 		// We never move from unlocked to anything higher than a shared lock. | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	case f.lock != LOCK_SHARED && lock == LOCK_RESERVED: | ||||||
|  | 		// A shared lock is always held when a reserved lock is requested. | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// If we already have an equal or more restrictive lock, do nothing. | ||||||
|  | 	if f.lock >= lock { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Do not allow any kind of write-lock on a read-only database. | ||||||
|  | 	if f.readOnly && lock >= LOCK_RESERVED { | ||||||
|  | 		return _IOERR_LOCK | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch lock { | ||||||
|  | 	case LOCK_SHARED: | ||||||
|  | 		// Must be unlocked to get SHARED. | ||||||
|  | 		if f.lock != LOCK_NONE { | ||||||
|  | 			panic(util.AssertErr()) | ||||||
|  | 		} | ||||||
|  | 		if rc := osGetSharedLock(f.File); rc != _OK { | ||||||
|  | 			return rc | ||||||
|  | 		} | ||||||
|  | 		f.lock = LOCK_SHARED | ||||||
|  | 		return nil | ||||||
|  | 
 | ||||||
|  | 	case LOCK_RESERVED: | ||||||
|  | 		// Must be SHARED to get RESERVED. | ||||||
|  | 		if f.lock != LOCK_SHARED { | ||||||
|  | 			panic(util.AssertErr()) | ||||||
|  | 		} | ||||||
|  | 		if rc := osGetReservedLock(f.File); rc != _OK { | ||||||
|  | 			return rc | ||||||
|  | 		} | ||||||
|  | 		f.lock = LOCK_RESERVED | ||||||
|  | 		return nil | ||||||
|  | 
 | ||||||
|  | 	case LOCK_EXCLUSIVE: | ||||||
|  | 		// Must be SHARED, RESERVED or PENDING to get EXCLUSIVE. | ||||||
|  | 		if f.lock <= LOCK_NONE || f.lock >= LOCK_EXCLUSIVE { | ||||||
|  | 			panic(util.AssertErr()) | ||||||
|  | 		} | ||||||
|  | 		reserved := f.lock == LOCK_RESERVED | ||||||
|  | 		// A PENDING lock is needed before acquiring an EXCLUSIVE lock. | ||||||
|  | 		if f.lock < LOCK_PENDING { | ||||||
|  | 			// If we're already RESERVED, we can block indefinitely, | ||||||
|  | 			// since only new readers may briefly hold the PENDING lock. | ||||||
|  | 			if rc := osGetPendingLock(f.File, reserved /* block */); rc != _OK { | ||||||
|  | 				return rc | ||||||
|  | 			} | ||||||
|  | 			f.lock = LOCK_PENDING | ||||||
|  | 		} | ||||||
|  | 		// We already have PENDING, so we're just waiting for readers to leave. | ||||||
|  | 		// If we were RESERVED, we can wait for a little while, before invoking | ||||||
|  | 		// the busy handler; we will only do this once. | ||||||
|  | 		if rc := osGetExclusiveLock(f.File, reserved /* wait */); rc != _OK { | ||||||
|  | 			return rc | ||||||
|  | 		} | ||||||
|  | 		f.lock = LOCK_EXCLUSIVE | ||||||
|  | 		return nil | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) Unlock(lock LockLevel) error { | ||||||
|  | 	// Argument check. | ||||||
|  | 	if lock != LOCK_NONE && lock != LOCK_SHARED { | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Connection state check. | ||||||
|  | 	if f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE { | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// If we don't have a more restrictive lock, do nothing. | ||||||
|  | 	if f.lock <= lock { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch lock { | ||||||
|  | 	case LOCK_SHARED: | ||||||
|  | 		rc := osDowngradeLock(f.File, f.lock) | ||||||
|  | 		f.lock = LOCK_SHARED | ||||||
|  | 		return rc | ||||||
|  | 
 | ||||||
|  | 	case LOCK_NONE: | ||||||
|  | 		rc := osReleaseLock(f.File, f.lock) | ||||||
|  | 		f.lock = LOCK_NONE | ||||||
|  | 		return rc | ||||||
|  | 
 | ||||||
|  | 	default: | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) CheckReservedLock() (bool, error) { | ||||||
|  | 	// Connection state check. | ||||||
|  | 	if f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE { | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if f.lock >= LOCK_RESERVED { | ||||||
|  | 		return true, nil | ||||||
|  | 	} | ||||||
|  | 	return osCheckReservedLock(f.File) | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/lock_other.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/lock_other.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | ||||||
|  | //go:build !(linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) || sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | // SupportsFileLocking is false on platforms that do not support file locking. | ||||||
|  | // To open a database file on those platforms, | ||||||
|  | // you need to use the [nolock] or [immutable] URI parameters. | ||||||
|  | // | ||||||
|  | // [nolock]: https://sqlite.org/uri.html#urinolock | ||||||
|  | // [immutable]: https://sqlite.org/uri.html#uriimmutable | ||||||
|  | const SupportsFileLocking = false | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) Lock(LockLevel) error { | ||||||
|  | 	return _IOERR_LOCK | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) Unlock(LockLevel) error { | ||||||
|  | 	return _IOERR_UNLOCK | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) CheckReservedLock() (bool, error) { | ||||||
|  | 	return false, _IOERR_CHECKRESERVEDLOCK | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/memdb/README.md
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/memdb/README.md
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | # Go `"memdb"` SQLite VFS | ||||||
|  | 
 | ||||||
|  | This package implements the [`"memdb"`](https://sqlite.org/src/doc/tip/src/memdb.c) | ||||||
|  | SQLite VFS in pure Go. | ||||||
|  | 
 | ||||||
|  | It has some benefits over the C version: | ||||||
|  | - the memory backing the database needs not be contiguous, | ||||||
|  | - the database can grow/shrink incrementally without copying, | ||||||
|  | - reader-writer concurrency is slightly improved. | ||||||
							
								
								
									
										68
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/memdb/api.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/memdb/api.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,68 @@ | ||||||
|  | // Package memdb implements the "memdb" SQLite VFS. | ||||||
|  | // | ||||||
|  | // The "memdb" [vfs.VFS] allows the same in-memory database to be shared | ||||||
|  | // among multiple database connections in the same process, | ||||||
|  | // as long as the database name begins with "/". | ||||||
|  | // | ||||||
|  | // Importing package memdb registers the VFS: | ||||||
|  | // | ||||||
|  | //	import _ "github.com/ncruces/go-sqlite3/vfs/memdb" | ||||||
|  | package memdb | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"sync" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/vfs" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	vfs.Register("memdb", memVFS{}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	memoryMtx sync.Mutex | ||||||
|  | 	// +checklocks:memoryMtx | ||||||
|  | 	memoryDBs = map[string]*memDB{} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Create creates a shared memory database, | ||||||
|  | // using data as its initial contents. | ||||||
|  | // The new database takes ownership of data, | ||||||
|  | // and the caller should not use data after this call. | ||||||
|  | func Create(name string, data []byte) { | ||||||
|  | 	memoryMtx.Lock() | ||||||
|  | 	defer memoryMtx.Unlock() | ||||||
|  | 
 | ||||||
|  | 	db := &memDB{ | ||||||
|  | 		refs: 1, | ||||||
|  | 		name: name, | ||||||
|  | 		size: int64(len(data)), | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Convert data from WAL to rollback journal. | ||||||
|  | 	if len(data) >= 20 && data[18] == 2 && data[19] == 2 { | ||||||
|  | 		data[18] = 1 | ||||||
|  | 		data[19] = 1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sectors := divRoundUp(db.size, sectorSize) | ||||||
|  | 	db.data = make([]*[sectorSize]byte, sectors) | ||||||
|  | 	for i := range db.data { | ||||||
|  | 		sector := data[i*sectorSize:] | ||||||
|  | 		if len(sector) >= sectorSize { | ||||||
|  | 			db.data[i] = (*[sectorSize]byte)(sector) | ||||||
|  | 		} else { | ||||||
|  | 			db.data[i] = new([sectorSize]byte) | ||||||
|  | 			copy((*db.data[i])[:], sector) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	memoryDBs[name] = db | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Delete deletes a shared memory database. | ||||||
|  | func Delete(name string) { | ||||||
|  | 	memoryMtx.Lock() | ||||||
|  | 	defer memoryMtx.Unlock() | ||||||
|  | 	delete(memoryDBs, name) | ||||||
|  | } | ||||||
							
								
								
									
										311
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/memdb/memdb.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/memdb/memdb.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,311 @@ | ||||||
|  | package memdb | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"io" | ||||||
|  | 	"runtime" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3" | ||||||
|  | 	"github.com/ncruces/go-sqlite3/vfs" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Must be a multiple of 64K (the largest page size). | ||||||
|  | const sectorSize = 65536 | ||||||
|  | 
 | ||||||
|  | type memVFS struct{} | ||||||
|  | 
 | ||||||
|  | func (memVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) { | ||||||
|  | 	// For simplicity, we do not support reading or writing data | ||||||
|  | 	// across "sector" boundaries. | ||||||
|  | 	// | ||||||
|  | 	// This is not a problem for most SQLite file types: | ||||||
|  | 	// - databases, which only do page aligned reads/writes; | ||||||
|  | 	// - temp journals, as used by the sorter, which does the same: | ||||||
|  | 	//   https://github.com/sqlite/sqlite/blob/b74eb0/src/vdbesort.c#L409-L412 | ||||||
|  | 	// | ||||||
|  | 	// We refuse to open all other file types, | ||||||
|  | 	// but returning OPEN_MEMORY means SQLite won't ask us to. | ||||||
|  | 	const types = vfs.OPEN_MAIN_DB | | ||||||
|  | 		vfs.OPEN_TEMP_DB | | ||||||
|  | 		vfs.OPEN_TEMP_JOURNAL | ||||||
|  | 	if flags&types == 0 { | ||||||
|  | 		return nil, flags, sqlite3.CANTOPEN | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// A shared database has a name that begins with "/". | ||||||
|  | 	shared := len(name) > 1 && name[0] == '/' | ||||||
|  | 
 | ||||||
|  | 	var db *memDB | ||||||
|  | 	if shared { | ||||||
|  | 		name = name[1:] | ||||||
|  | 		memoryMtx.Lock() | ||||||
|  | 		defer memoryMtx.Unlock() | ||||||
|  | 		db = memoryDBs[name] | ||||||
|  | 	} | ||||||
|  | 	if db == nil { | ||||||
|  | 		if flags&vfs.OPEN_CREATE == 0 { | ||||||
|  | 			return nil, flags, sqlite3.CANTOPEN | ||||||
|  | 		} | ||||||
|  | 		db = &memDB{name: name} | ||||||
|  | 	} | ||||||
|  | 	if shared { | ||||||
|  | 		db.refs++ // +checklocksforce: memoryMtx is held | ||||||
|  | 		memoryDBs[name] = db | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &memFile{ | ||||||
|  | 		memDB:    db, | ||||||
|  | 		readOnly: flags&vfs.OPEN_READONLY != 0, | ||||||
|  | 	}, flags | vfs.OPEN_MEMORY, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (memVFS) Delete(name string, dirSync bool) error { | ||||||
|  | 	return sqlite3.IOERR_DELETE | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (memVFS) Access(name string, flag vfs.AccessFlag) (bool, error) { | ||||||
|  | 	return false, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (memVFS) FullPathname(name string) (string, error) { | ||||||
|  | 	return name, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type memDB struct { | ||||||
|  | 	name string | ||||||
|  | 
 | ||||||
|  | 	// +checklocks:lockMtx | ||||||
|  | 	pending *memFile | ||||||
|  | 	// +checklocks:lockMtx | ||||||
|  | 	reserved *memFile | ||||||
|  | 
 | ||||||
|  | 	// +checklocks:dataMtx | ||||||
|  | 	data []*[sectorSize]byte | ||||||
|  | 
 | ||||||
|  | 	// +checklocks:dataMtx | ||||||
|  | 	size int64 | ||||||
|  | 
 | ||||||
|  | 	// +checklocks:lockMtx | ||||||
|  | 	shared int | ||||||
|  | 
 | ||||||
|  | 	// +checklocks:memoryMtx | ||||||
|  | 	refs int | ||||||
|  | 
 | ||||||
|  | 	lockMtx sync.Mutex | ||||||
|  | 	dataMtx sync.RWMutex | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *memDB) release() { | ||||||
|  | 	memoryMtx.Lock() | ||||||
|  | 	defer memoryMtx.Unlock() | ||||||
|  | 	if m.refs--; m.refs == 0 && m == memoryDBs[m.name] { | ||||||
|  | 		delete(memoryDBs, m.name) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type memFile struct { | ||||||
|  | 	*memDB | ||||||
|  | 	lock     vfs.LockLevel | ||||||
|  | 	readOnly bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	// Ensure these interfaces are implemented: | ||||||
|  | 	_ vfs.FileLockState = &memFile{} | ||||||
|  | 	_ vfs.FileSizeHint  = &memFile{} | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (m *memFile) Close() error { | ||||||
|  | 	m.release() | ||||||
|  | 	return m.Unlock(vfs.LOCK_NONE) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *memFile) ReadAt(b []byte, off int64) (n int, err error) { | ||||||
|  | 	m.dataMtx.RLock() | ||||||
|  | 	defer m.dataMtx.RUnlock() | ||||||
|  | 
 | ||||||
|  | 	if off >= m.size { | ||||||
|  | 		return 0, io.EOF | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	base := off / sectorSize | ||||||
|  | 	rest := off % sectorSize | ||||||
|  | 	have := int64(sectorSize) | ||||||
|  | 	if base == int64(len(m.data))-1 { | ||||||
|  | 		have = modRoundUp(m.size, sectorSize) | ||||||
|  | 	} | ||||||
|  | 	n = copy(b, (*m.data[base])[rest:have]) | ||||||
|  | 	if n < len(b) { | ||||||
|  | 		// Assume reads are page aligned. | ||||||
|  | 		return 0, io.ErrNoProgress | ||||||
|  | 	} | ||||||
|  | 	return n, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *memFile) WriteAt(b []byte, off int64) (n int, err error) { | ||||||
|  | 	m.dataMtx.Lock() | ||||||
|  | 	defer m.dataMtx.Unlock() | ||||||
|  | 
 | ||||||
|  | 	base := off / sectorSize | ||||||
|  | 	rest := off % sectorSize | ||||||
|  | 	for base >= int64(len(m.data)) { | ||||||
|  | 		m.data = append(m.data, new([sectorSize]byte)) | ||||||
|  | 	} | ||||||
|  | 	n = copy((*m.data[base])[rest:], b) | ||||||
|  | 	if n < len(b) { | ||||||
|  | 		// Assume writes are page aligned. | ||||||
|  | 		return n, io.ErrShortWrite | ||||||
|  | 	} | ||||||
|  | 	if size := off + int64(len(b)); size > m.size { | ||||||
|  | 		m.size = size | ||||||
|  | 	} | ||||||
|  | 	return n, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *memFile) Truncate(size int64) error { | ||||||
|  | 	m.dataMtx.Lock() | ||||||
|  | 	defer m.dataMtx.Unlock() | ||||||
|  | 	return m.truncate(size) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // +checklocks:m.dataMtx | ||||||
|  | func (m *memFile) truncate(size int64) error { | ||||||
|  | 	if size < m.size { | ||||||
|  | 		base := size / sectorSize | ||||||
|  | 		rest := size % sectorSize | ||||||
|  | 		if rest != 0 { | ||||||
|  | 			clear((*m.data[base])[rest:]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	sectors := divRoundUp(size, sectorSize) | ||||||
|  | 	for sectors > int64(len(m.data)) { | ||||||
|  | 		m.data = append(m.data, new([sectorSize]byte)) | ||||||
|  | 	} | ||||||
|  | 	clear(m.data[sectors:]) | ||||||
|  | 	m.data = m.data[:sectors] | ||||||
|  | 	m.size = size | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *memFile) Sync(flag vfs.SyncFlag) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *memFile) Size() (int64, error) { | ||||||
|  | 	m.dataMtx.RLock() | ||||||
|  | 	defer m.dataMtx.RUnlock() | ||||||
|  | 	return m.size, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const spinWait = 25 * time.Microsecond | ||||||
|  | 
 | ||||||
|  | func (m *memFile) Lock(lock vfs.LockLevel) error { | ||||||
|  | 	if m.lock >= lock { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if m.readOnly && lock >= vfs.LOCK_RESERVED { | ||||||
|  | 		return sqlite3.IOERR_LOCK | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	m.lockMtx.Lock() | ||||||
|  | 	defer m.lockMtx.Unlock() | ||||||
|  | 
 | ||||||
|  | 	switch lock { | ||||||
|  | 	case vfs.LOCK_SHARED: | ||||||
|  | 		if m.pending != nil { | ||||||
|  | 			return sqlite3.BUSY | ||||||
|  | 		} | ||||||
|  | 		m.shared++ | ||||||
|  | 
 | ||||||
|  | 	case vfs.LOCK_RESERVED: | ||||||
|  | 		if m.reserved != nil { | ||||||
|  | 			return sqlite3.BUSY | ||||||
|  | 		} | ||||||
|  | 		m.reserved = m | ||||||
|  | 
 | ||||||
|  | 	case vfs.LOCK_EXCLUSIVE: | ||||||
|  | 		if m.lock < vfs.LOCK_PENDING { | ||||||
|  | 			if m.pending != nil { | ||||||
|  | 				return sqlite3.BUSY | ||||||
|  | 			} | ||||||
|  | 			m.lock = vfs.LOCK_PENDING | ||||||
|  | 			m.pending = m | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for before := time.Now(); m.shared > 1; { | ||||||
|  | 			if time.Since(before) > spinWait { | ||||||
|  | 				return sqlite3.BUSY | ||||||
|  | 			} | ||||||
|  | 			m.lockMtx.Unlock() | ||||||
|  | 			runtime.Gosched() | ||||||
|  | 			m.lockMtx.Lock() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	m.lock = lock | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *memFile) Unlock(lock vfs.LockLevel) error { | ||||||
|  | 	if m.lock <= lock { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	m.lockMtx.Lock() | ||||||
|  | 	defer m.lockMtx.Unlock() | ||||||
|  | 
 | ||||||
|  | 	if m.pending == m { | ||||||
|  | 		m.pending = nil | ||||||
|  | 	} | ||||||
|  | 	if m.reserved == m { | ||||||
|  | 		m.reserved = nil | ||||||
|  | 	} | ||||||
|  | 	if lock < vfs.LOCK_SHARED { | ||||||
|  | 		m.shared-- | ||||||
|  | 	} | ||||||
|  | 	m.lock = lock | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *memFile) CheckReservedLock() (bool, error) { | ||||||
|  | 	if m.lock >= vfs.LOCK_RESERVED { | ||||||
|  | 		return true, nil | ||||||
|  | 	} | ||||||
|  | 	m.lockMtx.Lock() | ||||||
|  | 	defer m.lockMtx.Unlock() | ||||||
|  | 	return m.reserved != nil, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *memFile) SectorSize() int { | ||||||
|  | 	return sectorSize | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *memFile) DeviceCharacteristics() vfs.DeviceCharacteristic { | ||||||
|  | 	return vfs.IOCAP_ATOMIC | | ||||||
|  | 		vfs.IOCAP_SEQUENTIAL | | ||||||
|  | 		vfs.IOCAP_SAFE_APPEND | | ||||||
|  | 		vfs.IOCAP_POWERSAFE_OVERWRITE | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *memFile) SizeHint(size int64) error { | ||||||
|  | 	m.dataMtx.Lock() | ||||||
|  | 	defer m.dataMtx.Unlock() | ||||||
|  | 	if size > m.size { | ||||||
|  | 		return m.truncate(size) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *memFile) LockState() vfs.LockLevel { | ||||||
|  | 	return m.lock | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func divRoundUp(a, b int64) int64 { | ||||||
|  | 	return (a + b - 1) / b | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func modRoundUp(a, b int64) int64 { | ||||||
|  | 	return b - (b-a%b)%b | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | ||||||
|  | //go:build (freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/sys/unix" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func osUnlock(file *os.File, start, len int64) _ErrorCode { | ||||||
|  | 	if start == 0 && len == 0 { | ||||||
|  | 		err := unix.Flock(int(file.Fd()), unix.LOCK_UN) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return _IOERR_UNLOCK | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return _OK | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osLock(file *os.File, how int, def _ErrorCode) _ErrorCode { | ||||||
|  | 	err := unix.Flock(int(file.Fd()), how) | ||||||
|  | 	return osLockErrorCode(err, def) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osReadLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.Duration) _ErrorCode { | ||||||
|  | 	return osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osWriteLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.Duration) _ErrorCode { | ||||||
|  | 	return osLock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK) | ||||||
|  | } | ||||||
							
								
								
									
										95
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_darwin.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_darwin.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,95 @@ | ||||||
|  | //go:build !(sqlite3_flock || sqlite3_nosys) | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/sys/unix" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h | ||||||
|  | 	_F_OFD_SETLK         = 90 | ||||||
|  | 	_F_OFD_SETLKW        = 91 | ||||||
|  | 	_F_OFD_SETLKWTIMEOUT = 93 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type flocktimeout_t struct { | ||||||
|  | 	fl      unix.Flock_t | ||||||
|  | 	timeout unix.Timespec | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osSync(file *os.File, fullsync, _ /*dataonly*/ bool) error { | ||||||
|  | 	if fullsync { | ||||||
|  | 		return file.Sync() | ||||||
|  | 	} | ||||||
|  | 	return unix.Fsync(int(file.Fd())) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osAllocate(file *os.File, size int64) error { | ||||||
|  | 	off, err := file.Seek(0, io.SeekEnd) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if size <= off { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	store := unix.Fstore_t{ | ||||||
|  | 		Flags:   unix.F_ALLOCATEALL | unix.F_ALLOCATECONTIG, | ||||||
|  | 		Posmode: unix.F_PEOFPOSMODE, | ||||||
|  | 		Offset:  0, | ||||||
|  | 		Length:  size - off, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Try to get a continuous chunk of disk space. | ||||||
|  | 	err = unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// OK, perhaps we are too fragmented, allocate non-continuous. | ||||||
|  | 		store.Flags = unix.F_ALLOCATEALL | ||||||
|  | 		unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store) | ||||||
|  | 	} | ||||||
|  | 	return file.Truncate(size) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osUnlock(file *os.File, start, len int64) _ErrorCode { | ||||||
|  | 	err := unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &unix.Flock_t{ | ||||||
|  | 		Type:  unix.F_UNLCK, | ||||||
|  | 		Start: start, | ||||||
|  | 		Len:   len, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return _IOERR_UNLOCK | ||||||
|  | 	} | ||||||
|  | 	return _OK | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode { | ||||||
|  | 	lock := flocktimeout_t{fl: unix.Flock_t{ | ||||||
|  | 		Type:  typ, | ||||||
|  | 		Start: start, | ||||||
|  | 		Len:   len, | ||||||
|  | 	}} | ||||||
|  | 	var err error | ||||||
|  | 	switch { | ||||||
|  | 	case timeout == 0: | ||||||
|  | 		err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &lock.fl) | ||||||
|  | 	case timeout < 0: | ||||||
|  | 		err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKW, &lock.fl) | ||||||
|  | 	default: | ||||||
|  | 		lock.timeout = unix.NsecToTimespec(int64(timeout / time.Nanosecond)) | ||||||
|  | 		err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKWTIMEOUT, &lock.fl) | ||||||
|  | 	} | ||||||
|  | 	return osLockErrorCode(err, def) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { | ||||||
|  | 	return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { | ||||||
|  | 	return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK) | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_f2fs_linux.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_f2fs_linux.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | ||||||
|  | //go:build (amd64 || arm64 || riscv64) && !sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/sys/unix" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	_F2FS_IOC_START_ATOMIC_WRITE  = 62721 | ||||||
|  | 	_F2FS_IOC_COMMIT_ATOMIC_WRITE = 62722 | ||||||
|  | 	_F2FS_IOC_ABORT_ATOMIC_WRITE  = 62725 | ||||||
|  | 	_F2FS_IOC_GET_FEATURES        = 2147808524 | ||||||
|  | 	_F2FS_FEATURE_ATOMIC_WRITE    = 4 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func osBatchAtomic(file *os.File) bool { | ||||||
|  | 	flags, err := unix.IoctlGetInt(int(file.Fd()), _F2FS_IOC_GET_FEATURES) | ||||||
|  | 	return err == nil && flags&_F2FS_FEATURE_ATOMIC_WRITE != 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) BeginAtomicWrite() error { | ||||||
|  | 	return unix.IoctlSetInt(int(f.Fd()), _F2FS_IOC_START_ATOMIC_WRITE, 0) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) CommitAtomicWrite() error { | ||||||
|  | 	return unix.IoctlSetInt(int(f.Fd()), _F2FS_IOC_COMMIT_ATOMIC_WRITE, 0) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) RollbackAtomicWrite() error { | ||||||
|  | 	return unix.IoctlSetInt(int(f.Fd()), _F2FS_IOC_ABORT_ATOMIC_WRITE, 0) | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | ||||||
|  | //go:build !(sqlite3_flock || sqlite3_nosys) | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"math/rand" | ||||||
|  | 	"os" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/sys/unix" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func osSync(file *os.File, _ /*fullsync*/, _ /*dataonly*/ bool) error { | ||||||
|  | 	// SQLite trusts Linux's fdatasync for all fsync's. | ||||||
|  | 	return unix.Fdatasync(int(file.Fd())) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osAllocate(file *os.File, size int64) error { | ||||||
|  | 	if size == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return unix.Fallocate(int(file.Fd()), 0, 0, size) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osUnlock(file *os.File, start, len int64) _ErrorCode { | ||||||
|  | 	err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &unix.Flock_t{ | ||||||
|  | 		Type:  unix.F_UNLCK, | ||||||
|  | 		Start: start, | ||||||
|  | 		Len:   len, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return _IOERR_UNLOCK | ||||||
|  | 	} | ||||||
|  | 	return _OK | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode { | ||||||
|  | 	lock := unix.Flock_t{ | ||||||
|  | 		Type:  typ, | ||||||
|  | 		Start: start, | ||||||
|  | 		Len:   len, | ||||||
|  | 	} | ||||||
|  | 	var err error | ||||||
|  | 	switch { | ||||||
|  | 	case timeout == 0: | ||||||
|  | 		err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) | ||||||
|  | 	case timeout < 0: | ||||||
|  | 		err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLKW, &lock) | ||||||
|  | 	default: | ||||||
|  | 		before := time.Now() | ||||||
|  | 		for { | ||||||
|  | 			err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock) | ||||||
|  | 			if errno, _ := err.(unix.Errno); errno != unix.EAGAIN { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			if timeout < time.Since(before) { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			osSleep(time.Duration(rand.Int63n(int64(time.Millisecond)))) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return osLockErrorCode(err, def) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { | ||||||
|  | 	return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode { | ||||||
|  | 	return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK) | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_std_access.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_std_access.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | ||||||
|  | //go:build !unix || sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"io/fs" | ||||||
|  | 	"os" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func osAccess(path string, flags AccessFlag) error { | ||||||
|  | 	fi, err := os.Stat(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if flags == ACCESS_EXISTS { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	const ( | ||||||
|  | 		S_IREAD  = 0400 | ||||||
|  | 		S_IWRITE = 0200 | ||||||
|  | 		S_IEXEC  = 0100 | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	var want fs.FileMode = S_IREAD | ||||||
|  | 	if flags == ACCESS_READWRITE { | ||||||
|  | 		want |= S_IWRITE | ||||||
|  | 	} | ||||||
|  | 	if fi.IsDir() { | ||||||
|  | 		want |= S_IEXEC | ||||||
|  | 	} | ||||||
|  | 	if fi.Mode()&want != want { | ||||||
|  | 		return fs.ErrPermission | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_std_alloc.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_std_alloc.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | //go:build !(linux || darwin) || sqlite3_flock || sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func osAllocate(file *os.File, size int64) error { | ||||||
|  | 	off, err := file.Seek(0, io.SeekEnd) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if size <= off { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return file.Truncate(size) | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_std_atomic.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_std_atomic.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | //go:build !linux || !(amd64 || arm64 || riscv64) || sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import "os" | ||||||
|  | 
 | ||||||
|  | func osBatchAtomic(*os.File) bool { | ||||||
|  | 	return false | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_std_mode.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_std_mode.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | //go:build !unix || sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import "os" | ||||||
|  | 
 | ||||||
|  | func osSetMode(file *os.File, modeof string) error { | ||||||
|  | 	fi, err := os.Stat(modeof) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	file.Chmod(fi.Mode()) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_std_sleep.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_std_sleep.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | //go:build !windows || sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import "time" | ||||||
|  | 
 | ||||||
|  | func osSleep(d time.Duration) { | ||||||
|  | 	time.Sleep(d) | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_std_sync.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_std_sync.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | //go:build !(linux || darwin) || sqlite3_flock || sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import "os" | ||||||
|  | 
 | ||||||
|  | func osSync(file *os.File, _ /*fullsync*/, _ /*dataonly*/ bool) error { | ||||||
|  | 	return file.Sync() | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_unix.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_unix.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | ||||||
|  | //go:build unix && !sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"syscall" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/sys/unix" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func osAccess(path string, flags AccessFlag) error { | ||||||
|  | 	var access uint32 // unix.F_OK | ||||||
|  | 	switch flags { | ||||||
|  | 	case ACCESS_READWRITE: | ||||||
|  | 		access = unix.R_OK | unix.W_OK | ||||||
|  | 	case ACCESS_READ: | ||||||
|  | 		access = unix.R_OK | ||||||
|  | 	} | ||||||
|  | 	return unix.Access(path, access) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osSetMode(file *os.File, modeof string) error { | ||||||
|  | 	fi, err := os.Stat(modeof) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	file.Chmod(fi.Mode()) | ||||||
|  | 	if sys, ok := fi.Sys().(*syscall.Stat_t); ok { | ||||||
|  | 		file.Chown(int(sys.Uid), int(sys.Gid)) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										106
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_unix_lock.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_unix_lock.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,106 @@ | ||||||
|  | //go:build (linux || darwin || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/sys/unix" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func osGetSharedLock(file *os.File) _ErrorCode { | ||||||
|  | 	// Test the PENDING lock before acquiring a new SHARED lock. | ||||||
|  | 	if lock, _ := osGetLock(file, _PENDING_BYTE, 1); lock == unix.F_WRLCK { | ||||||
|  | 		return _BUSY | ||||||
|  | 	} | ||||||
|  | 	// Acquire the SHARED lock. | ||||||
|  | 	return osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osGetReservedLock(file *os.File) _ErrorCode { | ||||||
|  | 	// Acquire the RESERVED lock. | ||||||
|  | 	return osWriteLock(file, _RESERVED_BYTE, 1, 0) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osGetPendingLock(file *os.File, block bool) _ErrorCode { | ||||||
|  | 	var timeout time.Duration | ||||||
|  | 	if block { | ||||||
|  | 		timeout = -1 | ||||||
|  | 	} | ||||||
|  | 	// Acquire the PENDING lock. | ||||||
|  | 	return osWriteLock(file, _PENDING_BYTE, 1, timeout) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode { | ||||||
|  | 	var timeout time.Duration | ||||||
|  | 	if wait { | ||||||
|  | 		timeout = time.Millisecond | ||||||
|  | 	} | ||||||
|  | 	// Acquire the EXCLUSIVE lock. | ||||||
|  | 	return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode { | ||||||
|  | 	if state >= LOCK_EXCLUSIVE { | ||||||
|  | 		// Downgrade to a SHARED lock. | ||||||
|  | 		if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK { | ||||||
|  | 			// In theory, the downgrade to a SHARED cannot fail because another | ||||||
|  | 			// process is holding an incompatible lock. If it does, this | ||||||
|  | 			// indicates that the other process is not following the locking | ||||||
|  | 			// protocol. If this happens, return _IOERR_RDLOCK. Returning | ||||||
|  | 			// BUSY would confuse the upper layer. | ||||||
|  | 			return _IOERR_RDLOCK | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// Release the PENDING and RESERVED locks. | ||||||
|  | 	return osUnlock(file, _PENDING_BYTE, 2) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode { | ||||||
|  | 	// Release all locks. | ||||||
|  | 	return osUnlock(file, 0, 0) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osCheckReservedLock(file *os.File) (bool, _ErrorCode) { | ||||||
|  | 	// Test the RESERVED lock. | ||||||
|  | 	lock, rc := osGetLock(file, _RESERVED_BYTE, 1) | ||||||
|  | 	return lock == unix.F_WRLCK, rc | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osGetLock(file *os.File, start, len int64) (int16, _ErrorCode) { | ||||||
|  | 	lock := unix.Flock_t{ | ||||||
|  | 		Type:  unix.F_WRLCK, | ||||||
|  | 		Start: start, | ||||||
|  | 		Len:   len, | ||||||
|  | 	} | ||||||
|  | 	if unix.FcntlFlock(file.Fd(), unix.F_GETLK, &lock) != nil { | ||||||
|  | 		return 0, _IOERR_CHECKRESERVEDLOCK | ||||||
|  | 	} | ||||||
|  | 	return lock.Type, _OK | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osLockErrorCode(err error, def _ErrorCode) _ErrorCode { | ||||||
|  | 	if err == nil { | ||||||
|  | 		return _OK | ||||||
|  | 	} | ||||||
|  | 	if errno, ok := err.(unix.Errno); ok { | ||||||
|  | 		switch errno { | ||||||
|  | 		case | ||||||
|  | 			unix.EACCES, | ||||||
|  | 			unix.EAGAIN, | ||||||
|  | 			unix.EBUSY, | ||||||
|  | 			unix.EINTR, | ||||||
|  | 			unix.ENOLCK, | ||||||
|  | 			unix.EDEADLK, | ||||||
|  | 			unix.ETIMEDOUT: | ||||||
|  | 			return _BUSY | ||||||
|  | 		case unix.EPERM: | ||||||
|  | 			return _PERM | ||||||
|  | 		} | ||||||
|  | 		if errno == unix.EWOULDBLOCK && unix.EWOULDBLOCK != unix.EAGAIN { | ||||||
|  | 			return _BUSY | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return def | ||||||
|  | } | ||||||
							
								
								
									
										186
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,186 @@ | ||||||
|  | //go:build !sqlite3_nosys | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"math/rand" | ||||||
|  | 	"os" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"golang.org/x/sys/windows" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func osGetSharedLock(file *os.File) _ErrorCode { | ||||||
|  | 	// Acquire the PENDING lock temporarily before acquiring a new SHARED lock. | ||||||
|  | 	rc := osReadLock(file, _PENDING_BYTE, 1, 0) | ||||||
|  | 	if rc == _OK { | ||||||
|  | 		// Acquire the SHARED lock. | ||||||
|  | 		rc = osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) | ||||||
|  | 
 | ||||||
|  | 		// Release the PENDING lock. | ||||||
|  | 		osUnlock(file, _PENDING_BYTE, 1) | ||||||
|  | 	} | ||||||
|  | 	return rc | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osGetReservedLock(file *os.File) _ErrorCode { | ||||||
|  | 	// Acquire the RESERVED lock. | ||||||
|  | 	return osWriteLock(file, _RESERVED_BYTE, 1, 0) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osGetPendingLock(file *os.File, block bool) _ErrorCode { | ||||||
|  | 	var timeout time.Duration | ||||||
|  | 	if block { | ||||||
|  | 		timeout = -1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Acquire the PENDING lock. | ||||||
|  | 	return osWriteLock(file, _PENDING_BYTE, 1, timeout) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode { | ||||||
|  | 	var timeout time.Duration | ||||||
|  | 	if wait { | ||||||
|  | 		timeout = time.Millisecond | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Release the SHARED lock. | ||||||
|  | 	osUnlock(file, _SHARED_FIRST, _SHARED_SIZE) | ||||||
|  | 
 | ||||||
|  | 	// Acquire the EXCLUSIVE lock. | ||||||
|  | 	rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout) | ||||||
|  | 
 | ||||||
|  | 	if rc != _OK { | ||||||
|  | 		// Reacquire the SHARED lock. | ||||||
|  | 		osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0) | ||||||
|  | 	} | ||||||
|  | 	return rc | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode { | ||||||
|  | 	if state >= LOCK_EXCLUSIVE { | ||||||
|  | 		// Release the EXCLUSIVE lock. | ||||||
|  | 		osUnlock(file, _SHARED_FIRST, _SHARED_SIZE) | ||||||
|  | 
 | ||||||
|  | 		// Reacquire the SHARED lock. | ||||||
|  | 		if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK { | ||||||
|  | 			// This should never happen. | ||||||
|  | 			// We should always be able to reacquire the read lock. | ||||||
|  | 			return _IOERR_RDLOCK | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Release the PENDING and RESERVED locks. | ||||||
|  | 	if state >= LOCK_RESERVED { | ||||||
|  | 		osUnlock(file, _RESERVED_BYTE, 1) | ||||||
|  | 	} | ||||||
|  | 	if state >= LOCK_PENDING { | ||||||
|  | 		osUnlock(file, _PENDING_BYTE, 1) | ||||||
|  | 	} | ||||||
|  | 	return _OK | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osReleaseLock(file *os.File, state LockLevel) _ErrorCode { | ||||||
|  | 	// Release all locks. | ||||||
|  | 	if state >= LOCK_RESERVED { | ||||||
|  | 		osUnlock(file, _RESERVED_BYTE, 1) | ||||||
|  | 	} | ||||||
|  | 	if state >= LOCK_SHARED { | ||||||
|  | 		osUnlock(file, _SHARED_FIRST, _SHARED_SIZE) | ||||||
|  | 	} | ||||||
|  | 	if state >= LOCK_PENDING { | ||||||
|  | 		osUnlock(file, _PENDING_BYTE, 1) | ||||||
|  | 	} | ||||||
|  | 	return _OK | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osCheckReservedLock(file *os.File) (bool, _ErrorCode) { | ||||||
|  | 	// Test the RESERVED lock. | ||||||
|  | 	rc := osLock(file, 0, _RESERVED_BYTE, 1, 0, _IOERR_CHECKRESERVEDLOCK) | ||||||
|  | 	if rc == _BUSY { | ||||||
|  | 		return true, _OK | ||||||
|  | 	} | ||||||
|  | 	if rc == _OK { | ||||||
|  | 		// Release the RESERVED lock. | ||||||
|  | 		osUnlock(file, _RESERVED_BYTE, 1) | ||||||
|  | 	} | ||||||
|  | 	return false, rc | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osUnlock(file *os.File, start, len uint32) _ErrorCode { | ||||||
|  | 	err := windows.UnlockFileEx(windows.Handle(file.Fd()), | ||||||
|  | 		0, len, 0, &windows.Overlapped{Offset: start}) | ||||||
|  | 	if err == windows.ERROR_NOT_LOCKED { | ||||||
|  | 		return _OK | ||||||
|  | 	} | ||||||
|  | 	if err != nil { | ||||||
|  | 		return _IOERR_UNLOCK | ||||||
|  | 	} | ||||||
|  | 	return _OK | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode { | ||||||
|  | 	var err error | ||||||
|  | 	switch { | ||||||
|  | 	case timeout == 0: | ||||||
|  | 		err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len) | ||||||
|  | 	case timeout < 0: | ||||||
|  | 		err = osLockEx(file, flags, start, len) | ||||||
|  | 	default: | ||||||
|  | 		before := time.Now() | ||||||
|  | 		for { | ||||||
|  | 			err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len) | ||||||
|  | 			if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			if timeout < time.Since(before) { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			osSleep(time.Duration(rand.Int63n(int64(time.Millisecond)))) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return osLockErrorCode(err, def) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osLockEx(file *os.File, flags, start, len uint32) error { | ||||||
|  | 	return windows.LockFileEx(windows.Handle(file.Fd()), flags, | ||||||
|  | 		0, len, 0, &windows.Overlapped{Offset: start}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode { | ||||||
|  | 	return osLock(file, 0, start, len, timeout, _IOERR_RDLOCK) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode { | ||||||
|  | 	return osLock(file, windows.LOCKFILE_EXCLUSIVE_LOCK, start, len, timeout, _IOERR_LOCK) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osLockErrorCode(err error, def _ErrorCode) _ErrorCode { | ||||||
|  | 	if err == nil { | ||||||
|  | 		return _OK | ||||||
|  | 	} | ||||||
|  | 	if errno, ok := err.(windows.Errno); ok { | ||||||
|  | 		// https://devblogs.microsoft.com/oldnewthing/20140905-00/?p=63 | ||||||
|  | 		switch errno { | ||||||
|  | 		case | ||||||
|  | 			windows.ERROR_LOCK_VIOLATION, | ||||||
|  | 			windows.ERROR_IO_PENDING, | ||||||
|  | 			windows.ERROR_OPERATION_ABORTED: | ||||||
|  | 			return _BUSY | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return def | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func osSleep(d time.Duration) { | ||||||
|  | 	if d > 0 { | ||||||
|  | 		period := max(1, d/(5*time.Millisecond)) | ||||||
|  | 		if period < 16 { | ||||||
|  | 			windows.TimeBeginPeriod(uint32(period)) | ||||||
|  | 		} | ||||||
|  | 		time.Sleep(d) | ||||||
|  | 		if period < 16 { | ||||||
|  | 			windows.TimeEndPeriod(uint32(period)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										48
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/registry.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/registry.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import "sync" | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	// +checklocks:vfsRegistryMtx | ||||||
|  | 	vfsRegistry    map[string]VFS | ||||||
|  | 	vfsRegistryMtx sync.RWMutex | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Find returns a VFS given its name. | ||||||
|  | // If there is no match, nil is returned. | ||||||
|  | // If name is empty, the default VFS is returned. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/vfs_find.html | ||||||
|  | func Find(name string) VFS { | ||||||
|  | 	if name == "" || name == "os" { | ||||||
|  | 		return vfsOS{} | ||||||
|  | 	} | ||||||
|  | 	vfsRegistryMtx.RLock() | ||||||
|  | 	defer vfsRegistryMtx.RUnlock() | ||||||
|  | 	return vfsRegistry[name] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Register registers a VFS. | ||||||
|  | // Empty and "os" are reserved names. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/vfs_find.html | ||||||
|  | func Register(name string, vfs VFS) { | ||||||
|  | 	if name == "" || name == "os" { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	vfsRegistryMtx.Lock() | ||||||
|  | 	defer vfsRegistryMtx.Unlock() | ||||||
|  | 	if vfsRegistry == nil { | ||||||
|  | 		vfsRegistry = map[string]VFS{} | ||||||
|  | 	} | ||||||
|  | 	vfsRegistry[name] = vfs | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Unregister unregisters a VFS. | ||||||
|  | // | ||||||
|  | // https://sqlite.org/c3ref/vfs_find.html | ||||||
|  | func Unregister(name string) { | ||||||
|  | 	vfsRegistryMtx.Lock() | ||||||
|  | 	defer vfsRegistryMtx.Unlock() | ||||||
|  | 	delete(vfsRegistry, name) | ||||||
|  | } | ||||||
							
								
								
									
										173
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/shm.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								vendor/github.com/ncruces/go-sqlite3/vfs/shm.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,173 @@ | ||||||
|  | //go:build (darwin || linux) && (amd64 || arm64 || riscv64) && !(sqlite3_flock || sqlite3_noshm || sqlite3_nosys) | ||||||
|  | 
 | ||||||
|  | package vfs | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 
 | ||||||
|  | 	"github.com/ncruces/go-sqlite3/internal/util" | ||||||
|  | 	"github.com/tetratelabs/wazero/api" | ||||||
|  | 	"golang.org/x/sys/unix" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // SupportsSharedMemory is false on platforms that do not support shared memory. | ||||||
|  | // To use [WAL without shared-memory], you need to set [EXCLUSIVE locking mode]. | ||||||
|  | // | ||||||
|  | // [WAL without shared-memory]: https://sqlite.org/wal.html#noshm | ||||||
|  | // [EXCLUSIVE locking mode]: https://sqlite.org/pragma.html#pragma_locking_mode | ||||||
|  | const SupportsSharedMemory = true | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	_SHM_NLOCK = 8 | ||||||
|  | 	_SHM_BASE  = 120 | ||||||
|  | 	_SHM_DMS   = _SHM_BASE + _SHM_NLOCK | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (f *vfsFile) SharedMemory() SharedMemory { return f.shm } | ||||||
|  | 
 | ||||||
|  | // NewSharedMemory returns a shared-memory WAL-index | ||||||
|  | // backed by a file with the given path. | ||||||
|  | // It will return nil if shared-memory is not supported, | ||||||
|  | // or not appropriate for the given flags. | ||||||
|  | // Only [OPEN_MAIN_DB] databases may need a WAL-index. | ||||||
|  | // You must ensure all concurrent accesses to a database | ||||||
|  | // use shared-memory instances created with the same path. | ||||||
|  | func NewSharedMemory(path string, flags OpenFlag) SharedMemory { | ||||||
|  | 	if flags&OPEN_MAIN_DB == 0 || flags&(OPEN_DELETEONCLOSE|OPEN_MEMORY) != 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return &vfsShm{ | ||||||
|  | 		path:     path, | ||||||
|  | 		readOnly: flags&OPEN_READONLY != 0, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type vfsShm struct { | ||||||
|  | 	*os.File | ||||||
|  | 	path     string | ||||||
|  | 	regions  []*util.MappedRegion | ||||||
|  | 	readOnly bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, error) { | ||||||
|  | 	// Ensure size is a multiple of the OS page size. | ||||||
|  | 	if int(size)&(unix.Getpagesize()-1) != 0 { | ||||||
|  | 		return 0, _IOERR_SHMMAP | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if s.File == nil { | ||||||
|  | 		var flag int | ||||||
|  | 		if s.readOnly { | ||||||
|  | 			flag = unix.O_RDONLY | ||||||
|  | 		} else { | ||||||
|  | 			flag = unix.O_RDWR | ||||||
|  | 		} | ||||||
|  | 		f, err := os.OpenFile(s.path, | ||||||
|  | 			flag|unix.O_CREAT|unix.O_NOFOLLOW, 0666) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return 0, _CANTOPEN | ||||||
|  | 		} | ||||||
|  | 		s.File = f | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Dead man's switch. | ||||||
|  | 	if lock, rc := osGetLock(s.File, _SHM_DMS, 1); rc != _OK { | ||||||
|  | 		return 0, _IOERR_LOCK | ||||||
|  | 	} else if lock == unix.F_WRLCK { | ||||||
|  | 		return 0, _BUSY | ||||||
|  | 	} else if lock == unix.F_UNLCK { | ||||||
|  | 		if s.readOnly { | ||||||
|  | 			return 0, _READONLY_CANTINIT | ||||||
|  | 		} | ||||||
|  | 		if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc != _OK { | ||||||
|  | 			return 0, rc | ||||||
|  | 		} | ||||||
|  | 		if err := s.Truncate(0); err != nil { | ||||||
|  | 			return 0, _IOERR_SHMOPEN | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if rc := osReadLock(s.File, _SHM_DMS, 1, 0); rc != _OK { | ||||||
|  | 		return 0, rc | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Check if file is big enough. | ||||||
|  | 	o, err := s.Seek(0, io.SeekEnd) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, _IOERR_SHMSIZE | ||||||
|  | 	} | ||||||
|  | 	if n := (int64(id) + 1) * int64(size); n > o { | ||||||
|  | 		if !extend { | ||||||
|  | 			return 0, nil | ||||||
|  | 		} | ||||||
|  | 		err := osAllocate(s.File, n) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return 0, _IOERR_SHMSIZE | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var prot int | ||||||
|  | 	if s.readOnly { | ||||||
|  | 		prot = unix.PROT_READ | ||||||
|  | 	} else { | ||||||
|  | 		prot = unix.PROT_READ | unix.PROT_WRITE | ||||||
|  | 	} | ||||||
|  | 	r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, prot) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 	s.regions = append(s.regions, r) | ||||||
|  | 	return r.Ptr, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) error { | ||||||
|  | 	// Argument check. | ||||||
|  | 	if n <= 0 || offset < 0 || offset+n > _SHM_NLOCK { | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | 	switch flags { | ||||||
|  | 	case | ||||||
|  | 		_SHM_LOCK | _SHM_SHARED, | ||||||
|  | 		_SHM_LOCK | _SHM_EXCLUSIVE, | ||||||
|  | 		_SHM_UNLOCK | _SHM_SHARED, | ||||||
|  | 		_SHM_UNLOCK | _SHM_EXCLUSIVE: | ||||||
|  | 		// | ||||||
|  | 	default: | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | 	if n != 1 && flags&_SHM_EXCLUSIVE == 0 { | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	switch { | ||||||
|  | 	case flags&_SHM_UNLOCK != 0: | ||||||
|  | 		return osUnlock(s.File, _SHM_BASE+int64(offset), int64(n)) | ||||||
|  | 	case flags&_SHM_SHARED != 0: | ||||||
|  | 		return osReadLock(s.File, _SHM_BASE+int64(offset), int64(n), 0) | ||||||
|  | 	case flags&_SHM_EXCLUSIVE != 0: | ||||||
|  | 		return osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n), 0) | ||||||
|  | 	default: | ||||||
|  | 		panic(util.AssertErr()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *vfsShm) shmUnmap(delete bool) { | ||||||
|  | 	if s.File == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Unmap regions. | ||||||
|  | 	for _, r := range s.regions { | ||||||
|  | 		r.Unmap() | ||||||
|  | 	} | ||||||
|  | 	clear(s.regions) | ||||||
|  | 	s.regions = s.regions[:0] | ||||||
|  | 
 | ||||||
|  | 	// Close the file. | ||||||
|  | 	defer s.Close() | ||||||
|  | 	if delete { | ||||||
|  | 		os.Remove(s.Name()) | ||||||
|  | 	} | ||||||
|  | 	s.File = nil | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue