Skip to content

Commit b371210

Browse files
authored
fix(bigquery): empty slice instead of nil slice for primitive repeated fields (#7315)
1 parent d481e0e commit b371210

File tree

4 files changed

+53
-10
lines changed

4 files changed

+53
-10
lines changed

bigquery/integration_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"math/big"
2525
"net/http"
2626
"os"
27+
"reflect"
2728
"sort"
2829
"strings"
2930
"testing"
@@ -2527,6 +2528,37 @@ func TestIntegration_QueryParameters(t *testing.T) {
25272528
}
25282529
}
25292530

2531+
func TestIntegration_QueryEmptyArrays(t *testing.T) {
2532+
if client == nil {
2533+
t.Skip("Integration tests skipped")
2534+
}
2535+
ctx := context.Background()
2536+
2537+
q := client.Query("SELECT ARRAY<string>[] as a, ARRAY<STRUCT<name string>>[] as b")
2538+
it, err := q.Read(ctx)
2539+
if err != nil {
2540+
t.Fatal(err)
2541+
}
2542+
for {
2543+
vals := map[string]Value{}
2544+
if err := it.Next(&vals); err != nil {
2545+
if errors.Is(err, iterator.Done) {
2546+
break
2547+
}
2548+
}
2549+
2550+
valueOfA := reflect.ValueOf(vals["a"])
2551+
if testutil.Equal(vals["a"], nil) || valueOfA.IsNil() {
2552+
t.Fatalf("expected empty string array to not return nil, but found %v %v %T", valueOfA, vals["a"], vals["a"])
2553+
}
2554+
2555+
valueOfB := reflect.ValueOf(vals["b"])
2556+
if testutil.Equal(vals["b"], nil) || valueOfB.IsNil() {
2557+
t.Fatalf("expected empty struct array to not return nil, but found %v %v %T", valueOfB, vals["b"], vals["b"])
2558+
}
2559+
}
2560+
}
2561+
25302562
// This test can be merged with the TestIntegration_QueryParameters as soon as support for explicit typed query parameter lands.
25312563
// To test timestamps with different formats, we need to be able to specify the type explicitly.
25322564
func TestIntegration_TimestampFormat(t *testing.T) {

bigquery/iterator.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,12 @@ type pageFetcher func(ctx context.Context, _ *rowSource, _ Schema, startIndex ui
140140
// See https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#numeric-type
141141
// for more on NUMERIC.
142142
//
143-
// A repeated field corresponds to a slice or array of the element type. A STRUCT
144-
// type (RECORD or nested schema) corresponds to a nested struct or struct pointer.
143+
// A repeated field corresponds to a slice or array of the element type. BigQuery translates
144+
// NULL arrays into an empty array, so we follow that behavior.
145+
// See https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#array_nulls
146+
// for more about NULL and empty arrays.
147+
//
148+
// A STRUCT type (RECORD or nested schema) corresponds to a nested struct or struct pointer.
145149
// All calls to Next on the same iterator must use the same struct type.
146150
//
147151
// It is an error to attempt to read a BigQuery NULL value into a struct field,

bigquery/value.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ func loadMap(m map[string]Value, vals []Value, s Schema) {
8282
}
8383
v = vs
8484
}
85+
if f.Repeated && (v == nil || reflect.ValueOf(v).IsNil()) {
86+
v = []Value{}
87+
}
8588

8689
m[f.Name] = v
8790
}

bigquery/value_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -825,10 +825,12 @@ func TestValueMap(t *testing.T) {
825825
{Name: "i", Type: IntegerFieldType},
826826
{Name: "f", Type: FloatFieldType},
827827
{Name: "b", Type: BooleanFieldType},
828-
{Name: "n", Type: RecordFieldType, Schema: ns},
828+
{Name: "sn", Type: StringFieldType, Repeated: true},
829+
{Name: "r", Type: RecordFieldType, Schema: ns},
829830
{Name: "rn", Type: RecordFieldType, Schema: ns, Repeated: true},
830831
}
831832
in := []Value{"x", 7, 3.14, true,
833+
[]Value{"a", "b"},
832834
[]Value{1, 2},
833835
[]Value{[]Value{3, 4}, []Value{5, 6}},
834836
}
@@ -837,11 +839,12 @@ func TestValueMap(t *testing.T) {
837839
t.Fatal(err)
838840
}
839841
want := map[string]Value{
840-
"s": "x",
841-
"i": 7,
842-
"f": 3.14,
843-
"b": true,
844-
"n": map[string]Value{"x": 1, "y": 2},
842+
"s": "x",
843+
"i": 7,
844+
"f": 3.14,
845+
"b": true,
846+
"sn": []Value{"a", "b"},
847+
"r": map[string]Value{"x": 1, "y": 2},
845848
"rn": []Value{
846849
map[string]Value{"x": 3, "y": 4},
847850
map[string]Value{"x": 5, "y": 6},
@@ -857,8 +860,9 @@ func TestValueMap(t *testing.T) {
857860
"i": nil,
858861
"f": nil,
859862
"b": nil,
860-
"n": nil,
861-
"rn": nil,
863+
"sn": []Value{},
864+
"r": nil,
865+
"rn": []Value{},
862866
}
863867
var vm2 valueMap
864868
if err := vm2.Load(in, schema); err != nil {

0 commit comments

Comments
 (0)