Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(bigquery): empty slice instead of nil slice for primitive repeated fields #7315

Merged
merged 8 commits into from
May 17, 2024
32 changes: 32 additions & 0 deletions bigquery/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"math/big"
"net/http"
"os"
"reflect"
"sort"
"strings"
"testing"
Expand Down Expand Up @@ -2527,6 +2528,37 @@ func TestIntegration_QueryParameters(t *testing.T) {
}
}

func TestIntegration_QueryEmptyArrays(t *testing.T) {
if client == nil {
t.Skip("Integration tests skipped")
}
ctx := context.Background()

q := client.Query("SELECT ARRAY<string>[] as a, ARRAY<STRUCT<name string>>[] as b")
it, err := q.Read(ctx)
if err != nil {
t.Fatal(err)
}
for {
vals := map[string]Value{}
if err := it.Next(&vals); err != nil {
if errors.Is(err, iterator.Done) {
break
}
}

valueOfA := reflect.ValueOf(vals["a"])
if testutil.Equal(vals["a"], nil) || valueOfA.IsNil() {
t.Fatalf("expected empty string array to not return nil, but found %v %v %T", valueOfA, vals["a"], vals["a"])
}

valueOfB := reflect.ValueOf(vals["b"])
if testutil.Equal(vals["b"], nil) || valueOfB.IsNil() {
t.Fatalf("expected empty struct array to not return nil, but found %v %v %T", valueOfB, vals["b"], vals["b"])
}
}
}

// This test can be merged with the TestIntegration_QueryParameters as soon as support for explicit typed query parameter lands.
// To test timestamps with different formats, we need to be able to specify the type explicitly.
func TestIntegration_TimestampFormat(t *testing.T) {
Expand Down
8 changes: 6 additions & 2 deletions bigquery/iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,12 @@ type pageFetcher func(ctx context.Context, _ *rowSource, _ Schema, startIndex ui
// See https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#numeric-type
// for more on NUMERIC.
//
// A repeated field corresponds to a slice or array of the element type. A STRUCT
// type (RECORD or nested schema) corresponds to a nested struct or struct pointer.
// A repeated field corresponds to a slice or array of the element type. BigQuery translates
// NULL arrays into an empty array, so we follow that behavior.
// See https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#array_nulls
// for more about NULL and empty arrays.
//
// A STRUCT type (RECORD or nested schema) corresponds to a nested struct or struct pointer.
// All calls to Next on the same iterator must use the same struct type.
//
// It is an error to attempt to read a BigQuery NULL value into a struct field,
Expand Down
3 changes: 3 additions & 0 deletions bigquery/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ func loadMap(m map[string]Value, vals []Value, s Schema) {
}
v = vs
}
if f.Repeated && (v == nil || reflect.ValueOf(v).IsNil()) {
v = []Value{}
}

m[f.Name] = v
}
Expand Down
20 changes: 12 additions & 8 deletions bigquery/value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -825,10 +825,12 @@ func TestValueMap(t *testing.T) {
{Name: "i", Type: IntegerFieldType},
{Name: "f", Type: FloatFieldType},
{Name: "b", Type: BooleanFieldType},
{Name: "n", Type: RecordFieldType, Schema: ns},
{Name: "sn", Type: StringFieldType, Repeated: true},
{Name: "r", Type: RecordFieldType, Schema: ns},
{Name: "rn", Type: RecordFieldType, Schema: ns, Repeated: true},
}
in := []Value{"x", 7, 3.14, true,
[]Value{"a", "b"},
[]Value{1, 2},
[]Value{[]Value{3, 4}, []Value{5, 6}},
}
Expand All @@ -837,11 +839,12 @@ func TestValueMap(t *testing.T) {
t.Fatal(err)
}
want := map[string]Value{
"s": "x",
"i": 7,
"f": 3.14,
"b": true,
"n": map[string]Value{"x": 1, "y": 2},
"s": "x",
"i": 7,
"f": 3.14,
"b": true,
"sn": []Value{"a", "b"},
"r": map[string]Value{"x": 1, "y": 2},
"rn": []Value{
map[string]Value{"x": 3, "y": 4},
map[string]Value{"x": 5, "y": 6},
Expand All @@ -857,8 +860,9 @@ func TestValueMap(t *testing.T) {
"i": nil,
"f": nil,
"b": nil,
"n": nil,
"rn": nil,
"sn": []Value{},
"r": nil,
"rn": []Value{},
}
var vm2 valueMap
if err := vm2.Load(in, schema); err != nil {
Expand Down
Loading