Skip to content

Commit 776ec87

Browse files
craig[bot]yuzefovich
andcommitted
Merge #152466
152466: tree: harden DArray with NULL elements r=yuzefovich a=yuzefovich We've had `tree.DArray.HasNulls` and `tree.DArray.HasNonNulls` flags for many years, and they are used as a short-cut in some scenarios (initial use case was for pgwire serialization). However, we haven't had any validation of those flags, and given that we expose `DArray.Array` field, it's possible to construct a datum that doesn't have these flags set correctly (they do get set when using `DArray.Append`). I did a quick audit of existing spots and found a few where we didn't set the flags, but none seem concerning. To avoid bugs like this from slipping in, this commit changes the API to expose Get and Set methods, and then on every Get for the flag we now do the validation in test-only builds (the validation includes verifying NULL-related flags as well as the length of the array not exceeding MaxInt32). We could've taken this one step further and hidden `DArray.Array` altogether in favor of explicit Get and Set methods for elements, but I decided it's not worth the effort. I also decide to not force usage of `Append` method in a few places where we're appending to the underlying array in order to not have to deal with an explicit error checking. Additionally, this commit does some minor cleanup like pulling out a NULL element check outside of the loop and adding missing periods. Epic: None Release note: None Co-authored-by: Yahor Yuzefovich <[email protected]>
2 parents 3642319 + 9385671 commit 776ec87

28 files changed

+242
-210
lines changed

pkg/sql/execinfra/execexpr/expr_test.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,9 @@ func TestDeserializeExpressionConstantEval(t *testing.T) {
9696
t.Fatal(err)
9797
}
9898

99-
expected := &tree.DArray{
100-
ParamTyp: types.Int,
101-
Array: tree.Datums{tree.NewDInt(1), tree.NewDInt(2)},
102-
HasNonNulls: true,
103-
}
99+
expected := tree.NewDArrayFromDatums(
100+
types.Int, tree.Datums{tree.NewDInt(1), tree.NewDInt(2)},
101+
)
104102
if !reflect.DeepEqual(expr, expected) {
105103
t.Errorf("invalid expr '%v', expected '%v'", expr, expected)
106104
}

