Skip to content

Commit 980753c

Browse files
committed
feat(connection): implement available parameters
See: sqlitecloud/sqlitecloud-js#68
1 parent 982e6f2 commit 980753c

18 files changed

+632
-91
lines changed

.devcontainer/devcontainer.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
2+
// README at: https://github.com/devcontainers/templates/tree/main/src/go
3+
{
4+
"name": "Go",
5+
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6+
"image": "mcr.microsoft.com/devcontainers/go:1-1-bullseye"
7+
8+
// Features to add to the dev container. More info: https://containers.dev/features.
9+
// "features": {},
10+
11+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
12+
// "forwardPorts": [],
13+
14+
// Use 'postCreateCommand' to run commands after the container is created.
15+
// "postCreateCommand": "go version",
16+
17+
// Configure tool-specific properties.
18+
// "customizations": {},
19+
20+
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
21+
// "remoteUser": "root"
22+
}

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
SQLITE_CONNECTION_STRING=sqlitecloud://myhost.sqlite.cloud
2+
SQLITE_USER=admin
3+
SQLITE_PASSWORD=
4+
SQLITE_API_KEY=
5+
SQLITE_HOST=myhost.sqlite.cloud
6+
SQLITE_DB=chinook.sqlite
7+
SQLITE_PORT=8860

.github/dependabot.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# To get started with Dependabot version updates, you'll need to specify which
2+
# package ecosystems to update and where the package manifests are located.
3+
# Please see the documentation for more information:
4+
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5+
# https://containers.dev/guide/dependabot
6+
7+
version: 2
8+
updates:
9+
- package-ecosystem: "devcontainers"
10+
directory: "/"
11+
schedule:
12+
interval: weekly

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ cli/.DS_Store
33
.DS_Store
44
cli/.vscode
55
.vscode/launch.json
6+
pkg/
7+
bin
8+
.env

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"gopls": {
3+
"ui.semanticTokens": true
4+
}
5+
}

