Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions driver_with_mockserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6021,3 +6021,95 @@ func filterBeginReadOnlyRequests(requests []interface{}) []*sppb.BeginTransactio
}
return res
}

func TestArrayOfStruct(t *testing.T) {
t.Parallel()

db, server, teardown := setupTestDBConnection(t)
defer teardown()

query := "SELECT ARRAY(SELECT AS STRUCT * FROM UNNEST(@entries)) AS entries"

fields := []*sppb.StructType_Field{
{Name: "ID", Type: &sppb.Type{Code: sppb.TypeCode_INT64}},
{Name: "Name", Type: &sppb.Type{Code: sppb.TypeCode_STRING}},
}
structType := &sppb.StructType{Fields: fields}

metadata := &sppb.ResultSetMetadata{
RowType: &sppb.StructType{
Fields: []*sppb.StructType_Field{
{
Name: "entries",
Type: &sppb.Type{
Code: sppb.TypeCode_ARRAY,
ArrayElementType: &sppb.Type{
Code: sppb.TypeCode_STRUCT,
StructType: structType,
},
},
},
},
},
}

struct1 := &structpb.ListValue{Values: []*structpb.Value{
{Kind: &structpb.Value_StringValue{StringValue: "0"}},
{Kind: &structpb.Value_StringValue{StringValue: "Hello"}},
}}
struct2 := &structpb.ListValue{Values: []*structpb.Value{
{Kind: &structpb.Value_StringValue{StringValue: "1"}},
{Kind: &structpb.Value_StringValue{StringValue: "World"}},
}}
arrayVal := &structpb.Value{Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{Values: []*structpb.Value{
{Kind: &structpb.Value_ListValue{ListValue: struct1}},
{Kind: &structpb.Value_ListValue{ListValue: struct2}},
}}}}

resultSet := &sppb.ResultSet{
Metadata: metadata,
Rows: []*structpb.ListValue{
{Values: []*structpb.Value{arrayVal}},
},
}

_ = server.TestSpanner.PutStatementResult(query, &testutil.StatementResult{
Type: testutil.StatementResultResultSet,
ResultSet: resultSet,
})

type Entry struct {
ID int64
Name string
}
entries := []Entry{
{ID: 0, Name: "Hello"},
{ID: 1, Name: "World"},
}

ctx := context.Background()
rows, err := db.QueryContext(ctx, query, sql.Named("entries", entries), ExecOptions{DecodeOption: DecodeOptionProto})
if err != nil {
t.Fatal(err)
}
defer rows.Close()

if !rows.Next() {
t.Fatalf("expected rows.Next() to return true, got false, err: %v", rows.Err())
}
var col spanner.GenericColumnValue
if err := rows.Scan(&col); err != nil {
t.Fatalf("Scan returned error: %v", err)
}
var allEntries []*Entry
if err := col.Decode(&allEntries); err != nil {
t.Fatalf("Decode returned error: %v", err)
}
expected := []*Entry{
{ID: 0, Name: "Hello"},
{ID: 1, Name: "World"},
}
if !reflect.DeepEqual(allEntries, expected) {
t.Errorf("allEntries mismatch\n Got: %+v\nWant: %+v", allEntries, expected)
}
Comment on lines +6112 to +6114

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using reflect.DeepEqual on slices of pointers (like []*Entry) will print pointer addresses (e.g., 0xc000...) instead of the actual struct values in the test failure output, making debugging difficult. Since github.com/google/go-cmp/cmp is already imported and used in this file, using cmp.Diff is more idiomatic and provides a readable, field-by-field diff of the mismatch.

Suggested change
if !reflect.DeepEqual(allEntries, expected) {
t.Errorf("allEntries mismatch\n Got: %+v\nWant: %+v", allEntries, expected)
}
if diff := cmp.Diff(expected, allEntries); diff != "" {
t.Errorf("allEntries mismatch (-want +got):\n%s", diff)
}

}
23 changes: 17 additions & 6 deletions examples/emulator_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"io"
"log"
"net"
"os"
"time"

Expand Down Expand Up @@ -107,19 +108,29 @@ func startEmulator() error {
if err := cli.ContainerStart(ctx, containerId, container.StartOptions{}); err != nil {
return err
}
// Wait max 10 seconds or until the emulator is running.
for c := 0; c < 20; c++ {
// Always wait at least 500 milliseconds to ensure that the emulator is actually ready, as the
// state can be reported as ready, while the emulator (or network interface) is actually not ready.
<-time.After(500 * time.Millisecond)
// Wait max 10 seconds or until the emulator is running and port 9010 is open.
var portReady bool
for c := 0; c < 40; c++ {
resp, err := cli.ContainerInspect(ctx, containerId)
if err != nil {
return fmt.Errorf("failed to inspect container state: %v", err)
}
if resp.State.Running {
break
// Try to connect to localhost:9010
conn, err := net.DialTimeout("tcp", "localhost:9010", 250*time.Millisecond)
if err == nil {
_ = conn.Close()
portReady = true
break
}
}
<-time.After(250 * time.Millisecond)
}
if !portReady {
return fmt.Errorf("emulator did not start listening on port 9010 in time")
}
// Give the emulator gRPC server a moment to fully initialize after the TCP port opens.
<-time.After(1500 * time.Millisecond)

return nil
}
Expand Down
8 changes: 4 additions & 4 deletions transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,9 @@ func TestTransactionTimeout(t *testing.T) {
defer teardown()
ctx := context.Background()

server.TestSpanner.PutExecutionTime(testutil.MethodExecuteStreamingSql, testutil.SimulatedExecutionTime{MinimumExecutionTime: 50 * time.Millisecond})
server.TestSpanner.PutExecutionTime(testutil.MethodExecuteStreamingSql, testutil.SimulatedExecutionTime{MinimumExecutionTime: 200 * time.Millisecond})
tx, _ := db.BeginTx(ctx, &sql.TxOptions{})
if _, err := tx.ExecContext(ctx, "set local transaction_timeout=10ms"); err != nil {
if _, err := tx.ExecContext(ctx, "set local transaction_timeout=100ms"); err != nil {
t.Fatal(err)
}
_, err := tx.ExecContext(ctx, testutil.UpdateBarSetFoo)
Expand Down Expand Up @@ -367,14 +367,14 @@ func TestTransactionTimeoutSecondStatement(t *testing.T) {
ctx := context.Background()

tx, _ := db.BeginTx(ctx, &sql.TxOptions{})
if _, err := tx.ExecContext(ctx, "set local transaction_timeout=40ms"); err != nil {
if _, err := tx.ExecContext(ctx, "set local transaction_timeout=200ms"); err != nil {
t.Fatal(err)
}
if _, err := tx.ExecContext(ctx, testutil.UpdateBarSetFoo); err != nil {
t.Fatal(err)
}

server.TestSpanner.PutExecutionTime(testutil.MethodExecuteStreamingSql, testutil.SimulatedExecutionTime{MinimumExecutionTime: 60 * time.Millisecond})
server.TestSpanner.PutExecutionTime(testutil.MethodExecuteStreamingSql, testutil.SimulatedExecutionTime{MinimumExecutionTime: 300 * time.Millisecond})
rows, err := tx.QueryContext(ctx, testutil.SelectFooFromBar, ExecOptions{DirectExecuteQuery: true})
if rows != nil {
_ = rows.Close()
Expand Down
Loading