diff --git a/checksum_row_iterator_test.go b/checksum_row_iterator_test.go index 5d94a062..12281087 100644 --- a/checksum_row_iterator_test.go +++ b/checksum_row_iterator_test.go @@ -35,15 +35,24 @@ func TestUpdateChecksum(t *testing.T) { row1, err := spanner.NewRow( []string{ - "ColBool", "ColInt64", "ColFloat64", "ColNumeric", "ColString", "ColBytes", "ColDate", "ColTimestamp", - "ArrBool", "ArrInt64", "ArrFloat64", "ArrNumeric", "ArrString", "ArrBytes", "ArrDate", "ArrTimestamp", + "ColBool", "ColInt64", "ColFloat64", "ColNumeric", "ColString", "ColBytes", "ColDate", "ColTimestamp", "ColJson", + "ArrBool", "ArrInt64", "ArrFloat64", "ArrNumeric", "ArrString", "ArrBytes", "ArrDate", "ArrTimestamp", "ArrJson", }, []interface{}{ - true, int64(1), 3.14, numeric("6.626"), "test", []byte("testbytes"), civil.Date{Year: 2021, Month: 8, Day: 5}, time.Date(2021, 8, 5, 13, 19, 23, 123456789, time.UTC), + true, int64(1), 3.14, numeric("6.626"), "test", []byte("testbytes"), civil.Date{Year: 2021, Month: 8, Day: 5}, + time.Date(2021, 8, 5, 13, 19, 23, 123456789, time.UTC), + nullJson(true, `"key": "value", "other-key": ["value1", "value2"]}`), []bool{true, false}, []int64{1, 2}, []float64{3.14, 6.626}, []big.Rat{numeric("3.14"), numeric("6.626")}, []string{"test1", "test2"}, [][]byte{[]byte("testbytes1"), []byte("testbytes1")}, []civil.Date{{Year: 2021, Month: 8, Day: 5}, {Year: 2021, Month: 8, Day: 6}}, - []time.Time{time.Date(2021, 8, 5, 13, 19, 23, 123456789, time.UTC), time.Date(2021, 8, 6, 13, 19, 23, 123456789, time.UTC)}, + []time.Time{ + time.Date(2021, 8, 5, 13, 19, 23, 123456789, time.UTC), + time.Date(2021, 8, 6, 13, 19, 23, 123456789, time.UTC), + }, + []spanner.NullJSON{ + nullJson(true, `"key1": "value1", "other-key1": ["value1", "value2"]}`), + nullJson(true, `"key2": "value2", "other-key2": ["value1", "value2"]}`), + }, }, ) if err != nil { @@ -52,15 +61,24 @@ func TestUpdateChecksum(t *testing.T) { // row2 is different from row1 row2, err := spanner.NewRow( []string{ - "ColBool", "ColInt64", "ColFloat64", "ColNumeric", "ColString", "ColBytes", "ColDate", "ColTimestamp", - "ArrBool", "ArrInt64", "ArrFloat64", "ArrNumeric", "ArrString", "ArrBytes", "ArrDate", "ArrTimestamp", + "ColBool", "ColInt64", "ColFloat64", "ColNumeric", "ColString", "ColBytes", "ColDate", "ColTimestamp", "ColJson", + "ArrBool", "ArrInt64", "ArrFloat64", "ArrNumeric", "ArrString", "ArrBytes", "ArrDate", "ArrTimestamp", "ArrJson", }, []interface{}{ - true, int64(2), 6.626, numeric("3.14"), "test2", []byte("testbytes2"), civil.Date{Year: 2020, Month: 8, Day: 5}, time.Date(2020, 8, 5, 13, 19, 23, 123456789, time.UTC), + true, int64(2), 6.626, numeric("3.14"), "test2", []byte("testbytes2"), civil.Date{Year: 2020, Month: 8, Day: 5}, + time.Date(2020, 8, 5, 13, 19, 23, 123456789, time.UTC), + nullJson(true, `"key": "other-value", "other-key": ["other-value1", "other-value2"]}`), []bool{true, false}, []int64{1, 2}, []float64{3.14, 6.626}, []big.Rat{numeric("3.14"), numeric("6.626")}, []string{"test1_", "test2_"}, [][]byte{[]byte("testbytes1_"), []byte("testbytes1_")}, []civil.Date{{Year: 2020, Month: 8, Day: 5}, {Year: 2020, Month: 8, Day: 6}}, - []time.Time{time.Date(2020, 8, 5, 13, 19, 23, 123456789, time.UTC), time.Date(2020, 8, 6, 13, 19, 23, 123456789, time.UTC)}, + []time.Time{ + time.Date(2020, 8, 5, 13, 19, 23, 123456789, time.UTC), + time.Date(2020, 8, 6, 13, 19, 23, 123456789, time.UTC), + }, + []spanner.NullJSON{ + nullJson(true, `"key1": "other-value1", "other-key1": ["other-value1", "other-value2"]}`), + nullJson(true, `"key2": "other-value2", "other-key2": ["other-value1", "other-value2"]}`), + }, }, ) if err != nil { @@ -69,15 +87,24 @@ func TestUpdateChecksum(t *testing.T) { // row3 is equal to row1. row3, err := spanner.NewRow( []string{ - "ColBool", "ColInt64", "ColFloat64", "ColNumeric", "ColString", "ColBytes", "ColDate", "ColTimestamp", - "ArrBool", "ArrInt64", "ArrFloat64", "ArrNumeric", "ArrString", "ArrBytes", "ArrDate", "ArrTimestamp", + "ColBool", "ColInt64", "ColFloat64", "ColNumeric", "ColString", "ColBytes", "ColDate", "ColTimestamp", "ColJson", + "ArrBool", "ArrInt64", "ArrFloat64", "ArrNumeric", "ArrString", "ArrBytes", "ArrDate", "ArrTimestamp", "ArrJson", }, []interface{}{ - true, int64(1), 3.14, numeric("6.626"), "test", []byte("testbytes"), civil.Date{Year: 2021, Month: 8, Day: 5}, time.Date(2021, 8, 5, 13, 19, 23, 123456789, time.UTC), + true, int64(1), 3.14, numeric("6.626"), "test", []byte("testbytes"), civil.Date{Year: 2021, Month: 8, Day: 5}, + time.Date(2021, 8, 5, 13, 19, 23, 123456789, time.UTC), + nullJson(true, `"key": "value", "other-key": ["value1", "value2"]}`), []bool{true, false}, []int64{1, 2}, []float64{3.14, 6.626}, []big.Rat{numeric("3.14"), numeric("6.626")}, []string{"test1", "test2"}, [][]byte{[]byte("testbytes1"), []byte("testbytes1")}, []civil.Date{{Year: 2021, Month: 8, Day: 5}, {Year: 2021, Month: 8, Day: 6}}, - []time.Time{time.Date(2021, 8, 5, 13, 19, 23, 123456789, time.UTC), time.Date(2021, 8, 6, 13, 19, 23, 123456789, time.UTC)}, + []time.Time{ + time.Date(2021, 8, 5, 13, 19, 23, 123456789, time.UTC), + time.Date(2021, 8, 6, 13, 19, 23, 123456789, time.UTC), + }, + []spanner.NullJSON{ + nullJson(true, `"key1": "value1", "other-key1": ["value1", "value2"]}`), + nullJson(true, `"key2": "value2", "other-key2": ["value1", "value2"]}`), + }, }, ) if err != nil { @@ -138,15 +165,16 @@ func TestUpdateChecksumForNullValues(t *testing.T) { row, err := spanner.NewRow( []string{ - "ColBool", "ColInt64", "ColFloat64", "ColNumeric", "ColString", "ColBytes", "ColDate", "ColTimestamp", - "ArrBool", "ArrInt64", "ArrFloat64", "ArrNumeric", "ArrString", "ArrBytes", "ArrDate", "ArrTimestamp", + "ColBool", "ColInt64", "ColFloat64", "ColNumeric", "ColString", "ColBytes", "ColDate", "ColTimestamp", "ColJson", + "ArrBool", "ArrInt64", "ArrFloat64", "ArrNumeric", "ArrString", "ArrBytes", "ArrDate", "ArrTimestamp", "ArrJson", }, []interface{}{ spanner.NullBool{}, spanner.NullInt64{}, spanner.NullFloat64{}, spanner.NullNumeric{}, spanner.NullString{}, - []byte(nil), spanner.NullDate{}, spanner.NullTime{}, + []byte(nil), spanner.NullDate{}, spanner.NullTime{}, spanner.NullJSON{}, // Note: The following arrays all contain one NULL value. []spanner.NullBool{{}}, []spanner.NullInt64{{}}, []spanner.NullFloat64{{}}, []spanner.NullNumeric{{}}, []spanner.NullString{{}}, [][]byte{[]byte(nil)}, []spanner.NullDate{{}}, []spanner.NullTime{{}}, + []spanner.NullJSON{{}}, }, ) if err != nil { diff --git a/driver_with_mockserver_test.go b/driver_with_mockserver_test.go index 56110995..5c118f45 100644 --- a/driver_with_mockserver_test.go +++ b/driver_with_mockserver_test.go @@ -19,6 +19,7 @@ import ( "database/sql" "database/sql/driver" "encoding/base64" + "encoding/json" "fmt" "math/big" "reflect" @@ -323,6 +324,7 @@ func TestQueryWithAllTypes(t *testing.T) { AND ColNumeric=@numeric AND ColDate=@date AND ColTimestamp=@timestamp + AND ColJson=@json AND ColBoolArray=@boolArray AND ColStringArray=@stringArray AND ColBytesArray=@bytesArray @@ -330,7 +332,8 @@ func TestQueryWithAllTypes(t *testing.T) { AND ColFloatArray=@float64Array AND ColNumericArray=@numericArray AND ColDateArray=@dateArray - AND ColTimestampArray=@timestampArray` + AND ColTimestampArray=@timestampArray + AND ColJsonArray=@jsonArray` _ = server.TestSpanner.PutStatementResult( query, &testutil.StatementResult{ @@ -357,6 +360,7 @@ func TestQueryWithAllTypes(t *testing.T) { numeric("6.626"), civil.Date{Year: 2021, Month: 7, Day: 21}, ts, + nullJson(true, `{"key":"value","other-key":["value1","value2"]}`), []spanner.NullBool{{Valid: true, Bool: true}, {}, {Valid: true, Bool: false}}, []spanner.NullString{{Valid: true, StringVal: "test1"}, {}, {Valid: true, StringVal: "test2"}}, [][]byte{[]byte("testbytes1"), nil, []byte("testbytes2")}, @@ -365,6 +369,11 @@ func TestQueryWithAllTypes(t *testing.T) { []spanner.NullNumeric{nullNumeric(true, "3.14"), {}, nullNumeric(true, "10.01")}, []spanner.NullDate{{Valid: true, Date: civil.Date{Year: 2000, Month: 2, Day: 29}}, {}, {Valid: true, Date: civil.Date{Year: 2021, Month: 7, Day: 27}}}, []spanner.NullTime{{Valid: true, Time: ts1}, {}, {Valid: true, Time: ts2}}, + []spanner.NullJSON{ + nullJson(true, `{"key1": "value1", "other-key1": ["value1", "value2"]}`), + nullJson(false, ""), + nullJson(true, `{"key2": "value2", "other-key2": ["value1", "value2"]}`), + }, ) if err != nil { t.Fatal(err) @@ -380,6 +389,7 @@ func TestQueryWithAllTypes(t *testing.T) { var r spanner.NullNumeric var d spanner.NullDate var ts time.Time + var j spanner.NullJSON var bArray []spanner.NullBool var sArray []spanner.NullString var btArray [][]byte @@ -388,7 +398,8 @@ func TestQueryWithAllTypes(t *testing.T) { var rArray []spanner.NullNumeric var dArray []spanner.NullDate var tsArray []spanner.NullTime - err = rows.Scan(&b, &s, &bt, &i, &f, &r, &d, &ts, &bArray, &sArray, &btArray, &iArray, &fArray, &rArray, &dArray, &tsArray) + var jArray []spanner.NullJSON + err = rows.Scan(&b, &s, &bt, &i, &f, &r, &d, &ts, &j, &bArray, &sArray, &btArray, &iArray, &fArray, &rArray, &dArray, &tsArray, &jArray) if err != nil { t.Fatal(err) } @@ -416,6 +427,11 @@ func TestQueryWithAllTypes(t *testing.T) { if g, w := ts, time.Date(2021, 7, 21, 21, 7, 59, 339911800, time.UTC); g != w { t.Errorf("row value mismatch for timestamp\nGot: %v\nWant: %v", g, w) } + if !runsOnEmulator() { + if g, w := j, nullJson(true, `{"key":"value","other-key":["value1","value2"]}`); !cmp.Equal(g, w) { + t.Errorf("row value mismatch for json\nGot: %v\nWant: %v", g, w) + } + } if g, w := bArray, []spanner.NullBool{{Valid: true, Bool: true}, {}, {Valid: true, Bool: false}}; !cmp.Equal(g, w) { t.Errorf("row value mismatch for bool array\nGot: %v\nWant: %v", g, w) } @@ -440,6 +456,15 @@ func TestQueryWithAllTypes(t *testing.T) { if g, w := tsArray, []spanner.NullTime{{Valid: true, Time: ts1}, {}, {Valid: true, Time: ts2}}; !cmp.Equal(g, w) { t.Errorf("row value mismatch for timestamp array\nGot: %v\nWant: %v", g, w) } + if !runsOnEmulator() { + if g, w := jArray, []spanner.NullJSON{ + nullJson(true, `{"key1": "value1", "other-key1": ["value1", "value2"]}`), + nullJson(false, ""), + nullJson(true, `{"key2": "value2", "other-key2": ["value1", "value2"]}`), + }; !cmp.Equal(g, w) { + t.Errorf("row value mismatch for json array\nGot: %v\nWant: %v", g, w) + } + } } if rows.Err() != nil { t.Fatal(rows.Err()) @@ -450,10 +475,10 @@ func TestQueryWithAllTypes(t *testing.T) { t.Fatalf("sql requests count mismatch\nGot: %v\nWant: %v", g, w) } req := sqlRequests[0].(*sppb.ExecuteSqlRequest) - if g, w := len(req.ParamTypes), 16; g != w { + if g, w := len(req.ParamTypes), 18; g != w { t.Fatalf("param types length mismatch\nGot: %v\nWant: %v", g, w) } - if g, w := len(req.Params.Fields), 16; g != w { + if g, w := len(req.Params.Fields), 18; g != w { t.Fatalf("params length mismatch\nGot: %v\nWant: %v", g, w) } wantParams := []struct { @@ -502,6 +527,11 @@ func TestQueryWithAllTypes(t *testing.T) { code: sppb.TypeCode_TIMESTAMP, value: "2021-07-22T10:26:17.123Z", }, + { + name: "json", + code: sppb.TypeCode_JSON, + value: `{"key":"value","other-key":["value1","value2"]}`, + }, { name: "boolArray", code: sppb.TypeCode_BOOL, @@ -582,6 +612,16 @@ func TestQueryWithAllTypes(t *testing.T) { {Kind: &structpb.Value_StringValue{StringValue: "2021-07-27T21:07:59.3399118Z"}}, }}, }, + { + name: "jsonArray", + code: sppb.TypeCode_JSON, + array: true, + value: &structpb.ListValue{Values: []*structpb.Value{ + {Kind: &structpb.Value_StringValue{StringValue: `{"key1":"value1","other-key1":["value1","value2"]}`}}, + {Kind: &structpb.Value_NullValue{}}, + {Kind: &structpb.Value_StringValue{StringValue: `{"key2":"value2","other-key2":["value1","value2"]}`}}, + }}, + }, } for _, wantParam := range wantParams { if pt, ok := req.ParamTypes[wantParam.name]; ok { @@ -616,7 +656,7 @@ func TestQueryWithAllTypes(t *testing.T) { } if wantParam.array { if !cmp.Equal(g, wantParam.value, cmpopts.IgnoreUnexported(structpb.ListValue{}, structpb.Value{})) { - t.Errorf("array param value mismatch\nGot: %v\nWant: %v", g, wantParam.value) + t.Errorf("array param value mismatch\nGot: %v\nWant: %v", g, wantParam.value) } } else { if g != wantParam.value { @@ -644,6 +684,7 @@ func TestQueryWithNullParameters(t *testing.T) { AND ColNumeric=@numeric AND ColDate=@date AND ColTimestamp=@timestamp + AND ColJson=@json AND ColBoolArray=@boolArray AND ColStringArray=@stringArray AND ColBytesArray=@bytesArray @@ -651,7 +692,8 @@ func TestQueryWithNullParameters(t *testing.T) { AND ColFloatArray=@float64Array AND ColNumericArray=@numericArray AND ColDateArray=@dateArray - AND ColTimestampArray=@timestampArray` + AND ColTimestampArray=@timestampArray + AND ColJsonArray=@jsonArray` _ = server.TestSpanner.PutStatementResult( query, &testutil.StatementResult{ @@ -675,6 +717,7 @@ func TestQueryWithNullParameters(t *testing.T) { nil, // numeric nil, // date nil, // timestamp + nil, // json nil, // bool array nil, // string array nil, // bytes array @@ -683,6 +726,7 @@ func TestQueryWithNullParameters(t *testing.T) { nil, // numeric array nil, // date array nil, // timestamp array + nil, // json array ) if err != nil { t.Fatal(err) @@ -698,6 +742,7 @@ func TestQueryWithNullParameters(t *testing.T) { var r spanner.NullNumeric // There's no equivalent sql type. var d spanner.NullDate // There's no equivalent sql type. var ts sql.NullTime + var j spanner.NullJSON // There's no equivalent sql type. var bArray []spanner.NullBool var sArray []spanner.NullString var btArray [][]byte @@ -706,7 +751,8 @@ func TestQueryWithNullParameters(t *testing.T) { var rArray []spanner.NullNumeric var dArray []spanner.NullDate var tsArray []spanner.NullTime - err = rows.Scan(&b, &s, &bt, &i, &f, &r, &d, &ts, &bArray, &sArray, &btArray, &iArray, &fArray, &rArray, &dArray, &tsArray) + var jArray []spanner.NullJSON + err = rows.Scan(&b, &s, &bt, &i, &f, &r, &d, &ts, &j, &bArray, &sArray, &btArray, &iArray, &fArray, &rArray, &dArray, &tsArray, &jArray) if err != nil { t.Fatal(err) } @@ -734,6 +780,36 @@ func TestQueryWithNullParameters(t *testing.T) { if ts.Valid { t.Errorf("row value mismatch for timestamp\nGot: %v\nWant: %v", ts, spanner.NullTime{}) } + if j.Valid { + t.Errorf("row value mismatch for json\nGot: %v\nWant: %v", j, spanner.NullJSON{}) + } + if bArray != nil { + t.Errorf("row value mismatch for bool array\nGot: %v\nWant: %v", bArray, nil) + } + if sArray != nil { + t.Errorf("row value mismatch for string array\nGot: %v\nWant: %v", sArray, nil) + } + if btArray != nil { + t.Errorf("row value mismatch for bytes array array\nGot: %v\nWant: %v", btArray, nil) + } + if iArray != nil { + t.Errorf("row value mismatch for int64 array\nGot: %v\nWant: %v", iArray, nil) + } + if fArray != nil { + t.Errorf("row value mismatch for float64 array\nGot: %v\nWant: %v", fArray, nil) + } + if rArray != nil { + t.Errorf("row value mismatch for numeric array\nGot: %v\nWant: %v", rArray, nil) + } + if dArray != nil { + t.Errorf("row value mismatch for date array\nGot: %v\nWant: %v", dArray, nil) + } + if tsArray != nil { + t.Errorf("row value mismatch for timestamp array\nGot: %v\nWant: %v", tsArray, nil) + } + if jArray != nil { + t.Errorf("row value mismatch for json array\nGot: %v\nWant: %v", jArray, nil) + } } if rows.Err() != nil { t.Fatal(rows.Err()) @@ -748,7 +824,7 @@ func TestQueryWithNullParameters(t *testing.T) { if g, w := len(req.ParamTypes), 0; g != w { t.Fatalf("param types length mismatch\nGot: %v\nWant: %v", g, w) } - if g, w := len(req.Params.Fields), 16; g != w { + if g, w := len(req.Params.Fields), 18; g != w { t.Fatalf("params length mismatch\nGot: %v\nWant: %v", g, w) } for _, param := range req.Params.Fields { @@ -978,6 +1054,15 @@ func nullDate(valid bool, v string) spanner.NullDate { return spanner.NullDate{Valid: true, Date: date(v)} } +func nullJson(valid bool, v string) spanner.NullJSON { + if !valid { + return spanner.NullJSON{} + } + var m map[string]interface{} + _ = json.Unmarshal([]byte(v), &m) + return spanner.NullJSON{Valid: true, Value: m} +} + func setupTestDBConnection(t *testing.T) (db *sql.DB, server *testutil.MockedSpannerInMemTestServer, teardown func()) { return setupTestDBConnectionWithParams(t, "") } diff --git a/go.mod b/go.mod index 22f2a921..85e941f4 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module github.com/cloudspannerecosystem/go-sql-spanner go 1.14 require ( - cloud.google.com/go v0.88.0 - cloud.google.com/go/spanner v1.23.1-0.20210727075241-3d6c6c7873e1 + cloud.google.com/go v0.93.3 + cloud.google.com/go/spanner v1.25.0 github.com/golang/protobuf v1.5.2 github.com/google/go-cmp v0.5.6 - google.golang.org/api v0.51.0 - google.golang.org/genproto v0.0.0-20210726143408-b02e89920bf0 - google.golang.org/grpc v1.39.0 - google.golang.org/protobuf v1.27.1 // indirect + google.golang.org/api v0.54.0 + google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8 + google.golang.org/grpc v1.40.0 + google.golang.org/protobuf v1.27.1 ) diff --git a/go.sum b/go.sum index a816f653..b14ebacd 100644 --- a/go.sum +++ b/go.sum @@ -21,8 +21,9 @@ cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.88.0 h1:MZ2cf9Elnv1wqccq8ooKO2MqHQLc+ChCp/+QWObCpxg= -cloud.google.com/go v0.88.0/go.mod h1:dnKwfYbP9hQhefiUvpbcAyoGSHUrOxR20JVElLiUvEY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3 h1:wPBktZFzYBcCZVARvwVKqH1uEj+aLXofJEtrb4oOsio= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -35,8 +36,8 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/spanner v1.23.1-0.20210727075241-3d6c6c7873e1 h1:DOK5uvDxxzkTjLIb7xt15zWewNS66DnrOmOb2Hv7C9g= -cloud.google.com/go/spanner v1.23.1-0.20210727075241-3d6c6c7873e1/go.mod h1:EZI0yH1D/PrXK0XH9Ba5LGXTXWeqZv0ClOD/19a0Z58= +cloud.google.com/go/spanner v1.25.0 h1:oBLJVlW/v3QMntbpUavhneJEQyPcxbAY5+rI+Jv9hvE= +cloud.google.com/go/spanner v1.25.0/go.mod h1:kQUft3x355hzzaeFbObjsvkzZDgpDkesp3v75WBnI8w= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= @@ -45,8 +46,10 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -133,7 +136,7 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210715191844-86eeefc3e471/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -145,7 +148,6 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -155,6 +157,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -201,7 +204,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -213,7 +215,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -263,8 +264,9 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 h1:3B43BWw0xEBsLZ/NO1VALz6fppU3481pik+2Ksv45z8= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a h1:4Kd8OPUx1xgUwrHDaviWZO8MsgoZTZYC3g+8m16RBww= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -317,8 +319,9 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -382,7 +385,6 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -413,8 +415,9 @@ google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0 h1:SQaA2Cx57B+iPw2MBgyjEkoeMkRK2IenSGoia0U3lCk= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0 h1:ECJUVngj71QI6XEm7b1sAf8BljU5inEhMbKPR8Lxhhk= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -470,9 +473,11 @@ google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxH google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210721163202-f1cecdd8b78a/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210726143408-b02e89920bf0 h1:tcs4DyF9LYv8cynRAbX8JeBpuezJLaK6RfiATAsGwnY= -google.golang.org/genproto v0.0.0-20210726143408-b02e89920bf0/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8 h1:XosVttQUxX8erNhEruTu053/VchgYuksoS9Bj/OITjU= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -495,8 +500,10 @@ google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= diff --git a/integration_test.go b/integration_test.go index 5964fbc2..7d44cba1 100644 --- a/integration_test.go +++ b/integration_test.go @@ -1126,6 +1126,7 @@ func TestAllTypes(t *testing.T) { numericCol spanner.NullNumeric dateCol spanner.NullDate timestampCol sql.NullTime + jsonCol interface{} boolArrayCol []spanner.NullBool stringArrayCol []spanner.NullString bytesArrayCol [][]byte @@ -1134,6 +1135,7 @@ func TestAllTypes(t *testing.T) { numericArrayCol []spanner.NullNumeric dateArrayCol []spanner.NullDate timestampArrayCol []spanner.NullTime + jsonArrayCol interface{} } tests := []struct { @@ -1147,7 +1149,9 @@ func TestAllTypes(t *testing.T) { name: "Non-null values", key: 1, input: []interface{}{ - 1, true, "test", []byte("testbytes"), int64(1), 3.14, numeric("6.626"), date("2021-07-28"), time.Date(2021, 7, 28, 15, 8, 30, 30294, time.UTC), + 1, true, "test", []byte("testbytes"), int64(1), 3.14, numeric("6.626"), date("2021-07-28"), + time.Date(2021, 7, 28, 15, 8, 30, 30294, time.UTC), + nullJsonOrString(true, `{"key": "value", "other-key": ["value1", "value2"]}`), []spanner.NullBool{{Valid: true, Bool: true}, {}, {Valid: true, Bool: false}}, []spanner.NullString{{Valid: true, StringVal: "test1"}, {}, {Valid: true, StringVal: "test2"}}, [][]byte{[]byte("testbytes1"), nil, []byte("testbytes2")}, @@ -1156,11 +1160,14 @@ func TestAllTypes(t *testing.T) { []spanner.NullNumeric{{Valid: true, Numeric: numeric("3.14")}, {}, {Valid: true, Numeric: numeric("6.626")}}, []spanner.NullDate{{Valid: true, Date: date("2021-07-28")}, {}, {Valid: true, Date: date("2000-02-29")}}, []spanner.NullTime{{Valid: true, Time: time.Date(2021, 7, 28, 15, 16, 1, 999999999, time.UTC)}}, + nullJsonOrStringArray([]spanner.NullJSON{nullJson(true, `{"key1": "value1", "other-key1": ["value1", "value2"]}`)}), }, want: AllTypesRow{1, sql.NullBool{Valid: true, Bool: true}, sql.NullString{Valid: true, String: "test"}, []byte("testbytes"), sql.NullInt64{Valid: true, Int64: 1}, sql.NullFloat64{Valid: true, Float64: 3.14}, spanner.NullNumeric{Valid: true, Numeric: numeric("6.626")}, - spanner.NullDate{Valid: true, Date: date("2021-07-28")}, sql.NullTime{Valid: true, Time: time.Date(2021, 7, 28, 15, 8, 30, 30294, time.UTC)}, + spanner.NullDate{Valid: true, Date: date("2021-07-28")}, + sql.NullTime{Valid: true, Time: time.Date(2021, 7, 28, 15, 8, 30, 30294, time.UTC)}, + nullJsonOrString(true, `{"key": "value", "other-key": ["value1", "value2"]}`), []spanner.NullBool{{Valid: true, Bool: true}, {}, {Valid: true, Bool: false}}, []spanner.NullString{{Valid: true, StringVal: "test1"}, {}, {Valid: true, StringVal: "test2"}}, [][]byte{[]byte("testbytes1"), nil, []byte("testbytes2")}, @@ -1169,19 +1176,20 @@ func TestAllTypes(t *testing.T) { []spanner.NullNumeric{{Valid: true, Numeric: numeric("3.14")}, {}, {Valid: true, Numeric: numeric("6.626")}}, []spanner.NullDate{{Valid: true, Date: date("2021-07-28")}, {}, {Valid: true, Date: date("2000-02-29")}}, []spanner.NullTime{{Valid: true, Time: time.Date(2021, 7, 28, 15, 16, 1, 999999999, time.UTC)}}, + nullJsonOrStringArray([]spanner.NullJSON{nullJson(true, `{"key1": "value1", "other-key1": ["value1", "value2"]}`)}), }, }, { name: "Untyped null values", key: 2, input: []interface{}{ - 2, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, + 2, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, }, want: AllTypesRow{2, sql.NullBool{}, sql.NullString{}, []byte(nil), sql.NullInt64{}, sql.NullFloat64{}, spanner.NullNumeric{}, - spanner.NullDate{}, sql.NullTime{}, + spanner.NullDate{}, sql.NullTime{}, nullJsonOrString(false, ""), []spanner.NullBool(nil), []spanner.NullString(nil), [][]byte(nil), @@ -1190,6 +1198,7 @@ func TestAllTypes(t *testing.T) { []spanner.NullNumeric(nil), []spanner.NullDate(nil), []spanner.NullTime(nil), + nullJsonOrStringArray([]spanner.NullJSON(nil)), }, // The emulator does not support untyped null values. skipOnEmulator: true, @@ -1198,7 +1207,7 @@ func TestAllTypes(t *testing.T) { name: "Typed null values", key: 3, input: []interface{}{ - 3, nilBool(), nilString(), []byte(nil), nilInt64(), nilFloat64(), nilRat(), nilDate(), nilTime(), + 3, nilBool(), nilString(), []byte(nil), nilInt64(), nilFloat64(), nilRat(), nilDate(), nilTime(), nullJsonOrString(false, ""), []spanner.NullBool(nil), []spanner.NullString(nil), [][]byte(nil), @@ -1207,11 +1216,12 @@ func TestAllTypes(t *testing.T) { []spanner.NullNumeric(nil), []spanner.NullDate(nil), []spanner.NullTime(nil), + nullJsonOrStringArray([]spanner.NullJSON(nil)), }, want: AllTypesRow{3, sql.NullBool{}, sql.NullString{}, []byte(nil), sql.NullInt64{}, sql.NullFloat64{}, spanner.NullNumeric{}, - spanner.NullDate{}, sql.NullTime{}, + spanner.NullDate{}, sql.NullTime{}, nullJsonOrString(false, ""), []spanner.NullBool(nil), []spanner.NullString(nil), [][]byte(nil), @@ -1220,6 +1230,7 @@ func TestAllTypes(t *testing.T) { []spanner.NullNumeric(nil), []spanner.NullDate(nil), []spanner.NullTime(nil), + nullJsonOrStringArray([]spanner.NullJSON(nil)), }, }, { @@ -1228,7 +1239,7 @@ func TestAllTypes(t *testing.T) { input: []interface{}{ // TODO: Fix the requirement to use spanner.NullString here. 4, sql.NullBool{}, spanner.NullString{}, []byte(nil), sql.NullInt64{}, sql.NullFloat64{}, - spanner.NullNumeric{}, spanner.NullDate{}, sql.NullTime{}, + spanner.NullNumeric{}, spanner.NullDate{}, sql.NullTime{}, nullJsonOrString(false, ""), []spanner.NullBool(nil), []spanner.NullString(nil), [][]byte(nil), @@ -1237,11 +1248,12 @@ func TestAllTypes(t *testing.T) { []spanner.NullNumeric(nil), []spanner.NullDate(nil), []spanner.NullTime(nil), + nullJsonOrStringArray([]spanner.NullJSON(nil)), }, want: AllTypesRow{4, sql.NullBool{}, sql.NullString{}, []byte(nil), sql.NullInt64{}, sql.NullFloat64{}, spanner.NullNumeric{}, - spanner.NullDate{}, sql.NullTime{}, + spanner.NullDate{}, sql.NullTime{}, nullJsonOrString(false, ""), []spanner.NullBool(nil), []spanner.NullString(nil), [][]byte(nil), @@ -1250,16 +1262,17 @@ func TestAllTypes(t *testing.T) { []spanner.NullNumeric(nil), []spanner.NullDate(nil), []spanner.NullTime(nil), + nullJsonOrStringArray([]spanner.NullJSON(nil)), }, }, } stmt, err := db.PrepareContext(ctx, `INSERT INTO TestAllTypes (key, boolCol, stringCol, bytesCol, int64Col, - float64Col, numericCol, dateCol, timestampCol, boolArrayCol, + float64Col, numericCol, dateCol, timestampCol, jsonCol, boolArrayCol, stringArrayCol, bytesArrayCol, int64ArrayCol, float64ArrayCol, - numericArrayCol, dateArrayCol, timestampArrayCol) VALUES (@key, @bool, - @string, @bytes, @int64, @float64, @numeric, @date, @timestamp, - @boolArray, @stringArray, @bytesArray, @int64Array, @float64Array, - @numericArray, @dateArray, @timestampArray)`) + numericArrayCol, dateArrayCol, timestampArrayCol, jsonArrayCol) VALUES ( + @key, @bool, @string, @bytes, @int64, @float64, @numeric, @date, + @timestamp, @json, @boolArray, @stringArray, @bytesArray, @int64Array, + @float64Array, @numericArray, @dateArray, @timestampArray, @jsonArray)`) for _, test := range tests { if runsOnEmulator() && test.skipOnEmulator { t.Logf("skipping test %q on emulator", test.name) @@ -1267,7 +1280,7 @@ func TestAllTypes(t *testing.T) { } res, err := stmt.ExecContext(ctx, test.input...) if err != nil { - t.Fatalf("insert failed: %v", err) + t.Fatalf("%s: insert failed: %v", test.name, err) } affected, err := res.RowsAffected() if err != nil { @@ -1281,14 +1294,33 @@ func TestAllTypes(t *testing.T) { var allTypesRow AllTypesRow err = row.Scan( &allTypesRow.key, &allTypesRow.boolCol, &allTypesRow.stringCol, &allTypesRow.bytesCol, &allTypesRow.int64Col, - &allTypesRow.float64Col, &allTypesRow.numericCol, &allTypesRow.dateCol, &allTypesRow.timestampCol, + &allTypesRow.float64Col, &allTypesRow.numericCol, &allTypesRow.dateCol, &allTypesRow.timestampCol, &allTypesRow.jsonCol, &allTypesRow.boolArrayCol, &allTypesRow.stringArrayCol, &allTypesRow.bytesArrayCol, &allTypesRow.int64ArrayCol, &allTypesRow.float64ArrayCol, &allTypesRow.numericArrayCol, &allTypesRow.dateArrayCol, &allTypesRow.timestampArrayCol, + &allTypesRow.jsonArrayCol, ) if err != nil { t.Fatalf("could not query row: %v", err) } - if !cmp.Equal(allTypesRow, test.want, cmp.AllowUnexported(AllTypesRow{}, big.Rat{}, big.Int{})) { + if !cmp.Equal(allTypesRow, test.want, cmp.AllowUnexported(AllTypesRow{}, big.Rat{}, big.Int{}), cmp.FilterValues(func(v1, v2 interface{}) bool { + if !runsOnEmulator() { + return false + } + // TODO: Remove the following exceptions once the emulator supports JSON. + if reflect.TypeOf(v1) == reflect.TypeOf("") && reflect.TypeOf(v2) == reflect.TypeOf(spanner.NullString{}) { + return true + } + if reflect.TypeOf(v2) == reflect.TypeOf("") && reflect.TypeOf(v1) == reflect.TypeOf(spanner.NullString{}) { + return true + } + if reflect.TypeOf(v1) == reflect.TypeOf(nil) && reflect.TypeOf(v2) == reflect.TypeOf(spanner.NullString{}) { + return true + } + if reflect.TypeOf(v2) == reflect.TypeOf(nil) && reflect.TypeOf(v1) == reflect.TypeOf(spanner.NullString{}) { + return true + } + return false + }, cmp.Ignore())) { t.Fatalf("row mismatch\nGot: %v\nWant: %v", allTypesRow, test.want) } } @@ -1317,22 +1349,27 @@ func TestQueryInReadWriteTransaction(t *testing.T) { t.Fatalf("begin transaction failed: %v", err) } stmt, err := tx.PrepareContext(ctx, `INSERT INTO QueryReadWrite (key, boolCol, stringCol, bytesCol, int64Col, - float64Col, numericCol, dateCol, timestampCol, boolArrayCol, + float64Col, numericCol, dateCol, timestampCol, jsonCol, boolArrayCol, stringArrayCol, bytesArrayCol, int64ArrayCol, float64ArrayCol, - numericArrayCol, dateArrayCol, timestampArrayCol) VALUES (@key, @bool, - @string, @bytes, @int64, @float64, @numeric, @date, @timestamp, - @boolArray, @stringArray, @bytesArray, @int64Array, @float64Array, - @numericArray, @dateArray, @timestampArray)`) + numericArrayCol, dateArrayCol, timestampArrayCol, jsonArrayCol) VALUES ( + @key, @bool, @string, @bytes, @int64, @float64, @numeric, @date, + @timestamp, @json, @boolArray, @stringArray, @bytesArray, @int64Array, + @float64Array, @numericArray, @dateArray, @timestampArray, @jsonArray)`) for row := int64(0); row < wantRowCount; row++ { res, err := stmt.ExecContext(ctx, row, row%2 == 0, fmt.Sprintf("%v", row), []byte(fmt.Sprintf("%v", row)), row, float64(row)/float64(3), numeric(fmt.Sprintf("%v.%v", row, row)), civil.DateOf(time.Unix(row, row)), time.Unix(row*1000, row), + nullJsonOrString(true, fmt.Sprintf(`"key": "value%d"`, row)), []bool{row%2 == 0, row%2 != 0}, []string{fmt.Sprintf("%v", row), fmt.Sprintf("%v", row*2)}, [][]byte{[]byte(fmt.Sprintf("%v", row)), []byte(fmt.Sprintf("%v", row*2))}, []int64{row, row * 2}, []float64{float64(row) / float64(3), float64(row*2) / float64(3)}, []big.Rat{numeric(fmt.Sprintf("%v.%v", row, row)), numeric(fmt.Sprintf("%v.%v", row*2, row*2))}, []civil.Date{civil.DateOf(time.Unix(row, row)), civil.DateOf(time.Unix(row*2, row*2))}, []time.Time{time.Unix(row*1000, row), time.Unix(row*2000, row)}, + nullJsonOrStringArray([]spanner.NullJSON{ + nullJson(true, fmt.Sprintf(`"key1": "value%d"`, row)), + nullJson(true, fmt.Sprintf(`"key2": "value%d"`, row*1000)), + }), ) if err != nil { t.Fatalf("insert failed: %v", err) @@ -1520,6 +1557,10 @@ func insertRandomSingers(ctx context.Context, db *sql.DB) (err error) { } func getTableWithAllTypesDdl(name string) string { + jsonType := "JSON" + if runsOnEmulator() { + jsonType = "STRING(MAX)" + } return fmt.Sprintf(`CREATE TABLE %s ( key INT64, boolCol BOOL, @@ -1530,6 +1571,7 @@ func getTableWithAllTypesDdl(name string) string { numericCol NUMERIC, dateCol DATE, timestampCol TIMESTAMP, + jsonCol %s, boolArrayCol ARRAY, stringArrayCol ARRAY, bytesArrayCol ARRAY, @@ -1538,7 +1580,8 @@ func getTableWithAllTypesDdl(name string) string { numericArrayCol ARRAY, dateArrayCol ARRAY, timestampArrayCol ARRAY, - ) PRIMARY KEY (key)`, fmt.Sprintf("`%s`", name)) + jsonArrayCol ARRAY<%s>, + ) PRIMARY KEY (key)`, fmt.Sprintf("`%s`", name), jsonType, jsonType) } func nilBool() *bool { @@ -1575,3 +1618,24 @@ func nilTime() *time.Time { var t *time.Time return t } + +func nullJsonOrString(valid bool, v string) interface{} { + if runsOnEmulator() { + return spanner.NullString{Valid: valid, StringVal: v} + } + return nullJson(valid, v) +} + +func nullJsonOrStringArray(v []spanner.NullJSON) interface{} { + if !runsOnEmulator() { + return v + } + if reflect.ValueOf(v).IsNil() { + return []spanner.NullString(nil) + } + res := make([]spanner.NullString, len(v)) + for i, j := range v { + res[i] = spanner.NullString{Valid: j.Valid, StringVal: j.String()} + } + return res +} diff --git a/rows.go b/rows.go index 5c63c136..556eb0ce 100644 --- a/rows.go +++ b/rows.go @@ -146,6 +146,15 @@ func (r *rows) Next(dest []driver.Value) error { } else { dest[i] = nil } + case sppb.TypeCode_JSON: + var v spanner.NullJSON + if err := col.Decode(&v); err != nil { + return err + } + // We always assign `v` to dest[i] here because there is no native type + // for JSON in the Go sql package. That means that instead of returning + // nil we should return a NullJSON with valid=false. + dest[i] = v case sppb.TypeCode_BYTES: // The column value is a base64 encoded string. var v []byte @@ -208,6 +217,12 @@ func (r *rows) Next(dest []driver.Value) error { return err } dest[i] = v + case sppb.TypeCode_JSON: + var v []spanner.NullJSON + if err := col.Decode(&v); err != nil { + return err + } + dest[i] = v case sppb.TypeCode_BYTES: var v [][]byte if err := col.Decode(&v); err != nil { diff --git a/testutil/mocked_inmem_server.go b/testutil/mocked_inmem_server.go index 653f550c..d32e5adb 100644 --- a/testutil/mocked_inmem_server.go +++ b/testutil/mocked_inmem_server.go @@ -221,7 +221,7 @@ func createSingersRow(idx int64) *structpb.ListValue { } func CreateResultSetWithAllTypes(nullValues bool) *spannerpb.ResultSet { - fields := make([]*spannerpb.StructType_Field, 16) + fields := make([]*spannerpb.StructType_Field, 18) fields[0] = &spannerpb.StructType_Field{ Name: "ColBool", Type: &spannerpb.Type{Code: spannerpb.TypeCode_BOOL}, @@ -255,61 +255,72 @@ func CreateResultSetWithAllTypes(nullValues bool) *spannerpb.ResultSet { Type: &spannerpb.Type{Code: spannerpb.TypeCode_TIMESTAMP}, } fields[8] = &spannerpb.StructType_Field{ + Name: "ColJson", + Type: &spannerpb.Type{Code: spannerpb.TypeCode_JSON}, + } + fields[9] = &spannerpb.StructType_Field{ Name: "ColBoolArray", Type: &spannerpb.Type{ Code: spannerpb.TypeCode_ARRAY, ArrayElementType: &spannerpb.Type{Code: spannerpb.TypeCode_BOOL}, }, } - fields[9] = &spannerpb.StructType_Field{ + fields[10] = &spannerpb.StructType_Field{ Name: "ColStringArray", Type: &spannerpb.Type{ Code: spannerpb.TypeCode_ARRAY, ArrayElementType: &spannerpb.Type{Code: spannerpb.TypeCode_STRING}, }, } - fields[10] = &spannerpb.StructType_Field{ + fields[11] = &spannerpb.StructType_Field{ Name: "ColBytesArray", Type: &spannerpb.Type{ Code: spannerpb.TypeCode_ARRAY, ArrayElementType: &spannerpb.Type{Code: spannerpb.TypeCode_BYTES}, }, } - fields[11] = &spannerpb.StructType_Field{ + fields[12] = &spannerpb.StructType_Field{ Name: "ColIntArray", Type: &spannerpb.Type{ Code: spannerpb.TypeCode_ARRAY, ArrayElementType: &spannerpb.Type{Code: spannerpb.TypeCode_INT64}, }, } - fields[12] = &spannerpb.StructType_Field{ + fields[13] = &spannerpb.StructType_Field{ Name: "ColFloatArray", Type: &spannerpb.Type{ Code: spannerpb.TypeCode_ARRAY, ArrayElementType: &spannerpb.Type{Code: spannerpb.TypeCode_FLOAT64}, }, } - fields[13] = &spannerpb.StructType_Field{ + fields[14] = &spannerpb.StructType_Field{ Name: "ColNumericArray", Type: &spannerpb.Type{ Code: spannerpb.TypeCode_ARRAY, ArrayElementType: &spannerpb.Type{Code: spannerpb.TypeCode_NUMERIC}, }, } - fields[14] = &spannerpb.StructType_Field{ + fields[15] = &spannerpb.StructType_Field{ Name: "ColDateArray", Type: &spannerpb.Type{ Code: spannerpb.TypeCode_ARRAY, ArrayElementType: &spannerpb.Type{Code: spannerpb.TypeCode_DATE}, }, } - fields[15] = &spannerpb.StructType_Field{ + fields[16] = &spannerpb.StructType_Field{ Name: "ColTimestampArray", Type: &spannerpb.Type{ Code: spannerpb.TypeCode_ARRAY, ArrayElementType: &spannerpb.Type{Code: spannerpb.TypeCode_TIMESTAMP}, }, } + fields[17] = &spannerpb.StructType_Field{ + Name: "ColJsonArray", + Type: &spannerpb.Type{ + Code: spannerpb.TypeCode_ARRAY, + ArrayElementType: &spannerpb.Type{Code: spannerpb.TypeCode_JSON}, + }, + } rowType := &spannerpb.StructType{ Fields: fields, } @@ -331,62 +342,70 @@ func CreateResultSetWithAllTypes(nullValues bool) *spannerpb.ResultSet { rowValue[5] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "6.626"}} rowValue[6] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "2021-07-21"}} rowValue[7] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "2021-07-21T21:07:59.339911800Z"}} - rowValue[8] = &structpb.Value{Kind: &structpb.Value_ListValue{ + rowValue[8] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: `{"key": "value", "other-key": ["value1", "value2"]}`}} + rowValue[9] = &structpb.Value{Kind: &structpb.Value_ListValue{ ListValue: &structpb.ListValue{Values: []*structpb.Value{ {Kind: &structpb.Value_BoolValue{BoolValue: true}}, {Kind: &structpb.Value_NullValue{}}, {Kind: &structpb.Value_BoolValue{BoolValue: false}}, }}, }} - rowValue[9] = &structpb.Value{Kind: &structpb.Value_ListValue{ + rowValue[10] = &structpb.Value{Kind: &structpb.Value_ListValue{ ListValue: &structpb.ListValue{Values: []*structpb.Value{ {Kind: &structpb.Value_StringValue{StringValue: "test1"}}, {Kind: &structpb.Value_NullValue{}}, {Kind: &structpb.Value_StringValue{StringValue: "test2"}}, }}, }} - rowValue[10] = &structpb.Value{Kind: &structpb.Value_ListValue{ + rowValue[11] = &structpb.Value{Kind: &structpb.Value_ListValue{ ListValue: &structpb.ListValue{Values: []*structpb.Value{ {Kind: &structpb.Value_StringValue{StringValue: base64.StdEncoding.EncodeToString([]byte("testbytes1"))}}, {Kind: &structpb.Value_NullValue{}}, {Kind: &structpb.Value_StringValue{StringValue: base64.StdEncoding.EncodeToString([]byte("testbytes2"))}}, }}, }} - rowValue[11] = &structpb.Value{Kind: &structpb.Value_ListValue{ + rowValue[12] = &structpb.Value{Kind: &structpb.Value_ListValue{ ListValue: &structpb.ListValue{Values: []*structpb.Value{ {Kind: &structpb.Value_StringValue{StringValue: "1"}}, {Kind: &structpb.Value_NullValue{}}, {Kind: &structpb.Value_StringValue{StringValue: "2"}}, }}, }} - rowValue[12] = &structpb.Value{Kind: &structpb.Value_ListValue{ + rowValue[13] = &structpb.Value{Kind: &structpb.Value_ListValue{ ListValue: &structpb.ListValue{Values: []*structpb.Value{ {Kind: &structpb.Value_NumberValue{NumberValue: 6.626}}, {Kind: &structpb.Value_NullValue{}}, {Kind: &structpb.Value_NumberValue{NumberValue: 10.01}}, }}, }} - rowValue[13] = &structpb.Value{Kind: &structpb.Value_ListValue{ + rowValue[14] = &structpb.Value{Kind: &structpb.Value_ListValue{ ListValue: &structpb.ListValue{Values: []*structpb.Value{ {Kind: &structpb.Value_StringValue{StringValue: "3.14"}}, {Kind: &structpb.Value_NullValue{}}, {Kind: &structpb.Value_StringValue{StringValue: "10.01"}}, }}, }} - rowValue[14] = &structpb.Value{Kind: &structpb.Value_ListValue{ + rowValue[15] = &structpb.Value{Kind: &structpb.Value_ListValue{ ListValue: &structpb.ListValue{Values: []*structpb.Value{ {Kind: &structpb.Value_StringValue{StringValue: "2000-02-29"}}, {Kind: &structpb.Value_NullValue{}}, {Kind: &structpb.Value_StringValue{StringValue: "2021-07-27"}}, }}, }} - rowValue[15] = &structpb.Value{Kind: &structpb.Value_ListValue{ + rowValue[16] = &structpb.Value{Kind: &structpb.Value_ListValue{ ListValue: &structpb.ListValue{Values: []*structpb.Value{ {Kind: &structpb.Value_StringValue{StringValue: "2021-07-21T21:07:59.339911800Z"}}, {Kind: &structpb.Value_NullValue{}}, {Kind: &structpb.Value_StringValue{StringValue: "2021-07-27T21:07:59.339911800Z"}}, }}, }} + rowValue[17] = &structpb.Value{Kind: &structpb.Value_ListValue{ + ListValue: &structpb.ListValue{Values: []*structpb.Value{ + {Kind: &structpb.Value_StringValue{StringValue: `{"key1": "value1", "other-key1": ["value1", "value2"]}`}}, + {Kind: &structpb.Value_NullValue{}}, + {Kind: &structpb.Value_StringValue{StringValue: `{"key2": "value2", "other-key2": ["value1", "value2"]}`}}, + }}, + }} } rows[0] = &structpb.ListValue{ Values: rowValue,