cli/sqlc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ func main() {
514514
case ".timeout":
515515
parameter.Timeout = getNextTokenValueAsInteger(out, parameter.Timeout, 10, tokens, &parameter)
516516
case ".compress":
517-
parameter.Compress = getNextTokenValueAsString(out, parameter.Compress, "NO", "|no|lz4|", tokens, &parameter)
517+
parameter.Compress = getNextTokenValueAsString(out, parameter.Compress, sqlitecloud.CompressModeNo, "|no|lz4|", tokens, &parameter)
518518
db.Compress(parameter.Compress)
519519

520520
case ".format":

connection.go

Lines changed: 137 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,20 @@ type SQCloudConfig struct {
3737
Username string
3838
Password string
3939
Database string
40-
Timeout time.Duration
41-
CompressMode string
42-
Secure bool
43-
TlsInsecureSkipVerify bool
44-
Pem string
40+
PasswordHashed bool // Password is hashed
41+
Timeout time.Duration // Optional query timeout passed directly to TLS socket
42+
CompressMode string // eg: LZ4
43+
Compression bool // Enable compression
44+
Zerotext bool // Tell the server to zero-terminate strings
45+
Memory bool // Database will be created in memory
46+
Create bool // Create the database if it doesn't exist?
47+
Secure bool // Connect using plain TCP port, without TLS encryption, NOT RECOMMENDED (insecure)
48+
NonLinearizable bool // Request for immediate responses from the server node without waiting for linerizability guarantees
49+
TlsInsecureSkipVerify bool // Accept invalid TLS certificates (no_verify_certificate)
50+
Pem string // TODO: ?
51+
RootCertificate string
52+
ClientCertificate string
53+
ClientCertificateKey string
4554
ApiKey string
4655
NoBlob bool // flag to tell the server to not send BLOB columns
4756
MaxData int // value to tell the server to not send columns with more than max_data bytes
@@ -69,6 +78,9 @@ type SQCloud struct {
6978
ErrorMessage string
7079
}
7180

81+
const CompressModeNo = "NO"
82+
const CompressModeLZ4 = "LZ4"
83+
7284
const SQLiteCloudCA = "SQLiteCloudCA"
7385

7486
func New(config SQCloudConfig) *SQCloud {
@@ -104,15 +116,23 @@ func ParseConnectionString(ConnectionString string) (config *SQCloudConfig, err
104116
config.Password, _ = u.User.Password()
105117
config.Database = strings.TrimPrefix(u.Path, "/")
106118
config.Timeout = 0
107-
config.CompressMode = "NO"
119+
config.Compression = false
120+
config.CompressMode = CompressModeNo
121+
config.Zerotext = false
122+
config.Memory = false
123+
config.Create = false
108124
config.Secure = true
125+
config.NonLinearizable = false
109126
config.TlsInsecureSkipVerify = false
110127
config.Pem = ""
111-
config.ApiKey = ""
128+
config.RootCertificate = ""
129+
config.ClientCertificate = ""
130+
config.ClientCertificateKey = ""
112131
config.NoBlob = false
113132
config.MaxData = 0
114133
config.MaxRows = 0
115134
config.MaxRowset = 0
135+
config.ApiKey = ""
116136

117137
sPort := strings.TrimSpace(u.Port())
118138
if len(sPort) > 0 {
@@ -133,8 +153,50 @@ func ParseConnectionString(ConnectionString string) (config *SQCloudConfig, err
133153

134154
case "compress":
135155
config.CompressMode = strings.ToUpper(lastLiteral)
156+
if config.CompressMode == CompressModeLZ4 {
157+
config.Compression = true
158+
}
159+
case "compression":
160+
if b, err := parseBool(lastLiteral, config.Compression); err == nil && b {
161+
config.Compression = true
162+
config.CompressMode = CompressModeLZ4
163+
}
164+
case "zerotext":
165+
if b, err := parseBool(lastLiteral, config.Zerotext); err == nil {
166+
config.Zerotext = b
167+
}
168+
case "memory":
169+
if b, err := parseBool(lastLiteral, config.Memory); err == nil {
170+
config.Memory = b
171+
}
172+
case "create":
173+
if b, err := parseBool(lastLiteral, config.Create); err == nil {
174+
config.Create = b
175+
}
176+
case "secure":
177+
if b, err := parseBool(lastLiteral, config.Secure); err == nil {
178+
config.Secure = b
179+
}
180+
case "insecure":
181+
if b, err := parseBool(lastLiteral, config.Secure); err == nil {
182+
config.Secure = !b
183+
}
184+
case "non_linearizable", "nonlinearizable":
185+
if b, err := parseBool(lastLiteral, config.NonLinearizable); err == nil {
186+
config.NonLinearizable = b
187+
}
188+
case "no_verify_certificate":
189+
if b, err := parseBool(lastLiteral, config.TlsInsecureSkipVerify); err == nil {
190+
config.TlsInsecureSkipVerify = b
191+
}
136192
case "tls":
137193
config.Secure, config.TlsInsecureSkipVerify, config.Pem = ParseTlsString(lastLiteral)
194+
case "root_certificate":
195+
config.RootCertificate = lastLiteral
196+
case "client_certificate":
197+
config.ClientCertificate = lastLiteral
198+
case "client_certificate_key":
199+
config.ClientCertificateKey = lastLiteral
138200
case "apikey":
139201
config.ApiKey = lastLiteral
140202
case "noblob":
@@ -210,8 +272,8 @@ func (this *SQCloud) CheckConnectionParameter() error {
210272
return errors.New(fmt.Sprintf("Invalid Timeout (%s)", this.Timeout.String()))
211273
}
212274

213-
switch strings.ToUpper(this.CompressMode) {
214-
case "NO", "LZ4":
275+
switch this.CompressMode {
276+
case CompressModeNo, CompressModeLZ4:
215277
default:
216278
return errors.New(fmt.Sprintf("Invalid compression method (%s)", this.CompressMode))
217279
}
@@ -340,45 +402,7 @@ func (this *SQCloud) reconnect() error {
340402
}
341403
}
342404

343-
commands := ""
344-
args := []interface{}{}
345-
346-
if strings.TrimSpace(this.Username) != "" {
347-
c, a := authCommand(this.Username, this.Password)
348-
commands += c
349-
args = append(args, a...)
350-
351-
} else if strings.TrimSpace(this.ApiKey) != "" {
352-
c, a := authWithKeyCommand(this.ApiKey)
353-
commands += c
354-
args = append(args, a...)
355-
}
356-
357-
if strings.TrimSpace(this.Database) != "" {
358-
c, a := useDatabaseCommand(this.Database)
359-
commands += c
360-
args = append(args, a...)
361-
}
362-
363-
if this.NoBlob {
364-
commands += noblobCommand(this.NoBlob)
365-
}
366-
367-
if this.MaxData > 0 {
368-
commands += maxdataCommand(this.MaxData)
369-
}
370-
371-
if this.MaxRows > 0 {
372-
commands += maxrowsCommand(this.MaxRows)
373-
}
374-
375-
if this.MaxRowset > 0 {
376-
commands += maxrowsetCommand(this.MaxRowset)
377-
}
378-
379-
if this.CompressMode != "NO" {
380-
commands += compressCommand(this.CompressMode)
381-
}
405+
commands, args := connectionCommands(this.SQCloudConfig)
382406

383407
if commands != "" {
384408
if len(args) > 0 {
@@ -421,6 +445,53 @@ func (this *SQCloud) Close() error {
421445
return nil
422446
}
423447

448+
func connectionCommands(config SQCloudConfig) (string, []interface{}) {
449+
buffer := ""
450+
args := []interface{}{}
451+
452+
// it must be executed before authentication command
453+
if config.NonLinearizable {
454+
buffer += nonlinearizableCommand(config.NonLinearizable)
455+
}
456+
457+
if config.ApiKey != "" {
458+
c, a := authWithKeyCommand(config.ApiKey)
459+
buffer += c
460+
args = append(args, a...)
461+
}
462+
463+
if config.Username != "" && config.Password != "" {
464+
c, a := authCommand(config.Username, config.Password, config.PasswordHashed)
465+
buffer += c
466+
args = append(args, a...)
467+
}
468+
469+
if config.Database != "" {
470+
create := config.Create && !config.Memory
471+
c, a := useDatabaseCommand(config.Database, create)
472+
buffer += c
473+
args = append(args, a...)
474+
}
475+
476+
buffer += compressCommand(config.CompressMode)
477+
478+
if config.Zerotext {
479+
buffer += zerotextCommand(config.Zerotext)
480+
}
481+
482+
if config.NoBlob {
483+
buffer += noblobCommand(config.NoBlob)
484+
}
485+
486+
buffer += maxdataCommand(config.MaxData)
487+
488+
buffer += maxrowsCommand(config.MaxRows)
489+
490+
buffer += maxrowsetCommand(config.MaxRowset)
491+
492+
return buffer, args
493+
}
494+
424495
func noblobCommand(NoBlob bool) string {
425496
if NoBlob {
426497
return "SET CLIENT KEY NOBLOB TO 1;"
@@ -443,15 +514,31 @@ func maxrowsetCommand(v int) string {
443514

444515
func compressCommand(CompressMode string) string {
445516
switch compression := strings.ToUpper(CompressMode); {
446-
case compression == "NO":
517+
case compression == CompressModeNo:
447518
return "SET CLIENT KEY COMPRESSION TO 0;"
448-
case compression == "LZ4":
519+
case compression == CompressModeLZ4:
449520
return "SET CLIENT KEY COMPRESSION TO 1;"
450521
default:
451522
return ""
452523
}
453524
}
454525

526+
func nonlinearizableCommand(NonLinearizable bool) string {
527+
if NonLinearizable {
528+
return "SET CLIENT KEY NONLINEARIZABLE TO 1;"
529+
} else {
530+
return "SET CLIENT KEY NONLINEARIZABLE TO 0;"
531+
}
532+
}
533+
534+
func zerotextCommand(Zerotext bool) string {
535+
if Zerotext {
536+
return "SET CLIENT KEY ZEROTEXT TO 1;"
537+
} else {
538+
return "SET CLIENT KEY ZEROTEXT TO 0;"
539+
}
540+
}
541+
455542
// Compress enabled or disables data compression for this connection.
456543
// If enabled, the data is compressed with the LZ4 compression algorithm, otherwise no compression is applied the data.
457544
func (this *SQCloud) Compress(CompressMode string) error {

server.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -246,13 +246,18 @@ func (this *SQCloud) ListDatabaseConnections(Database string) ([]SQCloudConnecti
246246

247247
// Auth Functions
248248

249-
func authCommand(Username string, Password string) (string, []interface{}) {
250-
return "AUTH USER ? PASSWORD ?;", []interface{}{Username, Password}
249+
func authCommand(Username string, Password string, IsPasswordHashed bool) (string, []interface{}) {
250+
command := "PASSWORD"
251+
if IsPasswordHashed {
252+
command = "HASH"
253+
}
254+
255+
return fmt.Sprintf("AUTH USER ? %s ?;", command), []interface{}{Username, Password}
251256
}
252257

253258
// Auth - INTERNAL SERVER COMMAND: Authenticates User with the given credentials.
254259
func (this *SQCloud) Auth(Username string, Password string) error {
255-
return this.ExecuteArray(authCommand(Username, Password))
260+
return this.ExecuteArray(authCommand(Username, Password, false))
256261
}
257262

258263
func authWithKeyCommand(Key string) (string, []interface{}) {
@@ -318,16 +323,20 @@ func (this *SQCloud) GetDatabase() (string, error) {
318323
return "", err
319324
}
320325

321-
func useDatabaseCommand(Database string) (string, []interface{}) {
322-
return "USE DATABASE ?;", []interface{}{Database}
326+
func useDatabaseCommand(Database string, create bool) (string, []interface{}) {
327+
command := "USE DATABASE ?;"
328+
if create {
329+
command = "CREATE DATABASE ? IF NOT EXISTS;"
330+
}
331+
return command, []interface{}{Database}
323332
}
324333

325334
// UseDatabase - INTERNAL SERVER COMMAND: Selects the specified Database for usage.
326335
// Only if a database was selected, SQL Commands can be sent to this specific Database.
327336
// An error is returned if the specified Database was not found or the user has not the necessary access rights to work with this Database.
328337
func (this *SQCloud) UseDatabase(Database string) error {
329338
this.Database = Database
330-
return this.ExecuteArray(useDatabaseCommand(Database))
339+
return this.ExecuteArray(useDatabaseCommand(Database, false))
331340
}
332341

333342
// UseDatabase - INTERNAL SERVER COMMAND: Releases the actual Database.

0 commit comments

Comments
 (0)