From 751edad0edf4b4ba83f9be0e2243c094dc5f9b5c Mon Sep 17 00:00:00 2001 From: Marcus Gartner Date: Mon, 11 Jan 2021 17:05:10 -0800 Subject: [PATCH] opt: fold Null tuples in FoldColumnAccess It is possible for the input of a `ColumnAccess` expression to be `Null` when `FoldIndirection` has folded an `Indirection` expression with an out-of-bounds index to `Null`. `FoldColumnAccess` now correcty handles this case rather than erring. Release note (bug fix): A bug has been fixed that caused errors when accessing a tuple column (`tuple.column` syntax) of a tuples that could be statically determined to be null. --- pkg/sql/logictest/testdata/logic_test/tuple | 22 +++++++++++++++++++ pkg/sql/opt/norm/fold_constants_funcs.go | 11 ++++++++-- .../opt/norm/testdata/rules/fold_constants | 22 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/pkg/sql/logictest/testdata/logic_test/tuple b/pkg/sql/logictest/testdata/logic_test/tuple index 6d3136c2a0d3..60219aabd8d0 100644 --- a/pkg/sql/logictest/testdata/logic_test/tuple +++ b/pkg/sql/logictest/testdata/logic_test/tuple @@ -933,3 +933,25 @@ query B SELECT () = () ---- true + +# Regression tests for #58439. Ensure there are no errors when accessing columns +# of a null tuple. +subtest regression_58439 + +statement ok +CREATE TABLE t58439 (a INT, b INT); +INSERT INTO t58439 VALUES (1, 10), (2, 20), (3, 30); + +query II +SELECT (ARRAY[t58439.*][0]).* FROM t58439 +---- +NULL NULL +NULL NULL +NULL NULL + +query II +SELECT (ARRAY[t58439.*][2]).* FROM t58439 +---- +NULL NULL +NULL NULL +NULL NULL diff --git a/pkg/sql/opt/norm/fold_constants_funcs.go b/pkg/sql/opt/norm/fold_constants_funcs.go index 77c1f864b0f7..9b225e416ca0 100644 --- a/pkg/sql/opt/norm/fold_constants_funcs.go +++ b/pkg/sql/opt/norm/fold_constants_funcs.go @@ -311,12 +311,19 @@ func (c *CustomFuncs) FoldIndirection(input, index opt.ScalarExpr) opt.ScalarExp // It returns the referenced tuple field value, or nil if folding is not // possible or results in an error. func (c *CustomFuncs) FoldColumnAccess(input opt.ScalarExpr, idx memo.TupleOrdinal) opt.ScalarExpr { - // Case 1: The input is a static tuple constructor. + // Case 1: The input is NULL. This is possible when FoldIndirection has + // already folded an Indirection expression with an out-of-bounds index to + // Null. + if n, ok := input.(*memo.NullExpr); ok { + return c.f.ConstructNull(&n.Typ.TupleContents()[idx]) + } + + // Case 2: The input is a static tuple constructor. if tup, ok := input.(*memo.TupleExpr); ok { return tup.Elems[idx] } - // Case 2: The input is a constant DTuple. + // Case 3: The input is a constant DTuple. if memo.CanExtractConstDatum(input) { datum := memo.ExtractConstDatum(input) diff --git a/pkg/sql/opt/norm/testdata/rules/fold_constants b/pkg/sql/opt/norm/testdata/rules/fold_constants index 469db12f2a2a..3839c3caba0d 100644 --- a/pkg/sql/opt/norm/testdata/rules/fold_constants +++ b/pkg/sql/opt/norm/testdata/rules/fold_constants @@ -947,6 +947,28 @@ values ├── fd: ()-->(1) └── ('foo',) +# Fold when input is Null. This is possible when FoldIndirection has already +# folded an Indirection with an out-of-bounds index to Null. +norm expect=FoldColumnAccess +SELECT (ARRAY[(('foo', i) AS foo, bar)][0]).foo FROM a +---- +project + ├── columns: foo:7 + ├── fd: ()-->(7) + ├── scan a + └── projections + └── CAST(NULL AS STRING) [as=foo:7] + +norm expect=FoldColumnAccess +SELECT (ARRAY[(('foo', i) AS foo, bar)][0]).bar FROM a +---- +project + ├── columns: bar:7 + ├── fd: ()-->(7) + ├── scan a + └── projections + └── CAST(NULL AS INT8) [as=bar:7] + # -------------------------------------------------- # FoldEqualsAnyNull # --------------------------------------------------