From f429b55001abe3fdd531af9e53d7b819ccd4a158 Mon Sep 17 00:00:00 2001 From: Andrew Kimball Date: Fri, 16 Nov 2018 20:22:49 -0800 Subject: [PATCH] opt: Fix panic when hoisting expr with correlated subquery Fixes #32270. The panic occurs when hoisting an expression that has both a correlated and an uncorrelated subquery. The current code panics when calling Reconstruct on the relational input to the uncorrelated subquery. The fix is to test for relational inputs and skip over them. They are not correlated, and so no hoisting needs to be done within their subtree. Release note (sql change): Fix panic when expression contains both a correlated and uncorrelated subquery. --- pkg/sql/opt/norm/decorrelate.go | 16 +++++- pkg/sql/opt/norm/testdata/rules/decorrelate | 63 +++++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/pkg/sql/opt/norm/decorrelate.go b/pkg/sql/opt/norm/decorrelate.go index d928b364642c..876938446547 100644 --- a/pkg/sql/opt/norm/decorrelate.go +++ b/pkg/sql/opt/norm/decorrelate.go @@ -793,8 +793,20 @@ func (r *subqueryHoister) hoistAll(scalar opt.ScalarExpr) opt.ScalarExpr { } return r.f.Reconstruct(scalar, func(nd opt.Expr) opt.Expr { - // Recursively hoist subqueries in each child that contains them. - return r.hoistAll(nd.(opt.ScalarExpr)) + // Recursively hoist subqueries in each scalar child that contains them. + // Skip relational children, since only subquery scalar operators have a + // relational child, and either: + // + // 1. The child is correlated, and therefore was handled above by hoisting + // and rewriting (and therefore won't ever get here), + // + // 2. Or the child is uncorrelated, and therefore should be skipped, since + // uncorrelated subqueries are not hoisted. + // + if scalarChild, ok := nd.(opt.ScalarExpr); ok { + return r.hoistAll(scalarChild) + } + return nd }).(opt.ScalarExpr) } diff --git a/pkg/sql/opt/norm/testdata/rules/decorrelate b/pkg/sql/opt/norm/testdata/rules/decorrelate index 7cfbf4f5870c..005e2ddbe139 100644 --- a/pkg/sql/opt/norm/testdata/rules/decorrelate +++ b/pkg/sql/opt/norm/testdata/rules/decorrelate @@ -3235,6 +3235,69 @@ project └── projections └── NOT CASE WHEN bool_or AND (scalar IS NOT NULL) THEN true WHEN bool_or IS NULL THEN false END [type=bool, outer=(9,11)] +# Regress issue #32270: Panic when expression contains both correlated and +# uncorrelated subquery. +opt expect=HoistSelectSubquery +SELECT * FROM a WHERE EXISTS(SELECT * FROM xy) OR EXISTS(SELECT * FROM xy WHERE x=k) +---- +project + ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── key: (1) + ├── fd: (1)-->(2-5) + └── select + ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) true_agg:11(bool) + ├── key: (1) + ├── fd: (1)-->(2-5,11) + ├── group-by + │ ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) true_agg:11(bool) + │ ├── grouping columns: k:1(int!null) + │ ├── internal-ordering: +1 + │ ├── key: (1) + │ ├── fd: (1)-->(2-5,11) + │ ├── left-join (merge) + │ │ ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) xy.x:8(int) true:10(bool) + │ │ ├── left ordering: +1 + │ │ ├── right ordering: +8 + │ │ ├── key: (1,8) + │ │ ├── fd: (1)-->(2-5), ()~~>(10), (1,8)-->(10) + │ │ ├── ordering: +1 + │ │ ├── scan a + │ │ │ ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + │ │ │ ├── key: (1) + │ │ │ ├── fd: (1)-->(2-5) + │ │ │ └── ordering: +1 + │ │ ├── project + │ │ │ ├── columns: true:10(bool!null) xy.x:8(int!null) + │ │ │ ├── key: (8) + │ │ │ ├── fd: ()-->(10) + │ │ │ ├── ordering: +8 opt(10) [provided: +8] + │ │ │ ├── scan xy + │ │ │ │ ├── columns: xy.x:8(int!null) + │ │ │ │ ├── key: (8) + │ │ │ │ └── ordering: +8 + │ │ │ └── projections + │ │ │ └── true [type=bool] + │ │ └── filters (true) + │ └── aggregations + │ ├── const-not-null-agg [type=bool, outer=(10)] + │ │ └── variable: true [type=bool] + │ ├── const-agg [type=int, outer=(2)] + │ │ └── variable: i [type=int] + │ ├── const-agg [type=float, outer=(3)] + │ │ └── variable: f [type=float] + │ ├── const-agg [type=string, outer=(4)] + │ │ └── variable: s [type=string] + │ └── const-agg [type=jsonb, outer=(5)] + │ └── variable: j [type=jsonb] + └── filters + └── or [type=bool, outer=(11)] + ├── exists [type=bool] + │ └── scan xy + │ ├── columns: xy.x:6(int!null) xy.y:7(int) + │ ├── key: (6) + │ └── fd: (6)-->(7) + └── true_agg IS NOT NULL [type=bool] + # -------------------------------------------------- # HoistProjectSubquery # --------------------------------------------------