From 9e2ff61dd02f24cd25e9cf95579a198d11eb8916 Mon Sep 17 00:00:00 2001 From: Andrew Kimball Date: Mon, 2 Apr 2018 21:03:57 -0700 Subject: [PATCH 1/2] opt: Add support for weak keys Add a new WeakKeys logical property. Weak keys are derived from unique indexes and sometimes from operators (e.g. GroupBy). Cache the weak keys in metadata and propagate them through various relational operators. Release note: None --- pkg/sql/opt/exec/execbuilder/testdata/scan | 6 +- pkg/sql/opt/memo/expr_view.go | 26 +- pkg/sql/opt/memo/logical_props.go | 22 ++ pkg/sql/opt/memo/logical_props_factory.go | 68 +++- .../opt/memo/logical_props_factory_test.go | 31 +- .../opt/memo/testdata/logprops/constraints | 8 +- pkg/sql/opt/memo/testdata/logprops/groupby | 143 +++++++- pkg/sql/opt/memo/testdata/logprops/join | 114 ++++--- pkg/sql/opt/memo/testdata/logprops/limit | 44 ++- pkg/sql/opt/memo/testdata/logprops/project | 33 +- pkg/sql/opt/memo/testdata/logprops/scalar | 10 +- pkg/sql/opt/memo/testdata/logprops/scan | 37 +- pkg/sql/opt/memo/testdata/logprops/select | 10 +- pkg/sql/opt/memo/testdata/logprops/set | 23 +- pkg/sql/opt/memo/testdata/memo | 13 +- pkg/sql/opt/metadata.go | 50 ++- pkg/sql/opt/metadata_column.go | 42 +++ pkg/sql/opt/metadata_test.go | 67 ++++ pkg/sql/opt/norm/testdata/bool | 102 ++++-- pkg/sql/opt/norm/testdata/combo | 302 ++++++++++++----- pkg/sql/opt/norm/testdata/comp | 43 ++- pkg/sql/opt/norm/testdata/join | 72 ++-- pkg/sql/opt/norm/testdata/limit | 18 +- pkg/sql/opt/norm/testdata/project | 320 ++++++++++++------ pkg/sql/opt/norm/testdata/scalar | 6 +- pkg/sql/opt/norm/testdata/select | 160 ++++++--- pkg/sql/opt/xform/testdata/coster/join | 5 +- pkg/sql/opt/xform/testdata/coster/scan | 3 +- pkg/sql/opt/xform/testdata/coster/select | 4 +- pkg/sql/opt/xform/testdata/rules/limit | 7 +- pkg/sql/opt/xform/testdata/rules/scan | 3 + pkg/sql/opt/xform/testdata/rules/select | 30 +- 32 files changed, 1355 insertions(+), 467 deletions(-) diff --git a/pkg/sql/opt/exec/execbuilder/testdata/scan b/pkg/sql/opt/exec/execbuilder/testdata/scan index c3c71a34564a..b775f53997a0 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/scan +++ b/pkg/sql/opt/exec/execbuilder/testdata/scan @@ -13,7 +13,8 @@ SELECT * FROM t.a scan a ├── columns: x:1(int!null) y:2(float) s:3(string) ├── stats: [rows=1000] - └── cost: 1000.00 + ├── cost: 1000.00 + └── keys: (1) exec-explain SELECT * FROM t.a @@ -37,7 +38,8 @@ SELECT s, x FROM t.a scan a ├── columns: s:3(string) x:1(int!null) ├── stats: [rows=1000] - └── cost: 1000.00 + ├── cost: 1000.00 + └── keys: (1) exec-explain SELECT s, x FROM t.a diff --git a/pkg/sql/opt/memo/expr_view.go b/pkg/sql/opt/memo/expr_view.go index ef520cb44d7c..6e85791f1e61 100644 --- a/pkg/sql/opt/memo/expr_view.go +++ b/pkg/sql/opt/memo/expr_view.go @@ -208,6 +208,9 @@ const ( // ExprFmtHideConstraints does not show inferred constraints in the output. ExprFmtHideConstraints + // ExprFmtHideKeys does not show keys in the output. + ExprFmtHideKeys + // ExprFmtHideAll shows only the most basic properties of the expression. ExprFmtHideAll ExprFmtFlags = (1 << iota) - 1 ) @@ -256,7 +259,6 @@ func (ev ExprView) formatRelational(tp treeprinter.Node, flags ExprFmtFlags) { logProps := ev.Logical() tp = tp.Child(buf.String()) - buf.Reset() // If a particular column presentation is required of the expression, then // print columns using that information. @@ -320,6 +322,11 @@ func (ev ExprView) formatRelational(tp treeprinter.Node, flags ExprFmtFlags) { tp.Childf("cost: %.2f", ev.lookupBestExpr().cost) } + // Format weak keys. + if !flags.HasFlags(ExprFmtHideKeys) { + ev.formatWeakKeys(tp) + } + if physProps.Ordering.Defined() { tp.Childf("ordering: %s", physProps.Ordering.String()) } @@ -416,6 +423,23 @@ func (ev ExprView) formatPresentation(tp treeprinter.Node, presentation Presenta tp.Child(buf.String()) } +func (ev ExprView) formatWeakKeys(tp treeprinter.Node) { + var buf bytes.Buffer + rel := ev.Logical().Relational + for i, key := range rel.WeakKeys { + if i != 0 { + buf.WriteRune(' ') + } + if !key.SubsetOf(rel.NotNullCols) { + buf.WriteString("weak") + } + buf.WriteString(key.String()) + } + if buf.Len() != 0 { + tp.Childf("keys: %s", buf.String()) + } +} + // MatchesTupleOfConstants returns true if the expression is a TupleOp with // ConstValue children. func MatchesTupleOfConstants(ev ExprView) bool { diff --git a/pkg/sql/opt/memo/logical_props.go b/pkg/sql/opt/memo/logical_props.go index 42eb5d0d1efb..0fcd4fbfd01f 100644 --- a/pkg/sql/opt/memo/logical_props.go +++ b/pkg/sql/opt/memo/logical_props.go @@ -55,6 +55,28 @@ type RelationalProps struct { // derived from filters that are NULL-intolerant. NotNullCols opt.ColSet + // WeakKeys are the column sets which form weak keys and are subsets of the + // expression's output columns. A weak key set cannot contain any other weak + // key set (it would be redundant). + // + // A column set is a key if no two rows are equal after projection onto that + // set. This definition treats NULL as if were equal to NULL, so two rows + // having duplicate NULL values would *not* qualify as key rows. Therefore, + // in the usual case, the key columns are also not nullable. The simplest + // example of a key is the primary key for a table (recall that all of the + // columns of the primary key are defined to be NOT NULL). + // + // A weak key is similar to a key, with the difference that NULL values are + // treated as *not equal* to other NULL values. Therefore, two rows having + // duplicate NULL values could still qualify as weak key rows. A UNIQUE index + // on a table is a weak key and possibly a key if all of the columns are NOT + // NULL. A weak key is a key if "(WeakKeys[i] & NotNullCols) == WeakKeys[i]". + // + // An empty key is valid (an empty key implies there is at most one row). Note + // that an empty key is always the only key in the set, since it's a subset of + // every other key (i.e. every other key would be redundant). + WeakKeys opt.WeakKeys + // OuterCols is the set of columns that are referenced by variables within // this relational sub-expression, but are not bound within the scope of // the expression. For example: diff --git a/pkg/sql/opt/memo/logical_props_factory.go b/pkg/sql/opt/memo/logical_props_factory.go index c428ba226d84..bd3b9dcd22f5 100644 --- a/pkg/sql/opt/memo/logical_props_factory.go +++ b/pkg/sql/opt/memo/logical_props_factory.go @@ -101,6 +101,10 @@ func (f logicalPropsFactory) constructScanProps(ev ExprView) LogicalProps { } } + // Initialize weak keys from the table schema. + props.Relational.WeakKeys = md.TableWeakKeys(def.Table) + filterWeakKeys(props.Relational) + // TODO: Need actual number of rows. if def.Constraint != nil { props.Relational.Stats.RowCount = 100 @@ -121,11 +125,8 @@ func (f logicalPropsFactory) constructSelectProps(ev ExprView) LogicalProps { inputProps := ev.lookupChildGroup(0).logical.Relational - // Inherit output columns from input. - props.Relational.OutputCols = inputProps.OutputCols - - // Inherit not null columns from input. - props.Relational.NotNullCols = inputProps.NotNullCols + // Inherit input properties as starting point. + *props.Relational = *inputProps // TODO: Need better estimate based on actual filter conditions. props.Relational.Stats.RowCount = inputProps.Stats.RowCount / 10 @@ -141,10 +142,18 @@ func (f logicalPropsFactory) constructProjectProps(ev ExprView) LogicalProps { // Use output columns from projection list. props.Relational.OutputCols = opt.ColListToSet(ev.Child(1).Private().(opt.ColList)) - // Inherit not null columns from input. + // Inherit not null columns from input, but only use those that are also + // output columns. props.Relational.NotNullCols = inputProps.NotNullCols filterNullCols(props.Relational) + // Inherit outer columns from input. + props.Relational.OuterCols = inputProps.OuterCols + + // Inherit weak keys that are composed entirely of output columns. + props.Relational.WeakKeys = inputProps.WeakKeys + filterWeakKeys(props.Relational) + props.Relational.Stats.RowCount = inputProps.Stats.RowCount return props @@ -210,10 +219,25 @@ func (f logicalPropsFactory) constructGroupByProps(ev ExprView) LogicalProps { props.Relational.NotNullCols = inputProps.NotNullCols.Copy() props.Relational.NotNullCols.IntersectionWith(groupingColSet) + // Scalar group by has no grouping columns and always a single row. if groupingColSet.Empty() { - // Scalar group by. + // Any combination of columns is a weak key when there is one row. + props.Relational.WeakKeys = opt.WeakKeys{groupingColSet} props.Relational.Stats.RowCount = 1 } else { + // The grouping columns always form a key because the GroupBy operation + // eliminates all duplicates. The result WeakKeys property either contains + // only the grouping column set, or else it contains one or more weak keys + // that are strict subsets of the grouping column set. This is because + // the grouping column set contains every output column (except aggregate + // columns, which aren't relevant since they're newly synthesized). + if inputProps.WeakKeys.ContainsSubsetOf(groupingColSet) { + props.Relational.WeakKeys = inputProps.WeakKeys + filterWeakKeys(props.Relational) + } else { + props.Relational.WeakKeys = opt.WeakKeys{groupingColSet} + } + // TODO: Need better estimate. props.Relational.Stats.RowCount = inputProps.Stats.RowCount / 10 } @@ -324,11 +348,35 @@ func (f logicalPropsFactory) constructScalarProps(ev ExprView) LogicalProps { return props } -// filterNullCols will ensure that the set of null columns is a subset of the -// output columns. It respects immutability by making a copy of the null -// columns if they need to be updated. +// filterNullCols ensures that the set of null columns is a subset of the output +// columns. It respects immutability by making a copy of the null columns if +// they need to be updated. func filterNullCols(props *RelationalProps) { if !props.NotNullCols.SubsetOf(props.OutputCols) { props.NotNullCols = props.NotNullCols.Intersection(props.OutputCols) } } + +// filterWeakKeys ensures that each weak key is a subset of the output columns. +// It respects immutability by making a copy of the weak keys if they need to be +// updated. +func filterWeakKeys(props *RelationalProps) { + var filtered opt.WeakKeys + for i, weakKey := range props.WeakKeys { + // Discard weak keys that have columns that are not part of the output + // column set. + if !weakKey.SubsetOf(props.OutputCols) { + if filtered == nil { + filtered = make(opt.WeakKeys, i, len(props.WeakKeys)-1) + copy(filtered, props.WeakKeys[:i]) + } + } else { + if filtered != nil { + filtered = append(filtered, weakKey) + } + } + } + if filtered != nil { + props.WeakKeys = filtered + } +} diff --git a/pkg/sql/opt/memo/logical_props_factory_test.go b/pkg/sql/opt/memo/logical_props_factory_test.go index 2a4f3ff4b10e..67ef0a39a44f 100644 --- a/pkg/sql/opt/memo/logical_props_factory_test.go +++ b/pkg/sql/opt/memo/logical_props_factory_test.go @@ -24,7 +24,6 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/opt/norm" "github.com/cockroachdb/cockroach/pkg/sql/opt/testutils" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" - "github.com/cockroachdb/cockroach/pkg/util/treeprinter" ) func TestLogicalPropsFactory(t *testing.T) { @@ -61,14 +60,14 @@ func TestLogicalJoinProps(t *testing.T) { testLogicalProps(t, f.Metadata(), ev, expected) } - joinFunc(opt.InnerJoinApplyOp, "a.x:1(int!null) a.y:2(int) b.x:3(int!null) b.z:4(int!null)\n") - joinFunc(opt.LeftJoinApplyOp, "a.x:1(int!null) a.y:2(int) b.x:3(int) b.z:4(int)\n") - joinFunc(opt.RightJoinApplyOp, "a.x:1(int) a.y:2(int) b.x:3(int!null) b.z:4(int!null)\n") - joinFunc(opt.FullJoinApplyOp, "a.x:1(int) a.y:2(int) b.x:3(int) b.z:4(int)\n") - joinFunc(opt.SemiJoinOp, "a.x:1(int!null) a.y:2(int)\n") - joinFunc(opt.SemiJoinApplyOp, "a.x:1(int!null) a.y:2(int)\n") - joinFunc(opt.AntiJoinOp, "a.x:1(int!null) a.y:2(int)\n") - joinFunc(opt.AntiJoinApplyOp, "a.x:1(int!null) a.y:2(int)\n") + joinFunc(opt.InnerJoinApplyOp, "a.x:1(int!null) a.y:2(int) b.x:3(int!null) b.z:4(int!null)") + joinFunc(opt.LeftJoinApplyOp, "a.x:1(int!null) a.y:2(int) b.x:3(int) b.z:4(int)") + joinFunc(opt.RightJoinApplyOp, "a.x:1(int) a.y:2(int) b.x:3(int!null) b.z:4(int!null)") + joinFunc(opt.FullJoinApplyOp, "a.x:1(int) a.y:2(int) b.x:3(int) b.z:4(int)") + joinFunc(opt.SemiJoinOp, "a.x:1(int!null) a.y:2(int)") + joinFunc(opt.SemiJoinApplyOp, "a.x:1(int!null) a.y:2(int)") + joinFunc(opt.AntiJoinOp, "a.x:1(int!null) a.y:2(int)") + joinFunc(opt.AntiJoinApplyOp, "a.x:1(int!null) a.y:2(int)") } func constructScanOpDef(md *opt.Metadata, tabID opt.TableID) *memo.ScanOpDef { @@ -81,18 +80,10 @@ func constructScanOpDef(md *opt.Metadata, tabID opt.TableID) *memo.ScanOpDef { func testLogicalProps(t *testing.T, md *opt.Metadata, ev memo.ExprView, expected string) { t.Helper() + actual := ev.String() - logical := ev.Logical() - if logical.Relational == nil { - panic("only relational properties are supported") - } - - tp := treeprinter.New() - logical.FormatColSet(tp, md, "", logical.Relational.OutputCols) - actual := strings.Trim(tp.String(), " ") - - if actual != expected { - t.Fatalf("\nexpected: %s\nactual : %s", expected, actual) + if !strings.Contains(actual, expected) { + t.Fatalf("\nexpected to contain: %s\nactual:\n%s", expected, actual) } } diff --git a/pkg/sql/opt/memo/testdata/logprops/constraints b/pkg/sql/opt/memo/testdata/logprops/constraints index 79ffd9323066..86fda3b929d4 100644 --- a/pkg/sql/opt/memo/testdata/logprops/constraints +++ b/pkg/sql/opt/memo/testdata/logprops/constraints @@ -213,9 +213,11 @@ SELECT * FROM kuv WHERE u > 1::INT select ├── columns: k:1(int!null) u:2(float) v:3(string) ├── stats: [rows=100] + ├── keys: (1) ├── scan kuv │ ├── columns: kuv.k:1(int!null) kuv.u:2(float) kuv.v:3(string) - │ └── stats: [rows=1000] + │ ├── stats: [rows=1000] + │ └── keys: (1) └── filters [type=bool, outer=(2)] └── gt [type=bool, outer=(2)] ├── variable: kuv.u [type=float, outer=(2)] @@ -227,9 +229,11 @@ SELECT * FROM kuv WHERE v <= 'foo' AND v >= 'bar' select ├── columns: k:1(int!null) u:2(float) v:3(string) ├── stats: [rows=100] + ├── keys: (1) ├── scan kuv │ ├── columns: kuv.k:1(int!null) kuv.u:2(float) kuv.v:3(string) - │ └── stats: [rows=1000] + │ ├── stats: [rows=1000] + │ └── keys: (1) └── filters [type=bool, outer=(3), constraints=(/3: [/'bar' - /'foo']; tight)] ├── le [type=bool, outer=(3), constraints=(/3: (/NULL - /'foo']; tight)] │ ├── variable: kuv.v [type=string, outer=(3)] diff --git a/pkg/sql/opt/memo/testdata/logprops/groupby b/pkg/sql/opt/memo/testdata/logprops/groupby index 000904508610..89c203a89115 100644 --- a/pkg/sql/opt/memo/testdata/logprops/groupby +++ b/pkg/sql/opt/memo/testdata/logprops/groupby @@ -1,32 +1,48 @@ exec-ddl -CREATE TABLE a (x INT PRIMARY KEY, y INT, z FLOAT NOT NULL) +CREATE TABLE a (x INT PRIMARY KEY, y INT, z FLOAT NOT NULL, s STRING, UNIQUE (s DESC, z)) ---- TABLE a ├── x int not null ├── y int ├── z float not null - └── INDEX primary - └── x int not null + ├── s string + ├── INDEX primary + │ └── x int not null + └── INDEX secondary + ├── s string desc + ├── z float not null + └── x int not null (storing) build SELECT a.y, SUM(a.z), a.x, False FROM a GROUP BY a.x, a.y ---- project - ├── columns: y:2(int) column4:4(float) x:1(int!null) column5:5(bool) + ├── columns: y:2(int) column5:5(float) x:1(int!null) column6:6(bool) ├── stats: [rows=100] + ├── keys: (1) ├── group-by - │ ├── columns: a.x:1(int!null) a.y:2(int) column4:4(float) + │ ├── columns: a.x:1(int!null) a.y:2(int) column5:5(float) │ ├── grouping columns: a.x:1(int!null) a.y:2(int) │ ├── stats: [rows=100] - │ ├── scan a + │ ├── keys: (1) + │ ├── project │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.z:3(float!null) - │ │ └── stats: [rows=1000] + │ │ ├── stats: [rows=1000] + │ │ ├── keys: (1) + │ │ ├── scan a + │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.z:3(float!null) a.s:4(string) + │ │ │ ├── stats: [rows=1000] + │ │ │ └── keys: (1) weak(3,4) + │ │ └── projections [outer=(1-3)] + │ │ ├── variable: a.x [type=int, outer=(1)] + │ │ ├── variable: a.y [type=int, outer=(2)] + │ │ └── variable: a.z [type=float, outer=(3)] │ └── aggregations [outer=(3)] │ └── function: sum [type=float, outer=(3)] │ └── variable: a.z [type=float, outer=(3)] - └── projections [outer=(1,2,4)] + └── projections [outer=(1,2,5)] ├── variable: a.y [type=int, outer=(2)] - ├── variable: column4 [type=float, outer=(4)] + ├── variable: column5 [type=float, outer=(5)] ├── variable: a.x [type=int, outer=(1)] └── false [type=bool] @@ -35,14 +51,17 @@ build SELECT SUM(a.x), MAX(a.y) FROM a ---- group-by - ├── columns: column4:4(decimal) column5:5(int) + ├── columns: column5:5(decimal) column6:6(int) ├── stats: [rows=1] + ├── keys: () ├── project │ ├── columns: a.x:1(int!null) a.y:2(int) │ ├── stats: [rows=1000] + │ ├── keys: (1) │ ├── scan a - │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.z:3(float!null) - │ │ └── stats: [rows=1000] + │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.z:3(float!null) a.s:4(string) + │ │ ├── stats: [rows=1000] + │ │ └── keys: (1) weak(3,4) │ └── projections [outer=(1,2)] │ ├── variable: a.x [type=int, outer=(1)] │ └── variable: a.y [type=int, outer=(2)] @@ -51,3 +70,103 @@ group-by │ └── variable: a.x [type=int, outer=(1)] └── function: max [type=int, outer=(2)] └── variable: a.y [type=int, outer=(2)] + +# Group by unique index columns. +build +SELECT s FROM a GROUP BY z, s +---- +project + ├── columns: s:4(string) + ├── stats: [rows=100] + ├── group-by + │ ├── columns: a.z:3(float!null) a.s:4(string) + │ ├── grouping columns: a.z:3(float!null) a.s:4(string) + │ ├── stats: [rows=100] + │ ├── keys: weak(3,4) + │ ├── project + │ │ ├── columns: a.z:3(float!null) a.s:4(string) + │ │ ├── stats: [rows=1000] + │ │ ├── keys: weak(3,4) + │ │ ├── scan a + │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.z:3(float!null) a.s:4(string) + │ │ │ ├── stats: [rows=1000] + │ │ │ └── keys: (1) weak(3,4) + │ │ └── projections [outer=(3,4)] + │ │ ├── variable: a.z [type=float, outer=(3)] + │ │ └── variable: a.s [type=string, outer=(4)] + │ └── aggregations + └── projections [outer=(4)] + └── variable: a.s [type=string, outer=(4)] + +# Group by columns that otherwise wouldn't be weak key. +build +SELECT y, SUM(z) FROM a GROUP BY z, y +---- +project + ├── columns: y:2(int) column5:5(float) + ├── stats: [rows=100] + ├── group-by + │ ├── columns: a.y:2(int) a.z:3(float!null) column5:5(float) + │ ├── grouping columns: a.y:2(int) a.z:3(float!null) + │ ├── stats: [rows=100] + │ ├── keys: weak(2,3) + │ ├── project + │ │ ├── columns: a.z:3(float!null) a.y:2(int) + │ │ ├── stats: [rows=1000] + │ │ ├── scan a + │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.z:3(float!null) a.s:4(string) + │ │ │ ├── stats: [rows=1000] + │ │ │ └── keys: (1) weak(3,4) + │ │ └── projections [outer=(2,3)] + │ │ ├── variable: a.z [type=float, outer=(3)] + │ │ └── variable: a.y [type=int, outer=(2)] + │ └── aggregations [outer=(3)] + │ └── function: sum [type=float, outer=(3)] + │ └── variable: a.z [type=float, outer=(3)] + └── projections [outer=(2,5)] + ├── variable: a.y [type=int, outer=(2)] + └── variable: column5 [type=float, outer=(5)] + +# Group by column that is subset of unique index. +build +SELECT z, MAX(s) FROM a GROUP BY z +---- +group-by + ├── columns: z:3(float!null) column5:5(string) + ├── grouping columns: a.z:3(float!null) + ├── stats: [rows=100] + ├── keys: (3) + ├── project + │ ├── columns: a.z:3(float!null) a.s:4(string) + │ ├── stats: [rows=1000] + │ ├── keys: weak(3,4) + │ ├── scan a + │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.z:3(float!null) a.s:4(string) + │ │ ├── stats: [rows=1000] + │ │ └── keys: (1) weak(3,4) + │ └── projections [outer=(3,4)] + │ ├── variable: a.z [type=float, outer=(3)] + │ └── variable: a.s [type=string, outer=(4)] + └── aggregations [outer=(4)] + └── function: max [type=string, outer=(4)] + └── variable: a.s [type=string, outer=(4)] + +# Group by all columns. +build +SELECT s FROM a GROUP BY a.* +---- +project + ├── columns: s:4(string) + ├── stats: [rows=100] + ├── group-by + │ ├── columns: a.x:1(int!null) a.y:2(int) a.z:3(float!null) a.s:4(string) + │ ├── grouping columns: a.x:1(int!null) a.y:2(int) a.z:3(float!null) a.s:4(string) + │ ├── stats: [rows=100] + │ ├── keys: (1) weak(3,4) + │ ├── scan a + │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.z:3(float!null) a.s:4(string) + │ │ ├── stats: [rows=1000] + │ │ └── keys: (1) weak(3,4) + │ └── aggregations + └── projections [outer=(4)] + └── variable: a.s [type=string, outer=(4)] diff --git a/pkg/sql/opt/memo/testdata/logprops/join b/pkg/sql/opt/memo/testdata/logprops/join index b6d73281a424..4babb834700f 100644 --- a/pkg/sql/opt/memo/testdata/logprops/join +++ b/pkg/sql/opt/memo/testdata/logprops/join @@ -1,11 +1,17 @@ exec-ddl -CREATE TABLE a (x INT PRIMARY KEY, y INT) +CREATE TABLE a (x INT PRIMARY KEY, y INT, s STRING, d DECIMAL NOT NULL, UNIQUE (s DESC, d)) ---- TABLE a ├── x int not null ├── y int - └── INDEX primary - └── x int not null + ├── s string + ├── d decimal not null + ├── INDEX primary + │ └── x int not null + └── INDEX secondary + ├── s string desc + ├── d decimal not null + └── x int not null (storing) exec-ddl CREATE TABLE b (x INT, z INT NOT NULL) @@ -21,84 +27,112 @@ build SELECT *, rowid FROM a INNER JOIN b ON a.x=b.x ---- inner-join - ├── columns: x:1(int!null) y:2(int) x:3(int) z:4(int!null) rowid:5(int!null) + ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) x:5(int) z:6(int!null) rowid:7(int!null) ├── stats: [rows=100000] ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) - │ └── stats: [rows=1000] + │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:3(string) a.d:4(decimal!null) + │ ├── stats: [rows=1000] + │ └── keys: (1) weak(3,4) ├── scan b - │ ├── columns: b.x:3(int) b.z:4(int!null) b.rowid:5(int!null) - │ └── stats: [rows=1000] - └── eq [type=bool, outer=(1,3)] + │ ├── columns: b.x:5(int) b.z:6(int!null) b.rowid:7(int!null) + │ ├── stats: [rows=1000] + │ └── keys: (7) + └── eq [type=bool, outer=(1,5)] ├── variable: a.x [type=int, outer=(1)] - └── variable: b.x [type=int, outer=(3)] + └── variable: b.x [type=int, outer=(5)] build SELECT *, rowid FROM a LEFT JOIN b ON a.x=b.x ---- left-join - ├── columns: x:1(int!null) y:2(int) x:3(int) z:4(int) rowid:5(int) + ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) x:5(int) z:6(int) rowid:7(int) ├── stats: [rows=100000] ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) - │ └── stats: [rows=1000] + │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:3(string) a.d:4(decimal!null) + │ ├── stats: [rows=1000] + │ └── keys: (1) weak(3,4) ├── scan b - │ ├── columns: b.x:3(int) b.z:4(int!null) b.rowid:5(int!null) - │ └── stats: [rows=1000] - └── eq [type=bool, outer=(1,3)] + │ ├── columns: b.x:5(int) b.z:6(int!null) b.rowid:7(int!null) + │ ├── stats: [rows=1000] + │ └── keys: (7) + └── eq [type=bool, outer=(1,5)] ├── variable: a.x [type=int, outer=(1)] - └── variable: b.x [type=int, outer=(3)] + └── variable: b.x [type=int, outer=(5)] build SELECT *, rowid FROM a RIGHT JOIN b ON a.x=b.x ---- right-join - ├── columns: x:1(int) y:2(int) x:3(int) z:4(int!null) rowid:5(int!null) + ├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) x:5(int) z:6(int!null) rowid:7(int!null) ├── stats: [rows=100000] ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) - │ └── stats: [rows=1000] + │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:3(string) a.d:4(decimal!null) + │ ├── stats: [rows=1000] + │ └── keys: (1) weak(3,4) ├── scan b - │ ├── columns: b.x:3(int) b.z:4(int!null) b.rowid:5(int!null) - │ └── stats: [rows=1000] - └── eq [type=bool, outer=(1,3)] + │ ├── columns: b.x:5(int) b.z:6(int!null) b.rowid:7(int!null) + │ ├── stats: [rows=1000] + │ └── keys: (7) + └── eq [type=bool, outer=(1,5)] ├── variable: a.x [type=int, outer=(1)] - └── variable: b.x [type=int, outer=(3)] + └── variable: b.x [type=int, outer=(5)] build SELECT *, rowid FROM a FULL JOIN b ON a.x=b.x ---- full-join - ├── columns: x:1(int) y:2(int) x:3(int) z:4(int) rowid:5(int) + ├── columns: x:1(int) y:2(int) s:3(string) d:4(decimal) x:5(int) z:6(int) rowid:7(int) ├── stats: [rows=100000] ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) - │ └── stats: [rows=1000] + │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:3(string) a.d:4(decimal!null) + │ ├── stats: [rows=1000] + │ └── keys: (1) weak(3,4) ├── scan b - │ ├── columns: b.x:3(int) b.z:4(int!null) b.rowid:5(int!null) - │ └── stats: [rows=1000] - └── eq [type=bool, outer=(1,3)] + │ ├── columns: b.x:5(int) b.z:6(int!null) b.rowid:7(int!null) + │ ├── stats: [rows=1000] + │ └── keys: (7) + └── eq [type=bool, outer=(1,5)] ├── variable: a.x [type=int, outer=(1)] - └── variable: b.x [type=int, outer=(3)] + └── variable: b.x [type=int, outer=(5)] build SELECT * FROM a, b ---- project - ├── columns: x:1(int!null) y:2(int) x:3(int) z:4(int!null) + ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) x:5(int) z:6(int!null) ├── stats: [rows=1000000] ├── inner-join - │ ├── columns: a.x:1(int!null) a.y:2(int) b.x:3(int) b.z:4(int!null) b.rowid:5(int!null) + │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:3(string) a.d:4(decimal!null) b.x:5(int) b.z:6(int!null) b.rowid:7(int!null) │ ├── stats: [rows=1000000] │ ├── scan a - │ │ ├── columns: a.x:1(int!null) a.y:2(int) - │ │ └── stats: [rows=1000] + │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:3(string) a.d:4(decimal!null) + │ │ ├── stats: [rows=1000] + │ │ └── keys: (1) weak(3,4) │ ├── scan b - │ │ ├── columns: b.x:3(int) b.z:4(int!null) b.rowid:5(int!null) - │ │ └── stats: [rows=1000] + │ │ ├── columns: b.x:5(int) b.z:6(int!null) b.rowid:7(int!null) + │ │ ├── stats: [rows=1000] + │ │ └── keys: (7) │ └── true [type=bool] - └── projections [outer=(1-4)] + └── projections [outer=(1-6)] ├── variable: a.x [type=int, outer=(1)] ├── variable: a.y [type=int, outer=(2)] - ├── variable: b.x [type=int, outer=(3)] - └── variable: b.z [type=int, outer=(4)] + ├── variable: a.s [type=string, outer=(3)] + ├── variable: a.d [type=decimal, outer=(4)] + ├── variable: b.x [type=int, outer=(5)] + └── variable: b.z [type=int, outer=(6)] + +build +SELECT * FROM a, a +---- +inner-join + ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) x:5(int!null) y:6(int) s:7(string) d:8(decimal!null) + ├── stats: [rows=1000000] + ├── scan a + │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:3(string) a.d:4(decimal!null) + │ ├── stats: [rows=1000] + │ └── keys: (1) weak(3,4) + ├── scan a + │ ├── columns: a.x:5(int!null) a.y:6(int) a.s:7(string) a.d:8(decimal!null) + │ ├── stats: [rows=1000] + │ └── keys: (5) weak(7,8) + └── true [type=bool] diff --git a/pkg/sql/opt/memo/testdata/logprops/limit b/pkg/sql/opt/memo/testdata/logprops/limit index 68ab49036f98..169c8b07bebf 100644 --- a/pkg/sql/opt/memo/testdata/logprops/limit +++ b/pkg/sql/opt/memo/testdata/logprops/limit @@ -1,53 +1,65 @@ exec-ddl -CREATE TABLE a (x INT PRIMARY KEY, y INT) +CREATE TABLE a (x INT PRIMARY KEY, y INT, z FLOAT NOT NULL, s STRING, UNIQUE (s DESC, z)) ---- TABLE a ├── x int not null ├── y int - └── INDEX primary - └── x int not null + ├── z float not null + ├── s string + ├── INDEX primary + │ └── x int not null + └── INDEX secondary + ├── s string desc + ├── z float not null + └── x int not null (storing) build SELECT * FROM a LIMIT 1 ---- limit - ├── columns: x:1(int!null) y:2(int) + ├── columns: x:1(int!null) y:2(int) z:3(float!null) s:4(string) ├── stats: [rows=1] + ├── keys: (1) weak(3,4) ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) - │ └── stats: [rows=1000] + │ ├── columns: a.x:1(int!null) a.y:2(int) a.z:3(float!null) a.s:4(string) + │ ├── stats: [rows=1000] + │ └── keys: (1) weak(3,4) └── const: 1 [type=int] build SELECT * FROM a LIMIT (SELECT 1) ---- limit - ├── columns: x:1(int!null) y:2(int) + ├── columns: x:1(int!null) y:2(int) z:3(float!null) s:4(string) ├── stats: [rows=1000] + ├── keys: (1) weak(3,4) ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) - │ └── stats: [rows=1000] - └── subquery [type=int, outer=(3)] + │ ├── columns: a.x:1(int!null) a.y:2(int) a.z:3(float!null) a.s:4(string) + │ ├── stats: [rows=1000] + │ └── keys: (1) weak(3,4) + └── subquery [type=int, outer=(5)] ├── max1-row - │ ├── columns: column3:3(int) + │ ├── columns: column5:5(int) │ ├── stats: [rows=1] │ └── project - │ ├── columns: column3:3(int) + │ ├── columns: column5:5(int) │ ├── stats: [rows=1] │ ├── values │ │ ├── stats: [rows=1] │ │ └── tuple [type=tuple{}] │ └── projections │ └── const: 1 [type=int] - └── variable: column3 [type=int, outer=(3)] + └── variable: column5 [type=int, outer=(5)] build SELECT * FROM a LIMIT 0 ---- limit - ├── columns: x:1(int!null) y:2(int) + ├── columns: x:1(int!null) y:2(int) z:3(float!null) s:4(string) ├── stats: [rows=1000] + ├── keys: (1) weak(3,4) ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) - │ └── stats: [rows=1000] + │ ├── columns: a.x:1(int!null) a.y:2(int) a.z:3(float!null) a.s:4(string) + │ ├── stats: [rows=1000] + │ └── keys: (1) weak(3,4) └── const: 0 [type=int] diff --git a/pkg/sql/opt/memo/testdata/logprops/project b/pkg/sql/opt/memo/testdata/logprops/project index adc8dae43016..87a2f89626a5 100644 --- a/pkg/sql/opt/memo/testdata/logprops/project +++ b/pkg/sql/opt/memo/testdata/logprops/project @@ -1,21 +1,29 @@ exec-ddl -CREATE TABLE a (x INT PRIMARY KEY, y INT) +CREATE TABLE a (x INT PRIMARY KEY, y INT, s STRING, d DECIMAL NOT NULL, UNIQUE (s DESC, d)) ---- TABLE a ├── x int not null ├── y int - └── INDEX primary - └── x int not null + ├── s string + ├── d decimal not null + ├── INDEX primary + │ └── x int not null + └── INDEX secondary + ├── s string desc + ├── d decimal not null + └── x int not null (storing) build SELECT a.y, a.x+1, 1, a.x FROM a ---- project - ├── columns: y:2(int) column3:3(int) column4:4(int) x:1(int!null) + ├── columns: y:2(int) column5:5(int) column6:6(int) x:1(int!null) ├── stats: [rows=1000] + ├── keys: (1) ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) - │ └── stats: [rows=1000] + │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:3(string) a.d:4(decimal!null) + │ ├── stats: [rows=1000] + │ └── keys: (1) weak(3,4) └── projections [outer=(1,2)] ├── variable: a.y [type=int, outer=(2)] ├── plus [type=int, outer=(1)] @@ -23,3 +31,16 @@ project │ └── const: 1 [type=int] ├── const: 1 [type=int] └── variable: a.x [type=int, outer=(1)] + +build +SELECT s FROM a +---- +project + ├── columns: s:3(string) + ├── stats: [rows=1000] + ├── scan a + │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:3(string) a.d:4(decimal!null) + │ ├── stats: [rows=1000] + │ └── keys: (1) weak(3,4) + └── projections [outer=(3)] + └── variable: a.s [type=string, outer=(3)] diff --git a/pkg/sql/opt/memo/testdata/logprops/scalar b/pkg/sql/opt/memo/testdata/logprops/scalar index 45076fe49776..33e592df98f9 100644 --- a/pkg/sql/opt/memo/testdata/logprops/scalar +++ b/pkg/sql/opt/memo/testdata/logprops/scalar @@ -23,9 +23,11 @@ SELECT * FROM a WHERE x < 5 select ├── columns: x:1(int!null) y:2(int) ├── stats: [rows=100] + ├── keys: (1) ├── scan a │ ├── columns: a.x:1(int!null) a.y:2(int) - │ └── stats: [rows=1000] + │ ├── stats: [rows=1000] + │ └── keys: (1) └── lt [type=bool, outer=(1), constraints=(/1: (/NULL - /4]; tight)] ├── variable: a.x [type=int, outer=(1)] └── const: 5 [type=int] @@ -41,10 +43,12 @@ project │ ├── stats: [rows=1000000] │ ├── scan a │ │ ├── columns: a.x:1(int!null) a.y:2(int) - │ │ └── stats: [rows=1000] + │ │ ├── stats: [rows=1000] + │ │ └── keys: (1) │ ├── scan b │ │ ├── columns: b.x:3(int) b.z:4(int!null) b.rowid:5(int!null) - │ │ └── stats: [rows=1000] + │ │ ├── stats: [rows=1000] + │ │ └── keys: (5) │ └── true [type=bool] └── projections [outer=(1,2,5)] ├── eq [type=bool, outer=(1,2)] diff --git a/pkg/sql/opt/memo/testdata/logprops/scan b/pkg/sql/opt/memo/testdata/logprops/scan index bd9152265b0f..4d0f44793583 100644 --- a/pkg/sql/opt/memo/testdata/logprops/scan +++ b/pkg/sql/opt/memo/testdata/logprops/scan @@ -1,13 +1,17 @@ exec-ddl -CREATE TABLE a (x INT PRIMARY KEY, y INT, s STRING, d DECIMAL NOT NULL) +CREATE TABLE a (x INT PRIMARY KEY, y INT, s STRING, d DECIMAL NOT NULL, UNIQUE (s DESC, d)) ---- TABLE a ├── x int not null ├── y int ├── s string ├── d decimal not null - └── INDEX primary - └── x int not null + ├── INDEX primary + │ └── x int not null + └── INDEX secondary + ├── s string desc + ├── d decimal not null + └── x int not null (storing) exec-ddl CREATE TABLE b (x INT, z INT NOT NULL) @@ -24,7 +28,8 @@ SELECT * FROM a ---- scan a ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) - └── stats: [rows=1000] + ├── stats: [rows=1000] + └── keys: (1) weak(3,4) build SELECT * FROM b @@ -34,7 +39,8 @@ project ├── stats: [rows=1000] ├── scan b │ ├── columns: b.x:1(int) b.z:2(int!null) b.rowid:3(int!null) - │ └── stats: [rows=1000] + │ ├── stats: [rows=1000] + │ └── keys: (3) └── projections [outer=(1,2)] ├── variable: b.x [type=int, outer=(1)] └── variable: b.z [type=int, outer=(2)] @@ -45,18 +51,20 @@ SELECT s, x FROM a ---- scan a ├── columns: s:3(string) x:1(int!null) - └── stats: [rows=1000] + ├── stats: [rows=1000] + └── keys: (1) -# Test constrained span. +# Test constrained scan. opt SELECT s, x FROM a WHERE x=1 ---- scan a ├── columns: s:3(string) x:1(int!null) ├── constraint: /1: [/1 - /1] - └── stats: [rows=100] + ├── stats: [rows=100] + └── keys: (1) -# Test limited span. +# Test limited scan. opt SELECT s, x FROM a WHERE x=1 LIMIT 2 ---- @@ -64,4 +72,13 @@ scan a ├── columns: s:3(string) x:1(int!null) ├── constraint: /1: [/1 - /1] ├── limit: 2 - └── stats: [rows=2] + ├── stats: [rows=2] + └── keys: (1) + +# Test case where there are no weak keys available. +opt +SELECT d FROM a +---- +scan a + ├── columns: d:4(decimal!null) + └── stats: [rows=1000] diff --git a/pkg/sql/opt/memo/testdata/logprops/select b/pkg/sql/opt/memo/testdata/logprops/select index d0235d802076..96568cabab2d 100644 --- a/pkg/sql/opt/memo/testdata/logprops/select +++ b/pkg/sql/opt/memo/testdata/logprops/select @@ -23,9 +23,11 @@ SELECT * FROM a WHERE x=1 select ├── columns: x:1(int!null) y:2(int) ├── stats: [rows=100] + ├── keys: (1) ├── scan a │ ├── columns: a.x:1(int!null) a.y:2(int) - │ └── stats: [rows=1000] + │ ├── stats: [rows=1000] + │ └── keys: (1) └── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] ├── variable: a.x [type=int, outer=(1)] └── const: 1 [type=int] @@ -44,10 +46,12 @@ project │ │ ├── stats: [rows=1000000] │ │ ├── scan a │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) - │ │ │ └── stats: [rows=1000] + │ │ │ ├── stats: [rows=1000] + │ │ │ └── keys: (1) │ │ ├── scan b │ │ │ ├── columns: b.x:3(int) b.z:4(int!null) b.rowid:5(int!null) - │ │ │ └── stats: [rows=1000] + │ │ │ ├── stats: [rows=1000] + │ │ │ └── keys: (5) │ │ └── true [type=bool] │ └── eq [type=bool, outer=(1,3)] │ ├── variable: a.x [type=int, outer=(1)] diff --git a/pkg/sql/opt/memo/testdata/logprops/set b/pkg/sql/opt/memo/testdata/logprops/set index 0f816019adb2..cfdcdf9fabef 100644 --- a/pkg/sql/opt/memo/testdata/logprops/set +++ b/pkg/sql/opt/memo/testdata/logprops/set @@ -27,13 +27,15 @@ union ├── stats: [rows=2000] ├── scan a │ ├── columns: a.x:1(int!null) a.y:2(int) - │ └── stats: [rows=1000] + │ ├── stats: [rows=1000] + │ └── keys: (1) └── project ├── columns: b.x:3(int) b.z:4(int!null) ├── stats: [rows=1000] ├── scan b │ ├── columns: b.x:3(int) b.z:4(int!null) b.rowid:5(int!null) - │ └── stats: [rows=1000] + │ ├── stats: [rows=1000] + │ └── keys: (5) └── projections [outer=(3,4)] ├── variable: b.x [type=int, outer=(3)] └── variable: b.z [type=int, outer=(4)] @@ -49,21 +51,26 @@ intersect ├── project │ ├── columns: a.x:1(int!null) a.y:2(int) │ ├── stats: [rows=1000] + │ ├── keys: (1) │ ├── scan a │ │ ├── columns: a.x:1(int!null) a.y:2(int) - │ │ └── stats: [rows=1000] + │ │ ├── stats: [rows=1000] + │ │ └── keys: (1) │ └── projections [outer=(1,2)] │ ├── variable: a.x [type=int, outer=(1)] │ └── variable: a.y [type=int, outer=(2)] └── project ├── columns: b.z:4(int!null) b.x:3(int) b.rowid:5(int!null) ├── stats: [rows=100] + ├── keys: (5) ├── select │ ├── columns: b.x:3(int) b.z:4(int!null) b.rowid:5(int!null) │ ├── stats: [rows=100] + │ ├── keys: (5) │ ├── scan b │ │ ├── columns: b.x:3(int) b.z:4(int!null) b.rowid:5(int!null) - │ │ └── stats: [rows=1000] + │ │ ├── stats: [rows=1000] + │ │ └── keys: (5) │ └── eq [type=bool, outer=(3), constraints=(/3: [/1 - /1]; tight)] │ ├── variable: b.x [type=int, outer=(3)] │ └── const: 1 [type=int] @@ -83,9 +90,11 @@ except ├── project │ ├── columns: a.x:1(int!null) a.y:2(int) │ ├── stats: [rows=1000] + │ ├── keys: (1) │ ├── scan a │ │ ├── columns: a.x:1(int!null) a.y:2(int) - │ │ └── stats: [rows=1000] + │ │ ├── stats: [rows=1000] + │ │ └── keys: (1) │ └── projections [outer=(1,2)] │ ├── variable: a.x [type=int, outer=(1)] │ └── variable: a.y [type=int, outer=(2)] @@ -98,9 +107,11 @@ except │ ├── select │ │ ├── columns: b.x:3(int) b.z:4(int!null) b.rowid:5(int!null) │ │ ├── stats: [rows=100] + │ │ ├── keys: (5) │ │ ├── scan b │ │ │ ├── columns: b.x:3(int) b.z:4(int!null) b.rowid:5(int!null) - │ │ │ └── stats: [rows=1000] + │ │ │ ├── stats: [rows=1000] + │ │ │ └── keys: (5) │ │ └── eq [type=bool, outer=(3), constraints=(/3: [/1 - /1]; tight)] │ │ ├── variable: b.x [type=int, outer=(3)] │ │ └── const: 1 [type=int] diff --git a/pkg/sql/opt/memo/testdata/memo b/pkg/sql/opt/memo/testdata/memo index 1c017bce90fc..008a7d94e111 100644 --- a/pkg/sql/opt/memo/testdata/memo +++ b/pkg/sql/opt/memo/testdata/memo @@ -48,11 +48,13 @@ limit │ │ │ ├── scan a │ │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) │ │ │ │ ├── stats: [rows=1000] - │ │ │ │ └── cost: 1000.00 + │ │ │ │ ├── cost: 1000.00 + │ │ │ │ └── keys: (1) │ │ │ ├── scan b │ │ │ │ ├── columns: b.x:3(string!null) b.z:4(decimal!null) │ │ │ │ ├── stats: [rows=1000] - │ │ │ │ └── cost: 1000.00 + │ │ │ │ ├── cost: 1000.00 + │ │ │ │ └── keys: (3) │ │ │ └── true [type=bool] │ │ └── and [type=bool, outer=(1-3), constraints=(/2: [/1 - /1])] │ │ ├── eq [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight)] @@ -104,10 +106,12 @@ project │ │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) │ │ │ │ ├── stats: [rows=100] │ │ │ │ ├── cost: 1100.00 + │ │ │ │ ├── keys: (1) │ │ │ │ ├── scan a │ │ │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) │ │ │ │ │ ├── stats: [rows=1000] - │ │ │ │ │ └── cost: 1000.00 + │ │ │ │ │ ├── cost: 1000.00 + │ │ │ │ │ └── keys: (1) │ │ │ │ └── filters [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight)] │ │ │ │ └── eq [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight)] │ │ │ │ ├── variable: a.y [type=int, outer=(2)] @@ -115,7 +119,8 @@ project │ │ │ ├── scan b │ │ │ │ ├── columns: b.x:3(string!null) │ │ │ │ ├── stats: [rows=1000] - │ │ │ │ └── cost: 1000.00 + │ │ │ │ ├── cost: 1000.00 + │ │ │ │ └── keys: (3) │ │ │ └── filters [type=bool, outer=(1,3)] │ │ │ └── eq [type=bool, outer=(1,3)] │ │ │ ├── variable: b.x [type=string, outer=(3)] diff --git a/pkg/sql/opt/metadata.go b/pkg/sql/opt/metadata.go index b4bf9d9d3285..592775a654f5 100644 --- a/pkg/sql/opt/metadata.go +++ b/pkg/sql/opt/metadata.go @@ -55,17 +55,27 @@ type TableID int32 type Metadata struct { // cols stores information about each metadata column, indexed by ColumnID. // Skip id 0 so that it is reserved for "unknown column". - cols []mdCol + cols []mdColumn // tables maps from table id to the catalog metadata for the table. The // table id is the id of the first column in the table. The remaining // columns form a contiguous group following that id. - tables map[TableID]Table + tables map[TableID]mdTable } -// mdCol stores information about one of the columns stored in the metadata, +// mdTable stores information about one of the tables stored in the metadata. +type mdTable struct { + // tab is a reference to the table in the catalog. + tab Table + + // weakKeys is a cache of the weak key column combinations on this table. + // See RelationalProps.WeakKeys for more details. + weakKeys WeakKeys +} + +// mdColumn stores information about one of the columns stored in the metadata, // including its label and type. -type mdCol struct { +type mdColumn struct { // label is the best-effort name of this column. Since the same column can // have multiple labels (using aliasing), one of those is chosen to be used // for pretty-printing and debugging. This might be different than what is @@ -78,14 +88,14 @@ type mdCol struct { // NewMetadata constructs a new instance of metadata for the optimizer. func NewMetadata() *Metadata { - // Skip mdCol index 0 so that it is reserved for "unknown column". - return &Metadata{cols: make([]mdCol, 1)} + // Skip mdColumn index 0 so that it is reserved for "unknown column". + return &Metadata{cols: make([]mdColumn, 1)} } // AddColumn assigns a new unique id to a column within the query and records // its label and type. func (md *Metadata) AddColumn(label string, typ types.T) ColumnID { - md.cols = append(md.cols, mdCol{label: label, typ: typ}) + md.cols = append(md.cols, mdColumn{label: label, typ: typ}) return ColumnID(len(md.cols) - 1) } @@ -130,17 +140,17 @@ func (md *Metadata) AddTable(tab Table) TableID { } if md.tables == nil { - md.tables = make(map[TableID]Table) + md.tables = make(map[TableID]mdTable) } - md.tables[tabID] = tab + md.tables[tabID] = mdTable{tab: tab} return tabID } // Table looks up the catalog table associated with the given metadata id. The // same table can be associated with multiple metadata ids. func (md *Metadata) Table(tabID TableID) Table { - return md.tables[tabID] + return md.tables[tabID].tab } // TableColumn returns the metadata id of the column at the given ordinal @@ -148,3 +158,23 @@ func (md *Metadata) Table(tabID TableID) Table { func (md *Metadata) TableColumn(tabID TableID, ord int) ColumnID { return ColumnID(int(tabID) + ord) } + +// TableWeakKeys returns the weak key column combinations on the given table. +// Weak keys are derived lazily and are cached in the metadata, since they may +// be accessed multiple times during query optimization. For more details, see +// RelationalProps.WeakKeys. +func (md *Metadata) TableWeakKeys(tabID TableID) WeakKeys { + mdTab := md.tables[tabID] + if mdTab.weakKeys == nil { + mdTab.weakKeys = make(WeakKeys, 0, mdTab.tab.IndexCount()) + for idx := 0; idx < mdTab.tab.IndexCount(); idx++ { + var cs ColSet + index := mdTab.tab.Index(idx) + for col := 0; col < index.UniqueColumnCount(); col++ { + cs.Add(int(md.TableColumn(tabID, index.Column(col).Ordinal))) + } + mdTab.weakKeys.Add(cs) + } + } + return mdTab.weakKeys +} diff --git a/pkg/sql/opt/metadata_column.go b/pkg/sql/opt/metadata_column.go index 6467ff13a995..185e400b5e97 100644 --- a/pkg/sql/opt/metadata_column.go +++ b/pkg/sql/opt/metadata_column.go @@ -82,3 +82,45 @@ func ColListToSet(colList ColList) ColSet { } return r } + +// WeakKeys are combinations of columns that form a weak key. No two non-null +// rows are equal if they contain columns from a weak key. For more details, see +// LogicalProps.WeakKeys. +type WeakKeys []ColSet + +// ContainsSubsetOf returns true if the weak key list contains a key that is a +// subset of the given key. In that case, there's no reason to add the key to +// the list, since it's redundant. +func (wk *WeakKeys) ContainsSubsetOf(weakKey ColSet) bool { + for _, existing := range *wk { + if existing.SubsetOf(weakKey) { + return true + } + } + return false +} + +// Add appends a new weak key to the list of weak keys. It also ensures that no +// weak key is a superset of another, since that is a redundant weak key. +func (wk *WeakKeys) Add(new ColSet) { + // If one weak key is a subset of another, then use that, since the + // longer key is redundant. + insert := 0 + for i, existing := range *wk { + // If new key is redundant, don't add it. + if existing.SubsetOf(new) { + return + } + + // If existing key is redundant, then remove it from the list. Since + // there may be multiple redundant keys, wait until after looping to + // remove all at once. + if !new.SubsetOf(existing) { + if insert != i { + (*wk)[insert] = existing + } + insert++ + } + } + *wk = append((*wk)[:insert], new) +} diff --git a/pkg/sql/opt/metadata_test.go b/pkg/sql/opt/metadata_test.go index 36898334ecda..1c452ca85938 100644 --- a/pkg/sql/opt/metadata_test.go +++ b/pkg/sql/opt/metadata_test.go @@ -15,11 +15,13 @@ package opt_test import ( + "fmt" "testing" "github.com/cockroachdb/cockroach/pkg/sql/opt" "github.com/cockroachdb/cockroach/pkg/sql/opt/testutils" "github.com/cockroachdb/cockroach/pkg/sql/sem/types" + "github.com/cockroachdb/cockroach/pkg/util" ) func TestMetadataColumns(t *testing.T) { @@ -109,3 +111,68 @@ func TestMetadataTables(t *testing.T) { t.Fatalf("unexpected column label: %s", label) } } + +func TestMetadataWeakKeys(t *testing.T) { + test := func(weakKeys opt.WeakKeys, expected string) { + t.Helper() + actual := fmt.Sprintf("%v", weakKeys) + if actual != expected { + t.Errorf("expected: %s, actual: %s", expected, actual) + } + } + + testContains := func(weakKeys opt.WeakKeys, cs opt.ColSet, expected bool) { + t.Helper() + actual := weakKeys.ContainsSubsetOf(cs) + if actual != expected { + t.Errorf("expected: %v, actual: %v", expected, actual) + } + } + + md := opt.NewMetadata() + + // Create table with the following interesting indexes: + // 1. Primary key index with multiple columns. + // 2. Single column index. + // 3. Storing values (should have no impact). + // 4. Non-unique index (should always be superset of primary key). + // 5. Unique index that has subset of cols of another unique index, but + // which is defined afterwards (triggers removal of previous weak key). + cat := testutils.NewTestCatalog() + testutils.ExecuteTestDDL(t, + "CREATE TABLE a ("+ + "k INT, "+ + "i INT, "+ + "d DECIMAL, "+ + "f FLOAT, "+ + "s STRING, "+ + "PRIMARY KEY (k, i), "+ + "UNIQUE INDEX (f) STORING (s, i),"+ + "UNIQUE INDEX (d DESC, i, s),"+ + "UNIQUE INDEX (d, i DESC) STORING (f),"+ + "INDEX (s DESC, i))", + cat) + a := md.AddTable(cat.Table("a")) + + wk := md.TableWeakKeys(a) + test(wk, "[(1,2) (4) (2,3)]") + + // Test ContainsSubsetOf method. + testContains(wk, util.MakeFastIntSet(1, 2), true) + testContains(wk, util.MakeFastIntSet(1, 2, 3), true) + testContains(wk, util.MakeFastIntSet(4), true) + testContains(wk, util.MakeFastIntSet(4, 3, 2, 1), true) + testContains(wk, util.MakeFastIntSet(1), false) + testContains(wk, util.MakeFastIntSet(1, 3), false) + testContains(wk, util.MakeFastIntSet(5), false) + + // Add additional weak keys to additionally verify Add method. + wk.Add(util.MakeFastIntSet(1, 2, 3)) + test(wk, "[(1,2) (4) (2,3)]") + + wk.Add(util.MakeFastIntSet(2, 1)) + test(wk, "[(1,2) (4) (2,3)]") + + wk.Add(util.MakeFastIntSet(2)) + test(wk, "[(4) (2)]") +} diff --git a/pkg/sql/opt/norm/testdata/bool b/pkg/sql/opt/norm/testdata/bool index 952383a094ed..c04570f51157 100644 --- a/pkg/sql/opt/norm/testdata/bool +++ b/pkg/sql/opt/norm/testdata/bool @@ -26,7 +26,8 @@ opt SELECT * FROM a WHERE True AND True ---- scan a - └── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + └── keys: (1) # -------------------------------------------------- # EliminateEmptyOr @@ -90,7 +91,8 @@ SELECT true AND k=1 FROM a project ├── columns: column6:6(bool) ├── scan a - │ └── columns: a.k:1(int!null) + │ ├── columns: a.k:1(int!null) + │ └── keys: (1) └── projections [outer=(1)] └── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] ├── variable: a.k [type=int, outer=(1)] @@ -102,7 +104,8 @@ SELECT k=1 AND i=2 AND true FROM a project ├── columns: column6:6(bool) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) + │ ├── columns: a.k:1(int!null) a.i:2(int) + │ └── keys: (1) └── projections [outer=(1,2)] └── and [type=bool, outer=(1,2), constraints=(/1: [/1 - /1]; /2: [/2 - /2]; tight)] ├── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] @@ -117,7 +120,8 @@ opt SELECT * FROM a WHERE true AND true ---- scan a - └── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + └── keys: (1) # Flatten nested And operands. opt @@ -126,7 +130,8 @@ SELECT (k>1 AND k<5) AND (f=3.5 AND s='foo') FROM a project ├── columns: column6:6(bool) ├── scan a - │ └── columns: a.k:1(int!null) a.f:3(float) a.s:4(string) + │ ├── columns: a.k:1(int!null) a.f:3(float) a.s:4(string) + │ └── keys: (1) └── projections [outer=(1,3,4)] └── and [type=bool, outer=(1,3,4), constraints=(/1: [/2 - /4]; /3: [/3.5 - /3.5]; /4: [/'foo' - /'foo']; tight)] ├── gt [type=bool, outer=(1), constraints=(/1: [/2 - ]; tight)] @@ -172,7 +177,8 @@ SELECT false OR k=1 FROM a project ├── columns: column6:6(bool) ├── scan a - │ └── columns: a.k:1(int!null) + │ ├── columns: a.k:1(int!null) + │ └── keys: (1) └── projections [outer=(1)] └── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] ├── variable: a.k [type=int, outer=(1)] @@ -183,8 +189,10 @@ SELECT * FROM a WHERE k=1 OR i=2 OR false ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(1,2)] └── or [type=bool, outer=(1,2)] ├── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] @@ -200,7 +208,8 @@ SELECT * FROM a WHERE false OR false ---- scan a ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - └── constraint: /1: contradiction + ├── constraint: /1: contradiction + └── keys: (1) # Flatten nested Or operands. opt @@ -208,8 +217,10 @@ SELECT * FROM a WHERE (k=1 OR i=2) OR (f=3.5 OR s='foo') ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(1-4)] └── or [type=bool, outer=(1-4)] ├── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] @@ -234,7 +245,8 @@ SELECT (k=1 OR false) AND (false OR k=2 OR false) AND true FROM a project ├── columns: column6:6(bool) ├── scan a - │ └── columns: a.k:1(int!null) + │ ├── columns: a.k:1(int!null) + │ └── keys: (1) └── projections [outer=(1)] └── and [type=bool, outer=(1), constraints=(contradiction; tight)] ├── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] @@ -251,7 +263,8 @@ SELECT (k=1 OR (i=2 OR f=3.5)) AND (s='foo' AND s<>'bar') FROM a project ├── columns: column6:6(bool) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) + │ └── keys: (1) └── projections [outer=(1-4)] └── and [type=bool, outer=(1-4), constraints=(/4: [/'foo' - /'foo'])] ├── or [type=bool, outer=(1-3)] @@ -279,7 +292,8 @@ SELECT * FROM a WHERE Null ---- scan a ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - └── constraint: /1: contradiction + ├── constraint: /1: contradiction + └── keys: (1) opt SELECT * FROM a INNER JOIN b ON NULL @@ -287,9 +301,11 @@ SELECT * FROM a INNER JOIN b ON NULL inner-join ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) z:7(int) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) ├── scan b - │ └── columns: b.x:6(int!null) b.z:7(int) + │ ├── columns: b.x:6(int!null) b.z:7(int) + │ └── keys: (6) └── false [type=bool] opt @@ -297,7 +313,8 @@ SELECT * FROM a WHERE i=1 AND Null ---- scan a ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - └── constraint: /1: contradiction + ├── constraint: /1: contradiction + └── keys: (1) # -------------------------------------------------- # FoldNullAndOr @@ -336,7 +353,8 @@ SELECT null or (null and k=1) FROM a project ├── columns: column6:6(bool) ├── scan a - │ └── columns: a.k:1(int!null) + │ ├── columns: a.k:1(int!null) + │ └── keys: (1) └── projections [outer=(1)] └── or [type=bool, outer=(1)] ├── null [type=unknown] @@ -356,8 +374,10 @@ SELECT * FROM a WHERE NOT(i=1) AND NOT(i<>1) AND NOT(i>1) AND NOT(i>=1) AND NOT( ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(2), constraints=(contradiction)] ├── ne [type=bool, outer=(2)] │ ├── variable: a.i [type=int, outer=(2)] @@ -386,8 +406,10 @@ WHERE NOT(i IN (1,2)) AND NOT(i NOT IN (3,4)) AND NOT(i IS NULL) AND NOT(i IS NO ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(2), constraints=(/2: [/3 - /3] [/4 - /4])] ├── not-in [type=bool, outer=(2)] │ ├── variable: a.i [type=int, outer=(2)] @@ -414,8 +436,10 @@ WHERE NOT(s LIKE 'foo') AND NOT(s NOT LIKE 'foo') AND NOT(s ILIKE 'foo') AND NOT ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(4)] ├── not-like [type=bool, outer=(4)] │ ├── variable: a.s [type=string, outer=(4)] @@ -436,8 +460,10 @@ SELECT * FROM a WHERE NOT(s SIMILAR TO 'foo') AND NOT(s NOT SIMILAR TO 'foo') ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(4)] ├── not-similar-to [type=bool, outer=(4)] │ ├── variable: a.s [type=string, outer=(4)] @@ -452,8 +478,10 @@ SELECT * FROM a WHERE NOT(s ~ 'foo') AND NOT(s !~ 'foo') AND NOT(s ~* 'foo') AND ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(4)] ├── not-reg-match [type=bool, outer=(4)] │ ├── variable: a.s [type=string, outer=(4)] @@ -478,8 +506,10 @@ SELECT * FROM a WHERE ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(5)] ├── not [type=bool, outer=(5)] │ └── contains [type=bool, outer=(5)] @@ -512,8 +542,10 @@ SELECT * FROM a WHERE NOT(NOT('[1, 2]' @> j)) ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(5)] └── contains [type=bool, outer=(5)] ├── const: '[1, 2]' [type=jsonb] @@ -527,8 +559,10 @@ SELECT * FROM a WHERE NOT (k >= i AND i < f) ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(1-3)] └── or [type=bool, outer=(1-3)] ├── lt [type=bool, outer=(1,2)] @@ -543,8 +577,10 @@ SELECT * FROM a WHERE NOT (k >= i AND i < f AND (i > 5 AND i < 10 AND f > 1)) ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(1-3)] └── or [type=bool, outer=(1-3)] ├── lt [type=bool, outer=(1,2)] @@ -572,8 +608,10 @@ SELECT * FROM a WHERE NOT (k >= i OR i < f OR k + i < f) ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(1-3)] ├── lt [type=bool, outer=(1,2)] │ ├── variable: a.k [type=int, outer=(1)] @@ -592,8 +630,10 @@ SELECT * FROM a WHERE NOT (k >= i OR i < f OR (i > 5 OR i < 10 OR f > 1)) ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(1-3), constraints=(contradiction)] ├── lt [type=bool, outer=(1,2)] │ ├── variable: a.k [type=int, outer=(1)] @@ -619,8 +659,10 @@ SELECT * FROM a WHERE NOT ((k >= i OR i < f) AND (i > 5 OR f > 1)) ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(1-3)] └── or [type=bool, outer=(1-3)] ├── and [type=bool, outer=(1-3)] @@ -643,8 +685,10 @@ SELECT * FROM a WHERE NOT ((k >= i AND i < f) OR (i > 5 AND f > 1)) ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(1-3)] ├── or [type=bool, outer=(1-3)] │ ├── lt [type=bool, outer=(1,2)] diff --git a/pkg/sql/opt/norm/testdata/combo b/pkg/sql/opt/norm/testdata/combo index f8c4d25fdcea..ea0c904b2224 100644 --- a/pkg/sql/opt/norm/testdata/combo +++ b/pkg/sql/opt/norm/testdata/combo @@ -32,9 +32,11 @@ SELECT s FROM a INNER JOIN b ON a.x=b.x AND i+1=10 ├── inner-join │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int!null) b.z:7(int) │ ├── scan a - │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ └── keys: (1) │ ├── scan b - │ │ └── columns: b.x:6(int!null) b.z:7(int) + │ │ ├── columns: b.x:6(int!null) b.z:7(int) + │ │ └── keys: (6) │ └── and [type=bool, outer=(1,2,6)] │ ├── eq [type=bool, outer=(1,6)] │ │ ├── variable: a.x [type=int, outer=(1)] @@ -53,9 +55,11 @@ SELECT s FROM a INNER JOIN b ON a.x=b.x AND i+1=10 ├── inner-join │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int!null) b.z:7(int) │ ├── scan a - │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ └── keys: (1) │ ├── scan b - │ │ └── columns: b.x:6(int!null) b.z:7(int) + │ │ ├── columns: b.x:6(int!null) b.z:7(int) + │ │ └── keys: (6) │ └── and [type=bool, outer=(1,2,6)] │ ├── eq [type=bool, outer=(1,6)] │ │ ├── variable: a.x [type=int, outer=(1)] @@ -78,9 +82,11 @@ SELECT s FROM a INNER JOIN b ON a.x=b.x AND i+1=10 ├── inner-join │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int!null) b.z:7(int) │ ├── scan a - │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ └── keys: (1) │ ├── scan b - │ │ └── columns: b.x:6(int!null) b.z:7(int) + │ │ ├── columns: b.x:6(int!null) b.z:7(int) + │ │ └── keys: (6) - │ └── and [type=bool, outer=(1,2,6)] + │ └── filters [type=bool, outer=(1,2,6)] │ ├── eq [type=bool, outer=(1,6)] @@ -100,11 +106,13 @@ SELECT s FROM a INNER JOIN b ON a.x=b.x AND i+1=10 ├── inner-join │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int!null) b.z:7(int) - │ ├── scan a - - │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── select - + │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + - │ │ └── keys: (1) + + │ │ ├── keys: (1) + │ │ ├── scan a - + │ │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + + │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + + │ │ │ └── keys: (1) + │ │ └── filters [type=bool, outer=(2)] + │ │ └── eq [type=bool, outer=(2)] + │ │ ├── variable: a.i [type=int, outer=(2)] @@ -112,7 +120,8 @@ SELECT s FROM a INNER JOIN b ON a.x=b.x AND i+1=10 + │ │ ├── const: 10 [type=int] + │ │ └── const: 1 [type=int] │ ├── scan b - │ │ └── columns: b.x:6(int!null) b.z:7(int) + │ │ ├── columns: b.x:6(int!null) b.z:7(int) + │ │ └── keys: (6) - │ └── filters [type=bool, outer=(1,2,6)] - │ ├── eq [type=bool, outer=(1,6)] - │ │ ├── variable: a.x [type=int, outer=(1)] @@ -136,21 +145,24 @@ SELECT s FROM a INNER JOIN b ON a.x=b.x AND i+1=10 - │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int!null) b.z:7(int) - │ ├── select - │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + + │ ├── columns: a.x:1(int!null) a.s:4(string) b.x:6(int!null) b.z:7(int) + + │ ├── project + + │ │ ├── columns: a.x:1(int!null) a.s:4(string) + │ │ ├── keys: (1) - │ │ ├── scan a - - │ │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + + │ │ ├── select + │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + - │ │ │ └── keys: (1) - │ │ └── filters [type=bool, outer=(2)] - │ │ └── eq [type=bool, outer=(2)] - │ │ ├── variable: a.i [type=int, outer=(2)] - │ │ └── minus [type=int] - │ │ ├── const: 10 [type=int] - │ │ └── const: 1 [type=int] - + │ ├── columns: a.x:1(int!null) a.s:4(string) b.x:6(int!null) b.z:7(int) - + │ ├── project - + │ │ ├── columns: a.x:1(int!null) a.s:4(string) - + │ │ ├── select - + │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + + │ │ │ ├── keys: (1) + │ │ │ ├── scan a - + │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + + │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + + │ │ │ │ └── keys: (1) + │ │ │ └── filters [type=bool, outer=(2)] + │ │ │ └── eq [type=bool, outer=(2)] + │ │ │ ├── variable: a.i [type=int, outer=(2)] @@ -161,7 +173,8 @@ SELECT s FROM a INNER JOIN b ON a.x=b.x AND i+1=10 + │ │ ├── variable: a.x [type=int, outer=(1)] + │ │ └── variable: a.s [type=string, outer=(4)] │ ├── scan b - │ │ └── columns: b.x:6(int!null) b.z:7(int) + │ │ ├── columns: b.x:6(int!null) b.z:7(int) + │ │ └── keys: (6) │ └── filters [type=bool, outer=(1,6)] │ └── eq [type=bool, outer=(1,6)] │ ├── variable: a.x [type=int, outer=(1)] @@ -176,12 +189,15 @@ SELECT s FROM a INNER JOIN b ON a.x=b.x AND i+1=10 │ ├── columns: a.x:1(int!null) a.s:4(string) b.x:6(int!null) b.z:7(int) │ ├── project │ │ ├── columns: a.x:1(int!null) a.s:4(string) + │ │ ├── keys: (1) │ │ ├── select - │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.s:4(string) + │ │ │ ├── keys: (1) │ │ │ ├── scan a - - │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - + │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) a.s:4(string) + - │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + + │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.s:4(string) + │ │ │ │ └── keys: (1) │ │ │ └── filters [type=bool, outer=(2)] │ │ │ └── eq [type=bool, outer=(2)] │ │ │ ├── variable: a.i [type=int, outer=(2)] @@ -192,7 +208,8 @@ SELECT s FROM a INNER JOIN b ON a.x=b.x AND i+1=10 │ │ ├── variable: a.x [type=int, outer=(1)] │ │ └── variable: a.s [type=string, outer=(4)] │ ├── scan b - │ │ └── columns: b.x:6(int!null) b.z:7(int) + │ │ ├── columns: b.x:6(int!null) b.z:7(int) + │ │ └── keys: (6) │ └── filters [type=bool, outer=(1,6)] │ └── eq [type=bool, outer=(1,6)] │ ├── variable: a.x [type=int, outer=(1)] @@ -208,10 +225,13 @@ SELECT s FROM a INNER JOIN b ON a.x=b.x AND i+1=10 + │ ├── columns: a.x:1(int!null) a.s:4(string) b.x:6(int!null) │ ├── project │ │ ├── columns: a.x:1(int!null) a.s:4(string) + │ │ ├── keys: (1) │ │ ├── select │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.s:4(string) + │ │ │ ├── keys: (1) │ │ │ ├── scan a - │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) a.s:4(string) + │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.s:4(string) + │ │ │ │ └── keys: (1) │ │ │ └── filters [type=bool, outer=(2)] │ │ │ └── eq [type=bool, outer=(2)] │ │ │ ├── variable: a.i [type=int, outer=(2)] @@ -222,8 +242,9 @@ SELECT s FROM a INNER JOIN b ON a.x=b.x AND i+1=10 │ │ ├── variable: a.x [type=int, outer=(1)] │ │ └── variable: a.s [type=string, outer=(4)] │ ├── scan b - - │ │ └── columns: b.x:6(int!null) b.z:7(int) - + │ │ └── columns: b.x:6(int!null) + - │ │ ├── columns: b.x:6(int!null) b.z:7(int) + + │ │ ├── columns: b.x:6(int!null) + │ │ └── keys: (6) │ └── filters [type=bool, outer=(1,6)] │ └── eq [type=bool, outer=(1,6)] │ ├── variable: a.x [type=int, outer=(1)] @@ -244,10 +265,13 @@ SELECT s FROM a INNER JOIN b ON a.x=b.x AND i+1=10 │ ├── columns: a.x:1(int!null) a.s:4(string) b.x:6(int!null) │ ├── project │ │ ├── columns: a.x:1(int!null) a.s:4(string) + │ │ ├── keys: (1) │ │ ├── select │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.s:4(string) + │ │ │ ├── keys: (1) │ │ │ ├── scan a - │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) a.s:4(string) + │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.s:4(string) + │ │ │ │ └── keys: (1) │ │ │ └── filters [type=bool, outer=(2)] │ │ │ └── eq [type=bool, outer=(2)] │ │ │ ├── variable: a.i [type=int, outer=(2)] @@ -258,7 +282,8 @@ SELECT s FROM a INNER JOIN b ON a.x=b.x AND i+1=10 │ │ ├── variable: a.x [type=int, outer=(1)] │ │ └── variable: a.s [type=string, outer=(4)] │ ├── scan b - │ │ └── columns: b.x:6(int!null) + │ │ ├── columns: b.x:6(int!null) + │ │ └── keys: (6) │ └── filters [type=bool, outer=(1,6)] │ └── eq [type=bool, outer=(1,6)] │ ├── variable: a.x [type=int, outer=(1)] @@ -290,9 +315,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET │ │ │ ├── full-join │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int) b.z:7(int) │ │ │ │ ├── scan a - │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ │ │ │ └── keys: (1) │ │ │ │ ├── scan b - │ │ │ │ │ └── columns: b.x:6(int!null) b.z:7(int) + │ │ │ │ │ ├── columns: b.x:6(int!null) b.z:7(int) + │ │ │ │ │ └── keys: (6) │ │ │ │ └── eq [type=bool, outer=(1,6)] │ │ │ │ ├── variable: a.x [type=int, outer=(1)] │ │ │ │ └── variable: b.x [type=int, outer=(6)] @@ -324,9 +351,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET │ │ │ ├── full-join │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int) b.z:7(int) │ │ │ │ ├── scan a - │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ │ │ │ └── keys: (1) │ │ │ │ ├── scan b - │ │ │ │ │ └── columns: b.x:6(int!null) b.z:7(int) + │ │ │ │ │ ├── columns: b.x:6(int!null) b.z:7(int) + │ │ │ │ │ └── keys: (6) - │ │ │ │ └── eq [type=bool, outer=(1,6)] - │ │ │ │ ├── variable: a.x [type=int, outer=(1)] - │ │ │ │ └── variable: b.x [type=int, outer=(6)] @@ -362,9 +391,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET │ │ │ ├── full-join │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int) b.z:7(int) │ │ │ │ ├── scan a - │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ │ │ │ └── keys: (1) │ │ │ │ ├── scan b - │ │ │ │ │ └── columns: b.x:6(int!null) b.z:7(int) + │ │ │ │ │ ├── columns: b.x:6(int!null) b.z:7(int) + │ │ │ │ │ └── keys: (6) │ │ │ │ └── filters [type=bool, outer=(1,6)] │ │ │ │ └── eq [type=bool, outer=(1,6)] │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -401,9 +432,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET - │ │ │ ├── full-join - │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int) b.z:7(int) - │ │ │ │ ├── scan a - - │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + - │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + - │ │ │ │ │ └── keys: (1) - │ │ │ │ ├── scan b - - │ │ │ │ │ └── columns: b.x:6(int!null) b.z:7(int) + - │ │ │ │ │ ├── columns: b.x:6(int!null) b.z:7(int) + - │ │ │ │ │ └── keys: (6) - │ │ │ │ └── filters [type=bool, outer=(1,6)] - │ │ │ │ └── eq [type=bool, outer=(1,6)] - │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -414,9 +447,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET + │ │ │ │ ├── full-join + │ │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int) b.z:7(int) + │ │ │ │ │ ├── scan a - + │ │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + + │ │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + + │ │ │ │ │ │ └── keys: (1) + │ │ │ │ │ ├── scan b - + │ │ │ │ │ │ └── columns: b.x:6(int!null) b.z:7(int) + + │ │ │ │ │ │ ├── columns: b.x:6(int!null) b.z:7(int) + + │ │ │ │ │ │ └── keys: (6) + │ │ │ │ │ └── filters [type=bool, outer=(1,6)] + │ │ │ │ │ └── eq [type=bool, outer=(1,6)] + │ │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -455,10 +490,12 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET - │ │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int) b.z:7(int) + │ │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) b.z:7(int) │ │ │ │ │ ├── scan a - - │ │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - + │ │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + - │ │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + + │ │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + │ │ │ │ │ │ └── keys: (1) │ │ │ │ │ ├── scan b - │ │ │ │ │ │ └── columns: b.x:6(int!null) b.z:7(int) + │ │ │ │ │ │ ├── columns: b.x:6(int!null) b.z:7(int) + │ │ │ │ │ │ └── keys: (6) │ │ │ │ │ └── filters [type=bool, outer=(1,6)] │ │ │ │ │ └── eq [type=bool, outer=(1,6)] │ │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -497,10 +534,12 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET - │ │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) b.z:7(int) + │ │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) │ │ │ │ │ ├── scan a - │ │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + │ │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + │ │ │ │ │ │ └── keys: (1) │ │ │ │ │ ├── scan b - - │ │ │ │ │ │ └── columns: b.x:6(int!null) b.z:7(int) - + │ │ │ │ │ │ └── columns: b.x:6(int!null) + - │ │ │ │ │ │ ├── columns: b.x:6(int!null) b.z:7(int) + + │ │ │ │ │ │ ├── columns: b.x:6(int!null) + │ │ │ │ │ │ └── keys: (6) │ │ │ │ │ └── filters [type=bool, outer=(1,6)] │ │ │ │ │ └── eq [type=bool, outer=(1,6)] │ │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -539,9 +578,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET + │ │ │ │ ├── select │ │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) - │ │ │ │ │ ├── scan a - - │ │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + - │ │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + - │ │ │ │ │ │ └── keys: (1) - │ │ │ │ │ ├── scan b - - │ │ │ │ │ │ └── columns: b.x:6(int!null) + - │ │ │ │ │ │ ├── columns: b.x:6(int!null) + - │ │ │ │ │ │ └── keys: (6) - │ │ │ │ │ └── filters [type=bool, outer=(1,6)] - │ │ │ │ │ └── eq [type=bool, outer=(1,6)] - │ │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -549,9 +590,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET + │ │ │ │ │ ├── full-join + │ │ │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) + │ │ │ │ │ │ ├── scan a - + │ │ │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + + │ │ │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + + │ │ │ │ │ │ │ └── keys: (1) + │ │ │ │ │ │ ├── scan b - + │ │ │ │ │ │ │ └── columns: b.x:6(int!null) + + │ │ │ │ │ │ │ ├── columns: b.x:6(int!null) + + │ │ │ │ │ │ │ └── keys: (6) + │ │ │ │ │ │ └── filters [type=bool, outer=(1,6)] + │ │ │ │ │ │ └── eq [type=bool, outer=(1,6)] + │ │ │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -596,9 +639,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET │ │ │ │ │ ├── full-join │ │ │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) │ │ │ │ │ │ ├── scan a - │ │ │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + │ │ │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + │ │ │ │ │ │ │ └── keys: (1) │ │ │ │ │ │ ├── scan b - │ │ │ │ │ │ │ └── columns: b.x:6(int!null) + │ │ │ │ │ │ │ ├── columns: b.x:6(int!null) + │ │ │ │ │ │ │ └── keys: (6) │ │ │ │ │ │ └── filters [type=bool, outer=(1,6)] │ │ │ │ │ │ └── eq [type=bool, outer=(1,6)] │ │ │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -644,9 +689,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET - │ │ │ │ │ ├── full-join - │ │ │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) - │ │ │ │ │ │ ├── scan a - - │ │ │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + - │ │ │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + - │ │ │ │ │ │ │ └── keys: (1) - │ │ │ │ │ │ ├── scan b - - │ │ │ │ │ │ │ └── columns: b.x:6(int!null) + - │ │ │ │ │ │ │ ├── columns: b.x:6(int!null) + - │ │ │ │ │ │ │ └── keys: (6) - │ │ │ │ │ │ └── filters [type=bool, outer=(1,6)] - │ │ │ │ │ │ └── eq [type=bool, outer=(1,6)] - │ │ │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -659,9 +706,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET - │ │ │ │ └── variable: a.i [type=int, outer=(2)] - │ │ │ └── true [type=bool] + │ │ │ │ │ ├── scan a - + │ │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + + │ │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + + │ │ │ │ │ │ └── keys: (1) + │ │ │ │ │ ├── scan b - + │ │ │ │ │ │ └── columns: b.x:6(int!null) + + │ │ │ │ │ │ ├── columns: b.x:6(int!null) + + │ │ │ │ │ │ └── keys: (6) + │ │ │ │ │ └── filters [type=bool, outer=(1,6)] + │ │ │ │ │ └── eq [type=bool, outer=(1,6)] + │ │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -702,9 +751,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET - │ │ │ │ ├── full-join - │ │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) - │ │ │ │ │ ├── scan a - - │ │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + - │ │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + - │ │ │ │ │ │ └── keys: (1) - │ │ │ │ │ ├── scan b - - │ │ │ │ │ │ └── columns: b.x:6(int!null) + - │ │ │ │ │ │ ├── columns: b.x:6(int!null) + - │ │ │ │ │ │ └── keys: (6) - │ │ │ │ │ └── filters [type=bool, outer=(1,6)] - │ │ │ │ │ └── eq [type=bool, outer=(1,6)] - │ │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -716,9 +767,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET - │ │ │ └── projections [outer=(2)] - │ │ │ └── variable: a.i [type=int, outer=(2)] + │ │ │ │ ├── scan a - + │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + + │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + + │ │ │ │ │ └── keys: (1) + │ │ │ │ ├── scan b - + │ │ │ │ │ └── columns: b.x:6(int!null) + + │ │ │ │ │ ├── columns: b.x:6(int!null) + + │ │ │ │ │ └── keys: (6) + │ │ │ │ └── filters [type=bool, outer=(1,6)] + │ │ │ │ └── eq [type=bool, outer=(1,6)] + │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -755,9 +808,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET + │ │ │ ├── select │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) - │ │ │ │ ├── scan a - - │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + - │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + - │ │ │ │ │ └── keys: (1) - │ │ │ │ ├── scan b - - │ │ │ │ │ └── columns: b.x:6(int!null) + - │ │ │ │ │ ├── columns: b.x:6(int!null) + - │ │ │ │ │ └── keys: (6) - │ │ │ │ └── filters [type=bool, outer=(1,6)] - │ │ │ │ └── eq [type=bool, outer=(1,6)] - │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -769,9 +824,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET + │ │ │ │ ├── full-join + │ │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) + │ │ │ │ │ ├── scan a - + │ │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + + │ │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + + │ │ │ │ │ │ └── keys: (1) + │ │ │ │ │ ├── scan b - + │ │ │ │ │ │ └── columns: b.x:6(int!null) + + │ │ │ │ │ │ ├── columns: b.x:6(int!null) + + │ │ │ │ │ │ └── keys: (6) + │ │ │ │ │ └── filters [type=bool, outer=(1,6)] + │ │ │ │ │ └── eq [type=bool, outer=(1,6)] + │ │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -812,9 +869,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET - │ │ │ │ ├── full-join - │ │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) - │ │ │ │ │ ├── scan a - - │ │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + - │ │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + - │ │ │ │ │ │ └── keys: (1) - │ │ │ │ │ ├── scan b - - │ │ │ │ │ │ └── columns: b.x:6(int!null) + - │ │ │ │ │ │ ├── columns: b.x:6(int!null) + - │ │ │ │ │ │ └── keys: (6) - │ │ │ │ │ └── filters [type=bool, outer=(1,6)] - │ │ │ │ │ └── eq [type=bool, outer=(1,6)] - │ │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -826,9 +885,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET - │ │ │ └── projections [outer=(2)] - │ │ │ └── variable: a.i [type=int, outer=(2)] + │ │ │ │ ├── scan a - + │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + + │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + + │ │ │ │ │ └── keys: (1) + │ │ │ │ ├── scan b - + │ │ │ │ │ └── columns: b.x:6(int!null) + + │ │ │ │ │ ├── columns: b.x:6(int!null) + + │ │ │ │ │ └── keys: (6) + │ │ │ │ └── filters [type=bool, outer=(1,6)] + │ │ │ │ └── eq [type=bool, outer=(1,6)] + │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -865,9 +926,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET - │ │ │ ├── full-join - │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) - │ │ │ │ ├── scan a - - │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + - │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + - │ │ │ │ │ └── keys: (1) - │ │ │ │ ├── scan b - - │ │ │ │ │ └── columns: b.x:6(int!null) + - │ │ │ │ │ ├── columns: b.x:6(int!null) + - │ │ │ │ │ └── keys: (6) - │ │ │ │ └── filters [type=bool, outer=(1,6)] - │ │ │ │ └── eq [type=bool, outer=(1,6)] - │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -890,9 +953,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET + │ │ │ ├── full-join + │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) + │ │ │ │ ├── scan a - + │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + + │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + + │ │ │ │ │ └── keys: (1) + │ │ │ │ ├── scan b - + │ │ │ │ │ └── columns: b.x:6(int!null) + + │ │ │ │ │ ├── columns: b.x:6(int!null) + + │ │ │ │ │ └── keys: (6) + │ │ │ │ └── filters [type=bool, outer=(1,6)] + │ │ │ │ └── eq [type=bool, outer=(1,6)] + │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -932,9 +997,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET + │ │ │ ├── select │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) - │ │ │ │ ├── scan a - - │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + - │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + - │ │ │ │ │ └── keys: (1) - │ │ │ │ ├── scan b - - │ │ │ │ │ └── columns: b.x:6(int!null) + - │ │ │ │ │ ├── columns: b.x:6(int!null) + - │ │ │ │ │ └── keys: (6) - │ │ │ │ └── filters [type=bool, outer=(1,6)] - │ │ │ │ └── eq [type=bool, outer=(1,6)] - │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -946,9 +1013,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET + │ │ │ │ ├── full-join + │ │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) + │ │ │ │ │ ├── scan a - + │ │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + + │ │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + + │ │ │ │ │ │ └── keys: (1) + │ │ │ │ │ ├── scan b - + │ │ │ │ │ │ └── columns: b.x:6(int!null) + + │ │ │ │ │ │ ├── columns: b.x:6(int!null) + + │ │ │ │ │ │ └── keys: (6) + │ │ │ │ │ └── filters [type=bool, outer=(1,6)] + │ │ │ │ │ └── eq [type=bool, outer=(1,6)] + │ │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -990,9 +1059,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET │ │ │ │ ├── full-join │ │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) │ │ │ │ │ ├── scan a - │ │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + │ │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + │ │ │ │ │ │ └── keys: (1) │ │ │ │ │ ├── scan b - │ │ │ │ │ │ └── columns: b.x:6(int!null) + │ │ │ │ │ │ ├── columns: b.x:6(int!null) + │ │ │ │ │ │ └── keys: (6) │ │ │ │ │ └── filters [type=bool, outer=(1,6)] │ │ │ │ │ └── eq [type=bool, outer=(1,6)] │ │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -1041,9 +1112,11 @@ SELECT i, i+1 FROM a FULL JOIN b ON a.x=b.x WHERE i=10 ORDER BY i LIMIT 5 OFFSET │ │ │ │ ├── full-join │ │ │ │ │ ├── columns: a.x:1(int) a.i:2(int) b.x:6(int) │ │ │ │ │ ├── scan a - │ │ │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) + │ │ │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) + │ │ │ │ │ │ └── keys: (1) │ │ │ │ │ ├── scan b - │ │ │ │ │ │ └── columns: b.x:6(int!null) + │ │ │ │ │ │ ├── columns: b.x:6(int!null) + │ │ │ │ │ │ └── keys: (6) │ │ │ │ │ └── filters [type=bool, outer=(1,6)] │ │ │ │ │ └── eq [type=bool, outer=(1,6)] │ │ │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -1075,13 +1148,17 @@ SELECT SUM(x) FROM a GROUP BY s, x HAVING SUM(x)=1 ├── columns: column6:6(decimal) ├── select │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) + │ ├── keys: (1) │ ├── group-by │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + │ │ ├── keys: (1) │ │ ├── project │ │ │ ├── columns: a.s:4(string) a.x:1(int!null) + │ │ │ ├── keys: (1) │ │ │ ├── scan a - │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ │ │ └── keys: (1) │ │ │ └── projections [outer=(1,4)] │ │ │ ├── variable: a.s [type=string, outer=(4)] │ │ │ └── variable: a.x [type=int, outer=(1)] @@ -1099,14 +1176,18 @@ SELECT SUM(x) FROM a GROUP BY s, x HAVING SUM(x)=1 ├── columns: column6:6(decimal) ├── select │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) + │ ├── keys: (1) │ ├── group-by │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + │ │ ├── keys: (1) │ │ ├── project │ │ │ ├── columns: a.s:4(string) a.x:1(int!null) + │ │ │ ├── keys: (1) │ │ │ ├── scan a - - │ │ │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - + │ │ │ │ └── columns: a.x:1(int!null) a.s:4(string) + - │ │ │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + + │ │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) + │ │ │ │ └── keys: (1) │ │ │ └── projections [outer=(1,4)] │ │ │ ├── variable: a.s [type=string, outer=(4)] │ │ │ └── variable: a.x [type=int, outer=(1)] @@ -1124,18 +1205,23 @@ SELECT SUM(x) FROM a GROUP BY s, x HAVING SUM(x)=1 ├── columns: column6:6(decimal) ├── select │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) + │ ├── keys: (1) │ ├── group-by │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + │ │ ├── keys: (1) - │ │ ├── project - │ │ │ ├── columns: a.s:4(string) a.x:1(int!null) + - │ │ │ ├── keys: (1) - │ │ │ ├── scan a - - │ │ │ │ └── columns: a.x:1(int!null) a.s:4(string) + - │ │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) + - │ │ │ │ └── keys: (1) - │ │ │ └── projections [outer=(1,4)] - │ │ │ ├── variable: a.s [type=string, outer=(4)] - │ │ │ └── variable: a.x [type=int, outer=(1)] + │ │ ├── scan a - + │ │ │ └── columns: a.x:1(int!null) a.s:4(string) + + │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) + + │ │ │ └── keys: (1) │ │ └── aggregations [outer=(1)] │ │ └── function: sum [type=decimal, outer=(1)] │ │ └── variable: a.x [type=int, outer=(1)] @@ -1150,11 +1236,14 @@ SELECT SUM(x) FROM a GROUP BY s, x HAVING SUM(x)=1 ├── columns: column6:6(decimal) ├── select │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) + │ ├── keys: (1) │ ├── group-by │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + │ │ ├── keys: (1) │ │ ├── scan a - │ │ │ └── columns: a.x:1(int!null) a.s:4(string) + │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) + │ │ │ └── keys: (1) │ │ └── aggregations [outer=(1)] │ │ └── function: sum [type=decimal, outer=(1)] │ │ └── variable: a.x [type=int, outer=(1)] @@ -1173,11 +1262,14 @@ SELECT SUM(x) FROM a GROUP BY s, x HAVING SUM(x)=1 ├── columns: column6:6(decimal) ├── select - │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) + - │ ├── keys: (1) - │ ├── group-by - │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) - │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + - │ │ ├── keys: (1) - │ │ ├── scan a - - │ │ │ └── columns: a.x:1(int!null) a.s:4(string) + - │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) + - │ │ │ └── keys: (1) - │ │ └── aggregations [outer=(1)] - │ │ └── function: sum [type=decimal, outer=(1)] - │ │ └── variable: a.x [type=int, outer=(1)] @@ -1187,8 +1279,10 @@ SELECT SUM(x) FROM a GROUP BY s, x HAVING SUM(x)=1 + │ │ ├── group-by + │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) + │ │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + + │ │ │ ├── keys: (1) + │ │ │ ├── scan a - + │ │ │ │ └── columns: a.x:1(int!null) a.s:4(string) + + │ │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) + + │ │ │ │ └── keys: (1) + │ │ │ └── aggregations [outer=(1)] + │ │ │ └── function: sum [type=decimal, outer=(1)] + │ │ │ └── variable: a.x [type=int, outer=(1)] @@ -1212,16 +1306,20 @@ SELECT SUM(x) FROM a GROUP BY s, x HAVING SUM(x)=1 + │ │ ├── select │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) - │ │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + │ │ │ ├── keys: (1) - │ │ │ ├── scan a - - │ │ │ │ └── columns: a.x:1(int!null) a.s:4(string) + - │ │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) + - │ │ │ │ └── keys: (1) - │ │ │ └── aggregations [outer=(1)] - │ │ │ └── function: sum [type=decimal, outer=(1)] - │ │ │ └── variable: a.x [type=int, outer=(1)] + │ │ │ ├── group-by + │ │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) + │ │ │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + + │ │ │ │ ├── keys: (1) + │ │ │ │ ├── scan a - + │ │ │ │ │ └── columns: a.x:1(int!null) a.s:4(string) + + │ │ │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) + + │ │ │ │ │ └── keys: (1) + │ │ │ │ └── aggregations [outer=(1)] + │ │ │ │ └── function: sum [type=decimal, outer=(1)] + │ │ │ │ └── variable: a.x [type=int, outer=(1)] @@ -1248,11 +1346,14 @@ SELECT SUM(x) FROM a GROUP BY s, x HAVING SUM(x)=1 │ │ ├── columns: column6:6(decimal) │ │ ├── select │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) + │ │ │ ├── keys: (1) │ │ │ ├── group-by │ │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) │ │ │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + │ │ │ │ ├── keys: (1) │ │ │ │ ├── scan a - │ │ │ │ │ └── columns: a.x:1(int!null) a.s:4(string) + │ │ │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) + │ │ │ │ │ └── keys: (1) │ │ │ │ └── aggregations [outer=(1)] │ │ │ │ └── function: sum [type=decimal, outer=(1)] │ │ │ │ └── variable: a.x [type=int, outer=(1)] @@ -1278,13 +1379,18 @@ SELECT SUM(x) FROM a GROUP BY s, x HAVING SUM(x)=1 - │ │ ├── select + │ ├── select + │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) + + │ │ ├── keys: (1) + │ │ ├── group-by │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) + + │ │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + │ │ │ ├── keys: (1) - │ │ │ ├── group-by - │ │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) - │ │ │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + - │ │ │ │ ├── keys: (1) - │ │ │ │ ├── scan a - - │ │ │ │ │ └── columns: a.x:1(int!null) a.s:4(string) + - │ │ │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) + - │ │ │ │ │ └── keys: (1) - │ │ │ │ └── aggregations [outer=(1)] - │ │ │ │ └── function: sum [type=decimal, outer=(1)] - │ │ │ │ └── variable: a.x [type=int, outer=(1)] @@ -1295,9 +1401,9 @@ SELECT SUM(x) FROM a GROUP BY s, x HAVING SUM(x)=1 - │ │ └── projections [outer=(6)] - │ │ └── variable: column6 [type=decimal, outer=(6)] - │ └── true [type=bool] - + │ │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + │ │ │ ├── scan a - + │ │ │ │ └── columns: a.x:1(int!null) a.s:4(string) + + │ │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) + + │ │ │ │ └── keys: (1) + │ │ │ └── aggregations [outer=(1)] + │ │ │ └── function: sum [type=decimal, outer=(1)] + │ │ │ └── variable: a.x [type=int, outer=(1)] @@ -1318,13 +1424,18 @@ SELECT SUM(x) FROM a GROUP BY s, x HAVING SUM(x)=1 - │ ├── select + ├── select + │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) + + │ ├── keys: (1) + │ ├── group-by │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) + + │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + │ │ ├── keys: (1) - │ │ ├── group-by - │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) - │ │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + - │ │ │ ├── keys: (1) - │ │ │ ├── scan a - - │ │ │ │ └── columns: a.x:1(int!null) a.s:4(string) + - │ │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) + - │ │ │ │ └── keys: (1) - │ │ │ └── aggregations [outer=(1)] - │ │ │ └── function: sum [type=decimal, outer=(1)] - │ │ │ └── variable: a.x [type=int, outer=(1)] @@ -1334,9 +1445,9 @@ SELECT SUM(x) FROM a GROUP BY s, x HAVING SUM(x)=1 - │ │ └── const: 1 [type=decimal] - │ └── projections [outer=(6)] - │ └── variable: column6 [type=decimal, outer=(6)] - + │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + │ │ ├── scan a - + │ │ │ └── columns: a.x:1(int!null) a.s:4(string) + + │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) + + │ │ │ └── keys: (1) + │ │ └── aggregations [outer=(1)] + │ │ └── function: sum [type=decimal, outer=(1)] + │ │ └── variable: a.x [type=int, outer=(1)] @@ -1354,11 +1465,14 @@ SELECT SUM(x) FROM a GROUP BY s, x HAVING SUM(x)=1 ├── columns: column6:6(decimal) ├── select │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) + │ ├── keys: (1) │ ├── group-by │ │ ├── columns: a.x:1(int!null) a.s:4(string) column6:6(decimal) │ │ ├── grouping columns: a.x:1(int!null) a.s:4(string) + │ │ ├── keys: (1) │ │ ├── scan a - │ │ │ └── columns: a.x:1(int!null) a.s:4(string) + │ │ │ ├── columns: a.x:1(int!null) a.s:4(string) + │ │ │ └── keys: (1) │ │ └── aggregations [outer=(1)] │ │ └── function: sum [type=decimal, outer=(1)] │ │ └── variable: a.x [type=int, outer=(1)] diff --git a/pkg/sql/opt/norm/testdata/comp b/pkg/sql/opt/norm/testdata/comp index 91715534240c..2242e6bf1908 100644 --- a/pkg/sql/opt/norm/testdata/comp +++ b/pkg/sql/opt/norm/testdata/comp @@ -21,8 +21,10 @@ SELECT * FROM a WHERE 1+ik AND k/2>=i ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(1,2)] ├── gt [type=bool, outer=(1,2)] │ ├── variable: a.k [type=int, outer=(1)] @@ -53,8 +55,10 @@ SELECT * FROM a WHERE length('foo')+1i AND 'fo ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(1,2,4), constraints=(/2: (/NULL - /4]; /4: (/NULL - /'foo'])] ├── gt [type=bool, outer=(1,2)] │ ├── plus [type=int, outer=(1,2)] @@ -92,8 +96,10 @@ WHERE ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(2,3)] ├── eq [type=bool, outer=(2)] │ ├── variable: a.i [type=int, outer=(2)] @@ -133,8 +139,10 @@ SELECT * FROM a WHERE s::date + '02:00:00'::time = '2000-01-01T02:00:00'::timest ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(4)] └── eq [type=bool, outer=(4)] ├── plus [type=timestamp, outer=(4)] @@ -158,8 +166,10 @@ WHERE ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(2,3)] ├── eq [type=bool, outer=(2)] │ ├── variable: a.i [type=int, outer=(2)] @@ -201,8 +211,10 @@ SELECT * FROM a WHERE s::json - 1 = '[1]'::json ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(4)] └── eq [type=bool, outer=(4)] ├── minus [type=jsonb, outer=(4)] @@ -226,8 +238,10 @@ WHERE ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(2,3)] ├── eq [type=bool, outer=(2)] │ ├── variable: a.i [type=int, outer=(2)] @@ -269,8 +283,10 @@ SELECT * FROM a WHERE '[1, 2]'::json - i = '[1]' ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(2)] └── eq [type=bool, outer=(2)] ├── minus [type=jsonb, outer=(2)] @@ -286,8 +302,10 @@ SELECT * FROM a WHERE (i, f, s) = (1, 3.5, 'foo') ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(2-4), constraints=(/2: [/1 - /1]; /3: [/3.5 - /3.5]; /4: [/'foo' - /'foo']; tight)] ├── eq [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight)] │ ├── variable: a.i [type=int, outer=(2)] @@ -309,9 +327,11 @@ SELECT * FROM a WHERE (1, (2, 'foo')) = (k, (i, s)) ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - │ └── constraint: /1: [/1 - /1] + │ ├── constraint: /1: [/1 - /1] + │ └── keys: (1) └── filters [type=bool, outer=(2,4), constraints=(/2: [/2 - /2]; /4: [/'foo' - /'foo']; tight)] ├── eq [type=bool, outer=(2), constraints=(/2: [/2 - /2]; tight)] │ ├── variable: a.i [type=int, outer=(2)] @@ -352,4 +372,5 @@ WHERE ---- scan a ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - └── constraint: /1: contradiction + ├── constraint: /1: contradiction + └── keys: (1) diff --git a/pkg/sql/opt/norm/testdata/join b/pkg/sql/opt/norm/testdata/join index 19fb6e2db857..e04ef73664fe 100644 --- a/pkg/sql/opt/norm/testdata/join +++ b/pkg/sql/opt/norm/testdata/join @@ -28,9 +28,11 @@ SELECT * FROM a INNER JOIN b ON a.x=b.x AND b.z10) AND b.z=1 AND a.s='foo' AND b.x left-join ├── columns: x:1(int!null) z:2(int) x:3(int) i:4(int) f:5(float) s:6(string) j:7(jsonb) ├── scan b - │ └── columns: b.x:1(int!null) b.z:2(int) + │ ├── columns: b.x:1(int!null) b.z:2(int) + │ └── keys: (1) ├── select │ ├── columns: a.x:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) + │ ├── keys: (3) │ ├── scan a - │ │ └── columns: a.x:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) + │ │ ├── columns: a.x:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) + │ │ └── keys: (3) │ └── filters [type=bool, outer=(4,6), constraints=(/6: [/'foo' - /'foo'])] │ ├── or [type=bool, outer=(4)] │ │ ├── lt [type=bool, outer=(4), constraints=(/4: (/NULL - /-1]; tight)] @@ -205,9 +225,11 @@ SELECT * FROM b RIGHT JOIN a ON b.x=a.x AND a.i=1 right-join ├── columns: x:1(int) z:2(int) x:3(int!null) i:4(int) f:5(float) s:6(string) j:7(jsonb) ├── scan b - │ └── columns: b.x:1(int!null) b.z:2(int) + │ ├── columns: b.x:1(int!null) b.z:2(int) + │ └── keys: (1) ├── scan a - │ └── columns: a.x:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) + │ ├── columns: a.x:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) + │ └── keys: (3) └── filters [type=bool, outer=(1,3,4), constraints=(/4: [/1 - /1])] ├── eq [type=bool, outer=(1,3)] │ ├── variable: b.x [type=int, outer=(1)] @@ -227,16 +249,20 @@ inner-join ├── columns: x:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) z:7(int) ├── select │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── keys: (1) │ ├── scan a - │ │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ └── keys: (1) │ └── filters [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight)] │ └── eq [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight)] │ ├── variable: a.i [type=int, outer=(2)] │ └── const: 1 [type=int] ├── select │ ├── columns: b.x:6(int!null) b.z:7(int) + │ ├── keys: (6) │ ├── scan b - │ │ └── columns: b.x:6(int!null) b.z:7(int) + │ │ ├── columns: b.x:6(int!null) b.z:7(int) + │ │ └── keys: (6) │ └── filters [type=bool, outer=(7), constraints=(/7: [/1 - /1]; tight)] │ └── eq [type=bool, outer=(7), constraints=(/7: [/1 - /1]; tight)] │ ├── variable: b.z [type=int, outer=(7)] @@ -253,9 +279,11 @@ SELECT * FROM a FULL JOIN b ON a.x=b.x AND a.i=1 AND b.z=1 full-join ├── columns: x:1(int) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int) z:7(int) ├── scan a - │ └── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.x:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) ├── scan b - │ └── columns: b.x:6(int!null) b.z:7(int) + │ ├── columns: b.x:6(int!null) b.z:7(int) + │ └── keys: (6) └── filters [type=bool, outer=(1,2,6,7), constraints=(/2: [/1 - /1]; /7: [/1 - /1])] ├── eq [type=bool, outer=(1,6)] │ ├── variable: a.x [type=int, outer=(1)] diff --git a/pkg/sql/opt/norm/testdata/limit b/pkg/sql/opt/norm/testdata/limit index 4dafc98fdaf4..113dffd611b0 100644 --- a/pkg/sql/opt/norm/testdata/limit +++ b/pkg/sql/opt/norm/testdata/limit @@ -27,9 +27,11 @@ SELECT k, f*2.0 FROM a LIMIT 5 ---- project ├── columns: k:1(int!null) column6:6(float) + ├── keys: (1) ├── scan a │ ├── columns: a.k:1(int!null) a.f:3(float) - │ └── limit: 5 + │ ├── limit: 5 + │ └── keys: (1) └── projections [outer=(1,3)] ├── variable: a.k [type=int, outer=(1)] └── mult [type=float, outer=(3)] @@ -54,6 +56,7 @@ project │ │ ├── group-by │ │ │ ├── columns: a.i:2(int) a.f:3(float) │ │ │ ├── grouping columns: a.i:2(int) a.f:3(float) + │ │ │ ├── keys: weak(2,3) │ │ │ ├── scan a │ │ │ │ └── columns: a.i:2(int) a.f:3(float) │ │ │ └── aggregations @@ -74,10 +77,13 @@ SELECT k, f*2.0 FROM a OFFSET 5 ---- project ├── columns: k:1(int!null) column6:6(float) + ├── keys: (1) ├── offset │ ├── columns: a.k:1(int!null) a.f:3(float) + │ ├── keys: (1) │ ├── scan a - │ │ └── columns: a.k:1(int!null) a.f:3(float) + │ │ ├── columns: a.k:1(int!null) a.f:3(float) + │ │ └── keys: (1) │ └── const: 5 [type=int] └── projections [outer=(1,3)] ├── variable: a.k [type=int, outer=(1)] @@ -103,6 +109,7 @@ project │ │ ├── group-by │ │ │ ├── columns: a.i:2(int) a.f:3(float) │ │ │ ├── grouping columns: a.i:2(int) a.f:3(float) + │ │ │ ├── keys: weak(2,3) │ │ │ ├── scan a │ │ │ │ └── columns: a.i:2(int) a.f:3(float) │ │ │ └── aggregations @@ -123,12 +130,16 @@ SELECT k, f*2.0 FROM a OFFSET 5 LIMIT 10 ---- project ├── columns: k:1(int!null) column6:6(float) + ├── keys: (1) ├── limit │ ├── columns: a.k:1(int!null) a.f:3(float) + │ ├── keys: (1) │ ├── offset │ │ ├── columns: a.k:1(int!null) a.f:3(float) + │ │ ├── keys: (1) │ │ ├── scan a - │ │ │ └── columns: a.k:1(int!null) a.f:3(float) + │ │ │ ├── columns: a.k:1(int!null) a.f:3(float) + │ │ │ └── keys: (1) │ │ └── const: 5 [type=int] │ └── const: 10 [type=int] └── projections [outer=(1,3)] @@ -157,6 +168,7 @@ project │ │ │ ├── group-by │ │ │ │ ├── columns: a.i:2(int) a.f:3(float) │ │ │ │ ├── grouping columns: a.i:2(int) a.f:3(float) + │ │ │ │ ├── keys: weak(2,3) │ │ │ │ ├── scan a │ │ │ │ │ └── columns: a.i:2(int) a.f:3(float) │ │ │ │ └── aggregations diff --git a/pkg/sql/opt/norm/testdata/project b/pkg/sql/opt/norm/testdata/project index dcb5ac047afa..c10aa6bdd90b 100644 --- a/pkg/sql/opt/norm/testdata/project +++ b/pkg/sql/opt/norm/testdata/project @@ -27,21 +27,24 @@ opt SELECT x, y FROM t.a ---- scan a - └── columns: x:1(int!null) y:2(int) + ├── columns: x:1(int!null) y:2(int) + └── keys: (1) # Different order, aliased names. opt SELECT a.y AS aliasy, a.x FROM t.a ---- scan a - └── columns: aliasy:2(int) x:1(int!null) + ├── columns: aliasy:2(int) x:1(int!null) + └── keys: (1) # Reordered, duplicate, aliased columns. opt SELECT a.y AS alias1, a.x, a.y AS alias1, a.x FROM t.a ---- scan a - └── columns: alias1:2(int) x:1(int!null) alias1:2(int) x:1(int!null) + ├── columns: alias1:2(int) x:1(int!null) alias1:2(int) x:1(int!null) + └── keys: (1) # Added column (projection should not be eliminated). opt @@ -49,8 +52,10 @@ SELECT *, 1 FROM t.a ---- project ├── columns: x:1(int!null) y:2(int) f:3(float) s:4(string) column5:5(int) + ├── keys: (1) ├── scan a - │ └── columns: a.x:1(int!null) a.y:2(int) a.f:3(float) a.s:4(string) + │ ├── columns: a.x:1(int!null) a.y:2(int) a.f:3(float) a.s:4(string) + │ └── keys: (1) └── projections [outer=(1-4)] ├── variable: a.x [type=int, outer=(1)] ├── variable: a.y [type=int, outer=(2)] @@ -70,9 +75,11 @@ project ├── inner-join │ ├── columns: a.x:1(int!null) a.y:2(int) b.x:5(int!null) │ ├── scan a - │ │ └── columns: a.x:1(int!null) a.y:2(int) + │ │ ├── columns: a.x:1(int!null) a.y:2(int) + │ │ └── keys: (1) │ ├── scan b - │ │ └── columns: b.x:5(int!null) + │ │ ├── columns: b.x:5(int!null) + │ │ └── keys: (5) │ └── filters [type=bool, outer=(1,5)] │ └── eq [type=bool, outer=(1,5)] │ ├── variable: a.x [type=int, outer=(1)] @@ -91,7 +98,8 @@ opt SELECT x FROM (SELECT x, y+1 FROM t.a) a ---- scan a - └── columns: x:1(int!null) + ├── columns: x:1(int!null) + └── keys: (1) # Discard all columns. opt @@ -110,7 +118,8 @@ SELECT x+y FROM (SELECT y, x, s || 'foo' FROM t.a) a project ├── columns: column6:6(int) ├── scan a - │ └── columns: a.x:1(int!null) a.y:2(int) + │ ├── columns: a.x:1(int!null) a.y:2(int) + │ └── keys: (1) └── projections [outer=(1,2)] └── plus [type=int, outer=(1,2)] ├── variable: a.x [type=int, outer=(1)] @@ -122,8 +131,10 @@ SELECT l, x FROM (SELECT length(s) l, * FROM a) a ---- project ├── columns: l:5(int) x:1(int!null) + ├── keys: (1) ├── scan a - │ └── columns: a.x:1(int!null) a.s:4(string) + │ ├── columns: a.x:1(int!null) a.s:4(string) + │ └── keys: (1) └── projections [outer=(1,4)] ├── function: length [type=int, outer=(4)] │ └── variable: a.s [type=string, outer=(4)] @@ -135,10 +146,13 @@ SELECT l*l, x FROM (SELECT x, length(s) l, y FROM a) a ---- project ├── columns: column6:6(int) x:1(int!null) + ├── keys: (1) ├── project │ ├── columns: a.x:1(int!null) l:5(int) + │ ├── keys: (1) │ ├── scan a - │ │ └── columns: a.x:1(int!null) a.s:4(string) + │ │ ├── columns: a.x:1(int!null) a.s:4(string) + │ │ └── keys: (1) │ └── projections [outer=(1,4)] │ ├── variable: a.x [type=int, outer=(1)] │ └── function: length [type=int, outer=(4)] @@ -158,7 +172,8 @@ opt SELECT x FROM t.a ---- scan a - └── columns: x:1(int!null) + ├── columns: x:1(int!null) + └── keys: (1) # Project subset of columns, some used in computed columns. opt @@ -166,8 +181,10 @@ SELECT x, x+1, y+1 FROM t.a ---- project ├── columns: x:1(int!null) column5:5(int) column6:6(int) + ├── keys: (1) ├── scan a - │ └── columns: a.x:1(int!null) a.y:2(int) + │ ├── columns: a.x:1(int!null) a.y:2(int) + │ └── keys: (1) └── projections [outer=(1,2)] ├── variable: a.x [type=int, outer=(1)] ├── plus [type=int, outer=(1)] @@ -184,7 +201,8 @@ SELECT x+y FROM t.a project ├── columns: column5:5(int) ├── scan a - │ └── columns: a.x:1(int!null) a.y:2(int) + │ ├── columns: a.x:1(int!null) a.y:2(int) + │ └── keys: (1) └── projections [outer=(1,2)] └── plus [type=int, outer=(1,2)] ├── variable: a.x [type=int, outer=(1)] @@ -210,10 +228,13 @@ SELECT x FROM t.a WHERE y<5 ---- project ├── columns: x:1(int!null) + ├── keys: (1) ├── select │ ├── columns: a.x:1(int!null) a.y:2(int) + │ ├── keys: (1) │ ├── scan a - │ │ └── columns: a.x:1(int!null) a.y:2(int) + │ │ ├── columns: a.x:1(int!null) a.y:2(int) + │ │ └── keys: (1) │ └── filters [type=bool, outer=(2), constraints=(/2: (/NULL - /4]; tight)] │ └── lt [type=bool, outer=(2), constraints=(/2: (/NULL - /4]; tight)] │ ├── variable: a.y [type=int, outer=(2)] @@ -227,9 +248,11 @@ SELECT x, y FROM t.a WHERE x=1 AND y<5 ---- select ├── columns: x:1(int!null) y:2(int) + ├── keys: (1) ├── scan a │ ├── columns: a.x:1(int!null) a.y:2(int) - │ └── constraint: /1: [/1 - /1] + │ ├── constraint: /1: [/1 - /1] + │ └── keys: (1) └── filters [type=bool, outer=(2), constraints=(/2: (/NULL - /4]; tight)] └── lt [type=bool, outer=(2), constraints=(/2: (/NULL - /4]; tight)] ├── variable: a.y [type=int, outer=(2)] @@ -258,8 +281,10 @@ project ├── columns: column5:5(int) column6:6(int) ├── select │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ ├── keys: (1) │ ├── scan a - │ │ └── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ │ └── keys: (1) │ └── filters [type=bool, outer=(1,4)] │ ├── lt [type=bool, outer=(1)] │ │ ├── variable: a.x [type=int, outer=(1)] @@ -291,7 +316,8 @@ project │ │ ├── columns: a.y:2(int) f:5(float) │ │ ├── scan a │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.f:3(float) - │ │ │ └── constraint: /1: [/5 - /5] + │ │ │ ├── constraint: /1: [/5 - /5] + │ │ │ └── keys: (1) │ │ └── projections [outer=(2,3)] │ │ ├── variable: a.y [type=int, outer=(2)] │ │ └── plus [type=float, outer=(3)] @@ -313,11 +339,14 @@ project ├── columns: f:3(float) column6:6(float) ├── select │ ├── columns: a.x:1(int!null) a.f:3(float) column5:5(decimal) + │ ├── keys: (1) │ ├── group-by │ │ ├── columns: a.x:1(int!null) a.f:3(float) column5:5(decimal) │ │ ├── grouping columns: a.x:1(int!null) a.f:3(float) + │ │ ├── keys: (1) │ │ ├── scan a - │ │ │ └── columns: a.x:1(int!null) a.f:3(float) + │ │ │ ├── columns: a.x:1(int!null) a.f:3(float) + │ │ │ └── keys: (1) │ │ └── aggregations [outer=(1)] │ │ └── function: sum [type=decimal, outer=(1)] │ │ └── variable: a.x [type=int, outer=(1)] @@ -341,13 +370,17 @@ SELECT x FROM (SELECT x, y, f FROM t.a ORDER BY y LIMIT 10) ---- project ├── columns: x:1(int!null) + ├── keys: (1) ├── limit │ ├── columns: a.x:1(int!null) a.y:2(int) + │ ├── keys: (1) │ ├── sort │ │ ├── columns: a.x:1(int!null) a.y:2(int) + │ │ ├── keys: (1) │ │ ├── ordering: +2 │ │ └── scan a - │ │ └── columns: a.x:1(int!null) a.y:2(int) + │ │ ├── columns: a.x:1(int!null) a.y:2(int) + │ │ └── keys: (1) │ └── const: 10 [type=int] └── projections [outer=(1)] └── variable: a.x [type=int, outer=(1)] @@ -360,7 +393,8 @@ project ├── columns: s:4(string) ├── scan a │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) - │ └── limit: 10 + │ ├── limit: 10 + │ └── keys: (1) └── projections [outer=(4)] └── variable: a.s [type=string, outer=(4)] @@ -370,16 +404,18 @@ SELECT x, s FROM (SELECT x, y, f, s FROM t.a ORDER BY x, y LIMIT 10) ---- project ├── columns: x:1(int!null) s:4(string) + ├── keys: (1) ├── scan a │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) - │ └── limit: 10 + │ ├── limit: 10 + │ └── keys: (1) └── projections [outer=(1,4)] ├── variable: a.x [type=int, outer=(1)] └── variable: a.s [type=string, outer=(4)] # Project filter limit columns, but can't push all the way down to scan. opt -SELECT f, f*2.0 FROM (SELECT f, x FROM a GROUP BY f, x LIMIT 5) a +SELECT f, f*2.0 FROM (SELECT f, s FROM a GROUP BY f, s LIMIT 5) a ---- project ├── columns: f:3(float) column5:5(float) @@ -388,10 +424,11 @@ project │ ├── project │ │ ├── columns: a.f:3(float) │ │ ├── group-by - │ │ │ ├── columns: a.x:1(int!null) a.f:3(float) - │ │ │ ├── grouping columns: a.x:1(int!null) a.f:3(float) + │ │ │ ├── columns: a.f:3(float) a.s:4(string) + │ │ │ ├── grouping columns: a.f:3(float) a.s:4(string) + │ │ │ ├── keys: weak(3,4) │ │ │ ├── scan a - │ │ │ │ └── columns: a.x:1(int!null) a.f:3(float) + │ │ │ │ └── columns: a.f:3(float) a.s:4(string) │ │ │ └── aggregations │ │ └── projections [outer=(3)] │ │ └── variable: a.f [type=float, outer=(3)] @@ -411,13 +448,17 @@ SELECT x FROM (SELECT x, y, f FROM t.a ORDER BY y OFFSET 10) ---- project ├── columns: x:1(int!null) + ├── keys: (1) ├── offset │ ├── columns: a.x:1(int!null) a.y:2(int) + │ ├── keys: (1) │ ├── sort │ │ ├── columns: a.x:1(int!null) a.y:2(int) + │ │ ├── keys: (1) │ │ ├── ordering: +2 │ │ └── scan a - │ │ └── columns: a.x:1(int!null) a.y:2(int) + │ │ ├── columns: a.x:1(int!null) a.y:2(int) + │ │ └── keys: (1) │ └── const: 10 [type=int] └── projections [outer=(1)] └── variable: a.x [type=int, outer=(1)] @@ -430,8 +471,10 @@ project ├── columns: s:4(string) ├── offset │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ ├── keys: (1) │ ├── scan a │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ │ ├── keys: (1) │ │ └── ordering: +1,+2 │ └── const: 10 [type=int] └── projections [outer=(4)] @@ -443,10 +486,13 @@ SELECT x, s FROM (SELECT x, y, f, s FROM t.a ORDER BY x, y OFFSET 10) ---- project ├── columns: x:1(int!null) s:4(string) + ├── keys: (1) ├── offset │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ ├── keys: (1) │ ├── scan a │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ │ ├── keys: (1) │ │ └── ordering: +1,+2 │ └── const: 10 [type=int] └── projections [outer=(1,4)] @@ -455,7 +501,7 @@ project # Project filter offset columns, but can't push all the way down to scan. opt -SELECT f, f*2.0 FROM (SELECT f, x FROM a GROUP BY f, x OFFSET 5) a +SELECT f, f*2.0 FROM (SELECT f, s FROM a GROUP BY f, s OFFSET 5) a ---- project ├── columns: f:3(float) column5:5(float) @@ -464,10 +510,11 @@ project │ ├── project │ │ ├── columns: a.f:3(float) │ │ ├── group-by - │ │ │ ├── columns: a.x:1(int!null) a.f:3(float) - │ │ │ ├── grouping columns: a.x:1(int!null) a.f:3(float) + │ │ │ ├── columns: a.f:3(float) a.s:4(string) + │ │ │ ├── grouping columns: a.f:3(float) a.s:4(string) + │ │ │ ├── keys: weak(3,4) │ │ │ ├── scan a - │ │ │ │ └── columns: a.x:1(int!null) a.f:3(float) + │ │ │ │ └── columns: a.f:3(float) a.s:4(string) │ │ │ └── aggregations │ │ └── projections [outer=(3)] │ │ └── variable: a.f [type=float, outer=(3)] @@ -487,16 +534,21 @@ SELECT x FROM (SELECT x, y, f FROM t.a ORDER BY y LIMIT 10 OFFSET 10) ---- project ├── columns: x:1(int!null) + ├── keys: (1) ├── limit │ ├── columns: a.x:1(int!null) a.y:2(int) + │ ├── keys: (1) │ ├── offset │ │ ├── columns: a.x:1(int!null) a.y:2(int) + │ │ ├── keys: (1) │ │ ├── ordering: +2 │ │ ├── sort │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) + │ │ │ ├── keys: (1) │ │ │ ├── ordering: +2 │ │ │ └── scan a - │ │ │ └── columns: a.x:1(int!null) a.y:2(int) + │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) + │ │ │ └── keys: (1) │ │ └── const: 10 [type=int] │ └── const: 10 [type=int] └── projections [outer=(1)] @@ -510,11 +562,14 @@ project ├── columns: s:4(string) ├── limit │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ ├── keys: (1) │ ├── offset │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ │ ├── keys: (1) │ │ ├── ordering: +1,+2 │ │ ├── scan a │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ │ │ ├── keys: (1) │ │ │ └── ordering: +1,+2 │ │ └── const: 10 [type=int] │ └── const: 10 [type=int] @@ -527,13 +582,17 @@ SELECT x, s FROM (SELECT x, y, f, s FROM t.a ORDER BY x, y LIMIT 10 OFFSET 10) ---- project ├── columns: x:1(int!null) s:4(string) + ├── keys: (1) ├── limit │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ ├── keys: (1) │ ├── offset │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ │ ├── keys: (1) │ │ ├── ordering: +1,+2 │ │ ├── scan a │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ │ │ ├── keys: (1) │ │ │ └── ordering: +1,+2 │ │ └── const: 10 [type=int] │ └── const: 10 [type=int] @@ -543,7 +602,7 @@ project # Project filter offset/limit columns, but can't push all the way down to scan. opt -SELECT f, f*2.0 FROM (SELECT f, x FROM a GROUP BY f, x OFFSET 5 LIMIT 5) a +SELECT f, f*2.0 FROM (SELECT f, s FROM a GROUP BY f, s OFFSET 5 LIMIT 5) a ---- project ├── columns: f:3(float) column5:5(float) @@ -554,10 +613,11 @@ project │ │ ├── project │ │ │ ├── columns: a.f:3(float) │ │ │ ├── group-by - │ │ │ │ ├── columns: a.x:1(int!null) a.f:3(float) - │ │ │ │ ├── grouping columns: a.x:1(int!null) a.f:3(float) + │ │ │ │ ├── columns: a.f:3(float) a.s:4(string) + │ │ │ │ ├── grouping columns: a.f:3(float) a.s:4(string) + │ │ │ │ ├── keys: weak(3,4) │ │ │ │ ├── scan a - │ │ │ │ │ └── columns: a.x:1(int!null) a.f:3(float) + │ │ │ │ │ └── columns: a.f:3(float) a.s:4(string) │ │ │ │ └── aggregations │ │ │ └── projections [outer=(3)] │ │ │ └── variable: a.f [type=float, outer=(3)] @@ -582,9 +642,11 @@ project ├── inner-join │ ├── columns: a.x:1(int!null) a.y:2(int) b.x:5(int!null) b.z:6(int) │ ├── scan a - │ │ └── columns: a.x:1(int!null) a.y:2(int) + │ │ ├── columns: a.x:1(int!null) a.y:2(int) + │ │ └── keys: (1) │ ├── scan b - │ │ └── columns: b.x:5(int!null) b.z:6(int) + │ │ ├── columns: b.x:5(int!null) b.z:6(int) + │ │ └── keys: (5) │ └── filters [type=bool, outer=(1,5)] │ └── eq [type=bool, outer=(1,5)] │ ├── variable: a.x [type=int, outer=(1)] @@ -601,9 +663,11 @@ SELECT a.x, a.y, b.* FROM t.a LEFT JOIN t.b ON a.x=b.x AND a.y<5 left-join ├── columns: x:1(int!null) y:2(int) x:5(int) z:6(int) ├── scan a - │ └── columns: a.x:1(int!null) a.y:2(int) + │ ├── columns: a.x:1(int!null) a.y:2(int) + │ └── keys: (1) ├── scan b - │ └── columns: b.x:5(int!null) b.z:6(int) + │ ├── columns: b.x:5(int!null) b.z:6(int) + │ └── keys: (5) └── filters [type=bool, outer=(1,2,5), constraints=(/2: (/NULL - /4])] ├── eq [type=bool, outer=(1,5)] │ ├── variable: a.x [type=int, outer=(1)] @@ -621,9 +685,11 @@ project ├── right-join │ ├── columns: a.x:1(int) b.x:5(int!null) b.z:6(int) │ ├── scan a - │ │ └── columns: a.x:1(int!null) + │ │ ├── columns: a.x:1(int!null) + │ │ └── keys: (1) │ ├── scan b - │ │ └── columns: b.x:5(int!null) b.z:6(int) + │ │ ├── columns: b.x:5(int!null) b.z:6(int) + │ │ └── keys: (5) │ └── filters [type=bool, outer=(1,5)] │ └── eq [type=bool, outer=(1,5)] │ ├── variable: a.x [type=int, outer=(1)] @@ -641,9 +707,11 @@ project ├── full-join │ ├── columns: a.x:1(int) b.x:5(int) b.z:6(int) │ ├── scan a - │ │ └── columns: a.x:1(int!null) + │ │ ├── columns: a.x:1(int!null) + │ │ └── keys: (1) │ ├── scan b - │ │ └── columns: b.x:5(int!null) b.z:6(int) + │ │ ├── columns: b.x:5(int!null) b.z:6(int) + │ │ └── keys: (5) │ └── true [type=bool] └── projections [outer=(1,5,6)] ├── plus [type=int, outer=(1)] @@ -660,7 +728,8 @@ inner-join ├── columns: x:5(int!null) z:6(int) ├── scan a ├── scan b - │ └── columns: b.x:5(int!null) b.z:6(int) + │ ├── columns: b.x:5(int!null) b.z:6(int) + │ └── keys: (5) └── true [type=bool] # Computed columns. @@ -673,10 +742,13 @@ project │ ├── columns: a.x:1(int!null) a.y:2(int) b.x:5(int!null) b.z:6(int) │ ├── project │ │ ├── columns: a.x:1(int!null) a.y:2(int) + │ │ ├── keys: (1) │ │ ├── select │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ │ │ ├── keys: (1) │ │ │ ├── scan a - │ │ │ │ └── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.s:4(string) + │ │ │ │ └── keys: (1) │ │ │ └── filters [type=bool, outer=(4)] │ │ │ └── eq [type=bool, outer=(4)] │ │ │ ├── concat [type=string, outer=(4)] @@ -687,7 +759,8 @@ project │ │ ├── variable: a.x [type=int, outer=(1)] │ │ └── variable: a.y [type=int, outer=(2)] │ ├── scan b - │ │ └── columns: b.x:5(int!null) b.z:6(int) + │ │ ├── columns: b.x:5(int!null) b.z:6(int) + │ │ └── keys: (5) │ └── filters [type=bool, outer=(1,5)] │ └── eq [type=bool, outer=(1,5)] │ ├── variable: b.x [type=int, outer=(5)] @@ -723,9 +796,11 @@ project │ │ ├── inner-join │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) b.x:5(int!null) │ │ │ ├── scan a - │ │ │ │ └── columns: a.x:1(int!null) a.y:2(int) + │ │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) + │ │ │ │ └── keys: (1) │ │ │ ├── scan b - │ │ │ │ └── columns: b.x:5(int!null) + │ │ │ │ ├── columns: b.x:5(int!null) + │ │ │ │ └── keys: (5) │ │ │ └── filters [type=bool, outer=(1,5)] │ │ │ └── eq [type=bool, outer=(1,5)] │ │ │ ├── variable: a.x [type=int, outer=(1)] @@ -734,7 +809,8 @@ project │ │ ├── variable: a.x [type=int, outer=(1)] │ │ └── variable: a.y [type=int, outer=(2)] │ ├── scan b - │ │ └── columns: b.x:7(int!null) b.z:8(int) + │ │ ├── columns: b.x:7(int!null) b.z:8(int) + │ │ └── keys: (7) │ └── filters [type=bool, outer=(2,8)] │ └── lt [type=bool, outer=(2,8)] │ ├── variable: a.y [type=int, outer=(2)] @@ -757,9 +833,11 @@ project ├── inner-join │ ├── columns: b.x:1(int!null) b.z:2(int) a.x:3(int!null) a.y:4(int) │ ├── scan b - │ │ └── columns: b.x:1(int!null) b.z:2(int) + │ │ ├── columns: b.x:1(int!null) b.z:2(int) + │ │ └── keys: (1) │ ├── scan a - │ │ └── columns: a.x:3(int!null) a.y:4(int) + │ │ ├── columns: a.x:3(int!null) a.y:4(int) + │ │ └── keys: (3) │ └── filters [type=bool, outer=(1,3)] │ └── eq [type=bool, outer=(1,3)] │ ├── variable: b.x [type=int, outer=(1)] @@ -776,9 +854,11 @@ SELECT b.*, a.x, a.y FROM t.b LEFT JOIN t.a ON b.x=a.x AND a.y 5) ---- project ├── columns: x:1(int!null) + ├── keys: (1) ├── select │ ├── columns: a.x:1(int!null) column5:5(decimal) + │ ├── keys: (1) │ ├── group-by │ │ ├── columns: a.x:1(int!null) column5:5(decimal) │ │ ├── grouping columns: a.x:1(int!null) + │ │ ├── keys: (1) │ │ ├── scan a - │ │ │ └── columns: a.x:1(int!null) a.y:2(int) + │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) + │ │ │ └── keys: (1) │ │ └── aggregations [outer=(2)] │ │ └── function: sum [type=decimal, outer=(2)] │ │ └── variable: a.y [type=int, outer=(2)] diff --git a/pkg/sql/opt/norm/testdata/scalar b/pkg/sql/opt/norm/testdata/scalar index e06d3155aac3..1c9f1330355b 100644 --- a/pkg/sql/opt/norm/testdata/scalar +++ b/pkg/sql/opt/norm/testdata/scalar @@ -34,7 +34,8 @@ FROM a project ├── columns: column7:7(bool) column8:8(bool) column9:9(bool) column10:10(bool) column11:11(int) column12:12(int) column13:13(int) column14:14(int) column15:15(int) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) + │ ├── columns: a.k:1(int!null) a.i:2(int) + │ └── keys: (1) └── projections [outer=(1,2)] ├── eq [type=bool, outer=(1,2)] │ ├── variable: a.k [type=int, outer=(1)] @@ -102,7 +103,8 @@ FROM a project ├── columns: column7:7(bool) column8:8(bool) column9:9(bool) column10:10(bool) column11:11(float) column12:12(int) column13:13(int) column14:14(int) column15:15(int) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) + │ └── keys: (1) └── projections [outer=(1-3)] ├── eq [type=bool, outer=(1,2)] │ ├── plus [type=int, outer=(1,2)] diff --git a/pkg/sql/opt/norm/testdata/select b/pkg/sql/opt/norm/testdata/select index a4103a94cd3d..536dcad31917 100644 --- a/pkg/sql/opt/norm/testdata/select +++ b/pkg/sql/opt/norm/testdata/select @@ -27,8 +27,10 @@ SELECT * FROM a WHERE i=5 AND s<'foo' ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(2,4), constraints=(/2: [/5 - /5]; /4: (/NULL - /'foo'); tight)] ├── eq [type=bool, outer=(2), constraints=(/2: [/5 - /5]; tight)] │ ├── variable: a.i [type=int, outer=(2)] @@ -45,8 +47,10 @@ SELECT * FROM a WHERE i<5 ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(2), constraints=(/2: (/NULL - /4]; tight)] └── lt [type=bool, outer=(2), constraints=(/2: (/NULL - /4]; tight)] ├── variable: a.i [type=int, outer=(2)] @@ -57,8 +61,10 @@ SELECT * FROM a WHERE i<5 OR s='foo' ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(2,4)] └── or [type=bool, outer=(2,4)] ├── lt [type=bool, outer=(2), constraints=(/2: (/NULL - /4]; tight)] @@ -73,14 +79,16 @@ opt SELECT * FROM a WHERE True ---- scan a - └── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + └── keys: (1) opt SELECT * FROM a WHERE False ---- scan a ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - └── constraint: /1: contradiction + ├── constraint: /1: contradiction + └── keys: (1) # -------------------------------------------------- # EliminateSelect @@ -89,7 +97,8 @@ opt SELECT * FROM a WHERE True ---- scan a - └── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + └── keys: (1) # -------------------------------------------------- # MergeSelects @@ -99,29 +108,34 @@ SELECT * FROM (SELECT * FROM a WHERE False) WHERE s='foo' ---- scan a ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - └── constraint: /1: contradiction + ├── constraint: /1: contradiction + └── keys: (1) opt SELECT * FROM (SELECT * FROM a WHERE i=1) WHERE False ---- scan a ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - └── constraint: /1: contradiction + ├── constraint: /1: contradiction + └── keys: (1) opt SELECT * FROM (SELECT * FROM a WHERE i=1) WHERE False ---- scan a ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - └── constraint: /1: contradiction + ├── constraint: /1: contradiction + └── keys: (1) opt SELECT * FROM (SELECT * FROM a WHERE i<5) WHERE s='foo' ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(2,4), constraints=(/2: (/NULL - /4]; /4: [/'foo' - /'foo']; tight)] ├── lt [type=bool, outer=(2), constraints=(/2: (/NULL - /4]; tight)] │ ├── variable: a.i [type=int, outer=(2)] @@ -135,8 +149,10 @@ SELECT * FROM (SELECT * FROM a WHERE i>1 AND i<10) WHERE s='foo' OR k=5 ---- select ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) + ├── keys: (1) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) └── filters [type=bool, outer=(1,2,4), constraints=(/2: [/2 - /9])] ├── gt [type=bool, outer=(2), constraints=(/2: [/2 - ]; tight)] │ ├── variable: a.i [type=int, outer=(2)] @@ -235,9 +251,11 @@ project ├── columns: f:3(float) column7:7(float) ├── select │ ├── columns: a.i:2(int) a.f:3(float) column6:6(float) + │ ├── keys: weak(2,3) │ ├── group-by │ │ ├── columns: a.i:2(int) a.f:3(float) column6:6(float) │ │ ├── grouping columns: a.i:2(int) a.f:3(float) + │ │ ├── keys: weak(2,3) │ │ ├── scan a │ │ │ └── columns: a.i:2(int) a.f:3(float) │ │ └── aggregations [outer=(3)] @@ -264,14 +282,17 @@ inner-join ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) y:7(int) ├── select │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── keys: (1) │ ├── scan a - │ │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ └── keys: (1) │ └── filters [type=bool, outer=(3), constraints=(/3: [/1.1 - /1.1]; tight)] │ └── eq [type=bool, outer=(3), constraints=(/3: [/1.1 - /1.1]; tight)] │ ├── variable: a.f [type=float, outer=(3)] │ └── const: 1.1 [type=float] ├── scan b - │ └── columns: b.x:6(int!null) b.y:7(int) + │ ├── columns: b.x:6(int!null) b.y:7(int) + │ └── keys: (6) └── filters [type=bool, outer=(1,6)] └── eq [type=bool, outer=(1,6)] ├── variable: a.k [type=int, outer=(1)] @@ -286,8 +307,10 @@ select │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int) b.y:7(int) │ ├── select │ │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ ├── keys: (1) │ │ ├── scan a - │ │ │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ │ └── keys: (1) │ │ └── filters [type=bool, outer=(3,4), constraints=(/3: [/1.1 - /1.1])] │ │ ├── eq [type=bool, outer=(3), constraints=(/3: [/1.1 - /1.1]; tight)] │ │ │ ├── variable: a.f [type=float, outer=(3)] @@ -300,7 +323,8 @@ select │ │ ├── variable: a.s [type=string, outer=(4)] │ │ └── const: 'bar' [type=string] │ ├── scan b - │ │ └── columns: b.x:6(int!null) b.y:7(int) + │ │ ├── columns: b.x:6(int!null) b.y:7(int) + │ │ └── keys: (6) │ └── filters [type=bool, outer=(1,6)] │ └── eq [type=bool, outer=(1,6)] │ ├── variable: a.k [type=int, outer=(1)] @@ -318,8 +342,10 @@ inner-join ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) y:7(int) ├── select │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── keys: (1) │ ├── scan a - │ │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ └── keys: (1) │ └── filters [type=bool, outer=(2), constraints=(/2: [/100 - /100])] │ ├── eq [type=bool, outer=(2), constraints=(/2: [/100 - /100]; tight)] │ │ ├── variable: a.i [type=int, outer=(2)] @@ -328,7 +354,8 @@ inner-join │ ├── function: now [type=timestamptz] │ └── const: '2000-01-01 01:00:00+00:00' [type=timestamptz] ├── scan b - │ └── columns: b.x:6(int!null) b.y:7(int) + │ ├── columns: b.x:6(int!null) b.y:7(int) + │ └── keys: (6) └── true [type=bool] # Don't push down conditions in case of RIGHT JOIN. @@ -340,9 +367,11 @@ select ├── right-join │ ├── columns: a.k:1(int) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int!null) b.y:7(int) │ ├── scan a - │ │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ └── keys: (1) │ ├── scan b - │ │ └── columns: b.x:6(int!null) b.y:7(int) + │ │ ├── columns: b.x:6(int!null) b.y:7(int) + │ │ └── keys: (6) │ └── filters [type=bool, outer=(1,6)] │ └── eq [type=bool, outer=(1,6)] │ ├── variable: a.k [type=int, outer=(1)] @@ -361,9 +390,11 @@ select ├── full-join │ ├── columns: a.k:1(int) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int) b.y:7(int) │ ├── scan a - │ │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ └── keys: (1) │ ├── scan b - │ │ └── columns: b.x:6(int!null) b.y:7(int) + │ │ ├── columns: b.x:6(int!null) b.y:7(int) + │ │ └── keys: (6) │ └── filters [type=bool, outer=(1,6)] │ └── eq [type=bool, outer=(1,6)] │ ├── variable: a.k [type=int, outer=(1)] @@ -382,11 +413,14 @@ SELECT * FROM b INNER JOIN a ON b.x=a.k WHERE a.f=1.1 inner-join ├── columns: x:1(int!null) y:2(int) k:3(int!null) i:4(int) f:5(float) s:6(string) j:7(jsonb) ├── scan b - │ └── columns: b.x:1(int!null) b.y:2(int) + │ ├── columns: b.x:1(int!null) b.y:2(int) + │ └── keys: (1) ├── select │ ├── columns: a.k:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) + │ ├── keys: (3) │ ├── scan a - │ │ └── columns: a.k:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) + │ │ ├── columns: a.k:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) + │ │ └── keys: (3) │ └── filters [type=bool, outer=(5), constraints=(/5: [/1.1 - /1.1]; tight)] │ └── eq [type=bool, outer=(5), constraints=(/5: [/1.1 - /1.1]; tight)] │ ├── variable: a.f [type=float, outer=(5)] @@ -404,11 +438,14 @@ select ├── right-join │ ├── columns: b.x:1(int) b.y:2(int) a.k:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) │ ├── scan b - │ │ └── columns: b.x:1(int!null) b.y:2(int) + │ │ ├── columns: b.x:1(int!null) b.y:2(int) + │ │ └── keys: (1) │ ├── select │ │ ├── columns: a.k:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) + │ │ ├── keys: (3) │ │ ├── scan a - │ │ │ └── columns: a.k:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) + │ │ │ ├── columns: a.k:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) + │ │ │ └── keys: (3) │ │ └── filters [type=bool, outer=(5,6), constraints=(/5: [/1.1 - /1.1])] │ │ ├── eq [type=bool, outer=(5), constraints=(/5: [/1.1 - /1.1]; tight)] │ │ │ ├── variable: a.f [type=float, outer=(5)] @@ -438,9 +475,11 @@ select ├── left-join │ ├── columns: b.x:1(int!null) b.y:2(int) a.k:3(int) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) │ ├── scan b - │ │ └── columns: b.x:1(int!null) b.y:2(int) + │ │ ├── columns: b.x:1(int!null) b.y:2(int) + │ │ └── keys: (1) │ ├── scan a - │ │ └── columns: a.k:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) + │ │ ├── columns: a.k:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) + │ │ └── keys: (3) │ └── filters [type=bool, outer=(1,3)] │ └── eq [type=bool, outer=(1,3)] │ ├── variable: a.k [type=int, outer=(3)] @@ -459,9 +498,11 @@ select ├── full-join │ ├── columns: b.x:1(int) b.y:2(int) a.k:3(int) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) │ ├── scan b - │ │ └── columns: b.x:1(int!null) b.y:2(int) + │ │ ├── columns: b.x:1(int!null) b.y:2(int) + │ │ └── keys: (1) │ ├── scan a - │ │ └── columns: a.k:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) + │ │ ├── columns: a.k:3(int!null) a.i:4(int) a.f:5(float) a.s:6(string) a.j:7(jsonb) + │ │ └── keys: (3) │ └── filters [type=bool, outer=(1,3)] │ └── eq [type=bool, outer=(1,3)] │ ├── variable: a.k [type=int, outer=(3)] @@ -480,9 +521,11 @@ SELECT * FROM a, b WHERE a.k=b.x AND (a.s='foo' OR b.y<100) inner-join ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) y:7(int) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) ├── scan b - │ └── columns: b.x:6(int!null) b.y:7(int) + │ ├── columns: b.x:6(int!null) b.y:7(int) + │ └── keys: (6) └── filters [type=bool, outer=(1,4,6,7)] ├── eq [type=bool, outer=(1,6)] │ ├── variable: a.k [type=int, outer=(1)] @@ -501,9 +544,11 @@ SELECT * FROM a INNER JOIN b ON a.k=b.x WHERE (a.s='foo' OR b.y<100) inner-join ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) y:7(int) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) ├── scan b - │ └── columns: b.x:6(int!null) b.y:7(int) + │ ├── columns: b.x:6(int!null) b.y:7(int) + │ └── keys: (6) └── filters [type=bool, outer=(1,4,6,7)] ├── eq [type=bool, outer=(1,6)] │ ├── variable: a.k [type=int, outer=(1)] @@ -522,9 +567,11 @@ SELECT * FROM a INNER JOIN b ON a.k=b.x WHERE False inner-join ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) y:7(int) ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ └── keys: (1) ├── scan b - │ └── columns: b.x:6(int!null) b.y:7(int) + │ ├── columns: b.x:6(int!null) b.y:7(int) + │ └── keys: (6) └── false [type=bool] # Don't merge with LEFT JOIN. @@ -536,9 +583,11 @@ select ├── left-join │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int) b.y:7(int) │ ├── scan a - │ │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ └── keys: (1) │ ├── scan b - │ │ └── columns: b.x:6(int!null) b.y:7(int) + │ │ ├── columns: b.x:6(int!null) b.y:7(int) + │ │ └── keys: (6) │ └── true [type=bool] └── filters [type=bool, outer=(1,6)] └── eq [type=bool, outer=(1,6)] @@ -554,9 +603,11 @@ select ├── right-join │ ├── columns: a.k:1(int) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int!null) b.y:7(int) │ ├── scan a - │ │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ └── keys: (1) │ ├── scan b - │ │ └── columns: b.x:6(int!null) b.y:7(int) + │ │ ├── columns: b.x:6(int!null) b.y:7(int) + │ │ └── keys: (6) │ └── true [type=bool] └── filters [type=bool, outer=(1,6)] └── eq [type=bool, outer=(1,6)] @@ -572,9 +623,11 @@ select ├── full-join │ ├── columns: a.k:1(int) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) b.x:6(int) b.y:7(int) │ ├── scan a - │ │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ └── keys: (1) │ ├── scan b - │ │ └── columns: b.x:6(int!null) b.y:7(int) + │ │ ├── columns: b.x:6(int!null) b.y:7(int) + │ │ └── keys: (6) │ └── true [type=bool] └── filters [type=bool, outer=(1,6)] └── eq [type=bool, outer=(1,6)] @@ -591,8 +644,10 @@ inner-join ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) y:7(int) ├── select │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── keys: (1) │ ├── scan a - │ │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ └── keys: (1) │ └── filters [type=bool, outer=(3,4), constraints=(/3: [/1.1 - /1.1]; /4: [/'foo' - /'foo']; tight)] │ ├── eq [type=bool, outer=(3), constraints=(/3: [/1.1 - /1.1]; tight)] │ │ ├── variable: a.f [type=float, outer=(3)] @@ -602,8 +657,10 @@ inner-join │ └── const: 'foo' [type=string] ├── select │ ├── columns: b.x:6(int!null) b.y:7(int) + │ ├── keys: (6) │ ├── scan b - │ │ └── columns: b.x:6(int!null) b.y:7(int) + │ │ ├── columns: b.x:6(int!null) b.y:7(int) + │ │ └── keys: (6) │ └── filters [type=bool, outer=(7), constraints=(/7: [/10 - /10]; tight)] │ └── eq [type=bool, outer=(7), constraints=(/7: [/10 - /10]; tight)] │ ├── variable: b.y [type=int, outer=(7)] @@ -623,8 +680,10 @@ inner-join ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) x:6(int!null) y:7(int) ├── select │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ ├── keys: (1) │ ├── scan a - │ │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ ├── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) + │ │ └── keys: (1) │ └── filters [type=bool, outer=(2), constraints=(/2: [/100 - /100])] │ ├── eq [type=bool, outer=(2), constraints=(/2: [/100 - /100]; tight)] │ │ ├── variable: a.i [type=int, outer=(2)] @@ -633,7 +692,8 @@ inner-join │ ├── function: now [type=timestamptz] │ └── const: '2000-01-01 01:00:00+00:00' [type=timestamptz] ├── scan b - │ └── columns: b.x:6(int!null) b.y:7(int) + │ ├── columns: b.x:6(int!null) b.y:7(int) + │ └── keys: (6) └── filters [type=bool, outer=(1,6)] └── eq [type=bool, outer=(1,6)] ├── variable: b.x [type=int, outer=(6)] @@ -650,6 +710,7 @@ SELECT * FROM (SELECT i, COUNT(*) FROM a GROUP BY i) a WHERE i=1 group-by ├── columns: i:2(int) column6:6(int) ├── grouping columns: a.i:2(int) + ├── keys: weak(2) ├── select │ ├── columns: a.i:2(int) │ ├── scan a @@ -668,6 +729,7 @@ SELECT * FROM (SELECT i FROM a GROUP BY i) a WHERE i=1 group-by ├── columns: i:2(int) ├── grouping columns: a.i:2(int) + ├── keys: weak(2) ├── select │ ├── columns: a.i:2(int) │ ├── scan a @@ -684,13 +746,17 @@ SELECT * FROM (SELECT k, i, MAX(s) m FROM a GROUP BY k, i) a WHERE i=k AND m='fo ---- select ├── columns: k:1(int!null) i:2(int) m:6(string) + ├── keys: (1) ├── group-by │ ├── columns: a.k:1(int!null) a.i:2(int) m:6(string) │ ├── grouping columns: a.k:1(int!null) a.i:2(int) + │ ├── keys: (1) │ ├── select │ │ ├── columns: a.k:1(int!null) a.i:2(int) a.s:4(string) + │ │ ├── keys: (1) │ │ ├── scan a - │ │ │ └── columns: a.k:1(int!null) a.i:2(int) a.s:4(string) + │ │ │ ├── columns: a.k:1(int!null) a.i:2(int) a.s:4(string) + │ │ │ └── keys: (1) │ │ └── filters [type=bool, outer=(1,2)] │ │ └── eq [type=bool, outer=(1,2)] │ │ ├── variable: a.i [type=int, outer=(2)] @@ -709,8 +775,10 @@ SELECT * FROM (SELECT COUNT(*) c FROM a) a WHERE now()<'2000-01-01T10:00:00' AND ---- select ├── columns: c:6(int) + ├── keys: () ├── group-by │ ├── columns: c:6(int) + │ ├── keys: () │ ├── scan a │ └── aggregations │ └── function: count_rows [type=int] diff --git a/pkg/sql/opt/xform/testdata/coster/join b/pkg/sql/opt/xform/testdata/coster/join index 08e925028c6d..aa0031a66202 100644 --- a/pkg/sql/opt/xform/testdata/coster/join +++ b/pkg/sql/opt/xform/testdata/coster/join @@ -30,14 +30,17 @@ inner-join │ ├── columns: a.k:1(int!null) │ ├── stats: [rows=100] │ ├── cost: 1100.00 + │ ├── keys: (1) │ ├── select │ │ ├── columns: a.k:1(int!null) a.d:4(decimal!null) │ │ ├── stats: [rows=100] │ │ ├── cost: 1100.00 + │ │ ├── keys: (1) │ │ ├── scan a │ │ │ ├── columns: a.k:1(int!null) a.d:4(decimal!null) │ │ │ ├── stats: [rows=1000] - │ │ │ └── cost: 1000.00 + │ │ │ ├── cost: 1000.00 + │ │ │ └── keys: (1) │ │ └── filters [type=bool, outer=(4), constraints=(/4: [/1.0 - /1.0]; tight)] │ │ └── eq [type=bool, outer=(4), constraints=(/4: [/1.0 - /1.0]; tight)] │ │ ├── variable: a.d [type=decimal, outer=(4)] diff --git a/pkg/sql/opt/xform/testdata/coster/scan b/pkg/sql/opt/xform/testdata/coster/scan index b2970f917c08..64f69f92bd08 100644 --- a/pkg/sql/opt/xform/testdata/coster/scan +++ b/pkg/sql/opt/xform/testdata/coster/scan @@ -15,4 +15,5 @@ SELECT k, s FROM a scan a ├── columns: k:1(int!null) s:3(string) ├── stats: [rows=1000] - └── cost: 1000.00 + ├── cost: 1000.00 + └── keys: (1) diff --git a/pkg/sql/opt/xform/testdata/coster/select b/pkg/sql/opt/xform/testdata/coster/select index aaea5e7f5e07..9693af39d4dc 100644 --- a/pkg/sql/opt/xform/testdata/coster/select +++ b/pkg/sql/opt/xform/testdata/coster/select @@ -16,10 +16,12 @@ select ├── columns: k:1(int!null) s:3(string) ├── stats: [rows=100] ├── cost: 1100.00 + ├── keys: (1) ├── scan a │ ├── columns: a.k:1(int!null) a.s:3(string) │ ├── stats: [rows=1000] - │ └── cost: 1000.00 + │ ├── cost: 1000.00 + │ └── keys: (1) └── filters [type=bool, outer=(3), constraints=(/3: [/'foo' - ]; tight)] └── ge [type=bool, outer=(3), constraints=(/3: [/'foo' - ]; tight)] ├── variable: a.s [type=string, outer=(3)] diff --git a/pkg/sql/opt/xform/testdata/rules/limit b/pkg/sql/opt/xform/testdata/rules/limit index c7d9d044b193..9eccc61f0c40 100644 --- a/pkg/sql/opt/xform/testdata/rules/limit +++ b/pkg/sql/opt/xform/testdata/rules/limit @@ -38,7 +38,8 @@ SELECT * FROM a LIMIT 1 ---- scan a ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - └── limit: 1 + ├── limit: 1 + └── keys: (1) # Combine limit with needed columns. opt @@ -97,9 +98,11 @@ limit └── subquery [type=int, outer=(6)] ├── max1-row │ ├── columns: a.k:6(int!null) + │ ├── keys: (6) │ └── scan a │ ├── columns: a.k:6(int!null) - │ └── limit: 1 + │ ├── limit: 1 + │ └── keys: (6) └── variable: a.k [type=int, outer=(6)] memo diff --git a/pkg/sql/opt/xform/testdata/rules/scan b/pkg/sql/opt/xform/testdata/rules/scan index c6b5453e4ddd..2c43e4423996 100644 --- a/pkg/sql/opt/xform/testdata/rules/scan +++ b/pkg/sql/opt/xform/testdata/rules/scan @@ -39,6 +39,7 @@ SELECT s, i, f FROM a ORDER BY s, k, i ---- scan a@s_idx ├── columns: s:4(string) i:2(int) f:3(float) + ├── keys: (1) └── ordering: +4,+1,+2 memo @@ -64,6 +65,7 @@ SELECT s, i, f FROM a ORDER BY k, i, s ---- scan a ├── columns: s:4(string) i:2(int) f:3(float) + ├── keys: (1) └── ordering: +1,+2,+4 memo @@ -114,6 +116,7 @@ SELECT i, k FROM a ORDER BY s DESC, i, k ---- scan a@si_idx ├── columns: i:2(int) k:1(int!null) + ├── keys: (1) └── ordering: -4,+2,+1 memo diff --git a/pkg/sql/opt/xform/testdata/rules/select b/pkg/sql/opt/xform/testdata/rules/select index 021c85fd8231..d0b8b01a6e50 100644 --- a/pkg/sql/opt/xform/testdata/rules/select +++ b/pkg/sql/opt/xform/testdata/rules/select @@ -32,7 +32,8 @@ SELECT k FROM a WHERE k = 1 ---- scan a ├── columns: k:1(int!null) - └── constraint: /1: [/1 - /1] + ├── constraint: /1: [/1 - /1] + └── keys: (1) memo SELECT k FROM a WHERE k = 1 @@ -59,9 +60,11 @@ SELECT k FROM a WHERE v > 1 ---- project ├── columns: k:1(int!null) + ├── keys: (1) ├── scan a@v │ ├── columns: a.k:1(int!null) a.v:3(int) - │ └── constraint: /3: [/2 - ] + │ ├── constraint: /3: [/2 - ] + │ └── keys: (1) weak(3) └── projections [outer=(1)] └── variable: a.k [type=int, outer=(1)] @@ -94,9 +97,11 @@ SELECT k FROM a WHERE u = 1 AND k = 5 ---- project ├── columns: k:1(int!null) + ├── keys: (1) ├── scan a@u │ ├── columns: a.k:1(int!null) a.u:2(int) - │ └── constraint: /2/1: [/1/5 - /1/5] + │ ├── constraint: /2/1: [/1/5 - /1/5] + │ └── keys: (1) └── projections [outer=(1)] └── variable: a.k [type=int, outer=(1)] @@ -137,11 +142,14 @@ SELECT k FROM a WHERE u = 1 AND k+u = 1 ---- project ├── columns: k:1(int!null) + ├── keys: (1) ├── select │ ├── columns: a.k:1(int!null) a.u:2(int) + │ ├── keys: (1) │ ├── scan a@u │ │ ├── columns: a.k:1(int!null) a.u:2(int) - │ │ └── constraint: /2/1: [/1 - /1] + │ │ ├── constraint: /2/1: [/1 - /1] + │ │ └── keys: (1) │ └── filters [type=bool, outer=(1,2)] │ └── eq [type=bool, outer=(1,2)] │ ├── plus [type=int, outer=(1,2)] @@ -187,11 +195,14 @@ SELECT k FROM a WHERE u = 1 AND v = 5 ---- project ├── columns: k:1(int!null) + ├── keys: (1) ├── select │ ├── columns: a.k:1(int!null) a.u:2(int) a.v:3(int) + │ ├── keys: (1) weak(3) │ ├── scan a@u │ │ ├── columns: a.k:1(int!null) a.u:2(int) a.v:3(int) - │ │ └── constraint: /2/1: [/1 - /1] + │ │ ├── constraint: /2/1: [/1 - /1] + │ │ └── keys: (1) weak(3) │ └── filters [type=bool, outer=(3), constraints=(/3: [/5 - /5]; tight)] │ └── eq [type=bool, outer=(3), constraints=(/3: [/5 - /5]; tight)] │ ├── variable: a.v [type=int, outer=(3)] @@ -239,11 +250,14 @@ SELECT k FROM a WHERE u=v ---- project ├── columns: k:1(int!null) + ├── keys: (1) ├── select │ ├── columns: a.k:1(int!null) a.u:2(int) a.v:3(int) + │ ├── keys: (1) weak(3) │ ├── scan a@u │ │ ├── columns: a.k:1(int!null) a.u:2(int) a.v:3(int) - │ │ └── constraint: /2/1: (/NULL - ] + │ │ ├── constraint: /2/1: (/NULL - ] + │ │ └── keys: (1) weak(3) │ └── filters [type=bool, outer=(2,3)] │ └── eq [type=bool, outer=(2,3)] │ ├── variable: a.u [type=int, outer=(2)] @@ -257,9 +271,11 @@ SELECT k FROM (SELECT k FROM a ORDER BY u LIMIT 1) a WHERE k = 1 ---- select ├── columns: k:1(int!null) + ├── keys: (1) ├── scan a@u │ ├── columns: a.k:1(int!null) a.u:2(int) - │ └── limit: 1 + │ ├── limit: 1 + │ └── keys: (1) └── filters [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] └── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] ├── variable: a.k [type=int, outer=(1)] From e16ab2acdf988ce05e17674720c629085b8e54c4 Mon Sep 17 00:00:00 2001 From: Andrew Kimball Date: Mon, 2 Apr 2018 21:17:43 -0700 Subject: [PATCH 2/2] opt: Add rule to eliminate distinct group by Add a new EliminateDistinct rule. EliminateDistinct discards a GroupBy operator that is eliminating duplicate rows by using grouping columns that are statically known to form a strong key. By definition, a strong key does not allow duplicate values, so the GroupBy is redundant and can be eliminated. Release note: None --- .../opt/exec/execbuilder/testdata/aggregate | 30 ++--- pkg/sql/opt/memo/logical_props_factory.go | 3 + pkg/sql/opt/norm/factory.go | 26 +++++ pkg/sql/opt/norm/factory.og.go | 13 +++ pkg/sql/opt/norm/rules/groupby.opt | 16 +++ pkg/sql/opt/norm/testdata/groupby | 104 ++++++++++++++++++ pkg/sql/opt/rule_name.og.go | 1 + pkg/sql/opt/rule_name_string.go | 4 +- 8 files changed, 175 insertions(+), 22 deletions(-) create mode 100644 pkg/sql/opt/norm/rules/groupby.opt create mode 100644 pkg/sql/opt/norm/testdata/groupby diff --git a/pkg/sql/opt/exec/execbuilder/testdata/aggregate b/pkg/sql/opt/exec/execbuilder/testdata/aggregate index 5a659b518b72..9cbef6b46c58 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/aggregate +++ b/pkg/sql/opt/exec/execbuilder/testdata/aggregate @@ -1256,17 +1256,11 @@ INSERT INTO ab VALUES exec-explain SELECT 1 FROM kv GROUP BY kv.*; ---- -render 0 render · · (column5) · - │ 0 · render 0 1 · · - └── group 1 group · · (k, v, w, s) · - │ 1 · aggregate 0 k · · - │ 1 · aggregate 1 v · · - │ 1 · aggregate 2 w · · - │ 1 · aggregate 3 s · · - │ 1 · group by @1-@4 · · - └── scan 2 scan · · (k, v, w, s) · -· 2 · table kv@primary · · -· 2 · spans ALL · · +render 0 render · · (column5) · + │ 0 · render 0 1 · · + └── scan 1 scan · · () · +· 1 · table kv@primary · · +· 1 · spans ALL · · exec SELECT 1 FROM kv GROUP BY kv.*; @@ -1456,15 +1450,11 @@ column3:tuple{int, int} exec-explain SELECT (b, a) FROM ab GROUP BY (b, a) ---- -render 0 render · · (column3) · - │ 0 · render 0 (b, a) · · - └── group 1 group · · (a, b) · - │ 1 · aggregate 0 a · · - │ 1 · aggregate 1 b · · - │ 1 · group by @1-@2 · · - └── scan 2 scan · · (a, b) · -· 2 · table ab@primary · · -· 2 · spans ALL · · +render 0 render · · (column3) · + │ 0 · render 0 (b, a) · · + └── scan 1 scan · · (a, b) · +· 1 · table ab@primary · · +· 1 · spans ALL · · exec rowsort SELECT MIN(y), (b, a) FROM ab, xy GROUP BY (x, (a, b)) diff --git a/pkg/sql/opt/memo/logical_props_factory.go b/pkg/sql/opt/memo/logical_props_factory.go index bd3b9dcd22f5..1b1a38f49423 100644 --- a/pkg/sql/opt/memo/logical_props_factory.go +++ b/pkg/sql/opt/memo/logical_props_factory.go @@ -194,6 +194,9 @@ func (f logicalPropsFactory) constructJoinProps(ev ExprView) LogicalProps { props.Relational.NotNullCols.UnionWith(leftProps.NotNullCols) } + // TODO(andyk): Need to derive weak keys for joins, for example when weak + // keys on both sides are equivalent cols. + // TODO: Need better estimate based on actual on conditions. props.Relational.Stats.RowCount = leftProps.Stats.RowCount * rightProps.Stats.RowCount if ev.Child(2).Operator() != opt.TrueOp { diff --git a/pkg/sql/opt/norm/factory.go b/pkg/sql/opt/norm/factory.go index 926228ebbcf0..51772eb79735 100644 --- a/pkg/sql/opt/norm/factory.go +++ b/pkg/sql/opt/norm/factory.go @@ -350,6 +350,11 @@ func (f *Factory) onlyConstants(group memo.GroupID) bool { return f.lookupScalar(group).OuterCols.Empty() } +// hasNoCols returns true if the group has zero output columns. +func (f *Factory) hasNoCols(group memo.GroupID) bool { + return f.outputCols(group).Empty() +} + // hasSameCols returns true if the two groups have an identical set of output // columns. func (f *Factory) hasSameCols(left, right memo.GroupID) bool { @@ -683,6 +688,27 @@ func (f *Factory) concatFilters(left, right memo.GroupID) memo.GroupID { return f.ConstructFilters(f.InternList(conditions)) } +// ---------------------------------------------------------------------- +// +// GroupBy Rules +// Custom match and replace functions used with groupby.opt rules. +// +// ---------------------------------------------------------------------- + +// colsAreKey returns true if the given columns form a strong key for the output +// rows of the given group. A strong key means that the set of given column +// values are unique and not null. +func (f *Factory) colsAreKey(cols memo.PrivateID, group memo.GroupID) bool { + colSet := f.mem.LookupPrivate(cols).(opt.ColSet) + props := f.lookupLogical(group).Relational + for _, weakKey := range props.WeakKeys { + if weakKey.SubsetOf(colSet) && weakKey.SubsetOf(props.NotNullCols) { + return true + } + } + return false +} + // ---------------------------------------------------------------------- // // Boolean Rules diff --git a/pkg/sql/opt/norm/factory.og.go b/pkg/sql/opt/norm/factory.og.go index 619887d9dc24..6e712e57fb0a 100644 --- a/pkg/sql/opt/norm/factory.og.go +++ b/pkg/sql/opt/norm/factory.og.go @@ -1523,6 +1523,19 @@ func (_f *Factory) ConstructGroupBy( return _group } + // [EliminateDistinct] + { + if _f.hasNoCols(aggregations) { + if _f.colsAreKey(groupingCols, input) { + if _f.onRuleMatch == nil || _f.onRuleMatch(opt.EliminateDistinct) { + _group = input + _f.mem.AddAltFingerprint(_groupByExpr.Fingerprint(), _group) + return _group + } + } + } + } + // [FilterUnusedGroupByCols] { if _f.hasUnusedColumns(input, _f.neededColsGroupBy(aggregations, groupingCols)) { diff --git a/pkg/sql/opt/norm/rules/groupby.opt b/pkg/sql/opt/norm/rules/groupby.opt new file mode 100644 index 000000000000..bb62f3c50572 --- /dev/null +++ b/pkg/sql/opt/norm/rules/groupby.opt @@ -0,0 +1,16 @@ +# ============================================================================= +# groupby.opt contains normalization rules for the GroupBy operator. +# ============================================================================= + +# EliminateDistinct discards a GroupBy operator that is eliminating duplicate +# rows by using grouping columns that are statically known to form a strong key. +# By definition, a strong key does not allow duplicate values, so the GroupBy is +# redundant and can be eliminated. +[EliminateDistinct, Normalize] +(GroupBy + $input:* + $aggregations:* & (HasNoCols $aggregations) + $groupingCols:* & (ColsAreKey $groupingCols $input) +) +=> +$input diff --git a/pkg/sql/opt/norm/testdata/groupby b/pkg/sql/opt/norm/testdata/groupby new file mode 100644 index 000000000000..5699fcf0a5db --- /dev/null +++ b/pkg/sql/opt/norm/testdata/groupby @@ -0,0 +1,104 @@ +exec-ddl +CREATE TABLE a +( + k INT PRIMARY KEY, + i INT NOT NULL, + f FLOAT, + s STRING NOT NULL, + j JSON, + UNIQUE INDEX si_idx (s DESC, i) STORING (j), + UNIQUE INDEX fi_idx (f, i) +) +---- +TABLE a + ├── k int not null + ├── i int not null + ├── f float + ├── s string not null + ├── j jsonb + ├── INDEX primary + │ └── k int not null + ├── INDEX si_idx + │ ├── s string not null desc + │ ├── i int not null + │ ├── k int not null (storing) + │ └── j jsonb (storing) + └── INDEX fi_idx + ├── f float + ├── i int not null + └── k int not null (storing) + +exec-ddl +CREATE TABLE sort (k INT PRIMARY KEY, v INT, w INT) +---- +TABLE sort + ├── k int not null + ├── v int + ├── w int + └── INDEX primary + └── k int not null + +# -------------------------------------------------- +# EliminateDistinct +# -------------------------------------------------- +opt +SELECT DISTINCT k FROM a +---- +scan a + ├── columns: k:1(int!null) + └── keys: (1) + +opt +SELECT DISTINCT s, i FROM a +---- +scan a + ├── columns: s:4(string!null) i:2(int!null) + └── keys: (2,4) + +# Strict superset of key. +opt +SELECT DISTINCT s, i, f FROM a +---- +scan a + ├── columns: s:4(string!null) i:2(int!null) f:3(float) + └── keys: (2,4) weak(2,3) + +# Distinct not eliminated because columns aren't superset of any weak key. +opt +SELECT DISTINCT i FROM a +---- +group-by + ├── columns: i:2(int!null) + ├── grouping columns: a.i:2(int!null) + ├── keys: (2) + ├── scan a + │ └── columns: a.i:2(int!null) + └── aggregations + +# Distinct not eliminated because one column is nullable. +opt +SELECT DISTINCT f, i FROM a +---- +group-by + ├── columns: f:3(float) i:2(int!null) + ├── grouping columns: a.i:2(int!null) a.f:3(float) + ├── keys: weak(2,3) + ├── scan a + │ ├── columns: a.i:2(int!null) a.f:3(float) + │ └── keys: weak(2,3) + └── aggregations + +# Group by not eliminated because it has aggregation. +opt +SELECT s, i, SUM(i) FROM a GROUP BY s, i +---- +group-by + ├── columns: s:4(string!null) i:2(int!null) column6:6(decimal) + ├── grouping columns: a.i:2(int!null) a.s:4(string!null) + ├── keys: (2,4) + ├── scan a + │ ├── columns: a.i:2(int!null) a.s:4(string!null) + │ └── keys: (2,4) + └── aggregations [outer=(2)] + └── function: sum [type=decimal, outer=(2)] + └── variable: a.i [type=int, outer=(2)] diff --git a/pkg/sql/opt/rule_name.og.go b/pkg/sql/opt/rule_name.og.go index 3085e9978417..737c5543b526 100644 --- a/pkg/sql/opt/rule_name.og.go +++ b/pkg/sql/opt/rule_name.og.go @@ -27,6 +27,7 @@ const ( NormalizeTupleEquality FoldNullComparisonLeft FoldNullComparisonRight + EliminateDistinct EnsureJoinFiltersAnd EnsureJoinFilters PushFilterIntoJoinLeft diff --git a/pkg/sql/opt/rule_name_string.go b/pkg/sql/opt/rule_name_string.go index d872a75e7ba4..8034339cc98d 100644 --- a/pkg/sql/opt/rule_name_string.go +++ b/pkg/sql/opt/rule_name_string.go @@ -4,9 +4,9 @@ package opt import "strconv" -const _RuleName_name = "InvalidRuleNameNumManualRuleNamesEliminateEmptyAndEliminateEmptyOrEliminateSingletonAndOrSimplifyAndSimplifyOrSimplifyFiltersFoldNullAndOrNegateComparisonEliminateNotNegateAndNegateOrCommuteVarInequalityCommuteConstInequalityNormalizeCmpPlusConstNormalizeCmpMinusConstNormalizeCmpConstMinusNormalizeTupleEqualityFoldNullComparisonLeftFoldNullComparisonRightEnsureJoinFiltersAndEnsureJoinFiltersPushFilterIntoJoinLeftPushFilterIntoJoinRightPushLimitIntoProjectPushOffsetIntoProjectFoldPlusZeroFoldZeroPlusFoldMinusZeroFoldMultOneFoldOneMultFoldDivOneInvertMinusEliminateUnaryMinusEliminateProjectEliminateProjectProjectFilterUnusedProjectColsFilterUnusedScanColsFilterUnusedSelectColsFilterUnusedLimitColsFilterUnusedOffsetColsFilterUnusedJoinLeftColsFilterUnusedJoinRightColsFilterUnusedAggColsFilterUnusedGroupByColsFilterUnusedValueColsCommuteVarCommuteConstEliminateCoalesceSimplifyCoalesceEliminateCastFoldNullCastFoldNullUnaryFoldNullBinaryLeftFoldNullBinaryRightFoldNullInNonEmptyFoldNullInEmptyFoldNullNotInEmptyNormalizeInConstFoldInNullEnsureSelectFiltersAndEnsureSelectFiltersEliminateSelectMergeSelectsPushSelectIntoProjectPushSelectIntoJoinLeftPushSelectIntoJoinRightMergeSelectInnerJoinPushSelectIntoGroupByPushLimitIntoScanGenerateIndexScansConstrainScanNumRuleNames" +const _RuleName_name = "InvalidRuleNameNumManualRuleNamesEliminateEmptyAndEliminateEmptyOrEliminateSingletonAndOrSimplifyAndSimplifyOrSimplifyFiltersFoldNullAndOrNegateComparisonEliminateNotNegateAndNegateOrCommuteVarInequalityCommuteConstInequalityNormalizeCmpPlusConstNormalizeCmpMinusConstNormalizeCmpConstMinusNormalizeTupleEqualityFoldNullComparisonLeftFoldNullComparisonRightEliminateDistinctEnsureJoinFiltersAndEnsureJoinFiltersPushFilterIntoJoinLeftPushFilterIntoJoinRightPushLimitIntoProjectPushOffsetIntoProjectFoldPlusZeroFoldZeroPlusFoldMinusZeroFoldMultOneFoldOneMultFoldDivOneInvertMinusEliminateUnaryMinusEliminateProjectEliminateProjectProjectFilterUnusedProjectColsFilterUnusedScanColsFilterUnusedSelectColsFilterUnusedLimitColsFilterUnusedOffsetColsFilterUnusedJoinLeftColsFilterUnusedJoinRightColsFilterUnusedAggColsFilterUnusedGroupByColsFilterUnusedValueColsCommuteVarCommuteConstEliminateCoalesceSimplifyCoalesceEliminateCastFoldNullCastFoldNullUnaryFoldNullBinaryLeftFoldNullBinaryRightFoldNullInNonEmptyFoldNullInEmptyFoldNullNotInEmptyNormalizeInConstFoldInNullEnsureSelectFiltersAndEnsureSelectFiltersEliminateSelectMergeSelectsPushSelectIntoProjectPushSelectIntoJoinLeftPushSelectIntoJoinRightMergeSelectInnerJoinPushSelectIntoGroupByPushLimitIntoScanGenerateIndexScansConstrainScanNumRuleNames" -var _RuleName_index = [...]uint16{0, 15, 33, 50, 66, 89, 100, 110, 125, 138, 154, 166, 175, 183, 203, 225, 246, 268, 290, 312, 334, 357, 377, 394, 416, 439, 459, 480, 492, 504, 517, 528, 539, 549, 560, 579, 595, 618, 641, 661, 683, 704, 726, 750, 775, 794, 817, 838, 848, 860, 877, 893, 906, 918, 931, 949, 968, 986, 1001, 1019, 1035, 1045, 1067, 1086, 1101, 1113, 1134, 1156, 1179, 1199, 1220, 1237, 1255, 1268, 1280} +var _RuleName_index = [...]uint16{0, 15, 33, 50, 66, 89, 100, 110, 125, 138, 154, 166, 175, 183, 203, 225, 246, 268, 290, 312, 334, 357, 374, 394, 411, 433, 456, 476, 497, 509, 521, 534, 545, 556, 566, 577, 596, 612, 635, 658, 678, 700, 721, 743, 767, 792, 811, 834, 855, 865, 877, 894, 910, 923, 935, 948, 966, 985, 1003, 1018, 1036, 1052, 1062, 1084, 1103, 1118, 1130, 1151, 1173, 1196, 1216, 1237, 1254, 1272, 1285, 1297} func (i RuleName) String() string { if i >= RuleName(len(_RuleName_index)-1) {