pkg/sql/opt/memo/constraint_builder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ func (cb *constraintsBuilder) buildSingleColumnConstraintConst(
223223

224224
case opt.ContainsOp:
225225
if arr, ok := datum.(*tree.DArray); ok {
226-
if arr.HasNulls {
226+
if arr.HasNulls() {
227227
return contradiction, true
228228
}
229229
}

pkg/sql/opt/memo/extract.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,11 @@ func ExtractConstDatum(e opt.Expr) tree.Datum {
8181

8282
case *ArrayExpr:
8383
elementType := t.Typ.ArrayContents()
84-
a := tree.NewDArray(elementType)
85-
a.Array = make(tree.Datums, len(t.Elems))
86-
for i := range a.Array {
87-
a.Array[i] = ExtractConstDatum(t.Elems[i])
88-
if a.Array[i] == tree.DNull {
89-
a.HasNulls = true
90-
} else {
91-
a.HasNonNulls = true
92-
}
84+
elements := make(tree.Datums, len(t.Elems))
85+
for i := range elements {
86+
elements[i] = ExtractConstDatum(t.Elems[i])
9387
}
94-
return a
88+
return tree.NewDArrayFromDatums(elementType, elements)
9589
}
9690
panic(errors.AssertionFailedf("non-const expression: %+v", e))
9791
}

pkg/sql/opt/memo/interner_test.go

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,13 @@ func TestInterner(t *testing.T) {
6868
Typ: tupleTyp3,
6969
}
7070

71-
arr1 := tree.NewDArray(tupTyp1)
72-
arr1.Array = tree.Datums{tup1, tup2}
73-
arr2 := tree.NewDArray(tupTyp2)
74-
arr2.Array = tree.Datums{tup2, tup1}
75-
arr3 := tree.NewDArray(tupTyp3)
76-
arr3.Array = tree.Datums{tup2, tup3}
77-
arr4 := tree.NewDArray(types.Int)
78-
arr4.Array = tree.Datums{tree.DNull}
79-
arr5 := tree.NewDArray(types.String)
80-
arr5.Array = tree.Datums{tree.DNull}
81-
arr6 := tree.NewDArray(types.Int)
82-
arr6.Array = tree.Datums{}
83-
arr7 := tree.NewDArray(types.String)
84-
arr7.Array = tree.Datums{}
71+
arr1 := tree.NewDArrayFromDatums(tupTyp1, tree.Datums{tup1, tup2})
72+
arr2 := tree.NewDArrayFromDatums(tupTyp2, tree.Datums{tup2, tup1})
73+
arr3 := tree.NewDArrayFromDatums(tupTyp3, tree.Datums{tup2, tup3})
74+
arr4 := tree.NewDArrayFromDatums(types.Int, tree.Datums{tree.DNull})
75+
arr5 := tree.NewDArrayFromDatums(types.String, tree.Datums{tree.DNull})
76+
arr6 := tree.NewDArrayFromDatums(types.Int, tree.Datums{})
77+
arr7 := tree.NewDArrayFromDatums(types.String, tree.Datums{})
8578

8679
dec1, _ := tree.ParseDDecimal("1.0")
8780
dec2, _ := tree.ParseDDecimal("1.0")

pkg/sql/opt/norm/fold_constants_funcs.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -182,17 +182,11 @@ func (c *CustomFuncs) IsListOfConstants(elems memo.ScalarListExpr) bool {
182182
// array as a Const datum with type TArray.
183183
func (c *CustomFuncs) FoldArray(elems memo.ScalarListExpr, typ *types.T) opt.ScalarExpr {
184184
elemType := typ.ArrayContents()
185-
a := tree.NewDArray(elemType)
186-
a.Array = make(tree.Datums, len(elems))
187-
for i := range a.Array {
188-
a.Array[i] = memo.ExtractConstDatum(elems[i])
189-
if a.Array[i] == tree.DNull {
190-
a.HasNulls = true
191-
} else {
192-
a.HasNonNulls = true
193-
}
185+
elements := make(tree.Datums, len(elems))
186+
for i := range elements {
187+
elements[i] = memo.ExtractConstDatum(elems[i])
194188
}
195-
return c.f.ConstructConst(a, typ)
189+
return c.f.ConstructConst(tree.NewDArrayFromDatums(elemType, elements), typ)
196190
}
197191

198192
// IsConstValueOrGroupOfConstValues returns true if the input is a constant,

pkg/sql/opt/props/histogram_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,10 +1178,9 @@ func BenchmarkHistogram(b *testing.B) {
11781178
case types.StringFamily:
11791179
return tree.NewDString(strconv.Itoa(i * 2))
11801180
case types.ArrayFamily:
1181-
arr := tree.NewDArray(t.ArrayContents())
1182-
arr.Array = make(tree.Datums, 1)
1183-
arr.HasNonNulls = true
1184-
arr.Array[0] = makeDatum(t.ArrayContents(), i)
1181+
arr := tree.NewDArrayFromDatums(
1182+
t.ArrayContents(), tree.Datums{makeDatum(t.ArrayContents(), i)},
1183+
)
11851184
return arr
11861185
}
11871186
panic(errors.AssertionFailedf("unsupported type"))

pkg/sql/pg_catalog.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2523,11 +2523,14 @@ https://www.postgresql.org/docs/9.6/view-pg-prepared-statements.html`,
25232523
paramNames := make([]string, len(placeholderTypes))
25242524

25252525
for i, placeholderType := range placeholderTypes {
2526+
// TODO(yuzefovich): we're including INT8 into array containing
2527+
// REGTYPE. Figure it out.
25262528
paramTypes.Array[i] = tree.NewDOidWithTypeAndName(
25272529
placeholderType.Oid(),
25282530
placeholderType,
25292531
placeholderType.SQLStandardName(),
25302532
)
2533+
paramTypes.SetHasNonNulls(true /* hasNonNulls */)
25312534
paramNames[i] = placeholderType.Name()
25322535
}
25332536

@@ -2624,7 +2627,7 @@ func addPgProcBuiltinRow(name string, addRow func(...tree.Datum) error) error {
26242627
}
26252628

26262629
getVariadicStringArray := func() tree.Datum {
2627-
return &tree.DArray{ParamTyp: types.String, Array: tree.Datums{proArgModeVariadic}}
2630+
return tree.NewDArrayFromDatums(types.String, tree.Datums{proArgModeVariadic})
26282631
}
26292632

26302633
var argmodes tree.Datum

pkg/sql/pgwire/types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ func writeBinaryDatumNotNull(
843843
}
844844
b.putInt32(ndims)
845845
hasNulls := 0
846-
if v.HasNulls {
846+
if v.HasNulls() {
847847
hasNulls = 1
848848
}
849849
oid := v.ParamTyp.Oid()

pkg/sql/rowenc/index_encoding.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,7 @@ func encodeContainingArrayInvertedIndexSpans(
900900
return invertedExpr, nil
901901
}
902902

903-
if val.HasNulls {
903+
if val.HasNulls() {
904904
// If there are any nulls, return empty spans. This is needed to ensure
905905
// that `SELECT ARRAY[NULL, 2] @> ARRAY[NULL, 2]` is false.
906906
return &inverted.SpanExpression{Tight: true, Unique: true}, nil
@@ -981,7 +981,7 @@ func encodeOverlapsArrayInvertedIndexSpans(
981981
// we cannot generate an inverted expression.
982982

983983
// TODO: This should be a contradiction which is treated as a no-op.
984-
if val.Array.Len() == 0 || !val.HasNonNulls {
984+
if val.Array.Len() == 0 || !val.HasNonNulls() {
985985
return inverted.NonInvertedColExpression{}, nil
986986
}
987987

pkg/sql/rowenc/index_encoding_test.go

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -371,34 +371,22 @@ func TestInvertedIndexKey(t *testing.T) {
371371
expectedKeysExcludingEmptyArray int
372372
}{
373373
{
374-
value: &tree.DArray{
375-
ParamTyp: types.Int,
376-
Array: tree.Datums{},
377-
},
374+
value: tree.NewDArrayFromDatums(types.Int, tree.Datums{}),
378375
expectedKeys: 1,
379376
expectedKeysExcludingEmptyArray: 0,
380377
},
381378
{
382-
value: &tree.DArray{
383-
ParamTyp: types.Int,
384-
Array: tree.Datums{tree.NewDInt(1)},
385-
},
379+
value: tree.NewDArrayFromDatums(types.Int, tree.Datums{tree.NewDInt(1)}),
386380
expectedKeys: 1,
387381
expectedKeysExcludingEmptyArray: 1,
388382
},
389383
{
390-
value: &tree.DArray{
391-
ParamTyp: types.Int,
392-
Array: tree.Datums{tree.NewDString("foo")},
393-
},
384+
value: tree.NewDArrayFromDatums(types.String, tree.Datums{tree.NewDString("foo")}),
394385
expectedKeys: 1,
395386
expectedKeysExcludingEmptyArray: 1,
396387
},
397388
{
398-
value: &tree.DArray{
399-
ParamTyp: types.Int,
400-
Array: tree.Datums{tree.NewDInt(1), tree.NewDInt(2), tree.NewDInt(1)},
401-
},
389+
value: tree.NewDArrayFromDatums(types.Int, tree.Datums{tree.NewDInt(1), tree.NewDInt(2), tree.NewDInt(1)}),
402390
// The keys should be deduplicated.
403391
expectedKeys: 2,
404392
expectedKeysExcludingEmptyArray: 2,
@@ -985,8 +973,8 @@ func TestEncodeOverlapsArrayInvertedIndexSpans(t *testing.T) {
985973

986974
rightArr, _ := right.(*tree.DArray)
987975
// An inverted expression can only be generated if the value array is
988-
// non-empty or contains atleast one non-NULL element.
989-
ok := rightArr.Len() > 0 && rightArr.HasNonNulls
976+
// non-empty or contains at least one non-NULL element.
977+
ok := rightArr.Len() > 0 && rightArr.HasNonNulls()
990978
// A unique span expression can be guaranteed when the input is of
991979
// the form:
992980
// Array A && Array containing one or more entries of same non-null

0 commit comments

Comments
 (0)