Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/devcontainers/go:1-1-bullseye
FROM mcr.microsoft.com/devcontainers/go:1.18

# Python environment
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/testing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
go-version: '1.18'
- name: Quality Assurance
run: |
go install golang.org/x/tools/cmd/goimports@latest
go install golang.org/x/tools/cmd/goimports@v0.24.0
gofmt -l ./*.go
goimports -e -d ./*.go
- name: golangci-lint
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ cli/.vscode
pkg/
bin
.env
.cache
9 changes: 9 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
linters:
exclusions:
# Disable ST1006 (receiver name style) to allow existing receiver names in server.go
rules:
- path: server.go
linters:
- staticcheck
- stylecheck
text: ST1006
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ repos:
- -w
require_serial: true
- repo: https://github.com/golangci/golangci-lint
rev: v1.59.1
rev: v1.50.1
hooks:
- id: golangci-lint
exclude: ^pkg/
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ setup-ide:

# Test SDK
test:
cd test; go test -v .
cd test; go mod tidy && go test -v .

test-codecov:
cd test; go test -v -race -coverprofile=coverage.out -covermode=atomic .
cd test; go mod tidy && go test -v -race -coverprofile=coverage.out -covermode=atomic .

# GO SDK
sdk: *.go
Expand Down
1 change: 0 additions & 1 deletion cli/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ require (
)

require (
github.com/google/go-cmp v0.5.9 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mattn/go-runewidth v0.0.3 // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
Expand Down
3 changes: 1 addition & 2 deletions cli/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand Down
6 changes: 5 additions & 1 deletion cli/sqlc.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ General Options:
Output Format Options:
-o, --output FILE Switch to BATCH mode, execute SQL Commands and send output to FILE, then exit.
In BATCH mode, the default output format is switched to QUOTE.

--echo Disables --quiet, print command(s) before execution
--quiet Disables --echo, run command(s) quietly (no messages, only query output)
--noheader Turn headers off
Expand All @@ -96,6 +96,7 @@ Connection Options:
-e, --create Create database if it does not exist
-i, --nonlinearizable Use non-linearizable mode for queries
-a, --apikey KEY Use API key for authentication
-k, --token TOKEN Use TOKEN for authentication
-n, --noblob Disable BLOB support
-x, --maxdata SIZE Set maximum data size for queries
-y, --maxrows ROWS Set maximum number of rows for queries
Expand Down Expand Up @@ -150,6 +151,7 @@ type Parameter struct {
Create bool `docopt:"--create"`
NonLinearizable bool `docopt:"--nonlinearizable"`
ApiKey string `docopt:"--apikey"`
Token string `docopt:"--token"`
NoBlob bool `docopt:"--noblob"`
MaxData int `docopt:"--maxdata"`
MaxRows int `docopt:"--maxrows"`
Expand Down Expand Up @@ -294,6 +296,7 @@ func parseParameters() (Parameter, error) {
p["--tls"] = "SKIP"
}
p["--apikey"] = getFirstNoneEmptyString([]string{dropError(p.String("--apikey")), conf.ApiKey})
p["--token"] = getFirstNoneEmptyString([]string{dropError(p.String("--token")), conf.Token})
if conf.NoBlob {
if b, err := p.Bool("--noblob"); err == nil {
p["--noblob"] = b || conf.NoBlob
Expand Down Expand Up @@ -418,6 +421,7 @@ func main() {
Port: parameter.Port,
Username: parameter.User,
Password: parameter.Password,
Token: parameter.Token,
Database: parameter.Database,
Timeout: time.Duration(parameter.Timeout) * time.Second,
Compression: parameter.Compress == sqlitecloud.CompressModeLZ4,
Expand Down
29 changes: 21 additions & 8 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
type SQCloudConfig struct {
Host string
Port int
ProjectID string // ProjectID to identify the user's node
Username string
Password string
Database string
Expand All @@ -49,10 +50,11 @@ type SQCloudConfig struct {
TlsInsecureSkipVerify bool // Accept invalid TLS certificates (no_verify_certificate)
Pem string
ApiKey string
NoBlob bool // flag to tell the server to not send BLOB columns
MaxData int // value to tell the server to not send columns with more than max_data bytes
MaxRows int // value to control rowset chunks based on the number of rows
MaxRowset int // value to control the maximum allowed size for a rowset
Token string // Access Token for authentication
NoBlob bool // flag to tell the server to not send BLOB columns
MaxData int // value to tell the server to not send columns with more than max_data bytes
MaxRows int // value to control rowset chunks based on the number of rows
MaxRowset int // value to control the maximum allowed size for a rowset
}

type SQCloud struct {
Expand Down Expand Up @@ -131,6 +133,7 @@ func ParseConnectionString(ConnectionString string) (config *SQCloudConfig, err
config.MaxRows = 0
config.MaxRowset = 0
config.ApiKey = ""
config.Token = ""

sPort := strings.TrimSpace(u.Port())
if len(sPort) > 0 {
Expand All @@ -139,6 +142,12 @@ func ParseConnectionString(ConnectionString string) (config *SQCloudConfig, err
}
}

// eg: project ID "abvqqetyhq" in "abvqqetyhq.global3.ryujaz.sqlite.cloud"
config.ProjectID = strings.Split(config.Host, ".")[0]
if config.ProjectID == "" {
return nil, fmt.Errorf("invalid connection string: missing project ID in host")
}

for key, values := range u.Query() {
lastLiteral := strings.TrimSpace(values[len(values)-1])
switch strings.ToLower(strings.TrimSpace(key)) {
Expand Down Expand Up @@ -195,6 +204,8 @@ func ParseConnectionString(ConnectionString string) (config *SQCloudConfig, err
config.Secure, config.TlsInsecureSkipVerify, config.Pem = ParseTlsString(lastLiteral)
case "apikey":
config.ApiKey = lastLiteral
case "token":
config.Token = lastLiteral
case "noblob":
if b, err := parseBool(lastLiteral, config.NoBlob); err == nil {
config.NoBlob = b
Expand Down Expand Up @@ -434,12 +445,14 @@ func connectionCommands(config SQCloudConfig) (string, []interface{}) {
}

if config.ApiKey != "" {
c, a := authWithKeyCommand(config.ApiKey)
c, a := authWithApiKeyCommand(config.ApiKey)
buffer += c
args = append(args, a...)
}

if config.Username != "" && config.Password != "" {
} else if config.Token != "" {
c, a := authWithTokenCommand(config.Token)
buffer += c
args = append(args, a...)
} else if config.Username != "" && config.Password != "" {
c, a := authCommand(config.Username, config.Password, config.PasswordHashed)
buffer += c
args = append(args, a...)
Expand Down
37 changes: 37 additions & 0 deletions connection_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package sqlitecloud

import (
"reflect"
"strings"
"testing"
)

func TestConnectionCommandsAuthPreference(t *testing.T) {
tests := []struct {
name string
config SQCloudConfig
wantCmd string
wantArgs []interface{}
}{
{"api key wins", SQCloudConfig{ApiKey: "k", Token: "t", Username: "u", Password: "p"}, "AUTH APIKEY ?;", []interface{}{"k"}},
{"token next", SQCloudConfig{Token: "t", Username: "u", Password: "p"}, "AUTH TOKEN ?;", []interface{}{"t"}},
{"user/pass fallback", SQCloudConfig{Username: "u", Password: "p"}, "AUTH USER ? PASSWORD ?;", []interface{}{"u", "p"}},
{"hashed password", SQCloudConfig{Username: "u", Password: "hash", PasswordHashed: true}, "AUTH USER ? HASH ?;", []interface{}{"u", "hash"}},
{"no credentials", SQCloudConfig{}, "", []interface{}{}},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotBuf, gotArgs := connectionCommands(tt.config)
if tt.wantCmd == "" && gotBuf != "" {
t.Fatalf("expected no auth command, got %q", gotBuf)
}
if tt.wantCmd != "" && !strings.Contains(gotBuf, tt.wantCmd) {
t.Fatalf("expected %q in buffer, got %q", tt.wantCmd, gotBuf)
}
if !reflect.DeepEqual(gotArgs, tt.wantArgs) {
t.Fatalf("args mismatch: want %v got %v", tt.wantArgs, gotArgs)
}
})
}
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ require (

require (
github.com/frankban/quicktest v1.14.3 // indirect
golang.org/x/sys v0.7.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
golang.org/x/sys v0.6.0 // indirect
)
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
Expand All @@ -16,11 +17,10 @@ github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBO
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/xo/dburl v0.13.1 h1:EV+BCdo539sc/mBrny0VxaEGLM0b1U0mJA9RpP80ux0=
github.com/xo/dburl v0.13.1/go.mod h1:B7/G9FGungw6ighV8xJNwWYQPMfn3gsi2sn5SE8Bzco=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
28 changes: 19 additions & 9 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const (

type SQCloudNode struct {
NodeID int64
PublicAddr string
NodeInterface string
ClusterInterface string
Status SQCloudNodeStatus
Expand Down Expand Up @@ -157,16 +158,17 @@ func (this *SQCloud) ListNodes() ([]SQCloudNode, error) {
if err == nil {
if result != nil {
defer result.Free()
if result.GetNumberOfColumns() == 7 {
if result.GetNumberOfColumns() == 8 {
for row, rows := uint64(0), result.GetNumberOfRows(); row < rows; row++ {
node := SQCloudNode{}
node.NodeID, _ = result.GetInt64Value(row, 0)
node.NodeInterface, _ = result.GetStringValue(row, 1)
node.ClusterInterface, _ = result.GetStringValue(row, 2)
node.Status, _ = stringToSQCloudNodeStatus(result.GetStringValue_(row, 3))
node.Progress, _ = stringToSQCloudNodeProgress(result.GetStringValue_(row, 4))
node.Match, _ = result.GetInt64Value(row, 5)
node.LastActivity, _ = result.GetSQLDateTime(row, 6)
node.PublicAddr = result.GetStringValue_(row, 1)
node.NodeInterface, _ = result.GetStringValue(row, 2)
node.ClusterInterface, _ = result.GetStringValue(row, 3)
node.Status, _ = stringToSQCloudNodeStatus(result.GetStringValue_(row, 4))
node.Progress, _ = stringToSQCloudNodeProgress(result.GetStringValue_(row, 5))
node.Match, _ = result.GetInt64Value(row, 6)
node.LastActivity, _ = result.GetSQLDateTime(row, 7)
list = append(list, node)
}
return list, nil
Expand Down Expand Up @@ -260,13 +262,21 @@ func (this *SQCloud) Auth(Username string, Password string) error {
return this.ExecuteArray(authCommand(Username, Password, false))
}

func authWithKeyCommand(Key string) (string, []interface{}) {
func authWithApiKeyCommand(Key string) (string, []interface{}) {
return "AUTH APIKEY ?;", []interface{}{Key}
}

func authWithTokenCommand(Token string) (string, []interface{}) {
return "AUTH TOKEN ?;", []interface{}{Token}
}

// Auth - INTERNAL SERVER COMMAND: Authenticates User with the given API KEY.
func (this *SQCloud) AuthWithKey(Key string) error {
return this.ExecuteArray(authWithKeyCommand(Key))
return this.ExecuteArray(authWithApiKeyCommand(Key))
}

func (this *SQCloud) AuthWithToken(Token string) error {
return this.ExecuteArray(authWithTokenCommand(Token))
}

// Database funcitons
Expand Down
1 change: 0 additions & 1 deletion test/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ require (

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand Down
3 changes: 1 addition & 2 deletions test/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
Expand Down
37 changes: 35 additions & 2 deletions test/unit/connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import (
)

func TestParseConnectionString(t *testing.T) {
connectionString := "sqlitecloud://myhost.sqlite.cloud:8860/mydatabase?timeout=11&compress=yes"
connectionString := "sqlitecloud://myproject.sqlite.cloud:8860/mydatabase?timeout=11&compress=yes"

expectedConfig := &sqlitecloud.SQCloudConfig{
Host: "myhost.sqlite.cloud",
Host: "myproject.sqlite.cloud",
Port: 8860,
ProjectID: "myproject",
Username: "",
Password: "",
Database: "mydatabase",
Expand Down Expand Up @@ -68,6 +69,32 @@ func TestParseConnectionStringWithAPIKey(t *testing.T) {
assert.Truef(t, reflect.DeepEqual(expectedConfig, config), "Expected: %+v\nGot: %+v", expectedConfig, config)
}

func TestParseConnectionStringWithToken(t *testing.T) {
connectionString := "sqlitecloud://host.com:8860/dbname?token=123|tok123&compress=true"
expectedConfig := &sqlitecloud.SQCloudConfig{
Host: "host.com",
Port: 8860,
Username: "",
Password: "",
Database: "dbname",
Timeout: 0,
Compression: true,
CompressMode: sqlitecloud.CompressModeLZ4,
Secure: true,
TlsInsecureSkipVerify: false,
Pem: "",
Token: "123|tok123",
NoBlob: false,
MaxData: 0,
MaxRows: 0,
MaxRowset: 0,
}

config, err := sqlitecloud.ParseConnectionString(connectionString)
assert.NoError(t, err)
assert.Truef(t, reflect.DeepEqual(expectedConfig, config), "Expected: %+v\nGot: %+v", expectedConfig, config)
}

func TestParseConnectionStringWithCredentials(t *testing.T) {
connectionString := "sqlitecloud://user:pass@host.com:8860"
config, err := sqlitecloud.ParseConnectionString(connectionString)
Expand Down Expand Up @@ -187,6 +214,12 @@ func TestParseConnectionStringWithParameters(t *testing.T) {
value: "abc123",
expectedValue: "abc123",
},
{
param: "token",
configParam: "Token",
value: "123|tok123",
expectedValue: "123|tok123",
},
}

for _, tt := range tests {
Expand Down