fix(bigquery): empty slice instead of nil slice for primitive repeated fields (#7315)
diff --git a/bigquery/integration_test.go b/bigquery/integration_test.go
index 95c3a51..239aea0 100644
--- a/bigquery/integration_test.go
+++ b/bigquery/integration_test.go
@@ -24,6 +24,7 @@
"math/big"
"net/http"
"os"
+ "reflect"
"sort"
"strings"
"testing"
@@ -2527,6 +2528,37 @@
}
}
+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) {
diff --git a/bigquery/iterator.go b/bigquery/iterator.go
index 9d177d1..942be42 100644
--- a/bigquery/iterator.go
+++ b/bigquery/iterator.go
@@ -140,8 +140,12 @@
// 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,
diff --git a/bigquery/value.go b/bigquery/value.go
index c178f8b..5c0165f 100644
--- a/bigquery/value.go
+++ b/bigquery/value.go
@@ -82,6 +82,9 @@
}
v = vs
}
+ if f.Repeated && (v == nil || reflect.ValueOf(v).IsNil()) {
+ v = []Value{}
+ }
m[f.Name] = v
}
diff --git a/bigquery/value_test.go b/bigquery/value_test.go
index 7211835..024f680 100644
--- a/bigquery/value_test.go
+++ b/bigquery/value_test.go
@@ -825,10 +825,12 @@
{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}},
}
@@ -837,11 +839,12 @@
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},
@@ -857,8 +860,9 @@
"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 {