From d11fc397f1b9801de795ebe4c950de5ed75aa897 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 19 Sep 2024 15:50:12 -0500 Subject: [PATCH 01/41] try to recreate the issue with UUID to solve #1209 --- converter_test.go | 16 ++++++++++++---- go.mod | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/converter_test.go b/converter_test.go index 375359465..5b52f48d6 100644 --- a/converter_test.go +++ b/converter_test.go @@ -282,6 +282,14 @@ func TestValueToString(t *testing.T) { assertEqualE(t, *bv.value, "[1,2]") }) + t.Run("UUID - should return string", func(t *testing.T) { + u := uuid.New() + bv, err := valueToString(u, textType, nil) + assertNilF(t, err) + assertEmptyStringE(t, bv.format) + assertEqualE(t, *bv.value, u.String()) + }) + bv, err = valueToString(&testValueToStringStructuredObject{s: "some string", i: 123, date: time.Date(2024, time.May, 24, 0, 0, 0, 0, time.UTC)}, timestampLtzType, params) assertNilF(t, err) assertEqualE(t, bv.format, "json") @@ -2175,7 +2183,7 @@ func TestSmallTimestampBinding(t *testing.T) { rows := sct.mustQueryContext(ctx, "SELECT ?", parameters) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() scanValues := make([]driver.Value, 1) @@ -2213,7 +2221,7 @@ func TestTimestampConversionWithoutArrowBatches(t *testing.T) { query := fmt.Sprintf("SELECT '%s'::%s(%v)", tsStr, tp, scale) rows := sct.mustQueryContext(ctx, query, nil) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() if rows.Next() { @@ -2295,7 +2303,7 @@ func TestTimestampConversionWithArrowBatchesMicrosecondPassesForDistantDates(t * t.Fatalf("failed to query: %v", err) } defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() // getting result batches @@ -2356,7 +2364,7 @@ func TestTimestampConversionWithArrowBatchesAndWithOriginalTimestamp(t *testing. query := fmt.Sprintf("SELECT '%s'::%s(%v)", tsStr, tp, scale) rows := sct.mustQueryContext(ctx, query, []driver.NamedValue{}) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() // getting result batches diff --git a/go.mod b/go.mod index b4c1fca13..fdaf087cb 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/aws/smithy-go v1.20.2 github.com/gabriel-vasile/mimetype v1.4.2 github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/google/uuid v1.3.1 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/sirupsen/logrus v1.9.3 golang.org/x/crypto v0.22.0 From 49e0ee52db244cb9eedf6cfe2b31db6422a0b43f Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 19 Sep 2024 16:15:54 -0500 Subject: [PATCH 02/41] [AB#1669514] fix UUID --- .gitignore | 2 ++ converter.go | 16 ++++++++++++++++ converter_test.go | 2 +- go.mod | 1 - 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c8fad7b27..775b37cee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea/ +.vscode/ parameters*.json parameters*.bat *.p8 @@ -11,6 +12,7 @@ wss-golang-agent.config wss-unified-agent.jar whitesource/ *.swp +cp.out # exclude vendor vendor diff --git a/converter.go b/converter.go index 57ccd9293..a7105bc6d 100644 --- a/converter.go +++ b/converter.go @@ -276,6 +276,7 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri if v1.Kind() == reflect.Slice && v1.IsNil() { return bindingValue{nil, "json", nil}, nil } + if bd, ok := v.([][]byte); ok && tsmode == binaryType { schema := bindingSchema{ Typ: "array", @@ -358,6 +359,21 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } else if reflect.ValueOf(v).Len() == 0 { value := "[]" return bindingValue{&value, "json", nil}, nil + // } else if u, ok := v.(UUID); ok { + // value := u.String() + // return bindingValue{&value, "", nil}, nil + // } else if u, ok := v.([16]byte); ok { + // value := UUID(u).String() + // return bindingValue{&value, "", nil}, nil + } else if v1.Kind() == reflect.Array && v1.Type().Elem().Kind() == reflect.Uint8 && v1.Len() == 16 { // special case for all UUID + // Convert the value to [16]byte + var bytes UUID + for idx := 0; idx < 16; idx++ { + bytes[idx] = uint8(v1.Index(idx).Uint()) + } + + value := bytes.String() + return bindingValue{&value, "", nil}, nil } else if barr, ok := v.([]byte); ok { if tsmode == binaryType { res := hex.EncodeToString(barr) diff --git a/converter_test.go b/converter_test.go index 5b52f48d6..9baf3cc32 100644 --- a/converter_test.go +++ b/converter_test.go @@ -283,7 +283,7 @@ func TestValueToString(t *testing.T) { }) t.Run("UUID - should return string", func(t *testing.T) { - u := uuid.New() + u := NewUUID() bv, err := valueToString(u, textType, nil) assertNilF(t, err) assertEmptyStringE(t, bv.format) diff --git a/go.mod b/go.mod index fdaf087cb..b4c1fca13 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,6 @@ require ( github.com/aws/smithy-go v1.20.2 github.com/gabriel-vasile/mimetype v1.4.2 github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/google/uuid v1.3.1 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/sirupsen/logrus v1.9.3 golang.org/x/crypto v0.22.0 From 6d32e2cffa6d1d5f55f23c47d876b1bdafa40b19 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 19 Sep 2024 16:17:44 -0500 Subject: [PATCH 03/41] [AB#1669514] remove commented code --- converter.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/converter.go b/converter.go index a7105bc6d..c2ab4b982 100644 --- a/converter.go +++ b/converter.go @@ -276,7 +276,6 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri if v1.Kind() == reflect.Slice && v1.IsNil() { return bindingValue{nil, "json", nil}, nil } - if bd, ok := v.([][]byte); ok && tsmode == binaryType { schema := bindingSchema{ Typ: "array", @@ -359,12 +358,6 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } else if reflect.ValueOf(v).Len() == 0 { value := "[]" return bindingValue{&value, "json", nil}, nil - // } else if u, ok := v.(UUID); ok { - // value := u.String() - // return bindingValue{&value, "", nil}, nil - // } else if u, ok := v.([16]byte); ok { - // value := UUID(u).String() - // return bindingValue{&value, "", nil}, nil } else if v1.Kind() == reflect.Array && v1.Type().Elem().Kind() == reflect.Uint8 && v1.Len() == 16 { // special case for all UUID // Convert the value to [16]byte var bytes UUID From f8ebfc95a6c151ce7ce22d62ef8b9655813701dd Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 08:39:08 -0500 Subject: [PATCH 04/41] [AB#1669514] alternate approach; more generalized --- converter.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/converter.go b/converter.go index c2ab4b982..eca46179b 100644 --- a/converter.go +++ b/converter.go @@ -358,7 +358,14 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } else if reflect.ValueOf(v).Len() == 0 { value := "[]" return bindingValue{&value, "json", nil}, nil - } else if v1.Kind() == reflect.Array && v1.Type().Elem().Kind() == reflect.Uint8 && v1.Len() == 16 { // special case for all UUID + } else if hasStringMethod(v1) { // alternate approach; check for stringer method + method := v1.MethodByName("String") + result := method.Call(nil) // Call with no arguments + if len(result) == 1 && result[0].Kind() == reflect.String { + value := result[0].String() + return bindingValue{&value, "", nil}, nil + } + } else if v1.Type().Elem().Kind() == reflect.Uint8 && v1.Len() == 16 { // special case for all UUID; which do we like better? // Convert the value to [16]byte var bytes UUID for idx := 0; idx < 16; idx++ { @@ -750,6 +757,11 @@ func isArrayOfStructs(v any) bool { return reflect.TypeOf(v).Elem().Kind() == reflect.Struct || (reflect.TypeOf(v).Elem().Kind() == reflect.Pointer && reflect.TypeOf(v).Elem().Elem().Kind() == reflect.Struct) } +func hasStringMethod(v reflect.Value) bool { + method := v.MethodByName("String") + return method.IsValid() +} + func structValueToString(v driver.Value, tsmode snowflakeType, params map[string]*string) (bindingValue, error) { switch typedVal := v.(type) { case time.Time: From ba297f1ead74df8a0151dc37948842fd16bc9ccc Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 09:23:10 -0500 Subject: [PATCH 05/41] [AB#1669514] add stringer safe check; driver valuer check too --- converter.go | 56 +++++++++++++++++++++++++++++++++-------------- converter_test.go | 14 ++++++++++++ 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/converter.go b/converter.go index eca46179b..681c94449 100644 --- a/converter.go +++ b/converter.go @@ -358,22 +358,6 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } else if reflect.ValueOf(v).Len() == 0 { value := "[]" return bindingValue{&value, "json", nil}, nil - } else if hasStringMethod(v1) { // alternate approach; check for stringer method - method := v1.MethodByName("String") - result := method.Call(nil) // Call with no arguments - if len(result) == 1 && result[0].Kind() == reflect.String { - value := result[0].String() - return bindingValue{&value, "", nil}, nil - } - } else if v1.Type().Elem().Kind() == reflect.Uint8 && v1.Len() == 16 { // special case for all UUID; which do we like better? - // Convert the value to [16]byte - var bytes UUID - for idx := 0; idx < 16; idx++ { - bytes[idx] = uint8(v1.Index(idx).Uint()) - } - - value := bytes.String() - return bindingValue{&value, "", nil}, nil } else if barr, ok := v.([]byte); ok { if tsmode == binaryType { res := hex.EncodeToString(barr) @@ -401,6 +385,28 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri return bindingValue{&res, "json", &schemaForBytes}, nil } else if isSliceOfSlices(v) { return bindingValue{}, errors.New("array of arrays is not supported") + } else if valuer, ok := v1.Interface().(driver.Valuer); ok { // alternate approach; check for db valuer satisfaction + value, err := valuer.Value() + + if err != nil || value == nil { + return bindingValue{}, err + } + + if v, ok := value.(string); ok { + return bindingValue{&v, "", nil}, nil + } + + return bindingValue{}, nil + } else if hasStringMethod(v1) { // alternate approach; check for stringer method. Guarantees it's String() and returns string + method := v1.MethodByName("String") + result := method.Call(nil) // Call with no arguments + + // we already validated the output in the if statement above + if len(result) == 0 { + return bindingValue{}, nil + } + value := result[0].String() + return bindingValue{&value, "", nil}, nil } res, err := json.Marshal(v) if err != nil { @@ -757,9 +763,25 @@ func isArrayOfStructs(v any) bool { return reflect.TypeOf(v).Elem().Kind() == reflect.Struct || (reflect.TypeOf(v).Elem().Kind() == reflect.Pointer && reflect.TypeOf(v).Elem().Elem().Kind() == reflect.Struct) } +// hasStringMethod checks if the given reflect.Value has a "String" method that takes no arguments and returns a string func hasStringMethod(v reflect.Value) bool { method := v.MethodByName("String") - return method.IsValid() + if !method.IsValid() { + return false + } + + methodType := method.Type() + // Check if the method takes no arguments and returns one value + if methodType.NumIn() != 0 || methodType.NumOut() != 1 { + return false + } + + // Check if the return value is of type string + if methodType.Out(0).Kind() != reflect.String { + return false + } + + return true } func structValueToString(v driver.Value, tsmode snowflakeType, params map[string]*string) (bindingValue, error) { diff --git a/converter_test.go b/converter_test.go index 9baf3cc32..26c4f0d41 100644 --- a/converter_test.go +++ b/converter_test.go @@ -214,6 +214,12 @@ func (o *testValueToStringStructuredObject) Write(sowc StructuredObjectWriterCon return nil } +type testSQLUUID = UUID + +func (uuid testSQLUUID) Value() (driver.Value, error) { + return uuid.String(), nil +} + func TestValueToString(t *testing.T) { v := cmplx.Sqrt(-5 + 12i) // should never happen as Go sql package must have already validated. _, err := valueToString(v, nullType, nil) @@ -290,6 +296,14 @@ func TestValueToString(t *testing.T) { assertEqualE(t, *bv.value, u.String()) }) + t.Run("database/sql/driver - Valuer interface", func(t *testing.T) { + u := testSQLUUID(NewUUID()) + bv, err := valueToString(u, textType, nil) + assertNilF(t, err) + assertEmptyStringE(t, bv.format) + assertEqualE(t, *bv.value, u.String()) + }) + bv, err = valueToString(&testValueToStringStructuredObject{s: "some string", i: 123, date: time.Date(2024, time.May, 24, 0, 0, 0, 0, time.UTC)}, timestampLtzType, params) assertNilF(t, err) assertEqualE(t, bv.format, "json") From 70ae88a4f0cc03c418c2f2e38f0d9e75bb9ef8f4 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 09:34:14 -0500 Subject: [PATCH 06/41] [AB#1669514] Fix some comments --- converter.go | 2 +- converter_test.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/converter.go b/converter.go index 681c94449..6603cab88 100644 --- a/converter.go +++ b/converter.go @@ -385,7 +385,7 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri return bindingValue{&res, "json", &schemaForBytes}, nil } else if isSliceOfSlices(v) { return bindingValue{}, errors.New("array of arrays is not supported") - } else if valuer, ok := v1.Interface().(driver.Valuer); ok { // alternate approach; check for db valuer satisfaction + } else if valuer, ok := v1.Interface().(driver.Valuer); ok { // check for driver.Valuer satisfaction and honor that first value, err := valuer.Value() if err != nil || value == nil { diff --git a/converter_test.go b/converter_test.go index 26c4f0d41..bd09c2cdf 100644 --- a/converter_test.go +++ b/converter_test.go @@ -214,9 +214,9 @@ func (o *testValueToStringStructuredObject) Write(sowc StructuredObjectWriterCon return nil } -type testSQLUUID = UUID +type testSqlUuid = UUID -func (uuid testSQLUUID) Value() (driver.Value, error) { +func (uuid testSqlUuid) Value() (driver.Value, error) { return uuid.String(), nil } @@ -297,7 +297,7 @@ func TestValueToString(t *testing.T) { }) t.Run("database/sql/driver - Valuer interface", func(t *testing.T) { - u := testSQLUUID(NewUUID()) + u := testSqlUuid(NewUUID()) bv, err := valueToString(u, textType, nil) assertNilF(t, err) assertEmptyStringE(t, bv.format) From 442dc8676a4e7903c1e48f07973e7b5aeca96774 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 09:38:32 -0500 Subject: [PATCH 07/41] [AB#1669514] simplify stringer check --- converter.go | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/converter.go b/converter.go index 6603cab88..2200963f2 100644 --- a/converter.go +++ b/converter.go @@ -397,15 +397,8 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } return bindingValue{}, nil - } else if hasStringMethod(v1) { // alternate approach; check for stringer method. Guarantees it's String() and returns string - method := v1.MethodByName("String") - result := method.Call(nil) // Call with no arguments - - // we already validated the output in the if statement above - if len(result) == 0 { - return bindingValue{}, nil - } - value := result[0].String() + } else if stringer, ok := v1.Interface().(fmt.Stringer); ok { // alternate approach; check for stringer method. Guarantees it's String() and returns string + value := stringer.String() return bindingValue{&value, "", nil}, nil } res, err := json.Marshal(v) @@ -763,27 +756,6 @@ func isArrayOfStructs(v any) bool { return reflect.TypeOf(v).Elem().Kind() == reflect.Struct || (reflect.TypeOf(v).Elem().Kind() == reflect.Pointer && reflect.TypeOf(v).Elem().Elem().Kind() == reflect.Struct) } -// hasStringMethod checks if the given reflect.Value has a "String" method that takes no arguments and returns a string -func hasStringMethod(v reflect.Value) bool { - method := v.MethodByName("String") - if !method.IsValid() { - return false - } - - methodType := method.Type() - // Check if the method takes no arguments and returns one value - if methodType.NumIn() != 0 || methodType.NumOut() != 1 { - return false - } - - // Check if the return value is of type string - if methodType.Out(0).Kind() != reflect.String { - return false - } - - return true -} - func structValueToString(v driver.Value, tsmode snowflakeType, params map[string]*string) (bindingValue, error) { switch typedVal := v.(type) { case time.Time: From 5a4ba7df0789c41eecf860031e5a3e7bd1cd5625 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 14:06:50 -0500 Subject: [PATCH 08/41] [AB#1669514] simplify valuer --- converter.go | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/converter.go b/converter.go index 2200963f2..b6b77d9af 100644 --- a/converter.go +++ b/converter.go @@ -386,17 +386,11 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } else if isSliceOfSlices(v) { return bindingValue{}, errors.New("array of arrays is not supported") } else if valuer, ok := v1.Interface().(driver.Valuer); ok { // check for driver.Valuer satisfaction and honor that first - value, err := valuer.Value() - - if err != nil || value == nil { - return bindingValue{}, err - } - - if v, ok := value.(string); ok { - return bindingValue{&v, "", nil}, nil + if value, err := valuer.Value(); err == nil && value != nil { + if v, ok := value.(string); ok { + return bindingValue{&v, "", nil}, nil + } } - - return bindingValue{}, nil } else if stringer, ok := v1.Interface().(fmt.Stringer); ok { // alternate approach; check for stringer method. Guarantees it's String() and returns string value := stringer.String() return bindingValue{&value, "", nil}, nil From ab7db31a49b01c9a68dceef8a66c24aeb203961e Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 14:15:32 -0500 Subject: [PATCH 09/41] [AB#1669514] move the driver valuer short circuit above the other type checks --- converter.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/converter.go b/converter.go index b6b77d9af..5e723ad93 100644 --- a/converter.go +++ b/converter.go @@ -244,6 +244,15 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri return bindingValue{nil, "", nil}, nil } v1 := reflect.Indirect(reflect.ValueOf(v)) + + if valuer, ok := v.(driver.Valuer); ok { // check for driver.Valuer satisfaction and honor that first + if value, err := valuer.Value(); err == nil && value != nil { + if strVal, ok := value.(string); ok { + return bindingValue{&strVal, "", nil}, nil + } + } + } + switch v1.Kind() { case reflect.Bool: s := strconv.FormatBool(v1.Bool()) @@ -383,17 +392,11 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } res = res[0:len(res)-1] + "]" return bindingValue{&res, "json", &schemaForBytes}, nil - } else if isSliceOfSlices(v) { - return bindingValue{}, errors.New("array of arrays is not supported") - } else if valuer, ok := v1.Interface().(driver.Valuer); ok { // check for driver.Valuer satisfaction and honor that first - if value, err := valuer.Value(); err == nil && value != nil { - if v, ok := value.(string); ok { - return bindingValue{&v, "", nil}, nil - } - } } else if stringer, ok := v1.Interface().(fmt.Stringer); ok { // alternate approach; check for stringer method. Guarantees it's String() and returns string value := stringer.String() return bindingValue{&value, "", nil}, nil + } else if isSliceOfSlices(v) { + return bindingValue{}, errors.New("array of arrays is not supported") } res, err := json.Marshal(v) if err != nil { From 046ffa1a0863e8f4fdb70d55dbf9013191c77471 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 14:22:06 -0500 Subject: [PATCH 10/41] [AB#1669514] dont run stringer on everything arrays --- converter.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/converter.go b/converter.go index 5e723ad93..f41fc8051 100644 --- a/converter.go +++ b/converter.go @@ -392,7 +392,8 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } res = res[0:len(res)-1] + "]" return bindingValue{&res, "json", &schemaForBytes}, nil - } else if stringer, ok := v1.Interface().(fmt.Stringer); ok { // alternate approach; check for stringer method. Guarantees it's String() and returns string + } else if stringer, ok := v1.Interface().(fmt.Stringer); ok && v1.Type().Elem().Kind() == reflect.Uint8 && v1.Len() == 16 { + // special case for UUIDs (snowflake type and other implementers) check for stringer method and it's a len 16 byte array. Guarantees it's String() and returns string value := stringer.String() return bindingValue{&value, "", nil}, nil } else if isSliceOfSlices(v) { From e37107e894e5885b27da5facfc33a951833fff85 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 14:27:26 -0500 Subject: [PATCH 11/41] [AB#1669514] add array check too --- converter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/converter.go b/converter.go index f41fc8051..f3ac65523 100644 --- a/converter.go +++ b/converter.go @@ -392,7 +392,7 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } res = res[0:len(res)-1] + "]" return bindingValue{&res, "json", &schemaForBytes}, nil - } else if stringer, ok := v1.Interface().(fmt.Stringer); ok && v1.Type().Elem().Kind() == reflect.Uint8 && v1.Len() == 16 { + } else if stringer, ok := v1.Interface().(fmt.Stringer); ok && v1.Kind() == reflect.Array && v1.Type().Elem().Kind() == reflect.Uint8 && v1.Len() == 16 { // special case for UUIDs (snowflake type and other implementers) check for stringer method and it's a len 16 byte array. Guarantees it's String() and returns string value := stringer.String() return bindingValue{&value, "", nil}, nil From ad715dd4b3570cd5e6abb55e8d73a2903f5d9fa7 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 14:37:15 -0500 Subject: [PATCH 12/41] [AB#1669514] jsonFmtStr and ignore debug binary --- .gitignore | 1 + converter.go | 46 ++++++++++++++++++++++++---------------------- converter_test.go | 6 +++--- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 775b37cee..04e2c639d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ wss-unified-agent.jar whitesource/ *.swp cp.out +__debug_bin* # exclude vendor vendor diff --git a/converter.go b/converter.go index f3ac65523..c4cf4b8b8 100644 --- a/converter.go +++ b/converter.go @@ -28,6 +28,7 @@ import ( const format = "2006-01-02 15:04:05.999999999" const numberDefaultPrecision = 38 +const jsonFormatStr = "json" type timezoneType int @@ -239,7 +240,7 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri logger.Debugf("TYPE: %v, %v", reflect.TypeOf(v), reflect.ValueOf(v)) if v == nil { if tsmode == objectType || tsmode == arrayType || tsmode == sliceType { - return bindingValue{nil, "json", nil}, nil + return bindingValue{nil, jsonFormatStr, nil}, nil } return bindingValue{nil, "", nil}, nil } @@ -247,6 +248,7 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri if valuer, ok := v.(driver.Valuer); ok { // check for driver.Valuer satisfaction and honor that first if value, err := valuer.Value(); err == nil && value != nil { + // if the output value is a valid string, return that if strVal, ok := value.(string); ok { return bindingValue{&strVal, "", nil}, nil } @@ -266,7 +268,7 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri case reflect.String: s := v1.String() if tsmode == objectType || tsmode == arrayType || tsmode == sliceType { - return bindingValue{&s, "json", nil}, nil + return bindingValue{&s, jsonFormatStr, nil}, nil } return bindingValue{&s, "", nil}, nil case reflect.Slice, reflect.Array: @@ -283,7 +285,7 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*string) (bindingValue, error) { v1 := reflect.Indirect(reflect.ValueOf(v)) if v1.Kind() == reflect.Slice && v1.IsNil() { - return bindingValue{nil, "json", nil}, nil + return bindingValue{nil, jsonFormatStr, nil}, nil } if bd, ok := v.([][]byte); ok && tsmode == binaryType { schema := bindingSchema{ @@ -298,14 +300,14 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } if len(bd) == 0 { res := "[]" - return bindingValue{value: &res, format: "json", schema: &schema}, nil + return bindingValue{value: &res, format: jsonFormatStr, schema: &schema}, nil } s := "" for _, b := range bd { s += "\"" + hex.EncodeToString(b) + "\"," } s = "[" + s[:len(s)-1] + "]" - return bindingValue{&s, "json", &schema}, nil + return bindingValue{&s, jsonFormatStr, &schema}, nil } else if times, ok := v.([]time.Time); ok { typ := driverTypeToSnowflake[tsmode] sfFormat, err := dateTimeInputFormatByType(typ, params) @@ -322,7 +324,7 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } res, err := json.Marshal(v) if err != nil { - return bindingValue{nil, "json", &bindingSchema{ + return bindingValue{nil, jsonFormatStr, &bindingSchema{ Typ: "array", Nullable: true, Fields: []fieldMetadata{ @@ -334,7 +336,7 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri }}, err } resString := string(res) - return bindingValue{&resString, "json", nil}, nil + return bindingValue{&resString, jsonFormatStr, nil}, nil } else if isArrayOfStructs(v) { stringEntries := make([]string, v1.Len()) sowcForSingleElement, err := buildSowcFromType(params, reflect.TypeOf(v).Elem()) @@ -346,7 +348,7 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri if sow, ok := potentialSow.Interface().(StructuredObjectWriter); ok { bv, err := structValueToString(sow, tsmode, params) if err != nil { - return bindingValue{nil, "json", nil}, err + return bindingValue{nil, jsonFormatStr, nil}, err } stringEntries[i] = *bv.value } @@ -363,14 +365,14 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri }, }, } - return bindingValue{&value, "json", arraySchema}, nil + return bindingValue{&value, jsonFormatStr, arraySchema}, nil } else if reflect.ValueOf(v).Len() == 0 { value := "[]" - return bindingValue{&value, "json", nil}, nil + return bindingValue{&value, jsonFormatStr, nil}, nil } else if barr, ok := v.([]byte); ok { if tsmode == binaryType { res := hex.EncodeToString(barr) - return bindingValue{&res, "json", nil}, nil + return bindingValue{&res, jsonFormatStr, nil}, nil } schemaForBytes := bindingSchema{ Typ: "array", @@ -384,14 +386,14 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } if len(barr) == 0 { res := "[]" - return bindingValue{&res, "json", &schemaForBytes}, nil + return bindingValue{&res, jsonFormatStr, &schemaForBytes}, nil } res := "[" for _, b := range barr { res += fmt.Sprint(b) + "," } res = res[0:len(res)-1] + "]" - return bindingValue{&res, "json", &schemaForBytes}, nil + return bindingValue{&res, jsonFormatStr, &schemaForBytes}, nil } else if stringer, ok := v1.Interface().(fmt.Stringer); ok && v1.Kind() == reflect.Array && v1.Type().Elem().Kind() == reflect.Uint8 && v1.Len() == 16 { // special case for UUIDs (snowflake type and other implementers) check for stringer method and it's a len 16 byte array. Guarantees it's String() and returns string value := stringer.String() @@ -401,10 +403,10 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } res, err := json.Marshal(v) if err != nil { - return bindingValue{nil, "json", nil}, err + return bindingValue{nil, jsonFormatStr, nil}, err } resString := string(res) - return bindingValue{&resString, "json", nil}, nil + return bindingValue{&resString, jsonFormatStr, nil}, nil } func mapToString(v driver.Value, tsmode snowflakeType, params map[string]*string) (bindingValue, error) { @@ -562,7 +564,7 @@ func mapToString(v driver.Value, tsmode snowflakeType, params map[string]*string Typ: "MAP", Fields: []fieldMetadata{keyMetadata, *valueMetadata}, } - return bindingValue{&jsonString, "json", &schema}, nil + return bindingValue{&jsonString, jsonFormatStr, &schema}, nil } else { jsonBytes, err = json.Marshal(v) if err != nil { @@ -582,7 +584,7 @@ func mapToString(v driver.Value, tsmode snowflakeType, params map[string]*string Typ: "MAP", Fields: []fieldMetadata{keyMetadata, valueMetadata}, } - return bindingValue{&jsonString, "json", &schema}, nil + return bindingValue{&jsonString, jsonFormatStr, &schema}, nil } func toNullableInt64(val any) (int64, bool) { @@ -784,7 +786,7 @@ func structValueToString(v driver.Value, tsmode snowflakeType, params map[string case sql.NullString: fmt := "" if tsmode == objectType || tsmode == arrayType || tsmode == sliceType { - fmt = "json" + fmt = jsonFormatStr } if !typedVal.Valid { return bindingValue{nil, fmt, nil}, nil @@ -808,7 +810,7 @@ func structValueToString(v driver.Value, tsmode snowflakeType, params map[string Nullable: true, Fields: sowc.toFields(), } - return bindingValue{&jsonString, "json", &schema}, nil + return bindingValue{&jsonString, jsonFormatStr, &schema}, nil } else if typ, ok := v.(reflect.Type); ok && tsmode == nilArrayType { metadata, err := goTypeToFieldMetadata(typ, tsmode, params) if err != nil { @@ -821,7 +823,7 @@ func structValueToString(v driver.Value, tsmode snowflakeType, params map[string metadata, }, } - return bindingValue{nil, "json", &schema}, nil + return bindingValue{nil, jsonFormatStr, &schema}, nil } else if types, ok := v.(NilMapTypes); ok && tsmode == nilMapType { keyMetadata, err := goTypeToFieldMetadata(types.Key, tsmode, params) if err != nil { @@ -836,7 +838,7 @@ func structValueToString(v driver.Value, tsmode snowflakeType, params map[string Nullable: true, Fields: []fieldMetadata{keyMetadata, valueMetadata}, } - return bindingValue{nil, "json", &schema}, nil + return bindingValue{nil, jsonFormatStr, &schema}, nil } else if typ, ok := v.(reflect.Type); ok && tsmode == nilObjectType { metadata, err := goTypeToFieldMetadata(typ, tsmode, params) if err != nil { @@ -847,7 +849,7 @@ func structValueToString(v driver.Value, tsmode snowflakeType, params map[string Nullable: true, Fields: metadata.Fields, } - return bindingValue{nil, "json", &schema}, nil + return bindingValue{nil, jsonFormatStr, &schema}, nil } return bindingValue{}, fmt.Errorf("unknown binding for type %T and mode %v", v, tsmode) } diff --git a/converter_test.go b/converter_test.go index bd09c2cdf..4174e1142 100644 --- a/converter_test.go +++ b/converter_test.go @@ -278,13 +278,13 @@ func TestValueToString(t *testing.T) { t.Run("arrays", func(t *testing.T) { bv, err := valueToString([2]int{1, 2}, objectType, nil) assertNilF(t, err) - assertEqualE(t, bv.format, "json") + assertEqualE(t, bv.format, jsonFormatStr) assertEqualE(t, *bv.value, "[1,2]") }) t.Run("slices", func(t *testing.T) { bv, err := valueToString([]int{1, 2}, objectType, nil) assertNilF(t, err) - assertEqualE(t, bv.format, "json") + assertEqualE(t, bv.format, jsonFormatStr) assertEqualE(t, *bv.value, "[1,2]") }) @@ -306,7 +306,7 @@ func TestValueToString(t *testing.T) { bv, err = valueToString(&testValueToStringStructuredObject{s: "some string", i: 123, date: time.Date(2024, time.May, 24, 0, 0, 0, 0, time.UTC)}, timestampLtzType, params) assertNilF(t, err) - assertEqualE(t, bv.format, "json") + assertEqualE(t, bv.format, jsonFormatStr) assertDeepEqualE(t, *bv.schema, bindingSchema{ Typ: "object", Nullable: true, From fb98fdfe7a45f9cb592cc510ff12c4dded933b1a Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 14:56:57 -0500 Subject: [PATCH 13/41] [AB#1669514] make it simpler --- converter.go | 6 ++---- converter_test.go | 6 +++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/converter.go b/converter.go index c4cf4b8b8..6892aaf47 100644 --- a/converter.go +++ b/converter.go @@ -248,10 +248,8 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri if valuer, ok := v.(driver.Valuer); ok { // check for driver.Valuer satisfaction and honor that first if value, err := valuer.Value(); err == nil && value != nil { - // if the output value is a valid string, return that - if strVal, ok := value.(string); ok { - return bindingValue{&strVal, "", nil}, nil - } + strVal := fmt.Sprint(value) + return bindingValue{&strVal, "", nil}, nil } } diff --git a/converter_test.go b/converter_test.go index 4174e1142..f4b179ac5 100644 --- a/converter_test.go +++ b/converter_test.go @@ -214,12 +214,16 @@ func (o *testValueToStringStructuredObject) Write(sowc StructuredObjectWriterCon return nil } -type testSqlUuid = UUID +type testSqlUuid UUID func (uuid testSqlUuid) Value() (driver.Value, error) { return uuid.String(), nil } +func (u testSqlUuid) String() string { + return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:]) +} + func TestValueToString(t *testing.T) { v := cmplx.Sqrt(-5 + 12i) // should never happen as Go sql package must have already validated. _, err := valueToString(v, nullType, nil) From e657e079f0731a5df7131612c0205873ccaabc43 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 15:52:20 -0500 Subject: [PATCH 14/41] [AB#1669514] revert that change; wont handle complex types --- converter.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/converter.go b/converter.go index 6892aaf47..c4cf4b8b8 100644 --- a/converter.go +++ b/converter.go @@ -248,8 +248,10 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri if valuer, ok := v.(driver.Valuer); ok { // check for driver.Valuer satisfaction and honor that first if value, err := valuer.Value(); err == nil && value != nil { - strVal := fmt.Sprint(value) - return bindingValue{&strVal, "", nil}, nil + // if the output value is a valid string, return that + if strVal, ok := value.(string); ok { + return bindingValue{&strVal, "", nil}, nil + } } } From 2989d2fc1385722d370f4d666445f750ef201294 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Mon, 23 Sep 2024 09:17:35 -0500 Subject: [PATCH 15/41] [AB#1669514] add test for SQL Null time --- converter_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/converter_test.go b/converter_test.go index f4b179ac5..6aa4a11f8 100644 --- a/converter_test.go +++ b/converter_test.go @@ -279,6 +279,14 @@ func TestValueToString(t *testing.T) { assertNilE(t, bv.schema) assertEqualE(t, *bv.value, expectedString) + t.Run("SQL Time", func(t *testing.T) { + bv, err := valueToString(sql.NullTime{Time: localTime, Valid: true}, timestampLtzType, nil) + assertNilF(t, err) + assertEmptyStringE(t, bv.format) + assertNilE(t, bv.schema) + assertEqualE(t, *bv.value, expectedUnixTime) + }) + t.Run("arrays", func(t *testing.T) { bv, err := valueToString([2]int{1, 2}, objectType, nil) assertNilF(t, err) From 1b944d21719cde89011b7f8a1d76d3857f49e025 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 8 Oct 2024 15:28:46 -0500 Subject: [PATCH 16/41] [AB#1669514] make it simpler and mayyybe easier to understand --- converter.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/converter.go b/converter.go index c4cf4b8b8..7b9270b4f 100644 --- a/converter.go +++ b/converter.go @@ -282,6 +282,20 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri return bindingValue{}, fmt.Errorf("unsupported type: %v", v1.Kind()) } +// check for stringer method and it's a len 16 byte array. Guarantees it's String() and returns string +func isUUIDImplementer(v interface{}) bool { + rv := reflect.ValueOf(v) + rt := rv.Type() + + // Check if the type is an array of 16 bytes + if rt.Kind() == reflect.Array && rt.Elem().Kind() == reflect.Uint8 && rt.Len() == 16 { + // Check if the type implements fmt.Stringer + _, ok := v.(fmt.Stringer) + return ok + } + return false +} + func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*string) (bindingValue, error) { v1 := reflect.Indirect(reflect.ValueOf(v)) if v1.Kind() == reflect.Slice && v1.IsNil() { @@ -394,8 +408,8 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } res = res[0:len(res)-1] + "]" return bindingValue{&res, jsonFormatStr, &schemaForBytes}, nil - } else if stringer, ok := v1.Interface().(fmt.Stringer); ok && v1.Kind() == reflect.Array && v1.Type().Elem().Kind() == reflect.Uint8 && v1.Len() == 16 { - // special case for UUIDs (snowflake type and other implementers) check for stringer method and it's a len 16 byte array. Guarantees it's String() and returns string + } else if isUUIDImplementer(v) { // special case for UUIDs (snowflake type and other implementers) + stringer := v.(fmt.Stringer) value := stringer.String() return bindingValue{&value, "", nil}, nil } else if isSliceOfSlices(v) { From ddb5544418adafbec731eac9e2fbaf692e7a030b Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 8 Oct 2024 15:35:19 -0500 Subject: [PATCH 17/41] [AB#1669514] even simpler --- converter.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/converter.go b/converter.go index 7b9270b4f..629b97871 100644 --- a/converter.go +++ b/converter.go @@ -283,14 +283,13 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } // check for stringer method and it's a len 16 byte array. Guarantees it's String() and returns string -func isUUIDImplementer(v interface{}) bool { - rv := reflect.ValueOf(v) - rt := rv.Type() +func isUUIDImplementer(v reflect.Value) bool { + rt := v.Type() // Check if the type is an array of 16 bytes - if rt.Kind() == reflect.Array && rt.Elem().Kind() == reflect.Uint8 && rt.Len() == 16 { + if v.Kind() == reflect.Array && rt.Elem().Kind() == reflect.Uint8 && rt.Len() == 16 { // Check if the type implements fmt.Stringer - _, ok := v.(fmt.Stringer) + _, ok := v.Interface().(fmt.Stringer) return ok } return false @@ -408,7 +407,7 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri } res = res[0:len(res)-1] + "]" return bindingValue{&res, jsonFormatStr, &schemaForBytes}, nil - } else if isUUIDImplementer(v) { // special case for UUIDs (snowflake type and other implementers) + } else if isUUIDImplementer(v1) { // special case for UUIDs (snowflake type and other implementers) stringer := v.(fmt.Stringer) value := stringer.String() return bindingValue{&value, "", nil}, nil From 42e8125730fa9d92a69dddd5883e95d4acaa89b7 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 9 Oct 2024 19:52:04 -0500 Subject: [PATCH 18/41] [AB#1669514] nil safety on err --- bind_uploader.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bind_uploader.go b/bind_uploader.go index 04a266a8e..1e220d42f 100644 --- a/bind_uploader.go +++ b/bind_uploader.go @@ -118,7 +118,7 @@ func (bu *bindUploader) createStageIfNeeded() error { return (&SnowflakeError{ Number: code, SQLState: data.Data.SQLState, - Message: err.Error(), + Message: fmt.Sprint(err), QueryID: data.Data.QueryID, }).exceptionTelemetry(bu.sc) } From b450e5c4415bb4e882d8541ee28456db6f7f364d Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 9 Oct 2024 20:03:44 -0500 Subject: [PATCH 19/41] [AB#1669514] more complete converter checks --- converter.go | 21 ++++++++++++++++++--- converter_test.go | 11 +++++++++++ go.mod | 1 + 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/converter.go b/converter.go index 629b97871..f9f0fefdf 100644 --- a/converter.go +++ b/converter.go @@ -14,6 +14,7 @@ import ( "math" "math/big" "reflect" + "regexp" "strconv" "strings" "time" @@ -282,15 +283,29 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri return bindingValue{}, fmt.Errorf("unsupported type: %v", v1.Kind()) } -// check for stringer method and it's a len 16 byte array. Guarantees it's String() and returns string +// isUUIDImplementer checks if a value is a UUID that satisfies RFC 4122 func isUUIDImplementer(v reflect.Value) bool { rt := v.Type() // Check if the type is an array of 16 bytes if v.Kind() == reflect.Array && rt.Elem().Kind() == reflect.Uint8 && rt.Len() == 16 { // Check if the type implements fmt.Stringer - _, ok := v.Interface().(fmt.Stringer) - return ok + vInt := v.Interface() + if stringer, ok := vInt.(fmt.Stringer); ok { + uuidStr := stringer.String() + + rfc4122Regex := `^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$` + matched, err := regexp.MatchString(rfc4122Regex, uuidStr) + if err != nil { + return false + } + + if matched { + // parse the UUID and ensure it is the same as the original string + u := ParseUUID(uuidStr) + return u.String() == uuidStr + } + } } return false } diff --git a/converter_test.go b/converter_test.go index 6aa4a11f8..7b83cb783 100644 --- a/converter_test.go +++ b/converter_test.go @@ -316,6 +316,17 @@ func TestValueToString(t *testing.T) { assertEqualE(t, *bv.value, u.String()) }) + t.Run("google.UUID", func(t *testing.T) { + u := googleUUID.New() + + assertEqualE(t, u.String(), ParseUUID(u.String()).String()) + + bv, err := valueToString(UUID(u), textType, nil) + assertNilF(t, err) + assertEmptyStringE(t, bv.format) + assertEqualE(t, *bv.value, u.String()) + }) + bv, err = valueToString(&testValueToStringStructuredObject{s: "some string", i: 123, date: time.Date(2024, time.May, 24, 0, 0, 0, 0, time.UTC)}, timestampLtzType, params) assertNilF(t, err) assertEqualE(t, bv.format, jsonFormatStr) diff --git a/go.mod b/go.mod index b4c1fca13..9f8340634 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/aws/smithy-go v1.20.2 github.com/gabriel-vasile/mimetype v1.4.2 github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/google/uuid v1.6.0 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/sirupsen/logrus v1.9.3 golang.org/x/crypto v0.22.0 From 88979423b5ad20f5236ed15d12c6db57a2c2439a Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 19 Nov 2024 10:13:58 -0600 Subject: [PATCH 20/41] [AB#1669514] structured read test --- structured_type_read_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/structured_type_read_test.go b/structured_type_read_test.go index 362568182..90bb9f0e4 100644 --- a/structured_type_read_test.go +++ b/structured_type_read_test.go @@ -36,6 +36,7 @@ type objectWithAllTypes struct { sArr []string f64Arr []float64 someMap map[string]bool + uuid UUID } func (o *objectWithAllTypes) Scan(val any) error { @@ -112,6 +113,13 @@ func (o *objectWithAllTypes) Scan(val any) error { if someMap != nil { o.someMap = someMap.(map[string]bool) } + uuidBytes, err := st.GetBytes("uuid") + if err != nil { + return err + } + + o.uuid = UUID(uuidBytes) + return nil } @@ -173,6 +181,9 @@ func (o objectWithAllTypes) Write(sowc StructuredObjectWriterContext) error { if err := sowc.WriteRaw("someMap", o.someMap); err != nil { return err } + if err := sowc.WriteBytes("uuid", o.uuid[:]); err != nil { + return err + } return nil } From 74f145d2cb5765d29f22fd01e70d1ca714d57e67 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 19 Nov 2024 12:28:06 -0600 Subject: [PATCH 21/41] [AB#1669514] structured r/w tests --- structured_type_read_test.go | 50 ++++++++++++++++++++++++++--------- structured_type_write_test.go | 22 +++++++++++---- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/structured_type_read_test.go b/structured_type_read_test.go index 90bb9f0e4..db0159e4f 100644 --- a/structured_type_read_test.go +++ b/structured_type_read_test.go @@ -113,12 +113,12 @@ func (o *objectWithAllTypes) Scan(val any) error { if someMap != nil { o.someMap = someMap.(map[string]bool) } - uuidBytes, err := st.GetBytes("uuid") + uuidStr, err := st.GetString("uuid") if err != nil { return err } - o.uuid = UUID(uuidBytes) + o.uuid = ParseUUID(uuidStr) return nil } @@ -181,7 +181,7 @@ func (o objectWithAllTypes) Write(sowc StructuredObjectWriterContext) error { if err := sowc.WriteRaw("someMap", o.someMap); err != nil { return err } - if err := sowc.WriteBytes("uuid", o.uuid[:]); err != nil { + if err := sowc.WriteString("uuid", o.uuid.String()); err != nil { return err } return nil @@ -236,7 +236,8 @@ func TestObjectWithAllTypesAsObject(t *testing.T) { runDBTest(t, func(dbt *DBTest) { dbt.mustExec("ALTER SESSION SET TIMEZONE = 'Europe/Warsaw'") forAllStructureTypeFormats(dbt, func(t *testing.T, format string) { - rows := dbt.mustQueryContextT(ctx, t, "SELECT 1, {'s': 'some string', 'b': 1, 'i16': 2, 'i32': 3, 'i64': 9223372036854775807, 'f32': '1.1', 'f64': 2.2, 'nfraction': 3.3, 'bo': true, 'bi': TO_BINARY('616263', 'HEX'), 'date': '2024-03-21'::DATE, 'time': '13:03:02'::TIME, 'ltz': '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz': '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz': '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so': {'s': 'child', 'i': 9}, 'sArr': ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr': ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap': {'x': true, 'y': false}}::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 19), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN))") + uid := NewUUID() + rows := dbt.mustQueryContextT(ctx, t, fmt.Sprintf("SELECT 1, {'s': 'some string', 'b': 1, 'i16': 2, 'i32': 3, 'i64': 9223372036854775807, 'f32': '1.1', 'f64': 2.2, 'nfraction': 3.3, 'bo': true, 'bi': TO_BINARY('616263', 'HEX'), 'date': '2024-03-21'::DATE, 'time': '13:03:02'::TIME, 'ltz': '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz': '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz': '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so': {'s': 'child', 'i': 9}, 'sArr': ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr': ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap': {'x': true, 'y': false}, 'uuid': '%s' }::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 19), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)", uid)) defer rows.Close() rows.Next() var ignore int @@ -264,6 +265,7 @@ func TestObjectWithAllTypesAsObject(t *testing.T) { assertDeepEqualE(t, res.sArr, []string{"x", "y", "z"}) assertDeepEqualE(t, res.f64Arr, []float64{1.1, 2.2, 3.3}) assertDeepEqualE(t, res.someMap, map[string]bool{"x": true, "y": false}) + assertEqualE(t, res.uuid.String(), uid.String()) }) }) } @@ -273,7 +275,7 @@ func TestNullObject(t *testing.T) { runDBTest(t, func(dbt *DBTest) { forAllStructureTypeFormats(dbt, func(t *testing.T, format string) { t.Run("null", func(t *testing.T) { - rows := dbt.mustQueryContextT(ctx, t, "SELECT null::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 19), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN))") + rows := dbt.mustQueryContextT(ctx, t, "SELECT null::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 19), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)") defer rows.Close() assertTrueF(t, rows.Next()) var res *objectWithAllTypes @@ -282,7 +284,8 @@ func TestNullObject(t *testing.T) { assertNilE(t, res) }) t.Run("not null", func(t *testing.T) { - rows := dbt.mustQueryContextT(ctx, t, "SELECT {'s': 'some string', 'b': 1, 'i16': 2, 'i32': 3, 'i64': 9223372036854775807, 'f32': '1.1', 'f64': 2.2, 'nfraction': 3.3, 'bo': true, 'bi': TO_BINARY('616263', 'HEX'), 'date': '2024-03-21'::DATE, 'time': '13:03:02'::TIME, 'ltz': '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz': '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz': '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so': {'s': 'child', 'i': 9}, 'sArr': ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr': ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap': {'x': true, 'y': false}}::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 19), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN))") + uid := NewUUID() + rows := dbt.mustQueryContextT(ctx, t, fmt.Sprintf("SELECT {'s': 'some string', 'b': 1, 'i16': 2, 'i32': 3, 'i64': 9223372036854775807, 'f32': '1.1', 'f64': 2.2, 'nfraction': 3.3, 'bo': true, 'bi': TO_BINARY('616263', 'HEX'), 'date': '2024-03-21'::DATE, 'time': '13:03:02'::TIME, 'ltz': '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz': '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz': '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so': {'s': 'child', 'i': 9}, 'sArr': ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr': ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap': {'x': true, 'y': false}, 'uuid': '%s'}::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 19), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)", uid)) defer rows.Close() assertTrueF(t, rows.Next()) var res *objectWithAllTypes @@ -312,6 +315,7 @@ type objectWithAllTypesNullable struct { sArr []string f64Arr []float64 someMap map[string]bool + uuid UUID } func (o *objectWithAllTypesNullable) Scan(val any) error { @@ -386,6 +390,13 @@ func (o *objectWithAllTypesNullable) Scan(val any) error { if someMap != nil { o.someMap = someMap.(map[string]bool) } + uuidStr, err := st.GetNullString("uuid") + if err != nil { + return err + } + + o.uuid = ParseUUID(uuidStr.String) + return nil } @@ -441,6 +452,9 @@ func (o *objectWithAllTypesNullable) Write(sowc StructuredObjectWriterContext) e if err := sowc.WriteRaw("someMap", o.someMap); err != nil { return err } + if err := sowc.WriteNullString("uuid", sql.NullString{String: o.uuid.String(), Valid: true}); err != nil { + return err + } return nil } @@ -452,7 +466,7 @@ func TestObjectWithAllTypesNullable(t *testing.T) { dbt.mustExec("ALTER SESSION SET TIMEZONE = 'Europe/Warsaw'") forAllStructureTypeFormats(dbt, func(t *testing.T, format string) { t.Run("null", func(t *testing.T) { - rows := dbt.mustQueryContextT(ctx, t, "select null, object_construct_keep_null('s', null, 'b', null, 'i16', null, 'i32', null, 'i64', null, 'f64', null, 'bo', null, 'bi', null, 'date', null, 'time', null, 'ltz', null, 'tz', null, 'ntz', null, 'so', null, 'sArr', null, 'f64Arr', null, 'someMap', null)::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN))") + rows := dbt.mustQueryContextT(ctx, t, "select null, object_construct_keep_null('s', null, 'b', null, 'i16', null, 'i32', null, 'i64', null, 'f64', null, 'bo', null, 'bi', null, 'date', null, 'time', null, 'ltz', null, 'tz', null, 'ntz', null, 'so', null, 'sArr', null, 'f64Arr', null, 'someMap', null, 'uuid': null)::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)") defer rows.Close() rows.Next() var ignore sql.NullInt32 @@ -477,7 +491,8 @@ func TestObjectWithAllTypesNullable(t *testing.T) { assertDeepEqualE(t, res.so, so) }) t.Run("not null", func(t *testing.T) { - rows := dbt.mustQueryContextT(ctx, t, "select 1, object_construct_keep_null('s', 'abc', 'b', 1, 'i16', 2, 'i32', 3, 'i64', 9223372036854775807, 'f64', 2.2, 'bo', true, 'bi', TO_BINARY('616263', 'HEX'), 'date', '2024-03-21'::DATE, 'time', '13:03:02'::TIME, 'ltz', '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz', '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz', '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so', {'s': 'child', 'i': 9}::OBJECT, 'sArr', ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr', ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap', {'x': true, 'y': false})::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN))") + uid := NewUUID() + rows := dbt.mustQueryContextT(ctx, t, fmt.Sprintf("select 1, object_construct_keep_null('s', 'abc', 'b', 1, 'i16', 2, 'i32', 3, 'i64', 9223372036854775807, 'f64', 2.2, 'bo', true, 'bi', TO_BINARY('616263', 'HEX'), 'date', '2024-03-21'::DATE, 'time', '13:03:02'::TIME, 'ltz', '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz', '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz', '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so', {'s': 'child', 'i': 9}::OBJECT, 'sArr', ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr', ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap', {'x': true, 'y': false}, 'uuid': '%s')::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)", uid)) defer rows.Close() rows.Next() var ignore sql.NullInt32 @@ -508,6 +523,7 @@ func TestObjectWithAllTypesNullable(t *testing.T) { assertDeepEqualE(t, res.sArr, []string{"x", "y", "z"}) assertDeepEqualE(t, res.f64Arr, []float64{1.1, 2.2, 3.3}) assertDeepEqualE(t, res.someMap, map[string]bool{"x": true, "y": false}) + assertEqualE(t, res.uuid.String(), uid.String()) }) }) }) @@ -533,6 +549,7 @@ type objectWithAllTypesSimpleScan struct { SArr []string F64Arr []float64 SomeMap map[string]bool + UUID UUID } func (so *objectWithAllTypesSimpleScan) Scan(val any) error { @@ -545,13 +562,14 @@ func (so *objectWithAllTypesSimpleScan) Write(sowc StructuredObjectWriterContext } func TestObjectWithAllTypesSimpleScan(t *testing.T) { + uid := NewUUID() warsawTz, err := time.LoadLocation("Europe/Warsaw") assertNilF(t, err) ctx := WithStructuredTypesEnabled(context.Background()) runDBTest(t, func(dbt *DBTest) { dbt.mustExec("ALTER SESSION SET TIMEZONE = 'Europe/Warsaw'") forAllStructureTypeFormats(dbt, func(t *testing.T, format string) { - rows := dbt.mustQueryContextT(ctx, t, "SELECT 1, {'s': 'some string', 'b': 1, 'i16': 2, 'i32': 3, 'i64': 9223372036854775807, 'f32': '1.1', 'f64': 2.2, 'nfraction': 3.3, 'bo': true, 'bi': TO_BINARY('616263', 'HEX'), 'date': '2024-03-21'::DATE, 'time': '13:03:02'::TIME, 'ltz': '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz': '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz': '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so': {'s': 'child', 'i': 9}, 'sArr': ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr': ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap': {'x': true, 'y': false}}::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 19), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN))") + rows := dbt.mustQueryContextT(ctx, t, fmt.Sprintf("SELECT 1, {'s': 'some string', 'b': 1, 'i16': 2, 'i32': 3, 'i64': 9223372036854775807, 'f32': '1.1', 'f64': 2.2, 'nfraction': 3.3, 'bo': true, 'bi': TO_BINARY('616263', 'HEX'), 'date': '2024-03-21'::DATE, 'time': '13:03:02'::TIME, 'ltz': '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz': '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz': '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so': {'s': 'child', 'i': 9}, 'sArr': ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr': ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap': {'x': true, 'y': false}, 'uuid': '%s'}::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 19), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)", uid)) defer rows.Close() rows.Next() var ignore int @@ -579,6 +597,7 @@ func TestObjectWithAllTypesSimpleScan(t *testing.T) { assertDeepEqualE(t, res.SArr, []string{"x", "y", "z"}) assertDeepEqualE(t, res.F64Arr, []float64{1.1, 2.2, 3.3}) assertDeepEqualE(t, res.SomeMap, map[string]bool{"x": true, "y": false}) + assertEqualE(t, res.UUID.String(), uid.String()) }) }) } @@ -588,7 +607,7 @@ func TestNullObjectSimpleScan(t *testing.T) { runDBTest(t, func(dbt *DBTest) { forAllStructureTypeFormats(dbt, func(t *testing.T, format string) { t.Run("null", func(t *testing.T) { - rows := dbt.mustQueryContextT(ctx, t, "SELECT null::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 19), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN))") + rows := dbt.mustQueryContextT(ctx, t, "SELECT null::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 19), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)") defer rows.Close() assertTrueF(t, rows.Next()) var res *objectWithAllTypesSimpleScan @@ -597,7 +616,8 @@ func TestNullObjectSimpleScan(t *testing.T) { assertNilE(t, res) }) t.Run("not null", func(t *testing.T) { - rows := dbt.mustQueryContextT(ctx, t, "SELECT {'s': 'some string', 'b': 1, 'i16': 2, 'i32': 3, 'i64': 9223372036854775807, 'f32': '1.1', 'f64': 2.2, 'nfraction': 3.3, 'bo': true, 'bi': TO_BINARY('616263', 'HEX'), 'date': '2024-03-21'::DATE, 'time': '13:03:02'::TIME, 'ltz': '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz': '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz': '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so': {'s': 'child', 'i': 9}, 'sArr': ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr': ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap': {'x': true, 'y': false}}::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 19), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN))") + uid := NewUUID() + rows := dbt.mustQueryContextT(ctx, t, fmt.Sprintf("SELECT {'s': 'some string', 'b': 1, 'i16': 2, 'i32': 3, 'i64': 9223372036854775807, 'f32': '1.1', 'f64': 2.2, 'nfraction': 3.3, 'bo': true, 'bi': TO_BINARY('616263', 'HEX'), 'date': '2024-03-21'::DATE, 'time': '13:03:02'::TIME, 'ltz': '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz': '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz': '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so': {'s': 'child', 'i': 9}, 'sArr': ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr': ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap': {'x': true, 'y': false}, 'uuid': '%s'}::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 19), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)", uid)) defer rows.Close() assertTrueF(t, rows.Next()) var res *objectWithAllTypesSimpleScan @@ -627,6 +647,7 @@ type objectWithAllTypesNullableSimpleScan struct { SArr []string F64Arr []float64 SomeMap map[string]bool + UUID UUID } func (o *objectWithAllTypesNullableSimpleScan) Scan(val any) error { @@ -646,7 +667,7 @@ func TestObjectWithAllTypesSimpleScanNullable(t *testing.T) { dbt.mustExec("ALTER SESSION SET TIMEZONE = 'Europe/Warsaw'") forAllStructureTypeFormats(dbt, func(t *testing.T, format string) { t.Run("null", func(t *testing.T) { - rows := dbt.mustQueryContextT(ctx, t, "select null, object_construct_keep_null('s', null, 'b', null, 'i16', null, 'i32', null, 'i64', null, 'f64', null, 'bo', null, 'bi', null, 'date', null, 'time', null, 'ltz', null, 'tz', null, 'ntz', null, 'so', null, 'sArr', null, 'f64Arr', null, 'someMap', null)::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN))") + rows := dbt.mustQueryContextT(ctx, t, "select null, object_construct_keep_null('s', null, 'b', null, 'i16', null, 'i32', null, 'i64', null, 'f64', null, 'bo', null, 'bi', null, 'date', null, 'time', null, 'ltz', null, 'tz', null, 'ntz', null, 'so', null, 'sArr', null, 'f64Arr', null, 'someMap', null, 'uuid', null)::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)") defer rows.Close() rows.Next() var ignore sql.NullInt32 @@ -669,9 +690,11 @@ func TestObjectWithAllTypesSimpleScanNullable(t *testing.T) { assertEqualE(t, res.Ntz, sql.NullTime{Valid: false}) var so *simpleObject assertDeepEqualE(t, res.So, so) + assertEqualE(t, res.UUID, []string(nil)) }) t.Run("not null", func(t *testing.T) { - rows := dbt.mustQueryContextT(ctx, t, "select 1, object_construct_keep_null('s', 'abc', 'b', 1, 'i16', 2, 'i32', 3, 'i64', 9223372036854775807, 'f64', 2.2, 'bo', true, 'bi', TO_BINARY('616263', 'HEX'), 'date', '2024-03-21'::DATE, 'time', '13:03:02'::TIME, 'ltz', '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz', '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz', '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so', {'s': 'child', 'i': 9}::OBJECT, 'sArr', ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr', ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap', {'x': true, 'y': false})::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN))") + uid := NewUUID() + rows := dbt.mustQueryContextT(ctx, t, fmt.Sprintf("select 1, object_construct_keep_null('s', 'abc', 'b', 1, 'i16', 2, 'i32', 3, 'i64', 9223372036854775807, 'f64', 2.2, 'bo', true, 'bi', TO_BINARY('616263', 'HEX'), 'date', '2024-03-21'::DATE, 'time', '13:03:02'::TIME, 'ltz', '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz', '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz', '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so', {'s': 'child', 'i': 9}::OBJECT, 'sArr', ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr', ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap', {'x': true, 'y': false}, 'uuid': '%s')::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)", uid)) defer rows.Close() rows.Next() var ignore sql.NullInt32 @@ -702,6 +725,7 @@ func TestObjectWithAllTypesSimpleScanNullable(t *testing.T) { assertDeepEqualE(t, res.SArr, []string{"x", "y", "z"}) assertDeepEqualE(t, res.F64Arr, []float64{1.1, 2.2, 3.3}) assertDeepEqualE(t, res.SomeMap, map[string]bool{"x": true, "y": false}) + assertEqualE(t, res.UUID.String(), uid.String()) }) }) }) diff --git a/structured_type_write_test.go b/structured_type_write_test.go index 2be5e49c3..785f04105 100644 --- a/structured_type_write_test.go +++ b/structured_type_write_test.go @@ -132,7 +132,7 @@ func TestBindingObjectWithSchema(t *testing.T) { ctx := WithStructuredTypesEnabled(context.Background()) assertNilF(t, err) runDBTest(t, func(dbt *DBTest) { - dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 9), bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, ntz TIMESTAMPNTZ, tz TIMESTAMPTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN)))") + dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 9), bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, ntz TIMESTAMPNTZ, tz TIMESTAMPTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN)), uuid VARCHAR)") defer func() { dbt.mustExec("DROP TABLE IF EXISTS test_object_binding") }() @@ -159,6 +159,7 @@ func TestBindingObjectWithSchema(t *testing.T) { sArr: []string{"a", "b"}, f64Arr: []float64{1.1, 2.2}, someMap: map[string]bool{"a": true, "b": false}, + uuid: NewUUID(), } dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o) rows := dbt.mustQueryContextT(ctx, t, "SELECT * FROM test_object_binding WHERE obj = ?", o) @@ -189,6 +190,7 @@ func TestBindingObjectWithSchema(t *testing.T) { assertDeepEqualE(t, res.sArr, o.sArr) assertDeepEqualE(t, res.f64Arr, o.f64Arr) assertDeepEqualE(t, res.someMap, o.someMap) + assertEqualE(t, res.uuid, o.uuid) }) } @@ -197,7 +199,7 @@ func TestBindingObjectWithNullableFieldsWithSchema(t *testing.T) { assertNilF(t, err) ctx := WithStructuredTypesEnabled(context.Background()) runDBTest(t, func(dbt *DBTest) { - dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, ntz TIMESTAMPNTZ, tz TIMESTAMPTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN)))") + dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, ntz TIMESTAMPNTZ, tz TIMESTAMPTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN)), uuid VARCHAR)") defer func() { dbt.mustExec("DROP TABLE IF EXISTS test_object_binding") }() @@ -223,6 +225,7 @@ func TestBindingObjectWithNullableFieldsWithSchema(t *testing.T) { sArr: []string{"a", "b"}, f64Arr: []float64{1.1, 2.2}, someMap: map[string]bool{"a": true, "b": false}, + uuid: NewUUID(), } dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o) rows := dbt.mustQueryContextT(ctx, t, "SELECT * FROM test_object_binding WHERE obj = ?", o) @@ -251,6 +254,7 @@ func TestBindingObjectWithNullableFieldsWithSchema(t *testing.T) { assertDeepEqualE(t, res.sArr, o.sArr) assertDeepEqualE(t, res.f64Arr, o.f64Arr) assertDeepEqualE(t, res.someMap, o.someMap) + assertEqualE(t, res.uuid, o.uuid) }) t.Run("null", func(t *testing.T) { o := &objectWithAllTypesNullable{ @@ -271,6 +275,7 @@ func TestBindingObjectWithNullableFieldsWithSchema(t *testing.T) { sArr: nil, f64Arr: nil, someMap: nil, + uuid: UUID{}, } dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o) rows := dbt.mustQueryContextT(ctx, t, "SELECT * FROM test_object_binding WHERE obj = ?", o) @@ -299,6 +304,7 @@ func TestBindingObjectWithNullableFieldsWithSchema(t *testing.T) { assertDeepEqualE(t, res.sArr, o.sArr) assertDeepEqualE(t, res.f64Arr, o.f64Arr) assertDeepEqualE(t, res.someMap, o.someMap) + assertEqualE(t, res.uuid, o.uuid) }) }) } @@ -308,7 +314,7 @@ func TestBindingObjectWithSchemaSimpleWrite(t *testing.T) { assertNilF(t, err) ctx := WithStructuredTypesEnabled(context.Background()) runDBTest(t, func(dbt *DBTest) { - dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 9), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN)))") + dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 9), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR))") defer func() { dbt.mustExec("DROP TABLE IF EXISTS test_object_binding") }() @@ -335,6 +341,7 @@ func TestBindingObjectWithSchemaSimpleWrite(t *testing.T) { SArr: []string{"a", "b"}, F64Arr: []float64{1.1, 2.2}, SomeMap: map[string]bool{"a": true, "b": false}, + UUID: NewUUID(), } dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o) rows := dbt.mustQueryContextT(ctx, t, "SELECT * FROM test_object_binding WHERE obj = ?", o) @@ -365,6 +372,7 @@ func TestBindingObjectWithSchemaSimpleWrite(t *testing.T) { assertDeepEqualE(t, res.SArr, o.SArr) assertDeepEqualE(t, res.F64Arr, o.F64Arr) assertDeepEqualE(t, res.SomeMap, o.SomeMap) + assertEqualE(t, res.UUID, o.UUID) }) } @@ -374,7 +382,7 @@ func TestBindingObjectWithNullableFieldsWithSchemaSimpleWrite(t *testing.T) { ctx := WithStructuredTypesEnabled(context.Background()) runDBTest(t, func(dbt *DBTest) { dbt.forceJSON() - dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, tz TIMESTAMPTZ, ntz TIMESTAMPNTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN)))") + dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, tz TIMESTAMPTZ, ntz TIMESTAMPNTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR))") defer func() { dbt.mustExec("DROP TABLE IF EXISTS test_object_binding") }() @@ -400,6 +408,7 @@ func TestBindingObjectWithNullableFieldsWithSchemaSimpleWrite(t *testing.T) { SArr: []string{"a", "b"}, F64Arr: []float64{1.1, 2.2}, SomeMap: map[string]bool{"a": true, "b": false}, + UUID: NewUUID(), } dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o) rows := dbt.mustQueryContextT(ctx, t, "SELECT * FROM test_object_binding WHERE obj = ?", o) @@ -428,6 +437,7 @@ func TestBindingObjectWithNullableFieldsWithSchemaSimpleWrite(t *testing.T) { assertDeepEqualE(t, res.SArr, o.SArr) assertDeepEqualE(t, res.F64Arr, o.F64Arr) assertDeepEqualE(t, res.SomeMap, o.SomeMap) + assertEqualE(t, res.UUID, o.UUID) }) t.Run("null", func(t *testing.T) { o := &objectWithAllTypesNullableSimpleScan{ @@ -448,6 +458,7 @@ func TestBindingObjectWithNullableFieldsWithSchemaSimpleWrite(t *testing.T) { SArr: nil, F64Arr: nil, SomeMap: nil, + UUID: UUID{}, } dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o) rows := dbt.mustQueryContextT(ctx, t, "SELECT * FROM test_object_binding WHERE obj = ?", o) @@ -476,6 +487,7 @@ func TestBindingObjectWithNullableFieldsWithSchemaSimpleWrite(t *testing.T) { assertDeepEqualE(t, res.SArr, o.SArr) assertDeepEqualE(t, res.F64Arr, o.F64Arr) assertDeepEqualE(t, res.SomeMap, o.SomeMap) + assertEqualE(t, res.UUID, o.UUID) }) }) } @@ -503,7 +515,7 @@ func TestBindingObjectWithAllTypesNullable(t *testing.T) { ctx := WithStructuredTypesEnabled(context.Background()) runDBTest(t, func(dbt *DBTest) { dbt.forceJSON() - dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (o OBJECT(o OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 9), bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, tz TIMESTAMPTZ, ntz TIMESTAMPNTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN))))") + dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (o OBJECT(o OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 9), bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, tz TIMESTAMPTZ, ntz TIMESTAMPNTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)))") defer func() { dbt.mustExec("DROP TABLE IF EXISTS test_object_binding") }() From 4a1daa9beda492ff801010b6f4b673227ccf7df1 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 19 Nov 2024 12:34:50 -0600 Subject: [PATCH 22/41] [AB#1669514] driver test? --- driver_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/driver_test.go b/driver_test.go index f7ee5f0b9..88dc0f038 100644 --- a/driver_test.go +++ b/driver_test.go @@ -862,6 +862,48 @@ func testString(t *testing.T, json bool) { }) } +func TestUUID(t *testing.T) { + testUuid(t, false) +} + +func testUuid(t *testing.T, json bool) { + runDBTest(t, func(dbt *DBTest) { + if json { + dbt.mustExec(forceJSON) + } + + types := []string{"CHAR(255)", "VARCHAR(255)", "TEXT", "STRING"} + + in := make([]UUID, len(types)) + + for i := range types { + in[i] = NewUUID() + } + + for i, v := range types { + t.Run(v, func(t *testing.T) { + dbt.mustExec("CREATE OR REPLACE TABLE test (value " + v + ")") + dbt.mustExec("INSERT INTO test VALUES (?)", in[i]) + + rows := dbt.mustQuery("SELECT value FROM test") + defer func() { + assertNilF(t, rows.Close()) + }() + if rows.Next() { + var out UUID + assertNilF(t, rows.Scan(&out)) + if in[i] != out { + dbt.Errorf("%s: %s != %s", v, in, out) + } + } else { + dbt.Errorf("%s: no data", v) + } + }) + } + dbt.mustExec("DROP TABLE IF EXISTS test") + }) +} + type tcDateTimeTimestamp struct { dbtype string tlayout string From 67caed6a65d7f296fbe71818114fcecd3de307ea Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 19 Nov 2024 16:07:10 -0600 Subject: [PATCH 23/41] [AB#1669514] fix driver test --- driver_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/driver_test.go b/driver_test.go index 88dc0f038..1df291e74 100644 --- a/driver_test.go +++ b/driver_test.go @@ -18,6 +18,8 @@ import ( "syscall" "testing" "time" + + googleUUID "github.com/google/uuid" ) var ( @@ -874,10 +876,10 @@ func testUuid(t *testing.T, json bool) { types := []string{"CHAR(255)", "VARCHAR(255)", "TEXT", "STRING"} - in := make([]UUID, len(types)) + in := make([]googleUUID.UUID, len(types)) for i := range types { - in[i] = NewUUID() + in[i] = googleUUID.New() } for i, v := range types { @@ -890,7 +892,7 @@ func testUuid(t *testing.T, json bool) { assertNilF(t, rows.Close()) }() if rows.Next() { - var out UUID + var out googleUUID.UUID assertNilF(t, rows.Scan(&out)) if in[i] != out { dbt.Errorf("%s: %s != %s", v, in, out) From 6d3b0a5f6c83ecf2af72083bbcd4e08975fcb686 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 19 Nov 2024 16:14:44 -0600 Subject: [PATCH 24/41] [AB#1669514] not quite right, but structured driver test is almost done --- structured_type_write_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/structured_type_write_test.go b/structured_type_write_test.go index 785f04105..3723dc26a 100644 --- a/structured_type_write_test.go +++ b/structured_type_write_test.go @@ -132,7 +132,7 @@ func TestBindingObjectWithSchema(t *testing.T) { ctx := WithStructuredTypesEnabled(context.Background()) assertNilF(t, err) runDBTest(t, func(dbt *DBTest) { - dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 9), bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, ntz TIMESTAMPNTZ, tz TIMESTAMPTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN)), uuid VARCHAR)") + dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 9), bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, ntz TIMESTAMPNTZ, tz TIMESTAMPTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR))") defer func() { dbt.mustExec("DROP TABLE IF EXISTS test_object_binding") }() @@ -199,7 +199,7 @@ func TestBindingObjectWithNullableFieldsWithSchema(t *testing.T) { assertNilF(t, err) ctx := WithStructuredTypesEnabled(context.Background()) runDBTest(t, func(dbt *DBTest) { - dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, ntz TIMESTAMPNTZ, tz TIMESTAMPTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN)), uuid VARCHAR)") + dbt.mustExec("CREATE OR REPLACE TABLE test_object_binding (obj OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo boolean, bi BINARY, date DATE, time TIME, ltz TIMESTAMPLTZ, ntz TIMESTAMPNTZ, tz TIMESTAMPTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR))") defer func() { dbt.mustExec("DROP TABLE IF EXISTS test_object_binding") }() From 077f4da4a0c5ccc627019a61cf274a659313b5e3 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 20 Nov 2024 09:43:43 -0600 Subject: [PATCH 25/41] [AB#1669514] no hard dependencies on googleUUID --- converter_test.go | 9 ++++---- driver_test.go | 14 +++++------- go.mod | 1 - uuid.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 14 deletions(-) diff --git a/converter_test.go b/converter_test.go index 7b83cb783..173dc81c3 100644 --- a/converter_test.go +++ b/converter_test.go @@ -316,12 +316,11 @@ func TestValueToString(t *testing.T) { assertEqualE(t, *bv.value, u.String()) }) - t.Run("google.UUID", func(t *testing.T) { - u := googleUUID.New() + t.Run("testUUID", func(t *testing.T) { + u := newTestUUID() + assertEqualE(t, u.String(), parseTestUUID(u.String()).String()) - assertEqualE(t, u.String(), ParseUUID(u.String()).String()) - - bv, err := valueToString(UUID(u), textType, nil) + bv, err := valueToString(u, textType, nil) assertNilF(t, err) assertEmptyStringE(t, bv.format) assertEqualE(t, *bv.value, u.String()) diff --git a/driver_test.go b/driver_test.go index 1df291e74..9534f8688 100644 --- a/driver_test.go +++ b/driver_test.go @@ -18,8 +18,6 @@ import ( "syscall" "testing" "time" - - googleUUID "github.com/google/uuid" ) var ( @@ -272,9 +270,9 @@ func (dbt *DBTest) prepare(query string) (*sql.Stmt, error) { } func (dbt *DBTest) fail(method, query string, err error) { - if len(query) > 300 { - query = "[query too large to print]" - } + // if len(query) > 300 { + // query = "[query too large to print]" + // } dbt.Fatalf("error on %s [%s]: %s", method, query, err.Error()) } @@ -876,10 +874,10 @@ func testUuid(t *testing.T, json bool) { types := []string{"CHAR(255)", "VARCHAR(255)", "TEXT", "STRING"} - in := make([]googleUUID.UUID, len(types)) + in := make([]testUUID, len(types)) for i := range types { - in[i] = googleUUID.New() + in[i] = newTestUUID() } for i, v := range types { @@ -892,7 +890,7 @@ func testUuid(t *testing.T, json bool) { assertNilF(t, rows.Close()) }() if rows.Next() { - var out googleUUID.UUID + var out testUUID assertNilF(t, rows.Scan(&out)) if in[i] != out { dbt.Errorf("%s: %s != %s", v, in, out) diff --git a/go.mod b/go.mod index 9f8340634..b4c1fca13 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,6 @@ require ( github.com/aws/smithy-go v1.20.2 github.com/gabriel-vasile/mimetype v1.4.2 github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/google/uuid v1.6.0 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/sirupsen/logrus v1.9.3 golang.org/x/crypto v0.22.0 diff --git a/uuid.go b/uuid.go index 2fd1c35b5..a7bbea0e4 100644 --- a/uuid.go +++ b/uuid.go @@ -4,6 +4,7 @@ package gosnowflake import ( "crypto/rand" + "database/sql/driver" "fmt" "strconv" ) @@ -48,3 +49,60 @@ func ParseUUID(str string) UUID { func (u UUID) String() string { return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:]) } + +// This is for unit testing scans/value of UUIDs being inserted/read to/from the DB - not intended for public use +type testUUID = UUID + +func newTestUUID() testUUID { + return testUUID(NewUUID()) +} + +func parseTestUUID(str string) testUUID { + return testUUID(ParseUUID(str)) +} + +// Scan implements sql.Scanner so UUIDs can be read from databases transparently. +// Currently, database types that map to string and []byte are supported. Please +// consult database-specific driver documentation for matching types. +func (uuid *testUUID) Scan(src interface{}) error { + switch src := src.(type) { + case nil: + return nil + + case string: + // if an empty UUID comes from a table, we return a null UUID + if src == "" { + return nil + } + + // see Parse for required string format + u := ParseUUID(src) + + *uuid = testUUID(u) + + case []byte: + // if an empty UUID comes from a table, we return a null UUID + if len(src) == 0 { + return nil + } + + // assumes a simple slice of bytes if 16 bytes + // otherwise attempts to parse + if len(src) != 16 { + return uuid.Scan(string(src)) + } + copy((*uuid)[:], src) + + default: + return fmt.Errorf("Scan: unable to scan type %T into UUID", src) + } + + return nil +} + +// Value implements sql.Valuer so that UUIDs can be written to databases +// transparently. Currently, UUIDs map to strings. Please consult +// database-specific driver documentation for matching types. +func (uuid testUUID) Value() (driver.Value, error) { + return uuid.String(), nil +} From c7632ece21aa72199508cf051d999ad060447b05 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 20 Nov 2024 10:26:01 -0600 Subject: [PATCH 26/41] [AB#1669514] move type to test file so it's not bundled --- converter_test.go | 12 +------ driver_test.go | 60 +++++++++++++++++++++++++++++++++++ structured_type_read_test.go | 24 +++++++------- structured_type_write_test.go | 12 +++---- uuid.go | 58 --------------------------------- 5 files changed, 79 insertions(+), 87 deletions(-) diff --git a/converter_test.go b/converter_test.go index 173dc81c3..5cb12741c 100644 --- a/converter_test.go +++ b/converter_test.go @@ -214,16 +214,6 @@ func (o *testValueToStringStructuredObject) Write(sowc StructuredObjectWriterCon return nil } -type testSqlUuid UUID - -func (uuid testSqlUuid) Value() (driver.Value, error) { - return uuid.String(), nil -} - -func (u testSqlUuid) String() string { - return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:]) -} - func TestValueToString(t *testing.T) { v := cmplx.Sqrt(-5 + 12i) // should never happen as Go sql package must have already validated. _, err := valueToString(v, nullType, nil) @@ -309,7 +299,7 @@ func TestValueToString(t *testing.T) { }) t.Run("database/sql/driver - Valuer interface", func(t *testing.T) { - u := testSqlUuid(NewUUID()) + u := newTestUUID() bv, err := valueToString(u, textType, nil) assertNilF(t, err) assertEmptyStringE(t, bv.format) diff --git a/driver_test.go b/driver_test.go index 9534f8688..a9eba6a24 100644 --- a/driver_test.go +++ b/driver_test.go @@ -862,6 +862,66 @@ func testString(t *testing.T, json bool) { }) } +/** TESTING TYPES **/ +// testUUID is a wrapper around UUID for unit testing purposes and should not be used in production +type testUUID struct { + UUID +} + +func newTestUUID() testUUID { + return testUUID{NewUUID()} +} + +func parseTestUUID(str string) testUUID { + return testUUID{ParseUUID(str)} +} + +// Scan implements sql.Scanner so UUIDs can be read from databases transparently. +// Currently, database types that map to string and []byte are supported. Please +// consult database-specific driver documentation for matching types. +func (uuid *testUUID) Scan(src interface{}) error { + switch src := src.(type) { + case nil: + return nil + + case string: + // if an empty UUID comes from a table, we return a null UUID + if src == "" { + return nil + } + + // see Parse for required string format + u := ParseUUID(src) + + *uuid = testUUID{u} + + case []byte: + // if an empty UUID comes from a table, we return a null UUID + if len(src) == 0 { + return nil + } + + // assumes a simple slice of bytes if 16 bytes + // otherwise attempts to parse + if len(src) != 16 { + return uuid.Scan(string(src)) + } + copy((uuid.UUID)[:], src) + + default: + return fmt.Errorf("Scan: unable to scan type %T into UUID", src) + } + + return nil +} + +// Value implements sql.Valuer so that UUIDs can be written to databases +// transparently. Currently, UUIDs map to strings. Please consult +// database-specific driver documentation for matching types. +func (uuid testUUID) Value() (driver.Value, error) { + return uuid.String(), nil +} + func TestUUID(t *testing.T) { testUuid(t, false) } diff --git a/structured_type_read_test.go b/structured_type_read_test.go index db0159e4f..b9f3222e2 100644 --- a/structured_type_read_test.go +++ b/structured_type_read_test.go @@ -36,7 +36,7 @@ type objectWithAllTypes struct { sArr []string f64Arr []float64 someMap map[string]bool - uuid UUID + uuid testUUID } func (o *objectWithAllTypes) Scan(val any) error { @@ -118,7 +118,7 @@ func (o *objectWithAllTypes) Scan(val any) error { return err } - o.uuid = ParseUUID(uuidStr) + o.uuid = parseTestUUID(uuidStr) return nil } @@ -236,7 +236,7 @@ func TestObjectWithAllTypesAsObject(t *testing.T) { runDBTest(t, func(dbt *DBTest) { dbt.mustExec("ALTER SESSION SET TIMEZONE = 'Europe/Warsaw'") forAllStructureTypeFormats(dbt, func(t *testing.T, format string) { - uid := NewUUID() + uid := newTestUUID() rows := dbt.mustQueryContextT(ctx, t, fmt.Sprintf("SELECT 1, {'s': 'some string', 'b': 1, 'i16': 2, 'i32': 3, 'i64': 9223372036854775807, 'f32': '1.1', 'f64': 2.2, 'nfraction': 3.3, 'bo': true, 'bi': TO_BINARY('616263', 'HEX'), 'date': '2024-03-21'::DATE, 'time': '13:03:02'::TIME, 'ltz': '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz': '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz': '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so': {'s': 'child', 'i': 9}, 'sArr': ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr': ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap': {'x': true, 'y': false}, 'uuid': '%s' }::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 19), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)", uid)) defer rows.Close() rows.Next() @@ -284,7 +284,7 @@ func TestNullObject(t *testing.T) { assertNilE(t, res) }) t.Run("not null", func(t *testing.T) { - uid := NewUUID() + uid := newTestUUID() rows := dbt.mustQueryContextT(ctx, t, fmt.Sprintf("SELECT {'s': 'some string', 'b': 1, 'i16': 2, 'i32': 3, 'i64': 9223372036854775807, 'f32': '1.1', 'f64': 2.2, 'nfraction': 3.3, 'bo': true, 'bi': TO_BINARY('616263', 'HEX'), 'date': '2024-03-21'::DATE, 'time': '13:03:02'::TIME, 'ltz': '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz': '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz': '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so': {'s': 'child', 'i': 9}, 'sArr': ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr': ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap': {'x': true, 'y': false}, 'uuid': '%s'}::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 19), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)", uid)) defer rows.Close() assertTrueF(t, rows.Next()) @@ -315,7 +315,7 @@ type objectWithAllTypesNullable struct { sArr []string f64Arr []float64 someMap map[string]bool - uuid UUID + uuid testUUID } func (o *objectWithAllTypesNullable) Scan(val any) error { @@ -395,7 +395,7 @@ func (o *objectWithAllTypesNullable) Scan(val any) error { return err } - o.uuid = ParseUUID(uuidStr.String) + o.uuid = parseTestUUID(uuidStr.String) return nil } @@ -491,7 +491,7 @@ func TestObjectWithAllTypesNullable(t *testing.T) { assertDeepEqualE(t, res.so, so) }) t.Run("not null", func(t *testing.T) { - uid := NewUUID() + uid := newTestUUID() rows := dbt.mustQueryContextT(ctx, t, fmt.Sprintf("select 1, object_construct_keep_null('s', 'abc', 'b', 1, 'i16', 2, 'i32', 3, 'i64', 9223372036854775807, 'f64', 2.2, 'bo', true, 'bi', TO_BINARY('616263', 'HEX'), 'date', '2024-03-21'::DATE, 'time', '13:03:02'::TIME, 'ltz', '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz', '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz', '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so', {'s': 'child', 'i': 9}::OBJECT, 'sArr', ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr', ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap', {'x': true, 'y': false}, 'uuid': '%s')::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)", uid)) defer rows.Close() rows.Next() @@ -549,7 +549,7 @@ type objectWithAllTypesSimpleScan struct { SArr []string F64Arr []float64 SomeMap map[string]bool - UUID UUID + UUID testUUID } func (so *objectWithAllTypesSimpleScan) Scan(val any) error { @@ -562,7 +562,7 @@ func (so *objectWithAllTypesSimpleScan) Write(sowc StructuredObjectWriterContext } func TestObjectWithAllTypesSimpleScan(t *testing.T) { - uid := NewUUID() + uid := newTestUUID() warsawTz, err := time.LoadLocation("Europe/Warsaw") assertNilF(t, err) ctx := WithStructuredTypesEnabled(context.Background()) @@ -616,7 +616,7 @@ func TestNullObjectSimpleScan(t *testing.T) { assertNilE(t, res) }) t.Run("not null", func(t *testing.T) { - uid := NewUUID() + uid := newTestUUID() rows := dbt.mustQueryContextT(ctx, t, fmt.Sprintf("SELECT {'s': 'some string', 'b': 1, 'i16': 2, 'i32': 3, 'i64': 9223372036854775807, 'f32': '1.1', 'f64': 2.2, 'nfraction': 3.3, 'bo': true, 'bi': TO_BINARY('616263', 'HEX'), 'date': '2024-03-21'::DATE, 'time': '13:03:02'::TIME, 'ltz': '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz': '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz': '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so': {'s': 'child', 'i': 9}, 'sArr': ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr': ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap': {'x': true, 'y': false}, 'uuid': '%s'}::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f32 FLOAT, f64 DOUBLE, nfraction NUMBER(38, 19), bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)", uid)) defer rows.Close() assertTrueF(t, rows.Next()) @@ -647,7 +647,7 @@ type objectWithAllTypesNullableSimpleScan struct { SArr []string F64Arr []float64 SomeMap map[string]bool - UUID UUID + UUID testUUID } func (o *objectWithAllTypesNullableSimpleScan) Scan(val any) error { @@ -693,7 +693,7 @@ func TestObjectWithAllTypesSimpleScanNullable(t *testing.T) { assertEqualE(t, res.UUID, []string(nil)) }) t.Run("not null", func(t *testing.T) { - uid := NewUUID() + uid := newTestUUID() rows := dbt.mustQueryContextT(ctx, t, fmt.Sprintf("select 1, object_construct_keep_null('s', 'abc', 'b', 1, 'i16', 2, 'i32', 3, 'i64', 9223372036854775807, 'f64', 2.2, 'bo', true, 'bi', TO_BINARY('616263', 'HEX'), 'date', '2024-03-21'::DATE, 'time', '13:03:02'::TIME, 'ltz', '2021-07-21 11:22:33'::TIMESTAMP_LTZ, 'tz', '2022-08-31 13:43:22 +0200'::TIMESTAMP_TZ, 'ntz', '2023-05-22 01:17:19'::TIMESTAMP_NTZ, 'so', {'s': 'child', 'i': 9}::OBJECT, 'sArr', ARRAY_CONSTRUCT('x', 'y', 'z'), 'f64Arr', ARRAY_CONSTRUCT(1.1, 2.2, 3.3), 'someMap', {'x': true, 'y': false}, 'uuid': '%s')::OBJECT(s VARCHAR, b TINYINT, i16 SMALLINT, i32 INTEGER, i64 BIGINT, f64 DOUBLE, bo BOOLEAN, bi BINARY, date DATE, time TIME, ltz TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN), uuid VARCHAR)", uid)) defer rows.Close() rows.Next() diff --git a/structured_type_write_test.go b/structured_type_write_test.go index 3723dc26a..a9135f6c0 100644 --- a/structured_type_write_test.go +++ b/structured_type_write_test.go @@ -159,7 +159,7 @@ func TestBindingObjectWithSchema(t *testing.T) { sArr: []string{"a", "b"}, f64Arr: []float64{1.1, 2.2}, someMap: map[string]bool{"a": true, "b": false}, - uuid: NewUUID(), + uuid: newTestUUID(), } dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o) rows := dbt.mustQueryContextT(ctx, t, "SELECT * FROM test_object_binding WHERE obj = ?", o) @@ -225,7 +225,7 @@ func TestBindingObjectWithNullableFieldsWithSchema(t *testing.T) { sArr: []string{"a", "b"}, f64Arr: []float64{1.1, 2.2}, someMap: map[string]bool{"a": true, "b": false}, - uuid: NewUUID(), + uuid: newTestUUID(), } dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o) rows := dbt.mustQueryContextT(ctx, t, "SELECT * FROM test_object_binding WHERE obj = ?", o) @@ -275,7 +275,7 @@ func TestBindingObjectWithNullableFieldsWithSchema(t *testing.T) { sArr: nil, f64Arr: nil, someMap: nil, - uuid: UUID{}, + uuid: testUUID{}, } dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o) rows := dbt.mustQueryContextT(ctx, t, "SELECT * FROM test_object_binding WHERE obj = ?", o) @@ -341,7 +341,7 @@ func TestBindingObjectWithSchemaSimpleWrite(t *testing.T) { SArr: []string{"a", "b"}, F64Arr: []float64{1.1, 2.2}, SomeMap: map[string]bool{"a": true, "b": false}, - UUID: NewUUID(), + UUID: newTestUUID(), } dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o) rows := dbt.mustQueryContextT(ctx, t, "SELECT * FROM test_object_binding WHERE obj = ?", o) @@ -408,7 +408,7 @@ func TestBindingObjectWithNullableFieldsWithSchemaSimpleWrite(t *testing.T) { SArr: []string{"a", "b"}, F64Arr: []float64{1.1, 2.2}, SomeMap: map[string]bool{"a": true, "b": false}, - UUID: NewUUID(), + UUID: newTestUUID(), } dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o) rows := dbt.mustQueryContextT(ctx, t, "SELECT * FROM test_object_binding WHERE obj = ?", o) @@ -458,7 +458,7 @@ func TestBindingObjectWithNullableFieldsWithSchemaSimpleWrite(t *testing.T) { SArr: nil, F64Arr: nil, SomeMap: nil, - UUID: UUID{}, + UUID: testUUID{}, } dbt.mustExecT(t, "INSERT INTO test_object_binding SELECT (?)", o) rows := dbt.mustQueryContextT(ctx, t, "SELECT * FROM test_object_binding WHERE obj = ?", o) diff --git a/uuid.go b/uuid.go index a7bbea0e4..2fd1c35b5 100644 --- a/uuid.go +++ b/uuid.go @@ -4,7 +4,6 @@ package gosnowflake import ( "crypto/rand" - "database/sql/driver" "fmt" "strconv" ) @@ -49,60 +48,3 @@ func ParseUUID(str string) UUID { func (u UUID) String() string { return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:]) } - -// This is for unit testing scans/value of UUIDs being inserted/read to/from the DB - not intended for public use -type testUUID = UUID - -func newTestUUID() testUUID { - return testUUID(NewUUID()) -} - -func parseTestUUID(str string) testUUID { - return testUUID(ParseUUID(str)) -} - -// Scan implements sql.Scanner so UUIDs can be read from databases transparently. -// Currently, database types that map to string and []byte are supported. Please -// consult database-specific driver documentation for matching types. -func (uuid *testUUID) Scan(src interface{}) error { - switch src := src.(type) { - case nil: - return nil - - case string: - // if an empty UUID comes from a table, we return a null UUID - if src == "" { - return nil - } - - // see Parse for required string format - u := ParseUUID(src) - - *uuid = testUUID(u) - - case []byte: - // if an empty UUID comes from a table, we return a null UUID - if len(src) == 0 { - return nil - } - - // assumes a simple slice of bytes if 16 bytes - // otherwise attempts to parse - if len(src) != 16 { - return uuid.Scan(string(src)) - } - copy((*uuid)[:], src) - - default: - return fmt.Errorf("Scan: unable to scan type %T into UUID", src) - } - - return nil -} - -// Value implements sql.Valuer so that UUIDs can be written to databases -// transparently. Currently, UUIDs map to strings. Please consult -// database-specific driver documentation for matching types. -func (uuid testUUID) Value() (driver.Value, error) { - return uuid.String(), nil -} From ad6741a4571944b0732c3c5b610d0358757653a0 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 20 Nov 2024 12:56:06 -0600 Subject: [PATCH 27/41] [AB#1669514] dont commit the commented query too large to print error --- driver_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/driver_test.go b/driver_test.go index a9eba6a24..c62d5ea22 100644 --- a/driver_test.go +++ b/driver_test.go @@ -270,9 +270,9 @@ func (dbt *DBTest) prepare(query string) (*sql.Stmt, error) { } func (dbt *DBTest) fail(method, query string, err error) { - // if len(query) > 300 { - // query = "[query too large to print]" - // } + if len(query) > 300 { + query = "[query too large to print]" + } dbt.Fatalf("error on %s [%s]: %s", method, query, err.Error()) } From 11ce50bb0a997189343478aff3d74f2888ea56c2 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 20 Nov 2024 12:58:29 -0600 Subject: [PATCH 28/41] [AB#1669514] debug mode for local help --- driver_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/driver_test.go b/driver_test.go index c62d5ea22..d2a83691b 100644 --- a/driver_test.go +++ b/driver_test.go @@ -34,6 +34,7 @@ var ( protocol string customPrivateKey bool // Whether user has specified the private key path testPrivKey *rsa.PrivateKey // Valid private key used for all test cases + debugMode bool ) const ( @@ -76,6 +77,8 @@ func init() { setupPrivateKey() createDSN("UTC") + + debugMode, _ = strconv.ParseBool(os.Getenv("SNOWFLAKE_TEST_DEBUG")) } func createDSN(timezone string) { @@ -270,7 +273,7 @@ func (dbt *DBTest) prepare(query string) (*sql.Stmt, error) { } func (dbt *DBTest) fail(method, query string, err error) { - if len(query) > 300 { + if !debugMode && len(query) > 300 { query = "[query too large to print]" } dbt.Fatalf("error on %s [%s]: %s", method, query, err.Error()) @@ -398,7 +401,7 @@ type SCTest struct { } func (sct *SCTest) fail(method, query string, err error) { - if len(query) > 300 { + if !debugMode && len(query) > 300 { query = "[query too large to print]" } sct.Fatalf("error on %s [%s]: %s", method, query, err.Error()) From 53d5ad499dda90af8ca42081eafb575aa800eb63 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 20 Nov 2024 15:01:42 -0600 Subject: [PATCH 29/41] [AB#1669514] bump checkout; fix typo in yaml --- .github/workflows/build-test.yml | 228 +++++++++++++++---------------- 1 file changed, 114 insertions(+), 114 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index b393d483a..65bef1198 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -1,22 +1,22 @@ name: Build and Test on: - push: - braches: - - master - tags: - - v* - pull_request: - branches: - - master - - SNOW-* - schedule: - - cron: '7 3 * * *' - workflow_dispatch: - inputs: - goTestParams: - default: - description: "Parameters passed to go test" + push: + branches: + - master + tags: + - v* + pull_request: + branches: + - master + - SNOW-* + schedule: + - cron: "7 3 * * *" + workflow_dispatch: + inputs: + goTestParams: + default: + description: "Parameters passed to go test" concurrency: # older builds for the same pull request numer or branch should be cancelled @@ -24,101 +24,101 @@ concurrency: cancel-in-progress: true jobs: - lint: - runs-on: ubuntu-latest - strategy: - fail-fast: false - name: Check linter - steps: - - uses: actions/checkout@v1 - - name: Setup go - uses: actions/setup-go@v5 - with: - go-version: '1.21' - - name: golangci-lint - uses: golangci/golangci-lint-action@v6 - with: - version: v1.60 - - name: Format, Lint - shell: bash - run: ./ci/build.sh - build-test-linux: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - cloud: [ 'AWS', 'AZURE', 'GCP' ] - go: [ '1.21', '1.22', '1.23' ] - name: ${{ matrix.cloud }} Go ${{ matrix.go }} on Ubuntu - steps: - - uses: actions/checkout@v1 - - name: Setup go - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go }} - - name: Test - shell: bash - env: - PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }} - CLOUD_PROVIDER: ${{ matrix.cloud }} - GORACE: history_size=7 - GO_TEST_PARAMS: ${{ inputs.goTestParams }} - run: ./ci/test.sh - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} - build-test-mac: - runs-on: macos-latest - strategy: - fail-fast: false - matrix: - cloud: [ 'AWS', 'AZURE', 'GCP' ] - go: [ '1.21', '1.22', '1.23' ] - name: ${{ matrix.cloud }} Go ${{ matrix.go }} on Mac - steps: - - uses: actions/checkout@v1 - - name: Setup go - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go }} - - name: Test - shell: bash - env: - PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }} - CLOUD_PROVIDER: ${{ matrix.cloud }} - GO_TEST_PARAMS: ${{ inputs.goTestParams }} - run: ./ci/test.sh - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} - build-test-windows: - runs-on: windows-latest - strategy: - fail-fast: false - matrix: - cloud: [ 'AWS', 'AZURE', 'GCP' ] - go: [ '1.21', '1.22', '1.23' ] - name: ${{ matrix.cloud }} Go ${{ matrix.go }} on Windows - steps: - - uses: actions/checkout@v1 - - name: Setup go - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go }} - - uses: actions/setup-python@v1 - with: - python-version: '3.x' - architecture: 'x64' - - name: Test - shell: cmd - env: - PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }} - CLOUD_PROVIDER: ${{ matrix.cloud }} - GO_TEST_PARAMS: ${{ inputs.goTestParams }} - run: ci\\test.bat - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} + lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + name: Check linter + steps: + - uses: actions/checkout@v4 + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version-file: "./go.mod" + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.60 + - name: Format, Lint + shell: bash + run: ./ci/build.sh + build-test-linux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + cloud: ["AWS", "AZURE", "GCP"] + go: ["1.21", "1.22", "1.23"] + name: ${{ matrix.cloud }} Go ${{ matrix.go }} on Ubuntu + steps: + - uses: actions/checkout@v4 + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go }} + - name: Test + shell: bash + env: + PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }} + CLOUD_PROVIDER: ${{ matrix.cloud }} + GORACE: history_size=7 + GO_TEST_PARAMS: ${{ inputs.goTestParams }} + run: ./ci/test.sh + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} + build-test-mac: + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + cloud: ["AWS", "AZURE", "GCP"] + go: ["1.21", "1.22", "1.23"] + name: ${{ matrix.cloud }} Go ${{ matrix.go }} on Mac + steps: + - uses: actions/checkout@v4 + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go }} + - name: Test + shell: bash + env: + PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }} + CLOUD_PROVIDER: ${{ matrix.cloud }} + GO_TEST_PARAMS: ${{ inputs.goTestParams }} + run: ./ci/test.sh + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} + build-test-windows: + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + cloud: ["AWS", "AZURE", "GCP"] + go: ["1.21", "1.22", "1.23"] + name: ${{ matrix.cloud }} Go ${{ matrix.go }} on Windows + steps: + - uses: actions/checkout@v4 + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go }} + - uses: actions/setup-python@v1 + with: + python-version: "3.x" + architecture: "x64" + - name: Test + shell: cmd + env: + PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }} + CLOUD_PROVIDER: ${{ matrix.cloud }} + GO_TEST_PARAMS: ${{ inputs.goTestParams }} + run: ci\\test.bat + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} From 74c6fd8050c69fe0ca2a6eaf0b38a0a50ad92756 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 20 Nov 2024 15:36:49 -0600 Subject: [PATCH 30/41] [AB#1669514] Notes in bind_uploader --- bind_uploader.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/bind_uploader.go b/bind_uploader.go index 1e220d42f..a68b58664 100644 --- a/bind_uploader.go +++ b/bind_uploader.go @@ -111,15 +111,18 @@ func (bu *bindUploader) createStageIfNeeded() error { return err } if !data.Success { - code, err := strconv.Atoi(data.Code) - if err != nil { - return err + code, atoiErr := strconv.Atoi(data.Code) + if atoiErr != nil { + return atoiErr } return (&SnowflakeError{ Number: code, SQLState: data.Data.SQLState, - Message: fmt.Sprint(err), - QueryID: data.Data.QueryID, + // This err will always be nil because we are returning after the exec if err is not nil + // Also, the error presented by the AtoI conversion is short-circuited. Did we intend to use err here? + // Or should we use a field in data (like Message) instead? + Message: fmt.Sprint(err), // makes it pointer safe + QueryID: data.Data.QueryID, }).exceptionTelemetry(bu.sc) } bu.arrayBindStage = bindStageName From 088bfdc6730cf256b63dc7c90dee00b2d951c0b3 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 20 Nov 2024 15:47:03 -0600 Subject: [PATCH 31/41] [AB#1669514] dont commit formatting --- .github/workflows/build-test.yml | 228 +++++++++++++++---------------- 1 file changed, 114 insertions(+), 114 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 65bef1198..ebfb203d9 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -1,22 +1,22 @@ name: Build and Test on: - push: - branches: - - master - tags: - - v* - pull_request: - branches: - - master - - SNOW-* - schedule: - - cron: "7 3 * * *" - workflow_dispatch: - inputs: - goTestParams: - default: - description: "Parameters passed to go test" + push: + branches: + - master + tags: + - v* + pull_request: + branches: + - master + - SNOW-* + schedule: + - cron: '7 3 * * *' + workflow_dispatch: + inputs: + goTestParams: + default: + description: "Parameters passed to go test" concurrency: # older builds for the same pull request numer or branch should be cancelled @@ -24,101 +24,101 @@ concurrency: cancel-in-progress: true jobs: - lint: - runs-on: ubuntu-latest - strategy: - fail-fast: false - name: Check linter - steps: - - uses: actions/checkout@v4 - - name: Setup go - uses: actions/setup-go@v5 - with: - go-version-file: "./go.mod" - - name: golangci-lint - uses: golangci/golangci-lint-action@v6 - with: - version: v1.60 - - name: Format, Lint - shell: bash - run: ./ci/build.sh - build-test-linux: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - cloud: ["AWS", "AZURE", "GCP"] - go: ["1.21", "1.22", "1.23"] - name: ${{ matrix.cloud }} Go ${{ matrix.go }} on Ubuntu - steps: - - uses: actions/checkout@v4 - - name: Setup go - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go }} - - name: Test - shell: bash - env: - PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }} - CLOUD_PROVIDER: ${{ matrix.cloud }} - GORACE: history_size=7 - GO_TEST_PARAMS: ${{ inputs.goTestParams }} - run: ./ci/test.sh - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} - build-test-mac: - runs-on: macos-latest - strategy: - fail-fast: false - matrix: - cloud: ["AWS", "AZURE", "GCP"] - go: ["1.21", "1.22", "1.23"] - name: ${{ matrix.cloud }} Go ${{ matrix.go }} on Mac - steps: - - uses: actions/checkout@v4 - - name: Setup go - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go }} - - name: Test - shell: bash - env: - PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }} - CLOUD_PROVIDER: ${{ matrix.cloud }} - GO_TEST_PARAMS: ${{ inputs.goTestParams }} - run: ./ci/test.sh - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} - build-test-windows: - runs-on: windows-latest - strategy: - fail-fast: false - matrix: - cloud: ["AWS", "AZURE", "GCP"] - go: ["1.21", "1.22", "1.23"] - name: ${{ matrix.cloud }} Go ${{ matrix.go }} on Windows - steps: - - uses: actions/checkout@v4 - - name: Setup go - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go }} - - uses: actions/setup-python@v1 - with: - python-version: "3.x" - architecture: "x64" - - name: Test - shell: cmd - env: - PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }} - CLOUD_PROVIDER: ${{ matrix.cloud }} - GO_TEST_PARAMS: ${{ inputs.goTestParams }} - run: ci\\test.bat - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} + lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + name: Check linter + steps: + - uses: actions/checkout@v4 + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version-file: "./go.mod" + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.60 + - name: Format, Lint + shell: bash + run: ./ci/build.sh + build-test-linux: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + cloud: [ 'AWS', 'AZURE', 'GCP' ] + go: [ '1.21', '1.22', '1.23' ] + name: ${{ matrix.cloud }} Go ${{ matrix.go }} on Ubuntu + steps: + - uses: actions/checkout@v4 + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go }} + - name: Test + shell: bash + env: + PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }} + CLOUD_PROVIDER: ${{ matrix.cloud }} + GORACE: history_size=7 + GO_TEST_PARAMS: ${{ inputs.goTestParams }} + run: ./ci/test.sh + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} + build-test-mac: + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + cloud: [ 'AWS', 'AZURE', 'GCP' ] + go: [ '1.21', '1.22', '1.23' ] + name: ${{ matrix.cloud }} Go ${{ matrix.go }} on Mac + steps: + - uses: actions/checkout@v4 + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go }} + - name: Test + shell: bash + env: + PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }} + CLOUD_PROVIDER: ${{ matrix.cloud }} + GO_TEST_PARAMS: ${{ inputs.goTestParams }} + run: ./ci/test.sh + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} + build-test-windows: + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + cloud: [ 'AWS', 'AZURE', 'GCP' ] + go: [ '1.21', '1.22', '1.23' ] + name: ${{ matrix.cloud }} Go ${{ matrix.go }} on Windows + steps: + - uses: actions/checkout@v4 + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go }} + - uses: actions/setup-python@v1 + with: + python-version: '3.x' + architecture: 'x64' + - name: Test + shell: cmd + env: + PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }} + CLOUD_PROVIDER: ${{ matrix.cloud }} + GO_TEST_PARAMS: ${{ inputs.goTestParams }} + run: ci\\test.bat + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} From 03840ad1072c37ce33ed853f5249da450e323b57 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 21 Nov 2024 09:27:42 -0600 Subject: [PATCH 32/41] [AB#1669514] data.Message fix --- bind_uploader.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/bind_uploader.go b/bind_uploader.go index a68b58664..2711c98ca 100644 --- a/bind_uploader.go +++ b/bind_uploader.go @@ -118,11 +118,8 @@ func (bu *bindUploader) createStageIfNeeded() error { return (&SnowflakeError{ Number: code, SQLState: data.Data.SQLState, - // This err will always be nil because we are returning after the exec if err is not nil - // Also, the error presented by the AtoI conversion is short-circuited. Did we intend to use err here? - // Or should we use a field in data (like Message) instead? - Message: fmt.Sprint(err), // makes it pointer safe - QueryID: data.Data.QueryID, + Message: data.Message, + QueryID: data.Data.QueryID, }).exceptionTelemetry(bu.sc) } bu.arrayBindStage = bindStageName From 06a86c1b52608c251d55cf46d0b51b08543d3b98 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 21 Nov 2024 09:30:00 -0600 Subject: [PATCH 33/41] [AB#1669514] revert atoierr --- bind_uploader.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bind_uploader.go b/bind_uploader.go index 2711c98ca..553648d01 100644 --- a/bind_uploader.go +++ b/bind_uploader.go @@ -111,9 +111,9 @@ func (bu *bindUploader) createStageIfNeeded() error { return err } if !data.Success { - code, atoiErr := strconv.Atoi(data.Code) - if atoiErr != nil { - return atoiErr + code, err := strconv.Atoi(data.Code) + if err != nil { + return err } return (&SnowflakeError{ Number: code, From 9783b9cef37768ae1a6bd3e0ea0b8c46cb04e6f2 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 21 Nov 2024 09:39:03 -0600 Subject: [PATCH 34/41] [AB#1669514] JSON test vs basic type --- driver_test.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/driver_test.go b/driver_test.go index d2a83691b..4e536c236 100644 --- a/driver_test.go +++ b/driver_test.go @@ -926,7 +926,13 @@ func (uuid testUUID) Value() (driver.Value, error) { } func TestUUID(t *testing.T) { - testUuid(t, false) + t.Run("Basic Type", func(t *testing.T) { + testUuid(t, false) + }) + + t.Run("JSON", func(t *testing.T) { + testUuid(t, true) + }) } func testUuid(t *testing.T, json bool) { From afd6995f9299667384721fafd4000a9bbb08525f Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 21 Nov 2024 14:12:23 -0600 Subject: [PATCH 35/41] [AB#1669514] arrow test --- driver_test.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/driver_test.go b/driver_test.go index 4e536c236..164169fa9 100644 --- a/driver_test.go +++ b/driver_test.go @@ -927,18 +927,23 @@ func (uuid testUUID) Value() (driver.Value, error) { func TestUUID(t *testing.T) { t.Run("Basic Type", func(t *testing.T) { - testUuid(t, false) + testUuid(t, false, false) }) t.Run("JSON", func(t *testing.T) { - testUuid(t, true) + testUuid(t, true, false) + }) + t.Run("Arrow", func(t *testing.T) { + testUuid(t, false, true) }) } -func testUuid(t *testing.T, json bool) { +func testUuid(t *testing.T, json, arrow bool) { runDBTest(t, func(dbt *DBTest) { if json { dbt.mustExec(forceJSON) + } else if arrow { + dbt.mustExec(forceARROW) } types := []string{"CHAR(255)", "VARCHAR(255)", "TEXT", "STRING"} From 36c4510ba5db6e1a6b22036a9c17688f03f7f33c Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 21 Nov 2024 18:03:37 -0600 Subject: [PATCH 36/41] [AB#1669514] fix array binding --- bindings_test.go | 101 ++++++++++++++++++++++++++++++++++++----------- converter.go | 33 ++++++++++------ 2 files changed, 101 insertions(+), 33 deletions(-) diff --git a/bindings_test.go b/bindings_test.go index 91530dc5e..ef5998800 100644 --- a/bindings_test.go +++ b/bindings_test.go @@ -8,6 +8,7 @@ import ( "database/sql" "fmt" "log" + "math" "math/big" "math/rand" "reflect" @@ -70,7 +71,7 @@ func TestBindingFloat64(t *testing.T) { dbt.mustExec("INSERT INTO test VALUES (1, ?)", expected) rows = dbt.mustQuery("SELECT value FROM test WHERE id = ?", 1) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() if rows.Next() { assertNilF(t, rows.Scan(&out)) @@ -203,14 +204,14 @@ func TestBindingTimestampTZ(t *testing.T) { dbt.Fatal(err.Error()) } defer func() { - assertNilF(t, stmt.Close()) + assertNilF(t, stmt.Close()) }() if _, err = stmt.Exec(DataTypeTimestampTz, expected); err != nil { dbt.Fatal(err) } rows := dbt.mustQuery("SELECT tz FROM tztest WHERE id=?", 1) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() var v time.Time if rows.Next() { @@ -258,7 +259,7 @@ func TestBindingTimePtrInStruct(t *testing.T) { rows := dbt.mustQuery("SELECT tz FROM timeStructTest WHERE id=?", &expectedID) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() var v time.Time if rows.Next() { @@ -307,7 +308,7 @@ func TestBindingTimeInStruct(t *testing.T) { rows := dbt.mustQuery("SELECT tz FROM timeStructTest WHERE id=?", &expectedID) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() var v time.Time if rows.Next() { @@ -329,7 +330,7 @@ func TestBindingInterface(t *testing.T) { rows := dbt.mustQueryContext( WithHigherPrecision(context.Background()), selectVariousTypes) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() if !rows.Next() { dbt.Error("failed to query") @@ -357,7 +358,7 @@ func TestBindingInterfaceString(t *testing.T) { runDBTest(t, func(dbt *DBTest) { rows := dbt.mustQuery(selectVariousTypes) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() if !rows.Next() { dbt.Error("failed to query") @@ -382,6 +383,62 @@ func TestBindingInterfaceString(t *testing.T) { }) } +func TestBulkArrayBindingUUID(t *testing.T) { + max := math.Pow10(5) // 100K because my power is maximum + uuids := make([]any, int(max)) + + createTable := "CREATE OR REPLACE TABLE TEST_PREP_STATEMENT (id INT autoincrement start 1 increment 1, uuid VARCHAR)" + insert := "INSERT INTO TEST_PREP_STATEMENT (uuid) VALUES (?)" + + for i := range uuids { + uuids[i] = newTestUUID() + } + + runDBTest(t, func(dbt *DBTest) { + var rows *RowsExtended + t.Cleanup(func() { + if rows != nil { + assertNilF(t, rows.Close()) + } + + dbt.exec(deleteTableSQL) + }) + + dbt.mustExec(createTable) + + bound := Array(&uuids) + res := dbt.mustExec(insert, bound) + + if affected, _ := res.RowsAffected(); affected != int64(max) { + t.Fatalf("failed to insert all rows. expected: %f.0, got: %v", max, affected) + } + + rows = dbt.mustQuery("SELECT * FROM TEST_PREP_STATEMENT ORDER BY ID") + + for i := 0; rows.Next(); i++ { + var ( + id int + out testUUID + ) + if err := rows.Scan(&id, &out); err != nil { + t.Fatal(err) + } + + var found bool + for _, u := range uuids { + if u == out { + found = true + break + } + } + if !found { + t.Errorf("failed to find UUID. expected: %s, but it wasnt in the list", out) + } + } + }) + +} + func TestBulkArrayBindingInterfaceNil(t *testing.T) { nilArray := make([]any, 1) @@ -396,7 +453,7 @@ func TestBulkArrayBindingInterfaceNil(t *testing.T) { Array(&nilArray, TimeType)) rows := dbt.mustQuery(selectAllSQL) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() var v0 sql.NullInt32 @@ -481,7 +538,7 @@ func TestBulkArrayBindingInterface(t *testing.T) { Array(&boolArray), Array(&strArray), Array(&byteArray), Array(&int64Array)) rows := dbt.mustQuery(selectAllSQLBulkArray) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() var v0 sql.NullInt32 @@ -586,7 +643,7 @@ func TestBulkArrayBindingInterfaceDateTimeTimestamp(t *testing.T) { rows := dbt.mustQuery(selectAllSQLBulkArrayDateTimeTimestamp) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() var v0, v1, v2, v3, v4 sql.NullTime @@ -695,7 +752,7 @@ func testBindingArray(t *testing.T, bulk bool) { Array(&tmArray, TimeType)) rows := dbt.mustQuery(selectAllSQL) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() var v0 int @@ -777,7 +834,7 @@ func TestBulkArrayBinding(t *testing.T) { dbt.mustExec(fmt.Sprintf("insert into %v values (?, ?, ?, ?, ?, ?, ?, ?)", dbname), Array(&intArr), Array(&strArr), Array(<zArr, TimestampLTZType), Array(&tzArr, TimestampTZType), Array(&ntzArr, TimestampNTZType), Array(&dateArr, DateType), Array(&timeArr, TimeType), Array(&binArr)) rows := dbt.mustQuery("select * from " + dbname + " order by c1") defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() cnt := 0 var i int @@ -825,7 +882,7 @@ func TestBulkArrayBindingTimeWithPrecision(t *testing.T) { dbt.mustExec(fmt.Sprintf("insert into %v values (?, ?, ?, ?)", dbname), Array(&secondsArr, TimeType), Array(&millisecondsArr, TimeType), Array(µsecondsArr, TimeType), Array(&nanosecondsArr, TimeType)) rows := dbt.mustQuery("select * from " + dbname) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() cnt := 0 var s, ms, us, ns time.Time @@ -866,7 +923,7 @@ func TestBulkArrayMultiPartBinding(t *testing.T) { Array(&randomStrings)) rows := dbt.mustQuery("select count(*) from " + tempTableName) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() if rows.Next() { var count int @@ -878,7 +935,7 @@ func TestBulkArrayMultiPartBinding(t *testing.T) { rows := dbt.mustQuery("select count(*) from " + tempTableName) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() if rows.Next() { var count int @@ -909,7 +966,7 @@ func TestBulkArrayMultiPartBindingInt(t *testing.T) { rows := dbt.mustQuery("select * from binding_test order by c1") defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() cnt := startNum var i int @@ -959,7 +1016,7 @@ func TestBulkArrayMultiPartBindingWithNull(t *testing.T) { rows := dbt.mustQuery("select * from binding_test order by c1,c2") defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() cnt := startNum var i sql.NullInt32 @@ -1042,7 +1099,7 @@ func TestFunctionParameters(t *testing.T) { t.Fatal(err) } defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() if rows.Err() != nil { t.Fatal(err) @@ -1144,7 +1201,7 @@ func TestVariousBindingModes(t *testing.T) { t.Fatal(err) } defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() if !rows.Next() { t.Fatal("Expected to return a row") @@ -1194,7 +1251,7 @@ func testLOBRetrieval(t *testing.T, useArrowFormat bool) { rows, err := dbt.query(fmt.Sprintf("SELECT randstr(%v, 124)", testSize)) assertNilF(t, err) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() assertTrueF(t, rows.Next(), fmt.Sprintf("no rows returned for the LOB size %v", testSize)) @@ -1227,7 +1284,7 @@ func TestMaxLobSize(t *testing.T) { rows, err := dbt.query("select randstr(20000000, random())") assertNilF(t, err) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() }) }) @@ -1308,7 +1365,7 @@ func testInsertLOBData(t *testing.T, useArrowFormat bool, isLiteral bool) { rows, err := dbt.query("SELECT * FROM lob_test_table") assertNilF(t, err) defer func() { - assertNilF(t, rows.Close()) + assertNilF(t, rows.Close()) }() assertTrueF(t, rows.Next(), fmt.Sprintf("%s: no rows returned", tc.testDesc)) diff --git a/converter.go b/converter.go index f9f0fefdf..d6d40c1a7 100644 --- a/converter.go +++ b/converter.go @@ -423,7 +423,7 @@ func arrayToString(v driver.Value, tsmode snowflakeType, params map[string]*stri res = res[0:len(res)-1] + "]" return bindingValue{&res, jsonFormatStr, &schemaForBytes}, nil } else if isUUIDImplementer(v1) { // special case for UUIDs (snowflake type and other implementers) - stringer := v.(fmt.Stringer) + stringer := v.(fmt.Stringer) // we don't need to validate if it's a fmt.Stringer because we already checked if it's a UUID type with a stringer value := stringer.String() return bindingValue{&value, "", nil}, nil } else if isSliceOfSlices(v) { @@ -2696,44 +2696,38 @@ func interfaceSliceToString(interfaceSlice reflect.Value, stream bool, tzType .. for i := 0; i < interfaceSlice.Len(); i++ { val := interfaceSlice.Index(i) if val.CanInterface() { - switch val.Interface().(type) { + v := val.Interface() + + switch x := v.(type) { case int: t = fixedType - x := val.Interface().(int) v := strconv.Itoa(x) arr = append(arr, &v) case int32: t = fixedType - x := val.Interface().(int32) v := strconv.Itoa(int(x)) arr = append(arr, &v) case int64: t = fixedType - x := val.Interface().(int64) v := strconv.FormatInt(x, 10) arr = append(arr, &v) case float32: t = realType - x := val.Interface().(float32) v := fmt.Sprintf("%g", x) arr = append(arr, &v) case float64: t = realType - x := val.Interface().(float64) v := fmt.Sprintf("%g", x) arr = append(arr, &v) case bool: t = booleanType - x := val.Interface().(bool) v := strconv.FormatBool(x) arr = append(arr, &v) case string: t = textType - x := val.Interface().(string) arr = append(arr, &x) case []byte: t = binaryType - x := val.Interface().([]byte) v := hex.EncodeToString(x) arr = append(arr, &v) case time.Time: @@ -2741,7 +2735,6 @@ func interfaceSliceToString(interfaceSlice reflect.Value, stream bool, tzType .. return unSupportedType, nil } - x := val.Interface().(time.Time) switch tzType[0] { case TimestampNTZType: t = timestampNtzType @@ -2781,8 +2774,26 @@ func interfaceSliceToString(interfaceSlice reflect.Value, stream bool, tzType .. default: return unSupportedType, nil } + case driver.Valuer: // honor each driver's Valuer interface + if value, err := x.Value(); err == nil && value != nil { + // if the output value is a valid string, return that + if strVal, ok := value.(string); ok { + t = textType + arr = append(arr, &strVal) + } + } else if v != nil { + return unSupportedType, nil + } else { + arr = append(arr, nil) + } default: if val.Interface() != nil { + if isUUIDImplementer(val) { + t = textType + x := v.(fmt.Stringer).String() + arr = append(arr, &x) + continue + } return unSupportedType, nil } From ca283470166f0027fb6ede3941490b04793f5b40 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 22 Nov 2024 09:38:45 -0600 Subject: [PATCH 37/41] [AB#1669514] make it slightly faster by using nlogn complexity --- bindings_test.go | 52 +++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/bindings_test.go b/bindings_test.go index ef5998800..6647f7f10 100644 --- a/bindings_test.go +++ b/bindings_test.go @@ -12,6 +12,7 @@ import ( "math/big" "math/rand" "reflect" + "sort" "strconv" "strings" "testing" @@ -384,16 +385,20 @@ func TestBindingInterfaceString(t *testing.T) { } func TestBulkArrayBindingUUID(t *testing.T) { - max := math.Pow10(5) // 100K because my power is maximum - uuids := make([]any, int(max)) + max := math.Pow10(1) // 100K because my power is maximum + expectedUuids := make([]any, int(max)) - createTable := "CREATE OR REPLACE TABLE TEST_PREP_STATEMENT (id INT autoincrement start 1 increment 1, uuid VARCHAR)" + createTable := "CREATE OR REPLACE TABLE TEST_PREP_STATEMENT (uuid VARCHAR)" insert := "INSERT INTO TEST_PREP_STATEMENT (uuid) VALUES (?)" - for i := range uuids { - uuids[i] = newTestUUID() + for i := range expectedUuids { + expectedUuids[i] = newTestUUID() } + sort.Slice(expectedUuids, func(i, j int) bool { + return expectedUuids[i].(testUUID).String() < expectedUuids[j].(testUUID).String() + }) + runDBTest(t, func(dbt *DBTest) { var rows *RowsExtended t.Cleanup(func() { @@ -406,33 +411,40 @@ func TestBulkArrayBindingUUID(t *testing.T) { dbt.mustExec(createTable) - bound := Array(&uuids) - res := dbt.mustExec(insert, bound) + res := dbt.mustExec(insert, Array(&expectedUuids)) - if affected, _ := res.RowsAffected(); affected != int64(max) { + affected, err := res.RowsAffected() + if err != nil { + t.Fatalf("failed to get affected rows. err: %s", err) + } else if affected != int64(max) { t.Fatalf("failed to insert all rows. expected: %f.0, got: %v", max, affected) } - rows = dbt.mustQuery("SELECT * FROM TEST_PREP_STATEMENT ORDER BY ID") + rows = dbt.mustQuery("SELECT * FROM TEST_PREP_STATEMENT ORDER BY uuid") + if rows == nil { + t.Fatal("failed to query") + } + + if rows.Err() != nil { + t.Fatalf("failed to query. err: %s", rows.Err()) + } + + var actual = make([]testUUID, len(expectedUuids)) for i := 0; rows.Next(); i++ { var ( - id int out testUUID ) - if err := rows.Scan(&id, &out); err != nil { + if err := rows.Scan(&out); err != nil { t.Fatal(err) } - var found bool - for _, u := range uuids { - if u == out { - found = true - break - } - } - if !found { - t.Errorf("failed to find UUID. expected: %s, but it wasnt in the list", out) + actual[i] = out + } + + for i := range expectedUuids { + if expectedUuids[i].(testUUID).String() != actual[i].String() { + t.Fatalf("failed to fetch the UUID column. expected %v, got: %v", expectedUuids[i], actual[i]) } } }) From 22740b466cf6583fd78f2fee2222a4952ffc768c Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 22 Nov 2024 09:39:16 -0600 Subject: [PATCH 38/41] back to 100K --- bindings_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings_test.go b/bindings_test.go index 6647f7f10..ede3d167d 100644 --- a/bindings_test.go +++ b/bindings_test.go @@ -385,7 +385,7 @@ func TestBindingInterfaceString(t *testing.T) { } func TestBulkArrayBindingUUID(t *testing.T) { - max := math.Pow10(1) // 100K because my power is maximum + max := math.Pow10(5) // 100K because my power is maximum expectedUuids := make([]any, int(max)) createTable := "CREATE OR REPLACE TABLE TEST_PREP_STATEMENT (uuid VARCHAR)" From f5fe4a824bd6604bcf47da2822b6ad0a4baa6adb Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 22 Nov 2024 15:09:45 -0600 Subject: [PATCH 39/41] [AB#1669514] simplify if check in test --- bindings_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings_test.go b/bindings_test.go index ede3d167d..9e20943dd 100644 --- a/bindings_test.go +++ b/bindings_test.go @@ -443,7 +443,7 @@ func TestBulkArrayBindingUUID(t *testing.T) { } for i := range expectedUuids { - if expectedUuids[i].(testUUID).String() != actual[i].String() { + if expectedUuids[i] != actual[i] { t.Fatalf("failed to fetch the UUID column. expected %v, got: %v", expectedUuids[i], actual[i]) } } From f342a0a0026ca274a07773c098e0b58ac29bd988 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 22 Nov 2024 15:15:06 -0600 Subject: [PATCH 40/41] [AB#1669514] More performant sort --- bindings_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings_test.go b/bindings_test.go index 9e20943dd..8919d4e23 100644 --- a/bindings_test.go +++ b/bindings_test.go @@ -12,7 +12,7 @@ import ( "math/big" "math/rand" "reflect" - "sort" + "slices" "strconv" "strings" "testing" @@ -395,8 +395,8 @@ func TestBulkArrayBindingUUID(t *testing.T) { expectedUuids[i] = newTestUUID() } - sort.Slice(expectedUuids, func(i, j int) bool { - return expectedUuids[i].(testUUID).String() < expectedUuids[j].(testUUID).String() + slices.SortStableFunc(expectedUuids, func(i, j any) int { + return strings.Compare(i.(testUUID).String(), j.(testUUID).String()) }) runDBTest(t, func(dbt *DBTest) { From 380db14bca753254abee548c6c8602a30005e42a Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 22 Nov 2024 15:37:37 -0600 Subject: [PATCH 41/41] [AB#1669514] use internal assert method --- bindings_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bindings_test.go b/bindings_test.go index 8919d4e23..3cfba218c 100644 --- a/bindings_test.go +++ b/bindings_test.go @@ -443,9 +443,7 @@ func TestBulkArrayBindingUUID(t *testing.T) { } for i := range expectedUuids { - if expectedUuids[i] != actual[i] { - t.Fatalf("failed to fetch the UUID column. expected %v, got: %v", expectedUuids[i], actual[i]) - } + assertEqualE(t, actual[i], expectedUuids[i]) } })