Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
36 changes: 36 additions & 0 deletions .github/workflows/run-presto-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Run Tests

on:
pull_request:

jobs:
test:
name: Run Presto Go Client Tests
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'

- name: Install Docker
run: |
sudo apt-get update
sudo apt-get install -y docker.io
sudo systemctl start docker
sudo usermod -aG docker $USER

- name: Verify Docker Installation
run: docker --version

- name: Run integration tests
env:
PRESTO_SERVER_DSN: "http://test@localhost:8080" # Replace with actual DSN if required
run: |
chmod +x integration_tests/run.sh
./integration_tests/run.sh

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/jcmturner/gofork v1.0.0 // indirect
github.com/stretchr/testify v1.5.1 // indirect
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d // indirect
golang.org/x/crypto v0.31.0 // indirect
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
Expand Down
9 changes: 2 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
Expand Down
52 changes: 52 additions & 0 deletions presto/null_slices_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package presto

import (
"encoding/json"
"fmt"
"reflect"
)

// normalizeToInterfaceSlice accepts the raw `value` that Scan receives and returns a []interface{}
// representation, accepting these input shapes:
// - []interface{} -> returned as-is
// - JSON string (e.g. '["a","b"]') or []byte -> json.Unmarshal into []interface{}
// - []T where T is concrete (e.g. []string, []int64) -> convert by reflection
// Returns an error if it cannot be converted.
func normalizeToInterfaceSlice(value interface{}) ([]interface{}, error) {
if value == nil {
return nil, nil
}

switch v := value.(type) {
case []interface{}:
return v, nil
case string:
var tmp []interface{}
if err := json.Unmarshal([]byte(v), &tmp); err == nil {
return tmp, nil
} else {
return nil, fmt.Errorf("presto: cannot unmarshal string into slice: %v", err)
}
case []byte:
var tmp []interface{}
if err := json.Unmarshal(v, &tmp); err == nil {
return tmp, nil
} else {
return nil, fmt.Errorf("presto: cannot unmarshal bytes into slice: %v", err)
}
default:
// If it's a slice of a concrete type (e.g. []string, []int64), use reflection to
// copy elements into []interface{}.
rv := reflect.ValueOf(value)
if rv.Kind() == reflect.Slice {
l := rv.Len()
out := make([]interface{}, l)
for i := 0; i < l; i++ {
out[i] = rv.Index(i).Interface()
}
return out, nil
}
}

return nil, fmt.Errorf("presto: cannot convert %v (%T) to []interface{}", value, value)
}
42 changes: 24 additions & 18 deletions presto/presto.go
Original file line number Diff line number Diff line change
Expand Up @@ -965,10 +965,21 @@ func (c *typeConverter) ConvertValue(v interface{}) (driver.Value, error) {
}
return v, nil
case "array":
if err := validateSlice(v); err != nil {
return nil, err
switch val := v.(type) {
case nil:
return nil, nil
case string:
// Presto often returns array(...) as a JSON-encoded string
var arr []interface{}
if err := json.Unmarshal([]byte(val), &arr); err != nil {
return nil, fmt.Errorf("cannot parse array from string %q: %w", val, err)
}
return arr, nil
case []interface{}:
return val, nil
default:
return nil, fmt.Errorf("cannot convert %v (%T) to slice", v, v)
}
return v, nil
default:
return nil, fmt.Errorf("type not supported: %q", c.typeName)
}
Expand All @@ -984,16 +995,6 @@ func validateMap(v interface{}) error {
return nil
}

func validateSlice(v interface{}) error {
if v == nil {
return nil
}
if _, ok := v.([]interface{}); !ok {
return fmt.Errorf("cannot convert %v (%T) to slice", v, v)
}
return nil
}

func scanNullBool(v interface{}) (sql.NullBool, error) {
if v == nil {
return sql.NullBool{}, nil
Expand Down Expand Up @@ -1111,19 +1112,24 @@ type NullSliceString struct {
// Scan implements the sql.Scanner interface.
func (s *NullSliceString) Scan(value interface{}) error {
if value == nil {
// keep behaviour consistent: Null => valid false, empty slice
s.SliceString = nil
s.Valid = false
return nil
}
vs, ok := value.([]interface{})
if !ok {
return fmt.Errorf("presto: cannot convert %v (%T) to []string", value, value)

vs, err := normalizeToInterfaceSlice(value)
if err != nil {
return fmt.Errorf("presto: cannot convert %v (%T) to []string: %v", value, value, err)
}

slice := make([]sql.NullString, len(vs))
for i := range vs {
v, err := scanNullString(vs[i])
ns, err := scanNullString(vs[i]) // existing helper that turns an element into sql.NullString
if err != nil {
return err
}
slice[i] = v
slice[i] = ns
}
s.SliceString = slice
s.Valid = true
Expand Down