diff --git a/driver.go b/driver.go index ea2e891e..ba4c7bc1 100644 --- a/driver.go +++ b/driver.go @@ -858,6 +858,7 @@ func openDriverConn(ctx context.Context, c *connector) (driver.Conn, error) { } func addStateFromConnectorConfig(c *connector, values map[string]connectionstate.ConnectionPropertyValue) { + updateConnectionPropertyValueIfNotExists(propertyDecodeToNativeArrays, values, c.connectorConfig.DecodeToNativeArrays) updateConnectionPropertyValueIfNotExists(propertyIsolationLevel, values, c.connectorConfig.IsolationLevel) updateConnectionPropertyValueIfNotExists(propertyBeginTransactionOption, values, c.connectorConfig.BeginTransactionOption) updateConnectionPropertyValueIfNotExists(propertyRetryAbortsInternally, values, c.retryAbortsInternally) diff --git a/driver_with_mockserver_test.go b/driver_with_mockserver_test.go index 4ef3c3ae..58756a4b 100644 --- a/driver_with_mockserver_test.go +++ b/driver_with_mockserver_test.go @@ -1663,9 +1663,23 @@ func TestQueryWithAllTypes_ReturnProto(t *testing.T) { func TestQueryWithAllNativeTypes(t *testing.T) { t.Parallel() - db, server, teardown := setupTestDBConnectionWithParams(t, "DecodeToNativeArrays=true") - defer teardown() - query := `SELECT * + for _, useConnectorConfig := range []bool{true, false} { + t.Run(fmt.Sprintf("UseConnectorConfig:%v", useConnectorConfig), func(t *testing.T) { + var db *sql.DB + var server *testutil.MockedSpannerInMemTestServer + var teardown func() + if useConnectorConfig { + db, server, teardown = setupTestDBConnectionWithConnectorConfig(t, ConnectorConfig{ + Project: "p", + Instance: "i", + Database: "d", + DecodeToNativeArrays: true, + }) + } else { + db, server, teardown = setupTestDBConnectionWithParams(t, "DecodeToNativeArrays=true") + } + defer teardown() + query := `SELECT * FROM Test WHERE ColBool=@bool AND ColString=@string @@ -1689,437 +1703,439 @@ func TestQueryWithAllNativeTypes(t *testing.T) { AND ColTimestampArray=@timestampArray AND ColJsonArray=@jsonArray AND ColUuidArray=@uuidArray` - _ = server.TestSpanner.PutStatementResult( - query, - &testutil.StatementResult{ - Type: testutil.StatementResultResultSet, - ResultSet: testutil.CreateResultSetWithAllTypes(false, false, databasepb.DatabaseDialect_GOOGLE_STANDARD_SQL), - }, - ) + _ = server.TestSpanner.PutStatementResult( + query, + &testutil.StatementResult{ + Type: testutil.StatementResultResultSet, + ResultSet: testutil.CreateResultSetWithAllTypes(false, false, databasepb.DatabaseDialect_GOOGLE_STANDARD_SQL), + }, + ) - stmt, err := db.Prepare(query) - if err != nil { - t.Fatal(err) - } - defer silentClose(stmt) - ts, _ := time.Parse(time.RFC3339Nano, "2021-07-22T10:26:17.123Z") - ts1, _ := time.Parse(time.RFC3339Nano, "2021-07-21T21:07:59.339911800Z") - ts2, _ := time.Parse(time.RFC3339Nano, "2021-07-27T21:07:59.339911800Z") - tsAlt, _ := time.Parse(time.RFC3339Nano, "2000-01-01T00:00:00Z") - rows, err := stmt.QueryContext( - context.Background(), - true, - "test", - []byte("testbytes"), - uint(5), - float32(3.14), - 3.14, - numeric("6.626"), - civil.Date{Year: 2021, Month: 7, Day: 21}, - ts, - nullJson(true, `{"key":"value","other-key":["value1","value2"]}`), - uuid.MustParse("a4e71944-fe14-4047-9d0a-e68c281602e1"), - []bool{true, false}, - []string{"test1", "test2"}, - [][]byte{[]byte("testbytes1"), []byte("testbytes2")}, - []int64{1, 2}, - []float32{3.14, -99.99}, - []float64{6.626, 10.01}, - []spanner.NullNumeric{nullNumeric(true, "3.14"), nullNumeric(true, "10.01")}, - []civil.Date{{Year: 2000, Month: 2, Day: 29}, {Year: 2021, Month: 7, Day: 27}}, - []time.Time{ts1, ts2}, - []spanner.NullJSON{ - nullJson(true, `{"key1": "value1", "other-key1": ["value1", "value2"]}`), - nullJson(true, `{"key2": "value2", "other-key2": ["value1", "value2"]}`), - }, - []uuid.UUID{ - uuid.MustParse("d0546638-6d51-4d7c-a4a9-9062204ee5bb"), - uuid.MustParse("0dd0f9b7-05af-48e0-a5b1-35432a01c6bf"), - }, - ) - if err != nil { - t.Fatal(err) - } - defer silentClose(rows) + stmt, err := db.Prepare(query) + if err != nil { + t.Fatal(err) + } + defer silentClose(stmt) + ts, _ := time.Parse(time.RFC3339Nano, "2021-07-22T10:26:17.123Z") + ts1, _ := time.Parse(time.RFC3339Nano, "2021-07-21T21:07:59.339911800Z") + ts2, _ := time.Parse(time.RFC3339Nano, "2021-07-27T21:07:59.339911800Z") + tsAlt, _ := time.Parse(time.RFC3339Nano, "2000-01-01T00:00:00Z") + rows, err := stmt.QueryContext( + context.Background(), + true, + "test", + []byte("testbytes"), + uint(5), + float32(3.14), + 3.14, + numeric("6.626"), + civil.Date{Year: 2021, Month: 7, Day: 21}, + ts, + nullJson(true, `{"key":"value","other-key":["value1","value2"]}`), + uuid.MustParse("a4e71944-fe14-4047-9d0a-e68c281602e1"), + []bool{true, false}, + []string{"test1", "test2"}, + [][]byte{[]byte("testbytes1"), []byte("testbytes2")}, + []int64{1, 2}, + []float32{3.14, -99.99}, + []float64{6.626, 10.01}, + []spanner.NullNumeric{nullNumeric(true, "3.14"), nullNumeric(true, "10.01")}, + []civil.Date{{Year: 2000, Month: 2, Day: 29}, {Year: 2021, Month: 7, Day: 27}}, + []time.Time{ts1, ts2}, + []spanner.NullJSON{ + nullJson(true, `{"key1": "value1", "other-key1": ["value1", "value2"]}`), + nullJson(true, `{"key2": "value2", "other-key2": ["value1", "value2"]}`), + }, + []uuid.UUID{ + uuid.MustParse("d0546638-6d51-4d7c-a4a9-9062204ee5bb"), + uuid.MustParse("0dd0f9b7-05af-48e0-a5b1-35432a01c6bf"), + }, + ) + if err != nil { + t.Fatal(err) + } + defer silentClose(rows) - for rows.Next() { - var b bool - var s string - var bt []byte - var i int64 - var f32 float32 - var f float64 - var r big.Rat - var d civil.Date - var ts time.Time - var j spanner.NullJSON - var u uuid.UUID - var p []byte - var e int64 - var bArray []bool - var sArray []string - var btArray [][]byte - var iArray []int64 - var f32Array []float32 - var fArray []float64 - var rArray []spanner.NullNumeric - var dArray []civil.Date - var tsArray []time.Time - var jArray []spanner.NullJSON - var uArray []uuid.UUID - var pArray [][]byte - var eArray []int64 - err = rows.Scan(&b, &s, &bt, &i, &f32, &f, &r, &d, &ts, &j, &u, &p, &e, &bArray, &sArray, &btArray, &iArray, &f32Array, &fArray, &rArray, &dArray, &tsArray, &jArray, &uArray, &pArray, &eArray) - if err != nil { - t.Fatal(err) - } - if g, w := b, true; g != w { - t.Errorf("row value mismatch for bool\nGot: %v\nWant: %v", g, w) - } - if g, w := s, "test"; g != w { - t.Errorf("row value mismatch for string\nGot: %v\nWant: %v", g, w) - } - if g, w := bt, []byte("testbytes"); !cmp.Equal(g, w) { - t.Errorf("row value mismatch for bytes\nGot: %v\nWant: %v", g, w) - } - if g, w := i, int64(5); g != w { - t.Errorf("row value mismatch for int64\nGot: %v\nWant: %v", g, w) - } - if g, w := f32, float32(3.14); g != w { - t.Errorf("row value mismatch for float32\nGot: %v\nWant: %v", g, w) - } - if g, w := f, 3.14; g != w { - t.Errorf("row value mismatch for float64\nGot: %v\nWant: %v", g, w) - } - if g, w := r, numeric("6.626"); g.Cmp(&w) != 0 { - t.Errorf("row value mismatch for numeric\nGot: %v\nWant: %v", g, w) - } - if g, w := d, date("2021-07-21"); !cmp.Equal(g, w) { - t.Errorf("row value mismatch for date\nGot: %v\nWant: %v", g, w) - } - 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 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 := u, uuid.MustParse("a4e71944-fe14-4047-9d0a-e68c281602e1"); !cmp.Equal(g, w) { - t.Errorf("row value mismatch for uuid\n Got: %v\nWant: %v", g, w) - } - wantSingerEnumValue := pb.Genre_ROCK - wantSingerProtoMsg := pb.SingerInfo{ - SingerId: proto.Int64(1), - BirthDate: proto.String("January"), - Nationality: proto.String("Country1"), - Genre: &wantSingerEnumValue, - } - gotSingerProto := pb.SingerInfo{} - if err := proto.Unmarshal(p, &gotSingerProto); err != nil { - t.Fatalf("failed to unmarshal proto: %v", err) - } - if g, w := &gotSingerProto, &wantSingerProtoMsg; !cmp.Equal(g, w, cmpopts.IgnoreUnexported(pb.SingerInfo{})) { - t.Errorf("row value mismatch for proto\nGot: %v\nWant: %v", g, w) - } - if g, w := pb.Genre(e), wantSingerEnumValue; g != w { - t.Errorf("row value mismatch for enum\nGot: %v\nWant: %v", g, w) - } - if g, w := bArray, []bool{true, true, false}; !cmp.Equal(g, w) { - t.Errorf("row value mismatch for bool array\nGot: %v\nWant: %v", g, w) - } - if g, w := sArray, []string{"test1", "alt", "test2"}; !cmp.Equal(g, w) { - t.Errorf("row value mismatch for string array\nGot: %v\nWant: %v", g, w) - } - if g, w := btArray, [][]byte{[]byte("testbytes1"), []byte("altbytes"), []byte("testbytes2")}; !cmp.Equal(g, w) { - t.Errorf("row value mismatch for bytes array\nGot: %v\nWant: %v", g, w) - } - if g, w := iArray, []int64{1, 0, 2}; !cmp.Equal(g, w) { - t.Errorf("row value mismatch for int array\nGot: %v\nWant: %v", g, w) - } - if g, w := f32Array, []float32{3.14, 0.0, -99.99}; !cmp.Equal(g, w) { - t.Errorf("row value mismatch for float32 array\nGot: %v\nWant: %v", g, w) - } - if g, w := fArray, []float64{6.626, 0.0, 10.01}; !cmp.Equal(g, w) { - t.Errorf("row value mismatch for float64 array\nGot: %v\nWant: %v", g, w) - } - if g, w := rArray, []spanner.NullNumeric{nullNumeric(true, "3.14"), nullNumeric(true, "1.0"), nullNumeric(true, "10.01")}; !cmp.Equal(g, w, cmp.AllowUnexported(big.Rat{}, big.Int{})) { - t.Errorf("row value mismatch for numeric array\n Got: %v\nWant: %v", g, w) - } - if g, w := dArray, []civil.Date{{Year: 2000, Month: 2, Day: 29}, {Year: 2000, Month: 1, Day: 1}, {Year: 2021, Month: 7, Day: 27}}; !cmp.Equal(g, w) { - t.Errorf("row value mismatch for date array\nGot: %v\nWant: %v", g, w) - } - if g, w := tsArray, []time.Time{ts1, tsAlt, ts2}; !cmp.Equal(g, w) { - t.Errorf("row value mismatch for timestamp array\n Got: %v\nWant: %v", g, w) - } - if g, w := jArray, []spanner.NullJSON{ - nullJson(true, `{"key1": "value1", "other-key1": ["value1", "value2"]}`), - nullJson(true, "{}"), - nullJson(true, `{"key2": "value2", "other-key2": ["value1", "value2"]}`), - }; !cmp.Equal(g, w) { - t.Errorf("row value mismatch for json array\n Got: %v\nWant: %v", g, w) - } - if g, w := uArray, []uuid.UUID{ - uuid.MustParse("d0546638-6d51-4d7c-a4a9-9062204ee5bb"), - uuid.MustParse("00000000-0000-0000-0000-000000000000"), - uuid.MustParse("0dd0f9b7-05af-48e0-a5b1-35432a01c6bf"), - }; !cmp.Equal(g, w) { - t.Errorf("row value mismatch for json array\n Got: %v\nWant: %v", g, w) - } - if g, w := len(pArray), 3; g != w { - t.Errorf("row value length mismatch for proto array\nGot: %v\nWant: %v", g, w) - } - wantSinger2ProtoEnum := pb.Genre_FOLK - wantSinger2ProtoMsg := pb.SingerInfo{ - SingerId: proto.Int64(2), - BirthDate: proto.String("February"), - Nationality: proto.String("Country2"), - Genre: &wantSinger2ProtoEnum, - } - gotSingerProto1 := pb.SingerInfo{} - if err := proto.Unmarshal(pArray[0], &gotSingerProto1); err != nil { - t.Fatalf("failed to unmarshal proto: %v", err) - } - gotSingerProtoAlt := pb.SingerInfo{} - if err := proto.Unmarshal(pArray[1], &gotSingerProtoAlt); err != nil { - t.Fatalf("failed to unmarshal proto: %v", err) - } - gotSingerProto2 := pb.SingerInfo{} - if err := proto.Unmarshal(pArray[2], &gotSingerProto2); err != nil { - t.Fatalf("failed to unmarshal proto: %v", err) - } - if g, w := &gotSingerProto1, &wantSingerProtoMsg; !cmp.Equal(g, w, cmpopts.IgnoreUnexported(pb.SingerInfo{})) { - t.Errorf("row value mismatch for proto\nGot: %v\nWant: %v", g, w) - } - if g, w := &gotSingerProtoAlt, &wantSingerProtoMsg; !cmp.Equal(g, w, cmpopts.IgnoreUnexported(pb.SingerInfo{})) { - t.Errorf("row value mismatch for proto\n Got: %v\nWant: %v", g, w) - } - if g, w := &gotSingerProto2, &wantSinger2ProtoMsg; !cmp.Equal(g, w, cmpopts.IgnoreUnexported(pb.SingerInfo{})) { - t.Errorf("row value mismatch for proto\nGot: %v\nWant: %v", g, w) - } - } - if rows.Err() != nil { - t.Fatal(rows.Err()) - } - requests := server.TestSpanner.DrainRequestsFromServer() - sqlRequests := testutil.RequestsOfType(requests, reflect.TypeOf(&sppb.ExecuteSqlRequest{})) - if g, w := len(sqlRequests), 1; g != w { - t.Fatalf("sql requests count mismatch\nGot: %v\nWant: %v", g, w) - } - req := sqlRequests[0].(*sppb.ExecuteSqlRequest) - // Strings should be sent as untyped values. - if g, w := len(req.ParamTypes), 20; g != w { - t.Fatalf("param types length mismatch\nGot: %v\nWant: %v", g, w) - } - if g, w := len(req.Params.Fields), 22; g != w { - t.Fatalf("params length mismatch\nGot: %v\nWant: %v", g, w) - } - wantParams := []struct { - name string - code sppb.TypeCode - array bool - value interface{} - }{ - { - name: "bool", - code: sppb.TypeCode_BOOL, - value: true, - }, - { - name: "string", - code: sppb.TypeCode_TYPE_CODE_UNSPECIFIED, - value: "test", - }, - { - name: "bytes", - code: sppb.TypeCode_BYTES, - value: base64.StdEncoding.EncodeToString([]byte("testbytes")), - }, - { - name: "int64", - code: sppb.TypeCode_INT64, - value: "5", - }, - { - name: "float32", - code: sppb.TypeCode_FLOAT32, - value: float64(float32(3.14)), - }, - { - name: "float64", - code: sppb.TypeCode_FLOAT64, - value: 3.14, - }, - { - name: "numeric", - code: sppb.TypeCode_NUMERIC, - value: "6.626000000", - }, - { - name: "date", - code: sppb.TypeCode_DATE, - value: "2021-07-21", - }, - { - name: "timestamp", - 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: "uuid", - code: sppb.TypeCode_UUID, - value: `a4e71944-fe14-4047-9d0a-e68c281602e1`, - }, - { - name: "boolArray", - code: sppb.TypeCode_BOOL, - array: true, - value: &structpb.ListValue{Values: []*structpb.Value{ - {Kind: &structpb.Value_BoolValue{BoolValue: true}}, - {Kind: &structpb.Value_BoolValue{BoolValue: false}}, - }}, - }, - { - name: "stringArray", - code: sppb.TypeCode_TYPE_CODE_UNSPECIFIED, - array: true, - value: &structpb.ListValue{Values: []*structpb.Value{ - {Kind: &structpb.Value_StringValue{StringValue: "test1"}}, - {Kind: &structpb.Value_StringValue{StringValue: "test2"}}, - }}, - }, - { - name: "bytesArray", - code: sppb.TypeCode_BYTES, - array: true, - value: &structpb.ListValue{Values: []*structpb.Value{ - {Kind: &structpb.Value_StringValue{StringValue: base64.StdEncoding.EncodeToString([]byte("testbytes1"))}}, - {Kind: &structpb.Value_StringValue{StringValue: base64.StdEncoding.EncodeToString([]byte("testbytes2"))}}, - }}, - }, - { - name: "int64Array", - code: sppb.TypeCode_INT64, - array: true, - value: &structpb.ListValue{Values: []*structpb.Value{ - {Kind: &structpb.Value_StringValue{StringValue: "1"}}, - {Kind: &structpb.Value_StringValue{StringValue: "2"}}, - }}, - }, - { - name: "float32Array", - code: sppb.TypeCode_FLOAT32, - array: true, - value: &structpb.ListValue{Values: []*structpb.Value{ - {Kind: &structpb.Value_NumberValue{NumberValue: float64(float32(3.14))}}, - {Kind: &structpb.Value_NumberValue{NumberValue: float64(float32(-99.99))}}, - }}, - }, - { - name: "float64Array", - code: sppb.TypeCode_FLOAT64, - array: true, - value: &structpb.ListValue{Values: []*structpb.Value{ - {Kind: &structpb.Value_NumberValue{NumberValue: 6.626}}, - {Kind: &structpb.Value_NumberValue{NumberValue: 10.01}}, - }}, - }, - { - name: "numericArray", - code: sppb.TypeCode_NUMERIC, - array: true, - value: &structpb.ListValue{Values: []*structpb.Value{ - {Kind: &structpb.Value_StringValue{StringValue: "3.140000000"}}, - {Kind: &structpb.Value_StringValue{StringValue: "10.010000000"}}, - }}, - }, - { - name: "dateArray", - code: sppb.TypeCode_DATE, - array: true, - value: &structpb.ListValue{Values: []*structpb.Value{ - {Kind: &structpb.Value_StringValue{StringValue: "2000-02-29"}}, - {Kind: &structpb.Value_StringValue{StringValue: "2021-07-27"}}, - }}, - }, - { - name: "timestampArray", - code: sppb.TypeCode_TIMESTAMP, - array: true, - value: &structpb.ListValue{Values: []*structpb.Value{ - {Kind: &structpb.Value_StringValue{StringValue: "2021-07-21T21:07:59.3399118Z"}}, - {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_StringValue{StringValue: `{"key2":"value2","other-key2":["value1","value2"]}`}}, - }}, - }, - { - name: "uuidArray", - code: sppb.TypeCode_UUID, - array: true, - value: &structpb.ListValue{Values: []*structpb.Value{ - {Kind: &structpb.Value_StringValue{StringValue: `d0546638-6d51-4d7c-a4a9-9062204ee5bb`}}, - {Kind: &structpb.Value_StringValue{StringValue: `0dd0f9b7-05af-48e0-a5b1-35432a01c6bf`}}, - }}, - }, - } - for _, wantParam := range wantParams { - if pt, ok := req.ParamTypes[wantParam.name]; ok { - if wantParam.array { - if g, w := pt.Code, sppb.TypeCode_ARRAY; g != w { - t.Errorf("param type mismatch\nGot: %v\nWant: %v", g, w) + for rows.Next() { + var b bool + var s string + var bt []byte + var i int64 + var f32 float32 + var f float64 + var r big.Rat + var d civil.Date + var ts time.Time + var j spanner.NullJSON + var u uuid.UUID + var p []byte + var e int64 + var bArray []bool + var sArray []string + var btArray [][]byte + var iArray []int64 + var f32Array []float32 + var fArray []float64 + var rArray []spanner.NullNumeric + var dArray []civil.Date + var tsArray []time.Time + var jArray []spanner.NullJSON + var uArray []uuid.UUID + var pArray [][]byte + var eArray []int64 + err = rows.Scan(&b, &s, &bt, &i, &f32, &f, &r, &d, &ts, &j, &u, &p, &e, &bArray, &sArray, &btArray, &iArray, &f32Array, &fArray, &rArray, &dArray, &tsArray, &jArray, &uArray, &pArray, &eArray) + if err != nil { + t.Fatal(err) } - if g, w := pt.ArrayElementType.Code, wantParam.code; g != w { - t.Errorf("param array element type mismatch\nGot: %v\nWant: %v", g, w) + if g, w := b, true; g != w { + t.Errorf("row value mismatch for bool\nGot: %v\nWant: %v", g, w) } - } else { - if g, w := pt.Code, wantParam.code; g != w { - t.Errorf("param type mismatch\nGot: %v\nWant: %v", g, w) + if g, w := s, "test"; g != w { + t.Errorf("row value mismatch for string\nGot: %v\nWant: %v", g, w) + } + if g, w := bt, []byte("testbytes"); !cmp.Equal(g, w) { + t.Errorf("row value mismatch for bytes\nGot: %v\nWant: %v", g, w) + } + if g, w := i, int64(5); g != w { + t.Errorf("row value mismatch for int64\nGot: %v\nWant: %v", g, w) + } + if g, w := f32, float32(3.14); g != w { + t.Errorf("row value mismatch for float32\nGot: %v\nWant: %v", g, w) + } + if g, w := f, 3.14; g != w { + t.Errorf("row value mismatch for float64\nGot: %v\nWant: %v", g, w) + } + if g, w := r, numeric("6.626"); g.Cmp(&w) != 0 { + t.Errorf("row value mismatch for numeric\nGot: %v\nWant: %v", g, w) + } + if g, w := d, date("2021-07-21"); !cmp.Equal(g, w) { + t.Errorf("row value mismatch for date\nGot: %v\nWant: %v", g, w) + } + 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 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 := u, uuid.MustParse("a4e71944-fe14-4047-9d0a-e68c281602e1"); !cmp.Equal(g, w) { + t.Errorf("row value mismatch for uuid\n Got: %v\nWant: %v", g, w) + } + wantSingerEnumValue := pb.Genre_ROCK + wantSingerProtoMsg := pb.SingerInfo{ + SingerId: proto.Int64(1), + BirthDate: proto.String("January"), + Nationality: proto.String("Country1"), + Genre: &wantSingerEnumValue, + } + gotSingerProto := pb.SingerInfo{} + if err := proto.Unmarshal(p, &gotSingerProto); err != nil { + t.Fatalf("failed to unmarshal proto: %v", err) + } + if g, w := &gotSingerProto, &wantSingerProtoMsg; !cmp.Equal(g, w, cmpopts.IgnoreUnexported(pb.SingerInfo{})) { + t.Errorf("row value mismatch for proto\nGot: %v\nWant: %v", g, w) + } + if g, w := pb.Genre(e), wantSingerEnumValue; g != w { + t.Errorf("row value mismatch for enum\nGot: %v\nWant: %v", g, w) + } + if g, w := bArray, []bool{true, true, false}; !cmp.Equal(g, w) { + t.Errorf("row value mismatch for bool array\nGot: %v\nWant: %v", g, w) + } + if g, w := sArray, []string{"test1", "alt", "test2"}; !cmp.Equal(g, w) { + t.Errorf("row value mismatch for string array\nGot: %v\nWant: %v", g, w) + } + if g, w := btArray, [][]byte{[]byte("testbytes1"), []byte("altbytes"), []byte("testbytes2")}; !cmp.Equal(g, w) { + t.Errorf("row value mismatch for bytes array\nGot: %v\nWant: %v", g, w) + } + if g, w := iArray, []int64{1, 0, 2}; !cmp.Equal(g, w) { + t.Errorf("row value mismatch for int array\nGot: %v\nWant: %v", g, w) + } + if g, w := f32Array, []float32{3.14, 0.0, -99.99}; !cmp.Equal(g, w) { + t.Errorf("row value mismatch for float32 array\nGot: %v\nWant: %v", g, w) + } + if g, w := fArray, []float64{6.626, 0.0, 10.01}; !cmp.Equal(g, w) { + t.Errorf("row value mismatch for float64 array\nGot: %v\nWant: %v", g, w) + } + if g, w := rArray, []spanner.NullNumeric{nullNumeric(true, "3.14"), nullNumeric(true, "1.0"), nullNumeric(true, "10.01")}; !cmp.Equal(g, w, cmp.AllowUnexported(big.Rat{}, big.Int{})) { + t.Errorf("row value mismatch for numeric array\n Got: %v\nWant: %v", g, w) + } + if g, w := dArray, []civil.Date{{Year: 2000, Month: 2, Day: 29}, {Year: 2000, Month: 1, Day: 1}, {Year: 2021, Month: 7, Day: 27}}; !cmp.Equal(g, w) { + t.Errorf("row value mismatch for date array\nGot: %v\nWant: %v", g, w) + } + if g, w := tsArray, []time.Time{ts1, tsAlt, ts2}; !cmp.Equal(g, w) { + t.Errorf("row value mismatch for timestamp array\n Got: %v\nWant: %v", g, w) + } + if g, w := jArray, []spanner.NullJSON{ + nullJson(true, `{"key1": "value1", "other-key1": ["value1", "value2"]}`), + nullJson(true, "{}"), + nullJson(true, `{"key2": "value2", "other-key2": ["value1", "value2"]}`), + }; !cmp.Equal(g, w) { + t.Errorf("row value mismatch for json array\n Got: %v\nWant: %v", g, w) + } + if g, w := uArray, []uuid.UUID{ + uuid.MustParse("d0546638-6d51-4d7c-a4a9-9062204ee5bb"), + uuid.MustParse("00000000-0000-0000-0000-000000000000"), + uuid.MustParse("0dd0f9b7-05af-48e0-a5b1-35432a01c6bf"), + }; !cmp.Equal(g, w) { + t.Errorf("row value mismatch for json array\n Got: %v\nWant: %v", g, w) + } + if g, w := len(pArray), 3; g != w { + t.Errorf("row value length mismatch for proto array\nGot: %v\nWant: %v", g, w) + } + wantSinger2ProtoEnum := pb.Genre_FOLK + wantSinger2ProtoMsg := pb.SingerInfo{ + SingerId: proto.Int64(2), + BirthDate: proto.String("February"), + Nationality: proto.String("Country2"), + Genre: &wantSinger2ProtoEnum, + } + gotSingerProto1 := pb.SingerInfo{} + if err := proto.Unmarshal(pArray[0], &gotSingerProto1); err != nil { + t.Fatalf("failed to unmarshal proto: %v", err) + } + gotSingerProtoAlt := pb.SingerInfo{} + if err := proto.Unmarshal(pArray[1], &gotSingerProtoAlt); err != nil { + t.Fatalf("failed to unmarshal proto: %v", err) + } + gotSingerProto2 := pb.SingerInfo{} + if err := proto.Unmarshal(pArray[2], &gotSingerProto2); err != nil { + t.Fatalf("failed to unmarshal proto: %v", err) + } + if g, w := &gotSingerProto1, &wantSingerProtoMsg; !cmp.Equal(g, w, cmpopts.IgnoreUnexported(pb.SingerInfo{})) { + t.Errorf("row value mismatch for proto\nGot: %v\nWant: %v", g, w) + } + if g, w := &gotSingerProtoAlt, &wantSingerProtoMsg; !cmp.Equal(g, w, cmpopts.IgnoreUnexported(pb.SingerInfo{})) { + t.Errorf("row value mismatch for proto\n Got: %v\nWant: %v", g, w) + } + if g, w := &gotSingerProto2, &wantSinger2ProtoMsg; !cmp.Equal(g, w, cmpopts.IgnoreUnexported(pb.SingerInfo{})) { + t.Errorf("row value mismatch for proto\nGot: %v\nWant: %v", g, w) } } - } else { - if wantParam.code != sppb.TypeCode_TYPE_CODE_UNSPECIFIED { - t.Errorf("no param type found for @%s", wantParam.name) + if rows.Err() != nil { + t.Fatal(rows.Err()) } - } - if val, ok := req.Params.Fields[wantParam.name]; ok { - var g interface{} - if wantParam.array { - g = val.GetListValue() - } else { - switch wantParam.code { - case sppb.TypeCode_BOOL: - g = val.GetBoolValue() - case sppb.TypeCode_FLOAT32: - g = val.GetNumberValue() - case sppb.TypeCode_FLOAT64: - g = val.GetNumberValue() - default: - g = val.GetStringValue() - } + requests := server.TestSpanner.DrainRequestsFromServer() + sqlRequests := testutil.RequestsOfType(requests, reflect.TypeOf(&sppb.ExecuteSqlRequest{})) + if g, w := len(sqlRequests), 1; g != w { + t.Fatalf("sql requests count mismatch\nGot: %v\nWant: %v", g, w) + } + req := sqlRequests[0].(*sppb.ExecuteSqlRequest) + // Strings should be sent as untyped values. + if g, w := len(req.ParamTypes), 20; g != w { + t.Fatalf("param types length mismatch\nGot: %v\nWant: %v", g, w) + } + if g, w := len(req.Params.Fields), 22; g != w { + t.Fatalf("params length mismatch\nGot: %v\nWant: %v", g, w) } - 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) + wantParams := []struct { + name string + code sppb.TypeCode + array bool + value interface{} + }{ + { + name: "bool", + code: sppb.TypeCode_BOOL, + value: true, + }, + { + name: "string", + code: sppb.TypeCode_TYPE_CODE_UNSPECIFIED, + value: "test", + }, + { + name: "bytes", + code: sppb.TypeCode_BYTES, + value: base64.StdEncoding.EncodeToString([]byte("testbytes")), + }, + { + name: "int64", + code: sppb.TypeCode_INT64, + value: "5", + }, + { + name: "float32", + code: sppb.TypeCode_FLOAT32, + value: float64(float32(3.14)), + }, + { + name: "float64", + code: sppb.TypeCode_FLOAT64, + value: 3.14, + }, + { + name: "numeric", + code: sppb.TypeCode_NUMERIC, + value: "6.626000000", + }, + { + name: "date", + code: sppb.TypeCode_DATE, + value: "2021-07-21", + }, + { + name: "timestamp", + 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: "uuid", + code: sppb.TypeCode_UUID, + value: `a4e71944-fe14-4047-9d0a-e68c281602e1`, + }, + { + name: "boolArray", + code: sppb.TypeCode_BOOL, + array: true, + value: &structpb.ListValue{Values: []*structpb.Value{ + {Kind: &structpb.Value_BoolValue{BoolValue: true}}, + {Kind: &structpb.Value_BoolValue{BoolValue: false}}, + }}, + }, + { + name: "stringArray", + code: sppb.TypeCode_TYPE_CODE_UNSPECIFIED, + array: true, + value: &structpb.ListValue{Values: []*structpb.Value{ + {Kind: &structpb.Value_StringValue{StringValue: "test1"}}, + {Kind: &structpb.Value_StringValue{StringValue: "test2"}}, + }}, + }, + { + name: "bytesArray", + code: sppb.TypeCode_BYTES, + array: true, + value: &structpb.ListValue{Values: []*structpb.Value{ + {Kind: &structpb.Value_StringValue{StringValue: base64.StdEncoding.EncodeToString([]byte("testbytes1"))}}, + {Kind: &structpb.Value_StringValue{StringValue: base64.StdEncoding.EncodeToString([]byte("testbytes2"))}}, + }}, + }, + { + name: "int64Array", + code: sppb.TypeCode_INT64, + array: true, + value: &structpb.ListValue{Values: []*structpb.Value{ + {Kind: &structpb.Value_StringValue{StringValue: "1"}}, + {Kind: &structpb.Value_StringValue{StringValue: "2"}}, + }}, + }, + { + name: "float32Array", + code: sppb.TypeCode_FLOAT32, + array: true, + value: &structpb.ListValue{Values: []*structpb.Value{ + {Kind: &structpb.Value_NumberValue{NumberValue: float64(float32(3.14))}}, + {Kind: &structpb.Value_NumberValue{NumberValue: float64(float32(-99.99))}}, + }}, + }, + { + name: "float64Array", + code: sppb.TypeCode_FLOAT64, + array: true, + value: &structpb.ListValue{Values: []*structpb.Value{ + {Kind: &structpb.Value_NumberValue{NumberValue: 6.626}}, + {Kind: &structpb.Value_NumberValue{NumberValue: 10.01}}, + }}, + }, + { + name: "numericArray", + code: sppb.TypeCode_NUMERIC, + array: true, + value: &structpb.ListValue{Values: []*structpb.Value{ + {Kind: &structpb.Value_StringValue{StringValue: "3.140000000"}}, + {Kind: &structpb.Value_StringValue{StringValue: "10.010000000"}}, + }}, + }, + { + name: "dateArray", + code: sppb.TypeCode_DATE, + array: true, + value: &structpb.ListValue{Values: []*structpb.Value{ + {Kind: &structpb.Value_StringValue{StringValue: "2000-02-29"}}, + {Kind: &structpb.Value_StringValue{StringValue: "2021-07-27"}}, + }}, + }, + { + name: "timestampArray", + code: sppb.TypeCode_TIMESTAMP, + array: true, + value: &structpb.ListValue{Values: []*structpb.Value{ + {Kind: &structpb.Value_StringValue{StringValue: "2021-07-21T21:07:59.3399118Z"}}, + {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_StringValue{StringValue: `{"key2":"value2","other-key2":["value1","value2"]}`}}, + }}, + }, + { + name: "uuidArray", + code: sppb.TypeCode_UUID, + array: true, + value: &structpb.ListValue{Values: []*structpb.Value{ + {Kind: &structpb.Value_StringValue{StringValue: `d0546638-6d51-4d7c-a4a9-9062204ee5bb`}}, + {Kind: &structpb.Value_StringValue{StringValue: `0dd0f9b7-05af-48e0-a5b1-35432a01c6bf`}}, + }}, + }, + } + for _, wantParam := range wantParams { + if pt, ok := req.ParamTypes[wantParam.name]; ok { + if wantParam.array { + if g, w := pt.Code, sppb.TypeCode_ARRAY; g != w { + t.Errorf("param type mismatch\nGot: %v\nWant: %v", g, w) + } + if g, w := pt.ArrayElementType.Code, wantParam.code; g != w { + t.Errorf("param array element type mismatch\nGot: %v\nWant: %v", g, w) + } + } else { + if g, w := pt.Code, wantParam.code; g != w { + t.Errorf("param type mismatch\nGot: %v\nWant: %v", g, w) + } + } + } else { + if wantParam.code != sppb.TypeCode_TYPE_CODE_UNSPECIFIED { + t.Errorf("no param type found for @%s", wantParam.name) + } } - } else { - if g != wantParam.value { - t.Errorf("param value mismatch\nGot: %v\nWant: %v", g, wantParam.value) + if val, ok := req.Params.Fields[wantParam.name]; ok { + var g interface{} + if wantParam.array { + g = val.GetListValue() + } else { + switch wantParam.code { + case sppb.TypeCode_BOOL: + g = val.GetBoolValue() + case sppb.TypeCode_FLOAT32: + g = val.GetNumberValue() + case sppb.TypeCode_FLOAT64: + g = val.GetNumberValue() + default: + g = val.GetStringValue() + } + } + 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) + } + } else { + if g != wantParam.value { + t.Errorf("param value mismatch\nGot: %v\nWant: %v", g, wantParam.value) + } + } + } else { + t.Errorf("no value found for param @%s", wantParam.name) } } - } else { - t.Errorf("no value found for param @%s", wantParam.name) - } + }) } }