Skip to content

Commit d61c1b0

Browse files
authored
SNOW-1437452 Support semistructured types bindings (#1135)
1 parent 7f760af commit d61c1b0

6 files changed

+182
-7
lines changed

assert_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"bytes"
77
"fmt"
88
"reflect"
9+
"regexp"
910
"runtime"
1011
"strings"
1112
"testing"
@@ -35,6 +36,10 @@ func assertEqualF(t *testing.T, actual any, expected any, descriptions ...string
3536
fatalOnNonEmpty(t, validateEqual(actual, expected, descriptions...))
3637
}
3738

39+
func assertEqualIgnoringWhitespaceE(t *testing.T, actual string, expected string, descriptions ...string) {
40+
errorOnNonEmpty(t, validateEqualIgnoringWhitespace(actual, expected, descriptions...))
41+
}
42+
3843
func assertDeepEqualE(t *testing.T, actual any, expected any, descriptions ...string) {
3944
errorOnNonEmpty(t, validateDeepEqual(actual, expected, descriptions...))
4045
}
@@ -123,6 +128,22 @@ func validateEqual(actual any, expected any, descriptions ...string) string {
123128
return fmt.Sprintf("expected \"%s\" to be equal to \"%s\" but was not. %s", actual, expected, desc)
124129
}
125130

131+
func removeWhitespaces(s string) string {
132+
pattern, err := regexp.Compile(`\s+`)
133+
if err != nil {
134+
panic(err)
135+
}
136+
return pattern.ReplaceAllString(s, "")
137+
}
138+
139+
func validateEqualIgnoringWhitespace(actual string, expected string, descriptions ...string) string {
140+
if removeWhitespaces(expected) == removeWhitespaces(actual) {
141+
return ""
142+
}
143+
desc := joinDescriptions(descriptions...)
144+
return fmt.Sprintf("expected \"%s\" to be equal to \"%s\" but was not. %s", actual, expected, desc)
145+
}
146+
126147
func validateDeepEqual(actual any, expected any, descriptions ...string) string {
127148
if reflect.DeepEqual(actual, expected) {
128149
return ""

datatype.go

+6
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ func dataTypeMode(v driver.Value) (tsmode snowflakeType, err error) {
124124
tsmode = timestampTzType
125125
case bytes.Equal(bd, DataTypeBinary):
126126
tsmode = binaryType
127+
case bytes.Equal(bd, DataTypeObject):
128+
tsmode = objectType
129+
case bytes.Equal(bd, DataTypeArray):
130+
tsmode = arrayType
131+
case bytes.Equal(bd, DataTypeVariant):
132+
tsmode = variantType
127133
default:
128134
return nullType, fmt.Errorf(errMsgInvalidByteArray, v)
129135
}

datatype_test.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,21 @@ import (
88
"testing"
99
)
1010

11-
type tcDataTypeMode struct {
12-
tp driver.Value
13-
tmode snowflakeType
14-
err error
15-
}
16-
1711
func TestDataTypeMode(t *testing.T) {
18-
var testcases = []tcDataTypeMode{
12+
var testcases = []struct {
13+
tp driver.Value
14+
tmode snowflakeType
15+
err error
16+
}{
1917
{tp: DataTypeTimestampLtz, tmode: timestampLtzType, err: nil},
2018
{tp: DataTypeTimestampNtz, tmode: timestampNtzType, err: nil},
2119
{tp: DataTypeTimestampTz, tmode: timestampTzType, err: nil},
2220
{tp: DataTypeDate, tmode: dateType, err: nil},
2321
{tp: DataTypeTime, tmode: timeType, err: nil},
2422
{tp: DataTypeBinary, tmode: binaryType, err: nil},
23+
{tp: DataTypeObject, tmode: objectType, err: nil},
24+
{tp: DataTypeArray, tmode: arrayType, err: nil},
25+
{tp: DataTypeVariant, tmode: variantType, err: nil},
2526
{tp: DataTypeFixed, tmode: fixedType,
2627
err: fmt.Errorf(errMsgInvalidByteArray, DataTypeFixed)},
2728
{tp: DataTypeReal, tmode: realType,

doc.go

+18
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,22 @@ Example table definition:
431431
432432
The data not have any corresponding schema, so values in table may be slightly different.
433433
434+
### Semistructured types
435+
436+
Semistuctured variants, objects and arrays are always represented as strings for scanning:
437+
438+
rows, err := db.Query("SELECT {'a': 'b'}::OBJECT")
439+
// handle error
440+
defer rows.Close()
441+
rows.Next()
442+
var v string
443+
err := rows.Scan(&v)
444+
445+
When inserting, a marker indicating correct type must be used, for example:
446+
447+
db.Exec("CREATE TABLE test_object_binding (obj OBJECT)")
448+
db.Exec("INSERT INTO test_object_binding SELECT (?)", DataTypeObject, "{'s': 'some string'}")
449+
434450
### Structured types
435451
436452
Structured types differentiate from semistructured types by having specific schema.
@@ -484,6 +500,8 @@ If you want to scan array of structs, you have to use a helper function ScanArra
484500
var res map[string]*simpleObject
485501
err := rows.Scan(ScanMapOfScanners(&res))
486502
503+
## Using higher precision numbers
504+
487505
The following example shows how to retrieve very large values using the math/big
488506
package. This example retrieves a large INTEGER value to an interface and then
489507
extracts a big.Int value from that interface. If the value fits into an int64,
File renamed without changes.

structured_type_write_test.go

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package gosnowflake
2+
3+
import (
4+
"database/sql"
5+
"testing"
6+
)
7+
8+
func TestBindingVariant(t *testing.T) {
9+
runDBTest(t, func(dbt *DBTest) {
10+
skipStructuredTypesTestsOnGHActions(t)
11+
dbt.mustExec("CREATE TABLE test_variant_binding (var VARIANT)")
12+
defer func() {
13+
dbt.mustExec("DROP TABLE IF EXISTS test_variant_binding")
14+
}()
15+
dbt.mustExec("ALTER SESSION SET ENABLE_OBJECT_TYPED_BINDS = true")
16+
dbt.mustExec("ALTER SESSION SET ENABLE_STRUCTURED_TYPES_IN_BINDS = Enable")
17+
dbt.mustExec("INSERT INTO test_variant_binding SELECT (?)", DataTypeVariant, nil)
18+
dbt.mustExec("INSERT INTO test_variant_binding SELECT (?)", DataTypeVariant, sql.NullString{Valid: false})
19+
dbt.mustExec("INSERT INTO test_variant_binding SELECT (?)", DataTypeVariant, "{'s': 'some string'}")
20+
dbt.mustExec("INSERT INTO test_variant_binding SELECT (?)", DataTypeVariant, sql.NullString{Valid: true, String: "{'s': 'some string2'}"})
21+
rows := dbt.mustQuery("SELECT * FROM test_variant_binding")
22+
defer rows.Close()
23+
var res sql.NullString
24+
25+
assertTrueF(t, rows.Next())
26+
err := rows.Scan(&res)
27+
assertNilF(t, err)
28+
assertFalseF(t, res.Valid)
29+
30+
assertTrueF(t, rows.Next())
31+
err = rows.Scan(&res)
32+
assertNilF(t, err)
33+
assertFalseF(t, res.Valid)
34+
35+
assertTrueF(t, rows.Next())
36+
err = rows.Scan(&res)
37+
assertNilF(t, err)
38+
assertTrueE(t, res.Valid)
39+
assertEqualIgnoringWhitespaceE(t, res.String, `{"s": "some string"}`)
40+
41+
assertTrueF(t, rows.Next())
42+
err = rows.Scan(&res)
43+
assertNilF(t, err)
44+
assertTrueE(t, res.Valid)
45+
assertEqualIgnoringWhitespaceE(t, res.String, `{"s": "some string2"}`)
46+
})
47+
}
48+
49+
func TestBindingObjectWithoutSchema(t *testing.T) {
50+
skipStructuredTypesTestsOnGHActions(t)
51+
runDBTest(t, func(dbt *DBTest) {
52+
dbt.mustExec("CREATE TABLE test_object_binding (obj OBJECT)")
53+
defer func() {
54+
dbt.mustExec("DROP TABLE IF EXISTS test_object_binding")
55+
}()
56+
dbt.mustExec("ALTER SESSION SET ENABLE_OBJECT_TYPED_BINDS = true")
57+
dbt.mustExec("ALTER SESSION SET ENABLE_STRUCTURED_TYPES_IN_BINDS = Enable")
58+
dbt.mustExec("INSERT INTO test_object_binding SELECT (?)", DataTypeObject, nil)
59+
dbt.mustExec("INSERT INTO test_object_binding SELECT (?)", DataTypeObject, sql.NullString{Valid: false})
60+
dbt.mustExec("INSERT INTO test_object_binding SELECT (?)", DataTypeObject, "{'s': 'some string'}")
61+
dbt.mustExec("INSERT INTO test_object_binding SELECT (?)", DataTypeObject, sql.NullString{Valid: true, String: "{'s': 'some string2'}"})
62+
rows := dbt.mustQuery("SELECT * FROM test_object_binding")
63+
defer rows.Close()
64+
var res sql.NullString
65+
66+
assertTrueF(t, rows.Next())
67+
err := rows.Scan(&res)
68+
assertNilF(t, err)
69+
assertFalseF(t, res.Valid)
70+
71+
assertTrueF(t, rows.Next())
72+
err = rows.Scan(&res)
73+
assertNilF(t, err)
74+
assertFalseF(t, res.Valid)
75+
76+
assertTrueF(t, rows.Next())
77+
err = rows.Scan(&res)
78+
assertNilF(t, err)
79+
assertTrueE(t, res.Valid)
80+
assertEqualIgnoringWhitespaceE(t, res.String, `{"s": "some string"}`)
81+
82+
assertTrueF(t, rows.Next())
83+
err = rows.Scan(&res)
84+
assertNilF(t, err)
85+
assertTrueE(t, res.Valid)
86+
assertEqualIgnoringWhitespaceE(t, res.String, `{"s": "some string2"}`)
87+
})
88+
}
89+
90+
func TestBindingArrayWithoutSchema(t *testing.T) {
91+
skipStructuredTypesTestsOnGHActions(t)
92+
runDBTest(t, func(dbt *DBTest) {
93+
dbt.mustExec("CREATE TABLE test_array_binding (arr ARRAY)")
94+
defer func() {
95+
dbt.mustExec("DROP TABLE IF EXISTS test_array_binding")
96+
}()
97+
dbt.mustExec("ALTER SESSION SET ENABLE_OBJECT_TYPED_BINDS = true")
98+
dbt.mustExec("ALTER SESSION SET ENABLE_STRUCTURED_TYPES_IN_BINDS = Enable")
99+
dbt.mustExec("INSERT INTO test_array_binding SELECT (?)", DataTypeArray, nil)
100+
dbt.mustExec("INSERT INTO test_array_binding SELECT (?)", DataTypeArray, sql.NullString{Valid: false})
101+
dbt.mustExec("INSERT INTO test_array_binding SELECT (?)", DataTypeArray, "[1, 2, 3]")
102+
dbt.mustExec("INSERT INTO test_array_binding SELECT (?)", DataTypeArray, sql.NullString{Valid: true, String: "[1, 2, 3]"})
103+
rows := dbt.mustQuery("SELECT * FROM test_array_binding")
104+
defer rows.Close()
105+
var res sql.NullString
106+
107+
assertTrueF(t, rows.Next())
108+
err := rows.Scan(&res)
109+
assertNilF(t, err)
110+
assertFalseF(t, res.Valid)
111+
112+
assertTrueF(t, rows.Next())
113+
err = rows.Scan(&res)
114+
assertNilF(t, err)
115+
assertFalseF(t, res.Valid)
116+
117+
assertTrueF(t, rows.Next())
118+
err = rows.Scan(&res)
119+
assertNilF(t, err)
120+
assertTrueE(t, res.Valid)
121+
assertEqualIgnoringWhitespaceE(t, res.String, `[1, 2, 3]`)
122+
123+
assertTrueF(t, rows.Next())
124+
err = rows.Scan(&res)
125+
assertNilF(t, err)
126+
assertTrueE(t, res.Valid)
127+
assertEqualIgnoringWhitespaceE(t, res.String, `[1, 2, 3]`)
128+
})
129+
}

0 commit comments

Comments
 (0)