From 7ad7a5215fe06fa2ad60c078947c492dc1626ecf Mon Sep 17 00:00:00 2001 From: Piotr Fus Date: Wed, 4 Dec 2024 14:05:05 +0100 Subject: [PATCH] Fixes --- converter.go | 3 +++ driver_test.go | 3 +++ structured_type.go | 5 +++++ structured_type_read_test.go | 20 ++++++++------------ structured_type_write_test.go | 16 ++++------------ 5 files changed, 23 insertions(+), 24 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 394c4c76e..9d671d81d 100644 --- a/driver_test.go +++ b/driver_test.go @@ -876,6 +876,9 @@ func newTestUUID() testUUID { } func parseTestUUID(str string) testUUID { + if str == "" { + return testUUID{} + } return testUUID{ParseUUID(str)} } 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) }) }) }