From d3aaee081703716415fe0d2613afcfc1552efe5e Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 19 Sep 2024 15:50:12 -0500 Subject: [PATCH 01/46] 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 478b59395a02c0344a34cc407df77ccbaefe87bf Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 19 Sep 2024 16:15:54 -0500 Subject: [PATCH 02/46] [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 b76c390801519a27d37daa4fb2d67b8dd0ac16a8 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 19 Sep 2024 16:17:44 -0500 Subject: [PATCH 03/46] [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 018095b12c6122a6e439638e7d394f716b860c24 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 08:39:08 -0500 Subject: [PATCH 04/46] [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 f5808c49a04eb75d7c84ecc9c1b8b6902617d51c Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 09:23:10 -0500 Subject: [PATCH 05/46] [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 cd4468ac97b897dfb84b6d766d6ecba8dcb8cc2b Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 09:34:14 -0500 Subject: [PATCH 06/46] [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 38b21cd9166c6897c4a2db1a2600ab4fba2a3037 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 09:38:32 -0500 Subject: [PATCH 07/46] [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 e81bbf310b258562c4279e88e5d90227ca89b4df Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 14:06:50 -0500 Subject: [PATCH 08/46] [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 8eb0367c1131791498365c9e89fe2123d6b18ee4 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 14:15:32 -0500 Subject: [PATCH 09/46] [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 c3303c54fdac8c17d09da9a76ee3129c0159d9e4 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 14:22:06 -0500 Subject: [PATCH 10/46] [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 14bc2353783a679f4b3439ac1342c6fb347d80cb Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 14:27:26 -0500 Subject: [PATCH 11/46] [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 22af12d293663a9a677f96e85bfa4327e816a3f4 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 14:37:15 -0500 Subject: [PATCH 12/46] [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 9d000743b6d0daed17f1fd75d76ee057f92c6c74 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 14:56:57 -0500 Subject: [PATCH 13/46] [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 15d38928c59c14cca6dd80b30aeec6e62fc63a87 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 20 Sep 2024 15:52:20 -0500 Subject: [PATCH 14/46] [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 ab8cfffa7d18114c4e1e57c320b5a7b06b7c53f6 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Mon, 23 Sep 2024 09:17:35 -0500 Subject: [PATCH 15/46] [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 1b28c16b21d320a236ee8d451625ed6480bb3fb4 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 8 Oct 2024 15:28:46 -0500 Subject: [PATCH 16/46] [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 fe4f73713ec8618cf4d5327c6e2799e873e8cda3 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 8 Oct 2024 15:35:19 -0500 Subject: [PATCH 17/46] [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 28e3e722544cfff58634ebaa67e502084dfd9e88 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 9 Oct 2024 19:52:04 -0500 Subject: [PATCH 18/46] [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 aa8f8c207f7fd598b67d9f28ade6d43af568c9e1 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 9 Oct 2024 20:03:44 -0500 Subject: [PATCH 19/46] [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 97e988fa0adf86e6f7094c79f018fb44a351d354 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 19 Nov 2024 10:13:58 -0600 Subject: [PATCH 20/46] [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 845878737c2af6e3c7de21f0ce06f18a0f13a765 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 19 Nov 2024 12:28:06 -0600 Subject: [PATCH 21/46] [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 532d2f7a9b03319b7f998bcad93a24bf5acf0562 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 19 Nov 2024 12:34:50 -0600 Subject: [PATCH 22/46] [AB#1669514] driver test? --- driver_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/driver_test.go b/driver_test.go index cfb8501f1..f3aa5871b 100644 --- a/driver_test.go +++ b/driver_test.go @@ -959,6 +959,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 bdbab902d36b429f47c56020a01a19d5c36552cd Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 19 Nov 2024 16:07:10 -0600 Subject: [PATCH 23/46] [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 f3aa5871b..c630cd547 100644 --- a/driver_test.go +++ b/driver_test.go @@ -18,6 +18,8 @@ import ( "syscall" "testing" "time" + + googleUUID "github.com/google/uuid" ) var ( @@ -971,10 +973,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 { @@ -987,7 +989,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 393eb56dd444de343d8c6cbef43b02fc300c8efc Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 19 Nov 2024 16:14:44 -0600 Subject: [PATCH 24/46] [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 e7e805cda5d462bb41fe55f85b759fe56a5b7b9c Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 20 Nov 2024 09:43:43 -0600 Subject: [PATCH 25/46] [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 c630cd547..844a0cba7 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()) } @@ -973,10 +971,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 { @@ -989,7 +987,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 dd8010056ef60bde085f1c26ab5d294b1d36b6c7 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 20 Nov 2024 10:26:01 -0600 Subject: [PATCH 26/46] [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 844a0cba7..277290d5d 100644 --- a/driver_test.go +++ b/driver_test.go @@ -959,6 +959,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 bf927a48ceffc05862cfb0ecbf9403d2577b6fe2 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 20 Nov 2024 12:56:06 -0600 Subject: [PATCH 27/46] [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 277290d5d..54366876e 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 16d2c235ae1a1115d2bff22a9a323dcb0ff1e015 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 20 Nov 2024 12:58:29 -0600 Subject: [PATCH 28/46] [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 54366876e..2b7b88fa8 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 5a71ba0cf3ca011d6e94811ef5c7c1da325c2cfd Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 20 Nov 2024 15:01:42 -0600 Subject: [PATCH 29/46] [AB#1669514] bump checkout; fix typo in yaml --- .github/workflows/build-test.yml | 34 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index f91f80cf3..6d6216576 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 @@ -34,7 +34,7 @@ jobs: - name: Setup go uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version-file: './go.mod' - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: From cf0c850ebe25f7d46a7741228c4c45508639ce63 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Wed, 20 Nov 2024 15:36:49 -0600 Subject: [PATCH 30/46] [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 2742ee70ad5d2a9f1e657a04b4014b758a96c889 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 21 Nov 2024 09:27:42 -0600 Subject: [PATCH 31/46] [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 d3b224ac30956c860dbfb6a5b85307b5192ee770 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 21 Nov 2024 09:30:00 -0600 Subject: [PATCH 32/46] [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 dd72c7c338c7147df413ac96ed24d9bd46831f19 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 21 Nov 2024 09:39:03 -0600 Subject: [PATCH 33/46] [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 2b7b88fa8..c1a0b3572 100644 --- a/driver_test.go +++ b/driver_test.go @@ -1023,7 +1023,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 a15a02a2f609c4b1d0602f698f4c59fbf2d8c222 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 21 Nov 2024 14:12:23 -0600 Subject: [PATCH 34/46] [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 c1a0b3572..dc04136fa 100644 --- a/driver_test.go +++ b/driver_test.go @@ -1024,18 +1024,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 c2dd9c030c17d0861fcf7f5cd684315990c0a950 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Thu, 21 Nov 2024 18:03:37 -0600 Subject: [PATCH 35/46] [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 a1137f8da635693396d9bb99279aa038a6397be4 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 22 Nov 2024 09:38:45 -0600 Subject: [PATCH 36/46] [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 9f4877c3c2e309a3db9cab1d4055a237edd55066 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 22 Nov 2024 09:39:16 -0600 Subject: [PATCH 37/46] 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 b14e863c5d6341e21b163efbbcaf49279a3b1f47 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 22 Nov 2024 15:09:45 -0600 Subject: [PATCH 38/46] [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 a9416a5b82679e3b3a6348b70baf0a1e7fe3ca39 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 22 Nov 2024 15:15:06 -0600 Subject: [PATCH 39/46] [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 f8298459e54b04bef45503923f86ab2aa5b6476e Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 22 Nov 2024 15:37:37 -0600 Subject: [PATCH 40/46] [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]) } }) From f829ed9f25b9f0c0dbbc2831d65a7ddb7a741cdb Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Mon, 2 Dec 2024 09:55:24 -0600 Subject: [PATCH 41/46] [AB#1669514] fix errors; tests will still fail --- bindings_test.go | 5 ++++- structured_type_read_test.go | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bindings_test.go b/bindings_test.go index 3cfba218c..c12c15bad 100644 --- a/bindings_test.go +++ b/bindings_test.go @@ -406,7 +406,10 @@ func TestBulkArrayBindingUUID(t *testing.T) { assertNilF(t, rows.Close()) } - dbt.exec(deleteTableSQL) + _, err := dbt.exec(deleteTableSQL) + if err != nil { + t.Logf("failed to drop table. err: %s", err) + } }) dbt.mustExec(createTable) diff --git a/structured_type_read_test.go b/structured_type_read_test.go index b9f3222e2..e5c76cddb 100644 --- a/structured_type_read_test.go +++ b/structured_type_read_test.go @@ -237,7 +237,7 @@ func TestObjectWithAllTypesAsObject(t *testing.T) { dbt.mustExec("ALTER SESSION SET TIMEZONE = 'Europe/Warsaw'") forAllStructureTypeFormats(dbt, func(t *testing.T, format string) { 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)) + 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 @@ -466,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, '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)") + 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 @@ -694,7 +694,7 @@ func TestObjectWithAllTypesSimpleScanNullable(t *testing.T) { }) t.Run("not null", func(t *testing.T) { 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)) + 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 From e508b9e8a7e02ff4961e1b29718269cc95192199 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Mon, 2 Dec 2024 11:56:25 -0600 Subject: [PATCH 42/46] [AB#1669514] type assertions on casts in tests to avoid panics --- structured_type_read_test.go | 44 +++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/structured_type_read_test.go b/structured_type_read_test.go index e5c76cddb..2801d0a73 100644 --- a/structured_type_read_test.go +++ b/structured_type_read_test.go @@ -40,7 +40,11 @@ type objectWithAllTypes struct { } func (o *objectWithAllTypes) Scan(val any) error { - st := val.(StructuredObject) + st, ok := val.(StructuredObject) + if !ok { + return fmt.Errorf("expected StructuredObject, got %T", val) + } + var err error if o.s, err = st.GetString("s"); err != nil { return err @@ -193,7 +197,11 @@ type simpleObject struct { } func (so *simpleObject) Scan(val any) error { - st := val.(StructuredObject) + st, ok := val.(StructuredObject) + if !ok { + return fmt.Errorf("expected StructuredObject, got %T", val) + } + var err error if so.s, err = st.GetString("s"); err != nil { return err @@ -319,7 +327,11 @@ type objectWithAllTypesNullable struct { } func (o *objectWithAllTypesNullable) Scan(val any) error { - st := val.(StructuredObject) + st, ok := val.(StructuredObject) + if !ok { + return fmt.Errorf("expected StructuredObject, got %T", val) + } + var err error if o.s, err = st.GetNullString("s"); err != nil { return err @@ -492,7 +504,7 @@ func TestObjectWithAllTypesNullable(t *testing.T) { }) t.Run("not null", func(t *testing.T) { 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)) + 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 @@ -553,7 +565,11 @@ type objectWithAllTypesSimpleScan struct { } func (so *objectWithAllTypesSimpleScan) Scan(val any) error { - st := val.(StructuredObject) + st, ok := val.(StructuredObject) + if !ok { + return fmt.Errorf("expected StructuredObject, got %T", val) + } + return st.ScanTo(so) } @@ -651,7 +667,11 @@ type objectWithAllTypesNullableSimpleScan struct { } func (o *objectWithAllTypesNullableSimpleScan) Scan(val any) error { - st := val.(StructuredObject) + st, ok := val.(StructuredObject) + if !ok { + return fmt.Errorf("expected StructuredObject, got %T", val) + } + return st.ScanTo(o) } @@ -737,7 +757,11 @@ type objectWithCustomNameAndIgnoredField struct { } func (o *objectWithCustomNameAndIgnoredField) Scan(val any) error { - st := val.(StructuredObject) + st, ok := val.(StructuredObject) + if !ok { + return fmt.Errorf("expected StructuredObject, got %T", val) + } + return st.ScanTo(o) } @@ -1839,7 +1863,11 @@ type HigherPrecisionStruct struct { } func (hps *HigherPrecisionStruct) Scan(val any) error { - st := val.(StructuredObject) + st, ok := val.(StructuredObject) + if !ok { + return fmt.Errorf("expected StructuredObject, got %T", val) + } + var err error if hps.i, err = st.GetBigInt("i"); err != nil { return err From ae1fcabd1ed1ffe362387280f64158a75f6566f4 Mon Sep 17 00:00:00 2001 From: Piotr Fus Date: Wed, 4 Dec 2024 14:05:05 +0100 Subject: [PATCH 43/46] [AB#1669514] Fixes --- converter.go | 3 +++ driver_test.go | 13 ++++++------- structured_type.go | 5 +++++ structured_type_read_test.go | 20 ++++++++------------ structured_type_write_test.go | 16 ++++------------ 5 files changed, 26 insertions(+), 31 deletions(-) diff --git a/converter.go b/converter.go index d6d40c1a7..5ebda883e 100644 --- a/converter.go +++ b/converter.go @@ -251,6 +251,9 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri 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 { + if tsmode == objectType || tsmode == arrayType || tsmode == sliceType { + return bindingValue{&strVal, jsonFormatStr, nil}, nil + } return bindingValue{&strVal, "", nil}, nil } } diff --git a/driver_test.go b/driver_test.go index dc04136fa..d6245653c 100644 --- a/driver_test.go +++ b/driver_test.go @@ -973,6 +973,9 @@ func newTestUUID() testUUID { } func parseTestUUID(str string) testUUID { + if str == "" { + return testUUID{} + } return testUUID{ParseUUID(str)} } @@ -1023,19 +1026,15 @@ func (uuid testUUID) Value() (driver.Value, error) { } func TestUUID(t *testing.T) { - t.Run("Basic Type", func(t *testing.T) { - testUuid(t, false, false) - }) - t.Run("JSON", func(t *testing.T) { - testUuid(t, true, false) + testUUIDWithFormat(t, true, false) }) t.Run("Arrow", func(t *testing.T) { - testUuid(t, false, true) + testUUIDWithFormat(t, false, true) }) } -func testUuid(t *testing.T, json, arrow bool) { +func testUUIDWithFormat(t *testing.T, json, arrow bool) { runDBTest(t, func(dbt *DBTest) { if json { dbt.mustExec(forceJSON) diff --git a/structured_type.go b/structured_type.go index 7fdf174fa..9df2f8cc4 100644 --- a/structured_type.go +++ b/structured_type.go @@ -3,6 +3,7 @@ package gosnowflake import ( "context" "database/sql" + "database/sql/driver" "encoding/hex" "encoding/json" "errors" @@ -450,6 +451,10 @@ func buildSowcFromType(params map[string]*string, typ reflect.Type) (*structured if err := childSowc.WriteNullableStruct(fieldName, nil, field.Type); err != nil { return nil, err } + } else if t.Implements(reflect.TypeOf((*driver.Valuer)(nil)).Elem()) { + if err := childSowc.WriteNullString(fieldName, sql.NullString{}); err != nil { + return nil, err + } } else { return nil, fmt.Errorf("field %s has unsupported type", field.Name) } diff --git a/structured_type_read_test.go b/structured_type_read_test.go index 2801d0a73..69406bb56 100644 --- a/structured_type_read_test.go +++ b/structured_type_read_test.go @@ -480,7 +480,7 @@ func TestObjectWithAllTypesNullable(t *testing.T) { 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, '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() + assertTrueF(t, rows.Next()) var ignore sql.NullInt32 var res objectWithAllTypesNullable err := rows.Scan(&ignore, &res) @@ -501,10 +501,11 @@ func TestObjectWithAllTypesNullable(t *testing.T) { assertEqualE(t, res.ntz, sql.NullTime{Valid: false}) var so *simpleObject assertDeepEqualE(t, res.so, so) + assertEqualE(t, res.uuid, testUUID{}) }) t.Run("not null", func(t *testing.T) { - 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)) + uuid := 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)", uuid)) defer rows.Close() rows.Next() var ignore sql.NullInt32 @@ -535,7 +536,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()) + assertEqualE(t, res.uuid.String(), uuid.String()) }) }) }) @@ -561,7 +562,6 @@ type objectWithAllTypesSimpleScan struct { SArr []string F64Arr []float64 SomeMap map[string]bool - UUID testUUID } func (so *objectWithAllTypesSimpleScan) Scan(val any) error { @@ -613,7 +613,6 @@ 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()) }) }) } @@ -663,7 +662,6 @@ type objectWithAllTypesNullableSimpleScan struct { SArr []string F64Arr []float64 SomeMap map[string]bool - UUID testUUID } func (o *objectWithAllTypesNullableSimpleScan) Scan(val any) error { @@ -687,7 +685,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, '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)") + 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))") defer rows.Close() rows.Next() var ignore sql.NullInt32 @@ -710,11 +708,10 @@ 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) { - 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)) + uuid := 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)", uuid)) defer rows.Close() rows.Next() var ignore sql.NullInt32 @@ -745,7 +742,6 @@ 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 a9135f6c0..31720124e 100644 --- a/structured_type_write_test.go +++ b/structured_type_write_test.go @@ -190,7 +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) + assertEqualE(t, res.uuid.String(), o.uuid.String()) }) } @@ -254,7 +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) + assertEqualE(t, res.uuid.String(), o.uuid.String()) }) t.Run("null", func(t *testing.T) { o := &objectWithAllTypesNullable{ @@ -275,7 +275,6 @@ func TestBindingObjectWithNullableFieldsWithSchema(t *testing.T) { sArr: nil, f64Arr: nil, someMap: nil, - 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) @@ -304,7 +303,6 @@ 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) }) }) } @@ -314,7 +312,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), 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 TIMESTAMP_LTZ, tz TIMESTAMP_TZ, ntz TIMESTAMP_NTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN)))") defer func() { dbt.mustExec("DROP TABLE IF EXISTS test_object_binding") }() @@ -341,7 +339,6 @@ func TestBindingObjectWithSchemaSimpleWrite(t *testing.T) { SArr: []string{"a", "b"}, F64Arr: []float64{1.1, 2.2}, SomeMap: map[string]bool{"a": true, "b": false}, - 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) @@ -372,7 +369,6 @@ 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) }) } @@ -382,7 +378,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), 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, tz TIMESTAMPTZ, ntz TIMESTAMPNTZ, so OBJECT(s VARCHAR, i INTEGER), sArr ARRAY(VARCHAR), f64Arr ARRAY(DOUBLE), someMap MAP(VARCHAR, BOOLEAN)))") defer func() { dbt.mustExec("DROP TABLE IF EXISTS test_object_binding") }() @@ -408,7 +404,6 @@ func TestBindingObjectWithNullableFieldsWithSchemaSimpleWrite(t *testing.T) { SArr: []string{"a", "b"}, F64Arr: []float64{1.1, 2.2}, SomeMap: map[string]bool{"a": true, "b": false}, - 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) @@ -437,7 +432,6 @@ 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{ @@ -458,7 +452,6 @@ func TestBindingObjectWithNullableFieldsWithSchemaSimpleWrite(t *testing.T) { SArr: nil, F64Arr: nil, SomeMap: nil, - 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) @@ -487,7 +480,6 @@ 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) }) }) } From 0a002c8a7862c6099a125e23eff92b0b53ccd70f Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 6 Dec 2024 08:41:04 -0600 Subject: [PATCH 44/46] [AB#1669514] move json transform check to function to keep it in a single place --- converter.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/converter.go b/converter.go index 5ebda883e..7d56f1352 100644 --- a/converter.go +++ b/converter.go @@ -80,9 +80,13 @@ func isInterfaceArrayBinding(t interface{}) bool { } } +func isJsonFormatType(tsmode snowflakeType) bool { + return tsmode == objectType || tsmode == arrayType || tsmode == sliceType +} + // goTypeToSnowflake translates Go data type to Snowflake data type. func goTypeToSnowflake(v driver.Value, tsmode snowflakeType) snowflakeType { - if tsmode == objectType || tsmode == arrayType || tsmode == sliceType { + if isJsonFormatType(tsmode) { return tsmode } if v == nil { @@ -239,8 +243,9 @@ func snowflakeTypeToGoForMaps[K comparable](ctx context.Context, valueMetadata f // in queries. func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*string) (bindingValue, error) { logger.Debugf("TYPE: %v, %v", reflect.TypeOf(v), reflect.ValueOf(v)) + isJsonFormat := isJsonFormatType(tsmode) if v == nil { - if tsmode == objectType || tsmode == arrayType || tsmode == sliceType { + if isJsonFormat { return bindingValue{nil, jsonFormatStr, nil}, nil } return bindingValue{nil, "", nil}, nil @@ -251,7 +256,7 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri 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 { - if tsmode == objectType || tsmode == arrayType || tsmode == sliceType { + if isJsonFormat { return bindingValue{&strVal, jsonFormatStr, nil}, nil } return bindingValue{&strVal, "", nil}, nil @@ -271,7 +276,7 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri return bindingValue{&s, "", nil}, nil case reflect.String: s := v1.String() - if tsmode == objectType || tsmode == arrayType || tsmode == sliceType { + if isJsonFormat { return bindingValue{&s, jsonFormatStr, nil}, nil } return bindingValue{&s, "", nil}, nil @@ -816,7 +821,7 @@ func structValueToString(v driver.Value, tsmode snowflakeType, params map[string return bindingValue{&s, "", nil}, nil case sql.NullString: fmt := "" - if tsmode == objectType || tsmode == arrayType || tsmode == sliceType { + if isJsonFormatType(tsmode) { fmt = jsonFormatStr } if !typedVal.Valid { From e89206f1f835f9743e792cc2e8834c65b562127e Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Fri, 6 Dec 2024 09:50:35 -0600 Subject: [PATCH 45/46] [AB#1669514] add to readme and update it for debug mode --- README.md | 22 +++++++++++++--------- parameters.json.local | 3 ++- parameters.json.tmpl | 3 ++- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 719a2fdf8..290bbe7c3 100644 --- a/README.md +++ b/README.md @@ -56,13 +56,13 @@ This driver currently does not support GCP regional endpoints. Please ensure tha Snowflake provides a set of sample programs to test with. Set the environment variable ``$GOPATH`` to the top directory of your workspace, e.g., ``~/go`` and make certain to include ``$GOPATH/bin`` in the environment variable ``$PATH``. Run the ``make`` command to build all sample programs. -``` +```sh make install ``` In the following example, the program ``select1.go`` is built and installed in ``$GOPATH/bin`` and can be run from the command line: -``` +```sh SNOWFLAKE_TEST_ACCOUNT= \ SNOWFLAKE_TEST_USER= \ SNOWFLAKE_TEST_PASSWORD= \ @@ -79,7 +79,7 @@ The developer notes are hosted with the source code on [GitHub](https://github.c Set the Snowflake connection info in ``parameters.json``: -``` +```json { "testconnection": { "SNOWFLAKE_TEST_USER": "", @@ -88,21 +88,25 @@ Set the Snowflake connection info in ``parameters.json``: "SNOWFLAKE_TEST_WAREHOUSE": "", "SNOWFLAKE_TEST_DATABASE": "", "SNOWFLAKE_TEST_SCHEMA": "", - "SNOWFLAKE_TEST_ROLE": "" + "SNOWFLAKE_TEST_ROLE": "", + "SNOWFLAKE_TEST_DEBUG": "false" } } ``` Install [jq](https://stedolan.github.io/jq) so that the parameters can get parsed correctly, and run ``make test`` in your Go development environment: -``` +```sh make test ``` +### Setting debug mode during tests +This is for debugging Large SQL statements (greater than 300 characters). If you want to enable debug mode, set `SNOWFLAKE_TEST_DEBUG` to `true` in `parameters.json`, or export it in your shell instance. + ## customizing Logging Tags If you would like to ensure that certain tags are always present in the logs, `RegisterClientLogContextHook` can be used in your init function. See example below. -``` +```go import "github.com/snowflakedb/gosnowflake" func init() { @@ -116,7 +120,7 @@ func init() { ## Setting Log Level If you want to change the log level, `SetLogLevel` can be used in your init function like this: -``` +```go import "github.com/snowflakedb/gosnowflake" func init() { @@ -138,7 +142,7 @@ The following is a list of options you can pass in to set the level from least t Configure your testing environment as described above and run ``make cov``. The coverage percentage will be printed on the console when the testing completes. -``` +```sh make cov ``` @@ -146,7 +150,7 @@ For more detailed analysis, results are printed to ``coverage.txt`` in the proje To read the coverage report, run: -``` +```sh go tool cover -html=coverage.txt ``` diff --git a/parameters.json.local b/parameters.json.local index 8b526b5a1..6e4635374 100644 --- a/parameters.json.local +++ b/parameters.json.local @@ -9,6 +9,7 @@ "SNOWFLAKE_TEST_WAREHOUSE": "regress", "SNOWFLAKE_TEST_DATABASE": "testdb", "SNOWFLAKE_TEST_SCHEMA": "testschema", - "SNOWFLAKE_TEST_ROLE": "sysadmin" + "SNOWFLAKE_TEST_ROLE": "sysadmin", + "SNOWFLAKE_TEST_DEBUG": "false" } } diff --git a/parameters.json.tmpl b/parameters.json.tmpl index 8448f605d..19ec33bd5 100644 --- a/parameters.json.tmpl +++ b/parameters.json.tmpl @@ -6,6 +6,7 @@ "SNOWFLAKE_TEST_WAREHOUSE": "testwarehouse", "SNOWFLAKE_TEST_DATABASE": "testdatabase", "SNOWFLAKE_TEST_SCHEMA": "testschema", - "SNOWFLAKE_TEST_ROLE": "testrole" + "SNOWFLAKE_TEST_ROLE": "testrole", + "SNOWFLAKE_TEST_DEBUG": "false", } } From 3830f567b9cd206f278015dcdcefd83e5eef8191 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Mon, 9 Dec 2024 07:59:52 -0600 Subject: [PATCH 46/46] [AB#1669514] Linter fix --- converter.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/converter.go b/converter.go index 7d56f1352..48570f284 100644 --- a/converter.go +++ b/converter.go @@ -80,13 +80,13 @@ func isInterfaceArrayBinding(t interface{}) bool { } } -func isJsonFormatType(tsmode snowflakeType) bool { +func isJSONFormatType(tsmode snowflakeType) bool { return tsmode == objectType || tsmode == arrayType || tsmode == sliceType } // goTypeToSnowflake translates Go data type to Snowflake data type. func goTypeToSnowflake(v driver.Value, tsmode snowflakeType) snowflakeType { - if isJsonFormatType(tsmode) { + if isJSONFormatType(tsmode) { return tsmode } if v == nil { @@ -243,9 +243,9 @@ func snowflakeTypeToGoForMaps[K comparable](ctx context.Context, valueMetadata f // in queries. func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*string) (bindingValue, error) { logger.Debugf("TYPE: %v, %v", reflect.TypeOf(v), reflect.ValueOf(v)) - isJsonFormat := isJsonFormatType(tsmode) + isJSONFormat := isJSONFormatType(tsmode) if v == nil { - if isJsonFormat { + if isJSONFormat { return bindingValue{nil, jsonFormatStr, nil}, nil } return bindingValue{nil, "", nil}, nil @@ -256,7 +256,7 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri 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 { - if isJsonFormat { + if isJSONFormat { return bindingValue{&strVal, jsonFormatStr, nil}, nil } return bindingValue{&strVal, "", nil}, nil @@ -276,7 +276,7 @@ func valueToString(v driver.Value, tsmode snowflakeType, params map[string]*stri return bindingValue{&s, "", nil}, nil case reflect.String: s := v1.String() - if isJsonFormat { + if isJSONFormat { return bindingValue{&s, jsonFormatStr, nil}, nil } return bindingValue{&s, "", nil}, nil @@ -821,7 +821,7 @@ func structValueToString(v driver.Value, tsmode snowflakeType, params map[string return bindingValue{&s, "", nil}, nil case sql.NullString: fmt := "" - if isJsonFormatType(tsmode) { + if isJSONFormatType(tsmode) { fmt = jsonFormatStr } if !typedVal.Valid {