From e88949a6784b3e454348b889fdc3e34e860db46a Mon Sep 17 00:00:00 2001 From: Radu Berinde Date: Tue, 27 Mar 2018 11:15:26 -0400 Subject: [PATCH] opt: add exploration rule for constraining scans New "constrain scan" explore rule tries to push filters into scans, as constraints. The result is either just the constrained scan (if the filter was fully converted into a constraint), or a select on top of the constrained scan. Along with the rule, we needed some minor changes on the execution side to support scan constraints. Other fixes that were necessary: - optgen: the generated code for explore rules was adding expressions to the wrong group (because of shadowing of the original `_eid`). - idxconstraint support for `FiltersOp`. - move the idxconstraint test to `idxconstraint_test` package to avoid import loop. - test catalog fix: the primary index was returning the wrong ordinal value. - adding some cost to the select operator, to prefer a constrained scan with no remaining filter (vs a constrained scan with a remaining filter). Release note: None --- .../exec/execbuilder/relational_builder.go | 2 +- .../opt/exec/execbuilder/testdata/aggregate | 120 ++++---- pkg/sql/opt/exec/execbuilder/testdata/orderby | 116 ++++---- pkg/sql/opt/exec/execbuilder/testdata/project | 28 +- pkg/sql/opt/exec/execbuilder/testdata/select | 58 ++-- pkg/sql/opt/exec/factory.go | 10 +- .../opt/idxconstraint/index_constraints.go | 19 +- .../idxconstraint/index_constraints_test.go | 7 +- pkg/sql/opt/memo/expr_view.go | 9 +- pkg/sql/opt/memo/logical_props_factory.go | 7 +- pkg/sql/opt/memo/memo.go | 8 + pkg/sql/opt/memo/private_defs.go | 7 +- pkg/sql/opt/memo/private_storage.go | 5 + .../opt/memo/testdata/logprops/constraints | 51 ++-- pkg/sql/opt/memo/testdata/memo | 36 +-- pkg/sql/opt/norm/testdata/bool | 267 +++++++++--------- pkg/sql/opt/norm/testdata/combo | 2 + pkg/sql/opt/norm/testdata/comp | 16 +- pkg/sql/opt/norm/testdata/project | 19 +- pkg/sql/opt/norm/testdata/select | 24 +- pkg/sql/opt/optgen/cmd/optgen/explorer_gen.go | 10 +- pkg/sql/opt/optgen/cmd/optgen/rule_gen.go | 17 +- .../opt/optgen/cmd/optgen/testdata/explorer | 60 ++-- pkg/sql/opt/rule_name.og.go | 1 + pkg/sql/opt/rule_name_string.go | 4 +- pkg/sql/opt/testutils/create_table.go | 5 +- pkg/sql/opt/testutils/test_catalog.go | 4 +- pkg/sql/opt/xform/coster.go | 10 + pkg/sql/opt/xform/explorer.go | 96 ++++++- pkg/sql/opt/xform/explorer.og.go | 52 +++- pkg/sql/opt/xform/rules/scan.opt | 4 +- pkg/sql/opt/xform/rules/select.opt | 15 + pkg/sql/opt/xform/testdata/coster/join | 6 +- pkg/sql/opt/xform/testdata/coster/select | 26 ++ pkg/sql/opt/xform/testdata/physprops/ordering | 79 +++--- .../opt/xform/testdata/physprops/presentation | 4 +- pkg/sql/opt/xform/testdata/rules/scan | 25 ++ pkg/sql/opt/xform/testdata/rules/select | 130 +++++++++ pkg/sql/opt_exec_engine.go | 8 +- 39 files changed, 850 insertions(+), 517 deletions(-) create mode 100644 pkg/sql/opt/xform/rules/select.opt create mode 100644 pkg/sql/opt/xform/testdata/coster/select create mode 100644 pkg/sql/opt/xform/testdata/rules/select diff --git a/pkg/sql/opt/exec/execbuilder/relational_builder.go b/pkg/sql/opt/exec/execbuilder/relational_builder.go index 843a8acc09d7..131ce6329b4b 100644 --- a/pkg/sql/opt/exec/execbuilder/relational_builder.go +++ b/pkg/sql/opt/exec/execbuilder/relational_builder.go @@ -194,7 +194,7 @@ func (b *Builder) buildScan(ev memo.ExprView) (execPlan, error) { n++ } } - root, err := b.factory.ConstructScan(tab, tab.Index(def.Index), needed) + root, err := b.factory.ConstructScan(tab, tab.Index(def.Index), needed, def.Constraint) if err != nil { return execPlan{}, err } diff --git a/pkg/sql/opt/exec/execbuilder/testdata/aggregate b/pkg/sql/opt/exec/execbuilder/testdata/aggregate index 77940efa6bd1..5a659b518b72 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/aggregate +++ b/pkg/sql/opt/exec/execbuilder/testdata/aggregate @@ -798,13 +798,11 @@ column4:int exec-explain SELECT MIN(x) FROM xyz WHERE x in (0, 4, 7) ---- -group 0 group · · (column4) · - │ 0 · aggregate 0 min(x) · · - └── filter 1 filter · · (x) · - │ 1 · filter x IN (0, 4, 7) · · - └── scan 2 scan · · (x) · -· 2 · table xyz@primary · · -· 2 · spans ALL · · +group 0 group · · (column4) · + │ 0 · aggregate 0 min(x) · · + └── scan 1 scan · · (x) · +· 1 · table xyz@primary · · +· 1 · spans /0-/0/# /4-/4/# /7-/7/# · · exec SELECT MAX(x) FROM xyz @@ -831,15 +829,13 @@ column4:int exec-explain SELECT MIN(y) FROM xyz WHERE x = 1 ---- -group 0 group · · (column4) · - │ 0 · aggregate 0 min(xyz.y) · · - └── render 1 render · · ("xyz.y") · - │ 1 · render 0 y · · - └── filter 2 filter · · (x, y) · - │ 2 · filter x = 1 · · - └── scan 3 scan · · (x, y) · -· 3 · table xyz@primary · · -· 3 · spans ALL · · +group 0 group · · (column4) · + │ 0 · aggregate 0 min(xyz.y) · · + └── render 1 render · · ("xyz.y") · + │ 1 · render 0 y · · + └── scan 2 scan · · (x, y) · +· 2 · table xyz@primary · · +· 2 · spans /1-/1/# · · exec SELECT MAX(y) FROM xyz WHERE x = 1 @@ -850,15 +846,13 @@ column4:int exec-explain SELECT MAX(y) FROM xyz WHERE x = 1 ---- -group 0 group · · (column4) · - │ 0 · aggregate 0 max(xyz.y) · · - └── render 1 render · · ("xyz.y") · - │ 1 · render 0 y · · - └── filter 2 filter · · (x, y) · - │ 2 · filter x = 1 · · - └── scan 3 scan · · (x, y) · -· 3 · table xyz@primary · · -· 3 · spans ALL · · +group 0 group · · (column4) · + │ 0 · aggregate 0 max(xyz.y) · · + └── render 1 render · · ("xyz.y") · + │ 1 · render 0 y · · + └── scan 2 scan · · (x, y) · +· 2 · table xyz@primary · · +· 2 · spans /1-/1/# · · exec SELECT MIN(y) FROM xyz WHERE x = 7 @@ -869,15 +863,13 @@ NULL exec-explain SELECT MIN(y) FROM xyz WHERE x = 7 ---- -group 0 group · · (column4) · - │ 0 · aggregate 0 min(xyz.y) · · - └── render 1 render · · ("xyz.y") · - │ 1 · render 0 y · · - └── filter 2 filter · · (x, y) · - │ 2 · filter x = 7 · · - └── scan 3 scan · · (x, y) · -· 3 · table xyz@primary · · -· 3 · spans ALL · · +group 0 group · · (column4) · + │ 0 · aggregate 0 min(xyz.y) · · + └── render 1 render · · ("xyz.y") · + │ 1 · render 0 y · · + └── scan 2 scan · · (x, y) · +· 2 · table xyz@primary · · +· 2 · spans /7-/7/# · · exec SELECT MAX(y) FROM xyz WHERE x = 7 @@ -888,15 +880,13 @@ NULL exec-explain SELECT MAX(y) FROM xyz WHERE x = 7 ---- -group 0 group · · (column4) · - │ 0 · aggregate 0 max(xyz.y) · · - └── render 1 render · · ("xyz.y") · - │ 1 · render 0 y · · - └── filter 2 filter · · (x, y) · - │ 2 · filter x = 7 · · - └── scan 3 scan · · (x, y) · -· 3 · table xyz@primary · · -· 3 · spans ALL · · +group 0 group · · (column4) · + │ 0 · aggregate 0 max(xyz.y) · · + └── render 1 render · · ("xyz.y") · + │ 1 · render 0 y · · + └── scan 2 scan · · (x, y) · +· 2 · table xyz@primary · · +· 2 · spans /7-/7/# · · exec SELECT MIN(x) FROM xyz WHERE (y, z) = (2, 3.0) @@ -907,15 +897,13 @@ column4:int exec-explain SELECT MIN(x) FROM xyz WHERE (y, z) = (2, 3.0) ---- -group 0 group · · (column4) · - │ 0 · aggregate 0 min(xyz.x) · · - └── render 1 render · · ("xyz.x") · - │ 1 · render 0 x · · - └── filter 2 filter · · (x, y, z) · - │ 2 · filter (y = 2) AND (z = 3.0) · · - └── scan 3 scan · · (x, y, z) · -· 3 · table xyz@primary · · -· 3 · spans ALL · · +group 0 group · · (column4) · + │ 0 · aggregate 0 min(xyz.x) · · + └── render 1 render · · ("xyz.x") · + │ 1 · render 0 x · · + └── scan 2 scan · · (x, y, z) · +· 2 · table xyz@zyx · · +· 2 · spans /3/2-/3/3 · · exec SELECT MAX(x) FROM xyz WHERE (z, y) = (3.0, 2) @@ -926,15 +914,13 @@ column4:int exec-explain SELECT MAX(x) FROM xyz WHERE (z, y) = (3.0, 2) ---- -group 0 group · · (column4) · - │ 0 · aggregate 0 max(xyz.x) · · - └── render 1 render · · ("xyz.x") · - │ 1 · render 0 x · · - └── filter 2 filter · · (x, y, z) · - │ 2 · filter (z = 3.0) AND (y = 2) · · - └── scan 3 scan · · (x, y, z) · -· 3 · table xyz@primary · · -· 3 · spans ALL · · +group 0 group · · (column4) · + │ 0 · aggregate 0 max(xyz.x) · · + └── render 1 render · · ("xyz.x") · + │ 1 · render 0 x · · + └── scan 2 scan · · (x, y, z) · +· 2 · table xyz@zyx · · +· 2 · spans /3/2-/3/3 · · # VARIANCE/STDDEV @@ -959,13 +945,11 @@ NULL exec-explain SELECT VARIANCE(x) FROM xyz WHERE x = 1 ---- -group 0 group · · (column4) · - │ 0 · aggregate 0 variance(x) · · - └── filter 1 filter · · (x) · - │ 1 · filter x = 1 · · - └── scan 2 scan · · (x) · -· 2 · table xyz@primary · · -· 2 · spans ALL · · +group 0 group · · (column4) · + │ 0 · aggregate 0 variance(x) · · + └── scan 1 scan · · (x) · +· 1 · table xyz@primary · · +· 1 · spans /1-/1/# · · exec SELECT STDDEV(x), STDDEV(y::decimal), round(STDDEV(z), 14) FROM xyz diff --git a/pkg/sql/opt/exec/execbuilder/testdata/orderby b/pkg/sql/opt/exec/execbuilder/testdata/orderby index d201d23a908a..8f7b9eb3b1ee 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/orderby +++ b/pkg/sql/opt/exec/execbuilder/testdata/orderby @@ -704,41 +704,37 @@ a:int exec-explain SELECT c FROM abc WHERE b = 2 ORDER BY c ---- -sort 0 sort · · (c) +c - │ 0 · order +c · · - └── render 1 render · · (c) · - │ 1 · render 0 c · · - └── filter 2 filter · · (b, c) · - │ 2 · filter b = 2 · · - └── scan 3 scan · · (b, c) · -· 3 · table abc@primary · · -· 3 · spans ALL · · +sort 0 sort · · (c) +c + │ 0 · order +c · · + └── render 1 render · · (c) · + │ 1 · render 0 c · · + └── scan 2 scan · · (b, c) · +· 2 · table abc@bc · · +· 2 · spans /2-/3 · · exec-explain SELECT c FROM abc WHERE b = 2 ORDER BY c DESC ---- -sort 0 sort · · (c) -c - │ 0 · order -c · · - └── render 1 render · · (c) · - │ 1 · render 0 c · · - └── filter 2 filter · · (b, c) · - │ 2 · filter b = 2 · · - └── scan 3 scan · · (b, c) · -· 3 · table abc@primary · · -· 3 · spans ALL · · +sort 0 sort · · (c) -c + │ 0 · order -c · · + └── render 1 render · · (c) · + │ 1 · render 0 c · · + └── scan 2 scan · · (b, c) · +· 2 · table abc@bc · · +· 2 · spans /2-/3 · · # Verify that the ordering of the primary index is still used for the outer sort. exec-explain SELECT * FROM (SELECT b, c FROM abc WHERE a=1 ORDER BY a,b) ORDER BY b,c ---- -render 0 render · · (b, c) · - │ 0 · render 0 b · · - │ 0 · render 1 c · · - └── filter 1 filter · · (a, b, c) · - │ 1 · filter a = 1 · · - └── scan 2 scan · · (a, b, c) · -· 2 · table abc@bc · · -· 2 · spans ALL · · +render 0 render · · (b, c) · + │ 0 · render 0 b · · + │ 0 · render 1 c · · + └── sort 1 sort · · (a, b, c) +b,+c + │ 1 · order +b,+c · · + └── scan 2 scan · · (a, b, c) · +· 2 · table abc@primary · · +· 2 · spans /1-/2 · · exec-raw CREATE TABLE bar ( @@ -812,46 +808,38 @@ column5:int exec-explain SELECT a, b, c FROM abcd@abc WHERE (a, b) = (1, 4) ORDER BY c ---- -sort 0 sort · · (a, b, c) +c - │ 0 · order +c · · - └── filter 1 filter · · (a, b, c) · - │ 1 · filter (a = 1) AND (b = 4) · · - └── scan 2 scan · · (a, b, c) · -· 2 · table abcd@primary · · -· 2 · spans ALL · · +sort 0 sort · · (a, b, c) +c + │ 0 · order +c · · + └── scan 1 scan · · (a, b, c) · +· 1 · table abcd@abc · · +· 1 · spans /1/4-/1/5 · · exec-explain SELECT a, b, c FROM abcd@abc WHERE (a, b) = (1, 4) ORDER BY c, b, a ---- -sort 0 sort · · (a, b, c) +c,+b,+a - │ 0 · order +c,+b,+a · · - └── filter 1 filter · · (a, b, c) · - │ 1 · filter (a = 1) AND (b = 4) · · - └── scan 2 scan · · (a, b, c) · -· 2 · table abcd@primary · · -· 2 · spans ALL · · +sort 0 sort · · (a, b, c) +c,+b,+a + │ 0 · order +c,+b,+a · · + └── scan 1 scan · · (a, b, c) · +· 1 · table abcd@abc · · +· 1 · spans /1/4-/1/5 · · exec-explain SELECT a, b, c FROM abcd@abc WHERE (a, b) = (1, 4) ORDER BY b, a, c ---- -sort 0 sort · · (a, b, c) +b,+a,+c - │ 0 · order +b,+a,+c · · - └── filter 1 filter · · (a, b, c) · - │ 1 · filter (a = 1) AND (b = 4) · · - └── scan 2 scan · · (a, b, c) · -· 2 · table abcd@primary · · -· 2 · spans ALL · · +sort 0 sort · · (a, b, c) +b,+a,+c + │ 0 · order +b,+a,+c · · + └── scan 1 scan · · (a, b, c) · +· 1 · table abcd@abc · · +· 1 · spans /1/4-/1/5 · · exec-explain SELECT a, b, c FROM abcd@abc WHERE (a, b) = (1, 4) ORDER BY b, c, a ---- -sort 0 sort · · (a, b, c) +b,+c,+a - │ 0 · order +b,+c,+a · · - └── filter 1 filter · · (a, b, c) · - │ 1 · filter (a = 1) AND (b = 4) · · - └── scan 2 scan · · (a, b, c) · -· 2 · table abcd@primary · · -· 2 · spans ALL · · +sort 0 sort · · (a, b, c) +b,+c,+a + │ 0 · order +b,+c,+a · · + └── scan 1 scan · · (a, b, c) · +· 1 · table abcd@abc · · +· 1 · spans /1/4-/1/5 · · exec-raw CREATE TABLE nan (id INT PRIMARY KEY, x REAL) @@ -974,17 +962,15 @@ CREATE TABLE uvwxyz ( exec-explain SELECT * FROM (SELECT y, w, x FROM uvwxyz WHERE y = 1 ORDER BY w) ORDER BY w, x ---- -render 0 render · · (y, w, x) · - │ 0 · render 0 y · · - │ 0 · render 1 w · · - │ 0 · render 2 x · · - └── sort 1 sort · · (w, x, y) +w,+x - │ 1 · order +w,+x · · - └── filter 2 filter · · (w, x, y) · - │ 2 · filter y = 1 · · - └── scan 3 scan · · (w, x, y) · -· 3 · table uvwxyz@primary · · -· 3 · spans ALL · · +render 0 render · · (y, w, x) · + │ 0 · render 0 y · · + │ 0 · render 1 w · · + │ 0 · render 2 x · · + └── sort 1 sort · · (w, x, y) +w,+x + │ 1 · order +w,+x · · + └── scan 2 scan · · (w, x, y) · +· 2 · table uvwxyz@ywxz · · +· 2 · spans /1-/2 · · exec-raw CREATE TABLE blocks ( diff --git a/pkg/sql/opt/exec/execbuilder/testdata/project b/pkg/sql/opt/exec/execbuilder/testdata/project index df8e8c849d6f..2e34e6229c5e 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/project +++ b/pkg/sql/opt/exec/execbuilder/testdata/project @@ -13,11 +13,9 @@ INSERT INTO t.a VALUES (1, 10), (2, 20), (3, 30) exec-explain SELECT * FROM t.a WHERE x > 1 ---- -filter 0 filter · · (x, y) · - │ 0 · filter x > 1 · · - └── scan 1 scan · · (x, y) · -· 1 · table a@primary · · -· 1 · spans ALL · · +scan 0 scan · · (x, y) · +· 0 · table a@primary · · +· 0 · spans /2- · · exec SELECT * FROM t.a WHERE x > 1 @@ -46,11 +44,9 @@ x:int y:int exec-explain SELECT * FROM t.a WHERE x > 1 AND x < 3 ---- -filter 0 filter · · (x, y) · - │ 0 · filter (x > 1) AND (x < 3) · · - └── scan 1 scan · · (x, y) · -· 1 · table a@primary · · -· 1 · spans ALL · · +scan 0 scan · · (x, y) · +· 0 · table a@primary · · +· 0 · spans /2-/2/# · · exec SELECT * FROM t.a WHERE x > 1 AND x < 3 @@ -179,13 +175,11 @@ x:int y:int exec-explain SELECT x FROM t.b WHERE rowid > 0 ---- -render 0 render · · (x) · - │ 0 · render 0 x · · - └── filter 1 filter · · (x, rowid[hidden]) · - │ 1 · filter rowid > 0 · · - └── scan 2 scan · · (x, rowid[hidden]) · -· 2 · table b@primary · · -· 2 · spans ALL · · +render 0 render · · (x) · + │ 0 · render 0 x · · + └── scan 1 scan · · (x, rowid[hidden]) · +· 1 · table b@primary · · +· 1 · spans /1- · · exec SELECT x FROM t.b WHERE rowid > 0 diff --git a/pkg/sql/opt/exec/execbuilder/testdata/select b/pkg/sql/opt/exec/execbuilder/testdata/select index 8bab40f5b85d..9b1054fa3cdf 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/select +++ b/pkg/sql/opt/exec/execbuilder/testdata/select @@ -10,11 +10,9 @@ INSERT INTO t.a VALUES (1, 10), (2, 20), (3, 30) exec-explain SELECT * FROM t.a WHERE x > 1 ---- -filter 0 filter · · (x, y) · - │ 0 · filter x > 1 · · - └── scan 1 scan · · (x, y) · -· 1 · table a@primary · · -· 1 · spans ALL · · +scan 0 scan · · (x, y) · +· 0 · table a@primary · · +· 0 · spans /2- · · exec SELECT * FROM t.a WHERE x > 1 @@ -42,11 +40,9 @@ x:int y:int exec-explain SELECT * FROM t.a WHERE x > 1 AND x < 3 ---- -filter 0 filter · · (x, y) · - │ 0 · filter (x > 1) AND (x < 3) · · - └── scan 1 scan · · (x, y) · -· 1 · table a@primary · · -· 1 · spans ALL · · +scan 0 scan · · (x, y) · +· 0 · table a@primary · · +· 0 · spans /2-/2/# · · exec SELECT * FROM t.a WHERE x > 1 AND x < 3 @@ -57,11 +53,11 @@ x:int y:int exec-explain SELECT * FROM t.a WHERE x > 1 AND y < 30 ---- -filter 0 filter · · (x, y) · - │ 0 · filter (x > 1) AND (y < 30) · · - └── scan 1 scan · · (x, y) · -· 1 · table a@primary · · -· 1 · spans ALL · · +filter 0 filter · · (x, y) · + │ 0 · filter y < 30 · · + └── scan 1 scan · · (x, y) · +· 1 · table a@primary · · +· 1 · spans /2- · · exec SELECT * FROM t.a WHERE x > 1 AND y < 30 @@ -77,8 +73,30 @@ INSERT INTO t.b VALUES (1, 10), (2, 20), (3, 30) exec-explain SELECT x, y, rowid FROM t.b WHERE rowid > 0 ---- -filter 0 filter · · (x, y, rowid[hidden]) · - │ 0 · filter rowid > 0 · · - └── scan 1 scan · · (x, y, rowid[hidden]) · -· 1 · table b@primary · · -· 1 · spans ALL · · +scan 0 scan · · (x, y, rowid[hidden]) · +· 0 · table b@primary · · +· 0 · spans /1- · · + +exec-raw +CREATE TABLE t.c (n INT PRIMARY KEY, str STRING, INDEX str(str DESC)); +INSERT INTO t.c SELECT i, to_english(i) FROM GENERATE_SERIES(1, 10) AS g(i) +---- + +exec-explain +SELECT * FROM t.c WHERE str >= 'moo' +---- +scan 0 scan · · (n, str) · +· 0 · table c@str · · +· 0 · spans -/"moo"/PrefixEnd · · + +exec rowsort +SELECT * FROM t.c WHERE str >= 'moo' +---- +n:int str:string +1 one +2 two +3 three +6 six +7 seven +9 nine +10 one-zero diff --git a/pkg/sql/opt/exec/factory.go b/pkg/sql/opt/exec/factory.go index 24ad9ac810ab..28f3ae957f47 100644 --- a/pkg/sql/opt/exec/factory.go +++ b/pkg/sql/opt/exec/factory.go @@ -16,6 +16,7 @@ package exec import ( "github.com/cockroachdb/cockroach/pkg/sql/opt" + "github.com/cockroachdb/cockroach/pkg/sql/opt/constraint" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/sem/types" "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" @@ -41,8 +42,13 @@ type Factory interface { // ConstructScan returns a node that represents a scan of the given table. // Only the given set of needed columns are part of the result. - // TODO(radu): support index constraints - ConstructScan(table opt.Table, index opt.Index, needed ColumnOrdinalSet) (Node, error) + // Note: indexConstraint can be nil. + ConstructScan( + table opt.Table, + index opt.Index, + needed ColumnOrdinalSet, + indexConstraint *constraint.Constraint, + ) (Node, error) // ConstructFilter returns a node that applies a filter on the results of // the given input node. diff --git a/pkg/sql/opt/idxconstraint/index_constraints.go b/pkg/sql/opt/idxconstraint/index_constraints.go index 6d4b2ee87384..e2a756587255 100644 --- a/pkg/sql/opt/idxconstraint/index_constraints.go +++ b/pkg/sql/opt/idxconstraint/index_constraints.go @@ -583,6 +583,11 @@ func (c *indexConstraintCtx) makeSpansForExpr( } switch ev.Operator() { + case opt.FiltersOp: + if ev.ChildCount() == 1 { + return c.makeSpansForExpr(offset, ev.Child(0), out) + } + fallthrough case opt.AndOp: // We don't have enough information to know if the spans are "tight". c.makeSpansForAnd(offset, ev, out) @@ -661,7 +666,7 @@ func opRequiresNotNullArgs(op opt.Operator) bool { return false } -// makeSpansForAndcalculates spans for an AndOp. +// makeSpansForAnd calculates spans for an AndOp or FiltersOp. func (c *indexConstraintCtx) makeSpansForAnd( offset int, ev memo.ExprView, out *constraint.Constraint, ) { @@ -799,9 +804,13 @@ func (c *indexConstraintCtx) makeInvertedIndexSpansForExpr( return true } - case opt.AndOp: + case opt.AndOp, opt.FiltersOp: for i, n := 0, ev.ChildCount(); i < n; i++ { - c.makeInvertedIndexSpansForExpr(ev.Child(i), out) + tight := c.makeInvertedIndexSpansForExpr(ev.Child(i), out) + if n == 1 { + // Single child. + return tight + } if !out.IsUnconstrained() { // TODO(radu, masha): for now, the best we can do is to generate // constraints for at most one "contains" op in the disjunction; the @@ -910,7 +919,7 @@ func (c *indexConstraintCtx) simplifyFilter( ev memo.ExprView, final *constraint.Constraint, maxSimplifyPrefix int, ) memo.GroupID { // Special handling for AND and OR. - if ev.Operator() == opt.OrOp || ev.Operator() == opt.AndOp { + if ev.Operator() == opt.OrOp || ev.Operator() == opt.AndOp || ev.Operator() == opt.FiltersOp { newChildren := make([]memo.GroupID, ev.ChildCount()) for i := range newChildren { newChildren[i] = c.simplifyFilter(ev.Child(i), final, maxSimplifyPrefix) @@ -922,6 +931,8 @@ func (c *indexConstraintCtx) simplifyFilter( switch ev.Operator() { case opt.AndOp: return c.factory.ConstructAnd(c.factory.InternList(newChildren)) + case opt.FiltersOp: + return c.factory.ConstructFilters(c.factory.InternList(newChildren)) case opt.OrOp: return c.factory.ConstructOr(c.factory.InternList(newChildren)) } diff --git a/pkg/sql/opt/idxconstraint/index_constraints_test.go b/pkg/sql/opt/idxconstraint/index_constraints_test.go index 4e2acfdb1b95..9ffe86d62ece 100644 --- a/pkg/sql/opt/idxconstraint/index_constraints_test.go +++ b/pkg/sql/opt/idxconstraint/index_constraints_test.go @@ -12,7 +12,7 @@ // implied. See the License for the specific language governing // permissions and limitations under the License. -package idxconstraint +package idxconstraint_test import ( "bytes" @@ -25,6 +25,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/settings/cluster" "github.com/cockroachdb/cockroach/pkg/sql/opt" "github.com/cockroachdb/cockroach/pkg/sql/opt/exec/execbuilder" + "github.com/cockroachdb/cockroach/pkg/sql/opt/idxconstraint" "github.com/cockroachdb/cockroach/pkg/sql/opt/memo" "github.com/cockroachdb/cockroach/pkg/sql/opt/norm" "github.com/cockroachdb/cockroach/pkg/sql/opt/optbuilder" @@ -149,7 +150,7 @@ func TestIndexConstraints(t *testing.T) { } ev := memo.MakeNormExprView(f.Memo(), group) - var ic Instance + var ic idxconstraint.Instance ic.Init(ev, indexCols, notNullCols, invertedIndex, &evalCtx, f) result := ic.Constraint() var buf bytes.Buffer @@ -257,7 +258,7 @@ func BenchmarkIndexConstraints(b *testing.B) { ev := memo.MakeNormExprView(f.Memo(), group) b.ResetTimer() for i := 0; i < b.N; i++ { - var ic Instance + var ic idxconstraint.Instance ic.Init(ev, indexCols, notNullCols, false /*isInverted */, &evalCtx, f) _ = ic.Constraint() _ = ic.RemainingFilter() diff --git a/pkg/sql/opt/memo/expr_view.go b/pkg/sql/opt/memo/expr_view.go index f2aba0b75d87..1ee1853cd40f 100644 --- a/pkg/sql/opt/memo/expr_view.go +++ b/pkg/sql/opt/memo/expr_view.go @@ -288,13 +288,18 @@ func (ev ExprView) formatRelational(tp treeprinter.Node, flags ExprFmtFlags) { groupingColSet := ev.Private().(opt.ColSet) logProps.FormatColSet(tp, ev.Metadata(), "grouping columns:", groupingColSet) - // Special-case handling for set operators to show the left and right - // input columns that correspond to the output columns. + // Special-case handling for set operators to show the left and right + // input columns that correspond to the output columns. case opt.UnionOp, opt.IntersectOp, opt.ExceptOp, opt.UnionAllOp, opt.IntersectAllOp, opt.ExceptAllOp: colMap := ev.Private().(*SetOpColMap) logProps.FormatColList(tp, ev.Metadata(), "left columns:", colMap.Left) logProps.FormatColList(tp, ev.Metadata(), "right columns:", colMap.Right) + + case opt.ScanOp: + if def := ev.Private().(*ScanOpDef); def.Constraint != nil { + tp.Childf("constraint: %s", def.Constraint) + } } if !flags.HasFlags(ExprFmtHideStats) { diff --git a/pkg/sql/opt/memo/logical_props_factory.go b/pkg/sql/opt/memo/logical_props_factory.go index 2b8c1e51ba46..81d9a7a24560 100644 --- a/pkg/sql/opt/memo/logical_props_factory.go +++ b/pkg/sql/opt/memo/logical_props_factory.go @@ -99,8 +99,11 @@ func (f logicalPropsFactory) constructScanProps(ev ExprView) LogicalProps { } // TODO: Need actual number of rows. - props.Relational.Stats.RowCount = 1000 - + if def.Constraint != nil { + props.Relational.Stats.RowCount = 100 + } else { + props.Relational.Stats.RowCount = 1000 + } return props } diff --git a/pkg/sql/opt/memo/memo.go b/pkg/sql/opt/memo/memo.go index af86c9f9d68e..bc4e9affc0a1 100644 --- a/pkg/sql/opt/memo/memo.go +++ b/pkg/sql/opt/memo/memo.go @@ -279,6 +279,11 @@ func (m *Memo) BestExprCost(best BestExprID) Cost { return m.bestExpr(best).cost } +// BestExprLogical returns the logical properties of the given best expression. +func (m *Memo) BestExprLogical(best BestExprID) *LogicalProps { + return m.GroupProperties(best.group) +} + // bestExpr returns the best expression with the given id. // NOTE: The returned best expression is only valid until the next call to // EnsureBestExpr, since that may trigger a resize of the bestExprs slice @@ -435,6 +440,9 @@ func (m *Memo) formatPrivate(buf *bytes.Buffer, private interface{}) { } else { fmt.Fprintf(buf, " %s@%s", tab.TabName(), tab.Index(t.Index).IdxName()) } + if t.Constraint != nil { + fmt.Fprintf(buf, ",constrained") + } case opt.ColumnID: fmt.Fprintf(buf, " %s", m.metadata.ColumnLabel(t)) diff --git a/pkg/sql/opt/memo/private_defs.go b/pkg/sql/opt/memo/private_defs.go index 9f9f50f71e35..89a0b8f5464d 100644 --- a/pkg/sql/opt/memo/private_defs.go +++ b/pkg/sql/opt/memo/private_defs.go @@ -16,6 +16,7 @@ package memo import ( "github.com/cockroachdb/cockroach/pkg/sql/opt" + "github.com/cockroachdb/cockroach/pkg/sql/opt/constraint" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/sem/types" ) @@ -50,8 +51,12 @@ type ScanOpDef struct { Index int // Cols specifies the set of columns that the scan operator projects. This - // may be a subset of the columns that the table contains. + // may be a subset of the columns that the table/index contains. Cols opt.ColSet + + // If set, the scan is a constrained scan; the constraint contains the spans + // that need to be scanned. + Constraint *constraint.Constraint } // AltIndexHasCols returns true if the given alternate index on the table diff --git a/pkg/sql/opt/memo/private_storage.go b/pkg/sql/opt/memo/private_storage.go index bcc142749460..730b37463674 100644 --- a/pkg/sql/opt/memo/private_storage.go +++ b/pkg/sql/opt/memo/private_storage.go @@ -18,6 +18,7 @@ import ( "bytes" "encoding/binary" "reflect" + "unsafe" "github.com/cockroachdb/cockroach/pkg/sql/opt" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" @@ -172,6 +173,10 @@ func (ps *privateStorage) internScanOpDef(def *ScanOpDef) PrivateID { ps.keyBuf.Reset() ps.keyBuf.writeUvarint(uint64(def.Table)) ps.keyBuf.writeUvarint(uint64(def.Index)) + // TODO(radu): consider encoding the constraint rather than the pointer. + // It's unclear if we have cases where we expect the same constraints to be + // generated multiple times. + ps.keyBuf.writeUvarint(uint64(uintptr(unsafe.Pointer(def.Constraint)))) ps.keyBuf.writeColSet(def.Cols) typ := (*ScanOpDef)(nil) if id, ok := ps.privatesMap[privateKey{iface: typ, str: ps.keyBuf.String()}]; ok { diff --git a/pkg/sql/opt/memo/testdata/logprops/constraints b/pkg/sql/opt/memo/testdata/logprops/constraints index d00114e37adc..92030f522a86 100644 --- a/pkg/sql/opt/memo/testdata/logprops/constraints +++ b/pkg/sql/opt/memo/testdata/logprops/constraints @@ -1,11 +1,12 @@ exec-ddl -CREATE TABLE a (x INT PRIMARY KEY, y INT) +CREATE TABLE a (x INT, y INT) ---- TABLE a - ├── x int not null + ├── x int ├── y int + ├── rowid int not null (hidden) └── INDEX primary - └── x int not null + └── rowid int not null (hidden) exec-ddl CREATE TABLE kuv (k INT PRIMARY KEY, u FLOAT, v STRING) @@ -21,10 +22,10 @@ opt SELECT * FROM a WHERE x > 1 ---- select - ├── columns: x:1(int!null) y:2(int) + ├── columns: x:1(int) y:2(int) ├── stats: [rows=100] ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) + │ ├── columns: a.x:1(int) a.y:2(int) │ └── stats: [rows=1000] └── filters [type=bool, outer=(1), constraints=(/1: [/2 - ]; tight)] └── gt [type=bool, outer=(1), constraints=(/1: [/2 - ]; tight)] @@ -35,10 +36,10 @@ opt SELECT * FROM a WHERE x >= 1 ---- select - ├── columns: x:1(int!null) y:2(int) + ├── columns: x:1(int) y:2(int) ├── stats: [rows=100] ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) + │ ├── columns: a.x:1(int) a.y:2(int) │ └── stats: [rows=1000] └── filters [type=bool, outer=(1), constraints=(/1: [/1 - ]; tight)] └── ge [type=bool, outer=(1), constraints=(/1: [/1 - ]; tight)] @@ -49,10 +50,10 @@ opt SELECT * FROM a WHERE x < 1 ---- select - ├── columns: x:1(int!null) y:2(int) + ├── columns: x:1(int) y:2(int) ├── stats: [rows=100] ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) + │ ├── columns: a.x:1(int) a.y:2(int) │ └── stats: [rows=1000] └── filters [type=bool, outer=(1), constraints=(/1: (/NULL - /0]; tight)] └── lt [type=bool, outer=(1), constraints=(/1: (/NULL - /0]; tight)] @@ -63,10 +64,10 @@ opt SELECT * FROM a WHERE x <= 1 ---- select - ├── columns: x:1(int!null) y:2(int) + ├── columns: x:1(int) y:2(int) ├── stats: [rows=100] ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) + │ ├── columns: a.x:1(int) a.y:2(int) │ └── stats: [rows=1000] └── filters [type=bool, outer=(1), constraints=(/1: (/NULL - /1]; tight)] └── le [type=bool, outer=(1), constraints=(/1: (/NULL - /1]; tight)] @@ -77,10 +78,10 @@ opt SELECT * FROM a WHERE x = 1 ---- select - ├── columns: x:1(int!null) y:2(int) + ├── columns: x:1(int) y:2(int) ├── stats: [rows=100] ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) + │ ├── columns: a.x:1(int) a.y:2(int) │ └── stats: [rows=1000] └── filters [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] └── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] @@ -91,10 +92,10 @@ opt SELECT * FROM a WHERE x > 1 AND x < 5 ---- select - ├── columns: x:1(int!null) y:2(int) + ├── columns: x:1(int) y:2(int) ├── stats: [rows=100] ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) + │ ├── columns: a.x:1(int) a.y:2(int) │ └── stats: [rows=1000] └── filters [type=bool, outer=(1), constraints=(/1: [/2 - /4]; tight)] ├── gt [type=bool, outer=(1), constraints=(/1: [/2 - ]; tight)] @@ -108,10 +109,10 @@ opt SELECT * FROM a WHERE x = 1 AND y = 5 ---- select - ├── columns: x:1(int!null) y:2(int) + ├── columns: x:1(int) y:2(int) ├── stats: [rows=100] ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) + │ ├── columns: a.x:1(int) a.y:2(int) │ └── stats: [rows=1000] └── filters [type=bool, outer=(1,2), constraints=(/1: [/1 - /1]; /2: [/5 - /5]; tight)] ├── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] @@ -125,10 +126,10 @@ opt SELECT * FROM a WHERE x > 1 AND x < 5 AND y >= 7 AND y <= 9 ---- select - ├── columns: x:1(int!null) y:2(int) + ├── columns: x:1(int) y:2(int) ├── stats: [rows=100] ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) + │ ├── columns: a.x:1(int) a.y:2(int) │ └── stats: [rows=1000] └── filters [type=bool, outer=(1,2), constraints=(/1: [/2 - /4]; /2: [/7 - /9]; tight)] ├── gt [type=bool, outer=(1), constraints=(/1: [/2 - ]; tight)] @@ -149,10 +150,10 @@ opt SELECT * FROM a WHERE x > 1 AND x < 5 AND x + y = 5 ---- select - ├── columns: x:1(int!null) y:2(int) + ├── columns: x:1(int) y:2(int) ├── stats: [rows=100] ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) + │ ├── columns: a.x:1(int) a.y:2(int) │ └── stats: [rows=1000] └── filters [type=bool, outer=(1,2), constraints=(/1: [/2 - /4])] ├── gt [type=bool, outer=(1), constraints=(/1: [/2 - ]; tight)] @@ -171,10 +172,10 @@ opt SELECT * FROM a WHERE x > 1 AND x + y >= 5 AND x + y <= 7 ---- select - ├── columns: x:1(int!null) y:2(int) + ├── columns: x:1(int) y:2(int) ├── stats: [rows=100] ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) + │ ├── columns: a.x:1(int) a.y:2(int) │ └── stats: [rows=1000] └── filters [type=bool, outer=(1,2), constraints=(/1: [/2 - ])] ├── gt [type=bool, outer=(1), constraints=(/1: [/2 - ]; tight)] @@ -196,10 +197,10 @@ opt SELECT * FROM a WHERE x > 1.0 ---- select - ├── columns: x:1(int!null) y:2(int) + ├── columns: x:1(int) y:2(int) ├── stats: [rows=100] ├── scan a - │ ├── columns: a.x:1(int!null) a.y:2(int) + │ ├── columns: a.x:1(int) a.y:2(int) │ └── stats: [rows=1000] └── filters [type=bool, outer=(1), constraints=(/1: [/2 - ]; tight)] └── gt [type=bool, outer=(1), constraints=(/1: [/2 - ]; tight)] diff --git a/pkg/sql/opt/memo/testdata/memo b/pkg/sql/opt/memo/testdata/memo index 5aae0d690f05..76967fbd3e62 100644 --- a/pkg/sql/opt/memo/testdata/memo +++ b/pkg/sql/opt/memo/testdata/memo @@ -26,21 +26,21 @@ LIMIT 10 limit ├── columns: y:2(int) x:3(string!null) column5:5(int) ├── stats: [rows=100000] - ├── cost: 27000.00 + ├── cost: 127000.00 ├── ordering: +2 ├── sort │ ├── columns: a.y:2(int) b.x:3(string!null) column5:5(int) │ ├── stats: [rows=100000] - │ ├── cost: 27000.00 + │ ├── cost: 127000.00 │ ├── ordering: +2 │ └── project │ ├── columns: a.y:2(int) b.x:3(string!null) column5:5(int) │ ├── stats: [rows=100000] - │ ├── cost: 2000.00 + │ ├── cost: 102000.00 │ ├── select │ │ ├── columns: a.x:1(int!null) a.y:2(int) b.x:3(string!null) b.z:4(decimal!null) │ │ ├── stats: [rows=100000] - │ │ ├── cost: 2000.00 + │ │ ├── cost: 102000.00 │ │ ├── inner-join │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) b.x:3(string!null) b.z:4(decimal!null) │ │ │ ├── stats: [rows=1000000] @@ -80,25 +80,25 @@ LIMIT 10 limit ├── columns: y:2(int) x:3(string!null) column5:5(int) ├── stats: [rows=10000] - ├── cost: 4500.00 + ├── cost: 4600.00 ├── ordering: +2 ├── sort │ ├── columns: a.y:2(int) b.x:3(string!null) column5:5(int) │ ├── stats: [rows=10000] - │ ├── cost: 4500.00 + │ ├── cost: 4600.00 │ ├── ordering: +2 │ └── project │ ├── columns: a.y:2(int) b.x:3(string!null) column5:5(int) │ ├── stats: [rows=10000] - │ ├── cost: 2000.00 + │ ├── cost: 2100.00 │ ├── inner-join │ │ ├── columns: a.x:1(int!null) a.y:2(int) b.x:3(string!null) │ │ ├── stats: [rows=10000] - │ │ ├── cost: 2000.00 + │ │ ├── cost: 2100.00 │ │ ├── select │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) │ │ │ ├── stats: [rows=100] - │ │ │ ├── cost: 1000.00 + │ │ │ ├── cost: 1100.00 │ │ │ ├── scan a │ │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) │ │ │ │ ├── stats: [rows=1000] @@ -134,20 +134,20 @@ LIMIT 10 [25: "p:y:2,x:3,column5:5 o:+2"] memo ├── 25: (limit 23 24 +2) - │ ├── "" [cost=4500.00] + │ ├── "" [cost=4600.00] │ │ └── best: (limit 23="o:+2" 24 +2) - │ └── "p:y:2,x:3,column5:5 o:+2" [cost=4500.00] + │ └── "p:y:2,x:3,column5:5 o:+2" [cost=4600.00] │ └── best: (limit 23="o:+2" 24 +2) ├── 24: (const 10) ├── 23: (project 22 20) - │ ├── "" [cost=2000.00] + │ ├── "" [cost=2100.00] │ │ └── best: (project 22 20) - │ └── "o:+2" [cost=4500.00] + │ └── "o:+2" [cost=4600.00] │ └── best: (sort 23) ├── 22: (inner-join 15 21 17) - │ ├── "" [cost=2000.00] + │ ├── "" [cost=2100.00] │ │ └── best: (inner-join 15 21 17) - │ └── "o:+2" [cost=4500.00] + │ └── "o:+2" [cost=4600.00] │ └── best: (sort 22) ├── 21: (scan b) │ └── "" [cost=1000.00] @@ -158,7 +158,7 @@ memo ├── 17: (filters 11) ├── 16: (inner-join 15 2 3) ├── 15: (select 1 14) - │ └── "" [cost=1000.00] + │ └── "" [cost=1100.00] │ └── best: (select 1 14) ├── 14: (filters 7) ├── 13: (filters 7 11) @@ -186,7 +186,7 @@ WHERE z=1 AND concat(x, 'foo', x)=concat(x, 'foo', x) [17: "p:column3:3,column4:4,column5:5,column6:6"] memo ├── 17: (project 11 16) - │ └── "p:column3:3,column4:4,column5:5,column6:6" [cost=1000.00] + │ └── "p:column3:3,column4:4,column5:5,column6:6" [cost=1100.00] │ └── best: (project 11 16) ├── 16: (projections 12 13 15 14) ├── 15: (cast 14 timestamp) @@ -194,7 +194,7 @@ memo ├── 13: (plus 2 3) ├── 12: (const 1) ├── 11: (select 1 10) - │ └── "" [cost=1000.00] + │ └── "" [cost=1100.00] │ └── best: (select 1 10) ├── 10: (filters 4 8) ├── 9: (and 4 8) diff --git a/pkg/sql/opt/norm/testdata/bool b/pkg/sql/opt/norm/testdata/bool index d90a0d4e84d6..fa7761b0dc6a 100644 --- a/pkg/sql/opt/norm/testdata/bool +++ b/pkg/sql/opt/norm/testdata/bool @@ -32,31 +32,33 @@ scan a # EliminateEmptyOr # -------------------------------------------------- opt -SELECT * FROM a WHERE False OR False +SELECT False OR False ---- -select - ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - └── false [type=bool] +project + ├── columns: column1:1(bool) + ├── values + │ └── tuple [type=tuple{}] + └── projections + └── false [type=bool] # -------------------------------------------------- # EliminateSingletonAndOr # -------------------------------------------------- opt -SELECT * FROM a WHERE (i=5 OR False) AND (s<'foo' AND True) +SELECT (i=5 OR False) AND (s<'foo' AND True) FROM a ---- -select - ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) +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) a.j:5(jsonb) - └── 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)] - │ └── const: 5 [type=int] - └── lt [type=bool, outer=(4), constraints=(/4: (/NULL - /'foo'); tight)] - ├── variable: a.s [type=string, outer=(4)] - └── const: 'foo' [type=string] + │ └── columns: a.i:2(int) a.s:4(string) + └── projections [outer=(2,4)] + └── and [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)] + │ └── const: 5 [type=int] + └── lt [type=bool, outer=(4), constraints=(/4: (/NULL - /'foo'); tight)] + ├── variable: a.s [type=string, outer=(4)] + └── const: 'foo' [type=string] # -------------------------------------------------- # SimplifyAnd @@ -64,50 +66,51 @@ select # Replace with False if any operand is false. opt -SELECT * FROM a WHERE k=1 AND False AND f=3.5 +SELECT k=1 AND False AND f=3.5 FROM a ---- -select - ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) +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) a.j:5(jsonb) - └── false [type=bool] + └── projections + └── false [type=bool] opt -SELECT * FROM a WHERE False AND s='foo' +SELECT False AND s='foo' FROM a ---- -select - ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) +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) a.j:5(jsonb) - └── false [type=bool] + └── projections + └── false [type=bool] # Discard True operands. opt -SELECT * FROM a WHERE true AND k=1 +SELECT true AND k=1 FROM a ---- -select - ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) +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) a.j:5(jsonb) - └── filters [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] + │ └── columns: a.k:1(int!null) + └── projections [outer=(1)] └── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] ├── variable: a.k [type=int, outer=(1)] └── const: 1 [type=int] opt -SELECT * FROM a WHERE k=1 AND i=2 AND true +SELECT k=1 AND i=2 AND true FROM a ---- -select - ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) +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) a.j:5(jsonb) - └── filters [type=bool, outer=(1,2), constraints=(/1: [/1 - /1]; /2: [/2 - /2]; tight)] - ├── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] - │ ├── variable: a.k [type=int, outer=(1)] - │ └── const: 1 [type=int] - └── eq [type=bool, outer=(2), constraints=(/2: [/2 - /2]; tight)] - ├── variable: a.i [type=int, outer=(2)] - └── const: 2 [type=int] + │ └── columns: a.k:1(int!null) a.i:2(int) + └── 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)] + │ ├── variable: a.k [type=int, outer=(1)] + │ └── const: 1 [type=int] + └── eq [type=bool, outer=(2), constraints=(/2: [/2 - /2]; tight)] + ├── variable: a.i [type=int, outer=(2)] + └── const: 2 [type=int] # No conditions left after rule. opt @@ -118,25 +121,26 @@ scan a # Flatten nested And operands. opt -SELECT * FROM a WHERE (k>1 AND k<5) AND (f=3.5 AND s='foo') +SELECT (k>1 AND k<5) AND (f=3.5 AND s='foo') FROM a ---- -select - ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) +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) a.j:5(jsonb) - └── filters [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)] - │ ├── variable: a.k [type=int, outer=(1)] - │ └── const: 1 [type=int] - ├── lt [type=bool, outer=(1), constraints=(/1: (/NULL - /4]; tight)] - │ ├── variable: a.k [type=int, outer=(1)] - │ └── const: 5 [type=int] - ├── eq [type=bool, outer=(3), constraints=(/3: [/3.5 - /3.5]; tight)] - │ ├── variable: a.f [type=float, outer=(3)] - │ └── const: 3.5 [type=float] - └── eq [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight)] - ├── variable: a.s [type=string, outer=(4)] - └── const: 'foo' [type=string] + │ └── columns: a.k:1(int!null) a.f:3(float) a.s:4(string) + └── 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)] + │ ├── variable: a.k [type=int, outer=(1)] + │ └── const: 1 [type=int] + ├── lt [type=bool, outer=(1), constraints=(/1: (/NULL - /4]; tight)] + │ ├── variable: a.k [type=int, outer=(1)] + │ └── const: 5 [type=int] + ├── eq [type=bool, outer=(3), constraints=(/3: [/3.5 - /3.5]; tight)] + │ ├── variable: a.f [type=float, outer=(3)] + │ └── const: 3.5 [type=float] + └── eq [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight)] + ├── variable: a.s [type=string, outer=(4)] + └── const: 'foo' [type=string] # -------------------------------------------------- # SimplifyOr @@ -144,26 +148,32 @@ select # Replace with True if any operand is True. opt -SELECT * FROM a WHERE k=1 OR (i=2 OR True) +SELECT k=1 OR (i=2 OR True) FROM a ---- -scan a - └── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) +project + ├── columns: column6:6(bool) + ├── scan a + └── projections + └── true [type=bool] opt -SELECT * FROM a WHERE k=1 OR True OR f=3.5 +SELECT k=1 OR True OR f=3.5 FROM a ---- -scan a - └── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) +project + ├── columns: column6:6(bool) + ├── scan a + └── projections + └── true [type=bool] # Discard False operands. opt -SELECT * FROM a WHERE false OR k=1 +SELECT false OR k=1 FROM a ---- -select - ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) +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) a.j:5(jsonb) - └── filters [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] + │ └── columns: a.k:1(int!null) + └── projections [outer=(1)] └── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] ├── variable: a.k [type=int, outer=(1)] └── const: 1 [type=int] @@ -188,11 +198,9 @@ select opt SELECT * FROM a WHERE false OR false ---- -select +scan a,constrained ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - └── false [type=bool] + └── constraint: /1: contradiction # Flatten nested Or operands. opt @@ -221,45 +229,47 @@ select # SimplifyAnd + SimplifyOr # -------------------------------------------------- opt -SELECT * FROM a WHERE (k=1 OR false) AND (false OR k=2 OR false) AND true +SELECT (k=1 OR false) AND (false OR k=2 OR false) AND true FROM a ---- -select - ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) +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) a.j:5(jsonb) - └── filters [type=bool, outer=(1), constraints=(contradiction; tight)] - ├── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] - │ ├── variable: a.k [type=int, outer=(1)] - │ └── const: 1 [type=int] - └── eq [type=bool, outer=(1), constraints=(/1: [/2 - /2]; tight)] - ├── variable: a.k [type=int, outer=(1)] - └── const: 2 [type=int] + │ └── columns: a.k:1(int!null) + └── projections [outer=(1)] + └── and [type=bool, outer=(1), constraints=(contradiction; tight)] + ├── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] + │ ├── variable: a.k [type=int, outer=(1)] + │ └── const: 1 [type=int] + └── eq [type=bool, outer=(1), constraints=(/1: [/2 - /2]; tight)] + ├── variable: a.k [type=int, outer=(1)] + └── const: 2 [type=int] # Use parentheses to make and/or tree right-heavy instead of left-heavy. opt -SELECT * FROM a WHERE (k=1 OR (i=2 OR f=3.5)) AND (s='foo' AND s<>'bar') +SELECT (k=1 OR (i=2 OR f=3.5)) AND (s='foo' AND s<>'bar') FROM a ---- -select - ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) +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) a.j:5(jsonb) - └── filters [type=bool, outer=(1-4), constraints=(/4: [/'foo' - /'foo'])] - ├── or [type=bool, outer=(1-3)] - │ ├── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] - │ │ ├── variable: a.k [type=int, outer=(1)] - │ │ └── const: 1 [type=int] - │ ├── eq [type=bool, outer=(2), constraints=(/2: [/2 - /2]; tight)] - │ │ ├── variable: a.i [type=int, outer=(2)] - │ │ └── const: 2 [type=int] - │ └── eq [type=bool, outer=(3), constraints=(/3: [/3.5 - /3.5]; tight)] - │ ├── variable: a.f [type=float, outer=(3)] - │ └── const: 3.5 [type=float] - ├── eq [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight)] - │ ├── variable: a.s [type=string, outer=(4)] - │ └── const: 'foo' [type=string] - └── ne [type=bool, outer=(4)] - ├── variable: a.s [type=string, outer=(4)] - └── const: 'bar' [type=string] + │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) + └── projections [outer=(1-4)] + └── and [type=bool, outer=(1-4), constraints=(/4: [/'foo' - /'foo'])] + ├── or [type=bool, outer=(1-3)] + │ ├── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] + │ │ ├── variable: a.k [type=int, outer=(1)] + │ │ └── const: 1 [type=int] + │ ├── eq [type=bool, outer=(2), constraints=(/2: [/2 - /2]; tight)] + │ │ ├── variable: a.i [type=int, outer=(2)] + │ │ └── const: 2 [type=int] + │ └── eq [type=bool, outer=(3), constraints=(/3: [/3.5 - /3.5]; tight)] + │ ├── variable: a.f [type=float, outer=(3)] + │ └── const: 3.5 [type=float] + ├── eq [type=bool, outer=(4), constraints=(/4: [/'foo' - /'foo']; tight)] + │ ├── variable: a.s [type=string, outer=(4)] + │ └── const: 'foo' [type=string] + └── ne [type=bool, outer=(4)] + ├── variable: a.s [type=string, outer=(4)] + └── const: 'bar' [type=string] # -------------------------------------------------- # SimplifyFilters @@ -267,11 +277,9 @@ select opt SELECT * FROM a WHERE Null ---- -select +scan a,constrained ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - └── false [type=bool] + └── constraint: /1: contradiction opt SELECT * FROM a INNER JOIN b ON NULL @@ -287,11 +295,9 @@ inner-join opt SELECT * FROM a WHERE i=1 AND Null ---- -select +scan a,constrained ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - └── false [type=bool] + └── constraint: /1: contradiction # -------------------------------------------------- # FoldNullAndOr @@ -299,46 +305,31 @@ select opt SELECT * FROM a WHERE null and null ---- -select +scan a,constrained ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - └── false [type=bool] + └── constraint: /1: contradiction opt SELECT * FROM a WHERE null or null ---- -select +scan a,constrained ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - └── false [type=bool] + └── constraint: /1: contradiction opt SELECT * FROM a WHERE null or (null and null and null) or null ---- -select +scan a,constrained ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - └── false [type=bool] + └── constraint: /1: contradiction # Don't fold. opt SELECT * FROM a WHERE null or (null and k=1) ---- -select +scan a,constrained ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - └── filters [type=bool, outer=(1)] - └── or [type=bool, outer=(1)] - ├── null [type=unknown] - └── and [type=bool, outer=(1), constraints=(/1: [/1 - /1])] - ├── null [type=unknown] - └── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] - ├── variable: a.k [type=int, outer=(1)] - └── const: 1 [type=int] + └── constraint: /1: contradiction # -------------------------------------------------- # NegateComparison diff --git a/pkg/sql/opt/norm/testdata/combo b/pkg/sql/opt/norm/testdata/combo index 5fd5ac7f751a..c146f657ac9f 100644 --- a/pkg/sql/opt/norm/testdata/combo +++ b/pkg/sql/opt/norm/testdata/combo @@ -233,6 +233,8 @@ SELECT s FROM a INNER JOIN b ON a.x=b.x AND i+1=10 *** GenerateIndexScans applied; best expr unchanged. +*** ConstrainScan applied; best expr unchanged. + *** GenerateIndexScans applied; best expr unchanged. *** Final best expr: diff --git a/pkg/sql/opt/norm/testdata/comp b/pkg/sql/opt/norm/testdata/comp index 731e3936e756..71979258a71f 100644 --- a/pkg/sql/opt/norm/testdata/comp +++ b/pkg/sql/opt/norm/testdata/comp @@ -309,12 +309,10 @@ 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) - ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - └── filters [type=bool, outer=(1,2,4), constraints=(/1: [/1 - /1]; /2: [/2 - /2]; /4: [/'foo' - /'foo']; tight)] - ├── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] - │ ├── variable: a.k [type=int, outer=(1)] - │ └── const: 1 [type=int] + ├── scan a,constrained + │ ├── 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] + └── 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)] │ └── const: 2 [type=int] @@ -348,8 +346,6 @@ WHERE null::string ~* 'foo' AND 'foo' ~* null::string AND null::string !~* 'foo' AND 'foo' !~* null::string ---- -select +scan a,constrained ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - └── false [type=bool] + └── constraint: /1: contradiction diff --git a/pkg/sql/opt/norm/testdata/project b/pkg/sql/opt/norm/testdata/project index 2ee868fffdf3..6ca128399eee 100644 --- a/pkg/sql/opt/norm/testdata/project +++ b/pkg/sql/opt/norm/testdata/project @@ -203,12 +203,10 @@ SELECT x, y FROM t.a WHERE x=1 AND y<5 ---- select ├── columns: x:1(int!null) y:2(int) - ├── scan a - │ └── columns: a.x:1(int!null) a.y:2(int) - └── filters [type=bool, outer=(1,2), constraints=(/1: [/1 - /1]; /2: (/NULL - /4]; tight)] - ├── eq [type=bool, outer=(1), constraints=(/1: [/1 - /1]; tight)] - │ ├── variable: a.x [type=int, outer=(1)] - │ └── const: 1 [type=int] + ├── scan a,constrained + │ ├── columns: a.x:1(int!null) a.y:2(int) + │ └── constraint: /1: [/1 - /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)] └── const: 5 [type=int] @@ -267,14 +265,9 @@ project │ ├── columns: a.y:2(int) f:5(float) │ ├── project │ │ ├── columns: a.y:2(int) f:5(float) - │ │ ├── select + │ │ ├── scan a,constrained │ │ │ ├── columns: a.x:1(int!null) a.y:2(int) a.f:3(float) - │ │ │ ├── scan a - │ │ │ │ └── columns: a.x:1(int!null) a.y:2(int) a.f:3(float) - │ │ │ └── filters [type=bool, outer=(1), constraints=(/1: [/5 - /5]; tight)] - │ │ │ └── eq [type=bool, outer=(1), constraints=(/1: [/5 - /5]; tight)] - │ │ │ ├── variable: a.x [type=int, outer=(1)] - │ │ │ └── const: 5 [type=int] + │ │ │ └── constraint: /1: [/5 - /5] │ │ └── projections [outer=(2,3)] │ │ ├── variable: a.y [type=int, outer=(2)] │ │ └── plus [type=float, outer=(3)] diff --git a/pkg/sql/opt/norm/testdata/select b/pkg/sql/opt/norm/testdata/select index ae6fad8b0cea..c740ee657249 100644 --- a/pkg/sql/opt/norm/testdata/select +++ b/pkg/sql/opt/norm/testdata/select @@ -78,11 +78,9 @@ scan a opt SELECT * FROM a WHERE False ---- -select +scan a,constrained ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - └── false [type=bool] + └── constraint: /1: contradiction # -------------------------------------------------- # EliminateSelect @@ -99,29 +97,23 @@ scan a opt SELECT * FROM (SELECT * FROM a WHERE False) WHERE s='foo' ---- -select +scan a,constrained ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - └── false [type=bool] + └── constraint: /1: contradiction opt SELECT * FROM (SELECT * FROM a WHERE i=1) WHERE False ---- -select +scan a,constrained ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - └── false [type=bool] + └── constraint: /1: contradiction opt SELECT * FROM (SELECT * FROM a WHERE i=1) WHERE False ---- -select +scan a,constrained ├── columns: k:1(int!null) i:2(int) f:3(float) s:4(string) j:5(jsonb) - ├── scan a - │ └── columns: a.k:1(int!null) a.i:2(int) a.f:3(float) a.s:4(string) a.j:5(jsonb) - └── false [type=bool] + └── constraint: /1: contradiction opt SELECT * FROM (SELECT * FROM a WHERE i<5) WHERE s='foo' diff --git a/pkg/sql/opt/optgen/cmd/optgen/explorer_gen.go b/pkg/sql/opt/optgen/cmd/optgen/explorer_gen.go index d4b811583a90..6de8589d950b 100644 --- a/pkg/sql/opt/optgen/cmd/optgen/explorer_gen.go +++ b/pkg/sql/opt/optgen/cmd/optgen/explorer_gen.go @@ -15,7 +15,6 @@ package main import ( - "fmt" "io" "github.com/cockroachdb/cockroach/pkg/sql/opt/optgen/lang" @@ -85,8 +84,8 @@ func (g *explorerGen) genDispatcher() { // genRuleFuncs generates a method for each operator that has at least one // explore rule defined. The code is similar to this: // -// func (_e *explorer) exploreScan(_state *exploreState, _eid memo.ExprID) (_fullyExplored bool) { -// _scanExpr := _e.mem.Expr(_eid).AsScan() +// func (_e *explorer) exploreScan(_rootState *exploreState, _root memo.ExprID) (_fullyExplored bool) { +// _rootExpr := _e.mem.Expr(_root).AsScan() // _fullyExplored = true // // ... exploration rule code goes here ... @@ -101,10 +100,9 @@ func (g *explorerGen) genRuleFuncs() { continue } - exprName := fmt.Sprintf("_%sExpr", unTitle(string(define.Name))) - format := "func (_e *explorer) explore%s(_state *exploreState, _eid memo.ExprID) (_fullyExplored bool) {\n" + format := "func (_e *explorer) explore%s(_rootState *exploreState, _root memo.ExprID) (_fullyExplored bool) {\n" g.w.nestIndent(format, define.Name) - g.w.writeIndent("%s := _e.mem.Expr(_eid).As%s()\n", exprName, define.Name) + g.w.writeIndent("_rootExpr := _e.mem.Expr(_root).As%s()\n", define.Name) g.w.writeIndent("_fullyExplored = true\n\n") for _, rule := range rules { diff --git a/pkg/sql/opt/optgen/cmd/optgen/rule_gen.go b/pkg/sql/opt/optgen/cmd/optgen/rule_gen.go index 3a13e55ddd91..5d0f5222b502 100644 --- a/pkg/sql/opt/optgen/cmd/optgen/rule_gen.go +++ b/pkg/sql/opt/optgen/cmd/optgen/rule_gen.go @@ -94,22 +94,23 @@ func (g *ruleGen) init(compiled *lang.CompiledExpr, w *matchWriter) { // genRule generates match and replace code for one rule within the scope of // a particular op construction method. func (g *ruleGen) genRule(rule *lang.RuleExpr) { + matchName := string(rule.Match.Names[0]) + define := g.compiled.LookupDefine(matchName) // Determine whether the rule is a normalization or exploration rule and // set up context accordingly. g.normalize = rule.Tags.Contains("Normalize") + var exprName string if g.normalize { g.exprLookup = "NormExpr" g.thisVar = "_f" + exprName = fmt.Sprintf("_%sExpr", unTitle(string(define.Name))) } else { g.exprLookup = "Expr" g.thisVar = "_e" g.innerExploreMatch = g.findInnerExploreMatch(rule.Match) + exprName = "_rootExpr" } - matchName := string(rule.Match.Names[0]) - define := g.compiled.LookupDefine(matchName) - exprName := fmt.Sprintf("_%sExpr", unTitle(string(define.Name))) - g.uniquifier.init() g.uniquifier.makeUnique(exprName) @@ -129,12 +130,12 @@ func (g *ruleGen) genRule(rule *lang.RuleExpr) { if rule.Match == g.innerExploreMatch { // The top-level match is the only match in this rule. Skip the // expression if it was processed in a previous exploration pass. - g.w.nestIndent("if _eid.Expr >= _state.start {\n") + g.w.nestIndent("if _root.Expr >= _rootState.start {\n") } else { // Initialize _partlyExplored for the top-level match. This variable // will be shadowed by each nested loop. Only if all loops are bound // to already explored expressions can the innermost match skip. - g.w.writeIndent("_partlyExplored := _eid.Expr < _state.start\n") + g.w.writeIndent("_partlyExplored := _root.Expr < _rootState.start\n") } for index, matchArg := range rule.Match.Args { @@ -576,7 +577,7 @@ func (g *ruleGen) genReplace(define *lang.DefineExpr, rule *lang.RuleExpr) { g.w.write(",\n") } g.w.unnest(")\n") - g.w.writeIndent("_e.mem.MemoizeDenormExpr(_eid.Group, memo.Expr(_expr))\n") + g.w.writeIndent("_e.mem.MemoizeDenormExpr(_root.Group, memo.Expr(_expr))\n") case *lang.CustomFuncExpr: // Top-level custom function returns a memo.Expr slice, so iterate @@ -585,7 +586,7 @@ func (g *ruleGen) genReplace(define *lang.DefineExpr, rule *lang.RuleExpr) { g.genNestedExpr(rule.Replace) g.w.newline() g.w.nestIndent("for i := range exprs {\n") - g.w.writeIndent("_e.mem.MemoizeDenormExpr(_eid.Group, exprs[i])\n") + g.w.writeIndent("_e.mem.MemoizeDenormExpr(_root.Group, exprs[i])\n") g.w.unnest("}\n") default: diff --git a/pkg/sql/opt/optgen/cmd/optgen/testdata/explorer b/pkg/sql/opt/optgen/cmd/optgen/testdata/explorer index abb1fdc8acc4..5ce577ef6e2e 100644 --- a/pkg/sql/opt/optgen/cmd/optgen/testdata/explorer +++ b/pkg/sql/opt/optgen/cmd/optgen/testdata/explorer @@ -121,29 +121,29 @@ func (_e *explorer) exploreExpr(_state *exploreState, _eid memo.ExprID) (_fullyE return true } -func (_e *explorer) exploreInnerJoin(_state *exploreState, _eid memo.ExprID) (_fullyExplored bool) { - _innerJoinExpr := _e.mem.Expr(_eid).AsInnerJoin() +func (_e *explorer) exploreInnerJoin(_rootState *exploreState, _root memo.ExprID) (_fullyExplored bool) { + _rootExpr := _e.mem.Expr(_root).AsInnerJoin() _fullyExplored = true // [CommuteJoin] { - if _eid.Expr >= _state.start { - r := _innerJoinExpr.Left() - s := _innerJoinExpr.Right() + if _root.Expr >= _rootState.start { + r := _rootExpr.Left() + s := _rootExpr.Right() if _e.o.onRuleMatch == nil || _e.o.onRuleMatch(opt.CommuteJoin) { _expr := memo.MakeInnerJoinExpr( s, r, ) - _e.mem.MemoizeDenormExpr(_eid.Group, memo.Expr(_expr)) + _e.mem.MemoizeDenormExpr(_root.Group, memo.Expr(_expr)) } } } // [AssociateJoin] { - _partlyExplored := _eid.Expr < _state.start - _state := _e.exploreGroup(_innerJoinExpr.Left()) + _partlyExplored := _root.Expr < _rootState.start + _state := _e.exploreGroup(_rootExpr.Left()) if !_state.fullyExplored { _fullyExplored = false } @@ -152,18 +152,18 @@ func (_e *explorer) exploreInnerJoin(_state *exploreState, _eid memo.ExprID) (_f start = _state.start } for _ord := start; _ord < _state.end; _ord++ { - _eid := memo.ExprID{Group: _innerJoinExpr.Left(), Expr: _ord} - _innerJoinExpr2 := _e.mem.Expr(_eid).AsInnerJoin() - if _innerJoinExpr2 != nil { - r := _innerJoinExpr2.Left() - s := _innerJoinExpr2.Right() - _eid := memo.MakeNormExprID(_innerJoinExpr2.On()) + _eid := memo.ExprID{Group: _rootExpr.Left(), Expr: _ord} + _innerJoinExpr := _e.mem.Expr(_eid).AsInnerJoin() + if _innerJoinExpr != nil { + r := _innerJoinExpr.Left() + s := _innerJoinExpr.Right() + _eid := memo.MakeNormExprID(_innerJoinExpr.On()) _expr := _e.mem.Expr(_eid) if _expr.Operator() == opt.FiltersOp || _expr.IsHasConditions() || _expr.Operator() == opt.AndOp { lowerConditions := _expr.ChildGroup(_e.mem, 0) - t := _innerJoinExpr.Right() - filters := _innerJoinExpr.On() - _eid := memo.MakeNormExprID(_innerJoinExpr.On()) + t := _rootExpr.Right() + filters := _rootExpr.On() + _eid := memo.MakeNormExprID(_rootExpr.On()) _filtersExpr := _e.mem.Expr(_eid).AsFilters() if _filtersExpr != nil { upperConditions := _filtersExpr.Conditions() @@ -183,7 +183,7 @@ func (_e *explorer) exploreInnerJoin(_state *exploreState, _eid memo.ExprID) (_f _e.constructConditionsUsing(s, lowerConditions, upperConditions), ), ) - _e.mem.MemoizeDenormExpr(_eid.Group, memo.Expr(_expr)) + _e.mem.MemoizeDenormExpr(_root.Group, memo.Expr(_expr)) } } } @@ -196,20 +196,20 @@ func (_e *explorer) exploreInnerJoin(_state *exploreState, _eid memo.ExprID) (_f return _fullyExplored } -func (_e *explorer) exploreSelect(_state *exploreState, _eid memo.ExprID) (_fullyExplored bool) { - _selectExpr := _e.mem.Expr(_eid).AsSelect() +func (_e *explorer) exploreSelect(_rootState *exploreState, _root memo.ExprID) (_fullyExplored bool) { + _rootExpr := _e.mem.Expr(_root).AsSelect() _fullyExplored = true // [PushDownGroupBy] { - _partlyExplored := _eid.Expr < _state.start - _state := _e.exploreGroup(_selectExpr.Input()) + _partlyExplored := _root.Expr < _rootState.start + _state := _e.exploreGroup(_rootExpr.Input()) if !_state.fullyExplored { _fullyExplored = false } for _ord := 0; _ord < _state.end; _ord++ { _partlyExplored := _partlyExplored && _ord < _state.start - _eid := memo.ExprID{Group: _selectExpr.Input(), Expr: _ord} + _eid := memo.ExprID{Group: _rootExpr.Input(), Expr: _ord} _groupByExpr := _e.mem.Expr(_eid).AsGroupBy() if _groupByExpr != nil { input := _groupByExpr.Input() @@ -230,7 +230,7 @@ func (_e *explorer) exploreSelect(_state *exploreState, _eid memo.ExprID) (_full on := _expr.ChildGroup(_e.mem, 2) aggregations := _groupByExpr.Aggregations() groupingCols := _groupByExpr.GroupingCols() - filter := _selectExpr.Filter() + filter := _rootExpr.Filter() if !_e.isCorrelated(filter, right) { if _e.o.onRuleMatch == nil || _e.o.onRuleMatch(opt.PushDownGroupBy) { _expr := memo.MakeInnerJoinExpr( @@ -245,7 +245,7 @@ func (_e *explorer) exploreSelect(_state *exploreState, _eid memo.ExprID) (_full right, on, ) - _e.mem.MemoizeDenormExpr(_eid.Group, memo.Expr(_expr)) + _e.mem.MemoizeDenormExpr(_root.Group, memo.Expr(_expr)) } } } @@ -257,19 +257,19 @@ func (_e *explorer) exploreSelect(_state *exploreState, _eid memo.ExprID) (_full return _fullyExplored } -func (_e *explorer) exploreScan(_state *exploreState, _eid memo.ExprID) (_fullyExplored bool) { - _scanExpr := _e.mem.Expr(_eid).AsScan() +func (_e *explorer) exploreScan(_rootState *exploreState, _root memo.ExprID) (_fullyExplored bool) { + _rootExpr := _e.mem.Expr(_root).AsScan() _fullyExplored = true // [GenerateIndexScans] { - if _eid.Expr >= _state.start { - def := _scanExpr.Def() + if _root.Expr >= _rootState.start { + def := _rootExpr.Def() if _e.isPrimaryScan(def) { if _e.o.onRuleMatch == nil || _e.o.onRuleMatch(opt.GenerateIndexScans) { exprs := _e.generateIndexScans(def) for i := range exprs { - _e.mem.MemoizeDenormExpr(_eid.Group, exprs[i]) + _e.mem.MemoizeDenormExpr(_root.Group, exprs[i]) } } } diff --git a/pkg/sql/opt/rule_name.og.go b/pkg/sql/opt/rule_name.og.go index 94aee8af45b0..9694c40f07d6 100644 --- a/pkg/sql/opt/rule_name.og.go +++ b/pkg/sql/opt/rule_name.og.go @@ -77,6 +77,7 @@ const ( // Explore Rule Names // ------------------------------------------------------------ GenerateIndexScans + ConstrainScan // NumRuleNames tracks the total count of rule names. NumRuleNames diff --git a/pkg/sql/opt/rule_name_string.go b/pkg/sql/opt/rule_name_string.go index f8da19955548..c031721cbdc7 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 = "InvalidRuleNameNumManualRuleNamesEliminateEmptyAndEliminateEmptyOrEliminateSingletonAndOrSimplifyAndSimplifyOrSimplifyFiltersFoldNullAndOrNegateComparisonEliminateNotNegateAndNegateOrCommuteVarInequalityCommuteConstInequalityNormalizeCmpPlusConstNormalizeCmpMinusConstNormalizeCmpConstMinusNormalizeTupleEqualityFoldNullComparisonLeftFoldNullComparisonRightEnsureJoinFiltersAndEnsureJoinFiltersPushDownJoinLeftPushDownJoinRightFoldPlusZeroFoldZeroPlusFoldMinusZeroFoldMultOneFoldOneMultFoldDivOneInvertMinusEliminateUnaryMinusEliminateProjectFilterUnusedProjectColsFilterUnusedScanColsFilterUnusedSelectColsFilterUnusedLimitColsFilterUnusedOffsetColsFilterUnusedJoinLeftColsFilterUnusedJoinRightColsFilterUnusedAggColsFilterUnusedGroupByColsFilterUnusedValueColsCommuteVarCommuteConstEliminateCoalesceSimplifyCoalesceEliminateCastFoldNullCastFoldNullUnaryFoldNullBinaryLeftFoldNullBinaryRightFoldNullInNonEmptyFoldNullInEmptyFoldNullNotInEmptyNormalizeInConstFoldInNullEnsureSelectFiltersAndEnsureSelectFiltersEliminateSelectMergeSelectsPushDownSelectJoinLeftPushDownSelectJoinRightMergeSelectInnerJoinPushDownSelectGroupByGenerateIndexScansNumRuleNames" +const _RuleName_name = "InvalidRuleNameNumManualRuleNamesEliminateEmptyAndEliminateEmptyOrEliminateSingletonAndOrSimplifyAndSimplifyOrSimplifyFiltersFoldNullAndOrNegateComparisonEliminateNotNegateAndNegateOrCommuteVarInequalityCommuteConstInequalityNormalizeCmpPlusConstNormalizeCmpMinusConstNormalizeCmpConstMinusNormalizeTupleEqualityFoldNullComparisonLeftFoldNullComparisonRightEnsureJoinFiltersAndEnsureJoinFiltersPushDownJoinLeftPushDownJoinRightFoldPlusZeroFoldZeroPlusFoldMinusZeroFoldMultOneFoldOneMultFoldDivOneInvertMinusEliminateUnaryMinusEliminateProjectFilterUnusedProjectColsFilterUnusedScanColsFilterUnusedSelectColsFilterUnusedLimitColsFilterUnusedOffsetColsFilterUnusedJoinLeftColsFilterUnusedJoinRightColsFilterUnusedAggColsFilterUnusedGroupByColsFilterUnusedValueColsCommuteVarCommuteConstEliminateCoalesceSimplifyCoalesceEliminateCastFoldNullCastFoldNullUnaryFoldNullBinaryLeftFoldNullBinaryRightFoldNullInNonEmptyFoldNullInEmptyFoldNullNotInEmptyNormalizeInConstFoldInNullEnsureSelectFiltersAndEnsureSelectFiltersEliminateSelectMergeSelectsPushDownSelectJoinLeftPushDownSelectJoinRightMergeSelectInnerJoinPushDownSelectGroupByGenerateIndexScansConstrainScanNumRuleNames" -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, 410, 427, 439, 451, 464, 475, 486, 496, 507, 526, 542, 565, 585, 607, 628, 650, 674, 699, 718, 741, 762, 772, 784, 801, 817, 830, 842, 855, 873, 892, 910, 925, 943, 959, 969, 991, 1010, 1025, 1037, 1059, 1082, 1102, 1123, 1141, 1153} +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, 410, 427, 439, 451, 464, 475, 486, 496, 507, 526, 542, 565, 585, 607, 628, 650, 674, 699, 718, 741, 762, 772, 784, 801, 817, 830, 842, 855, 873, 892, 910, 925, 943, 959, 969, 991, 1010, 1025, 1037, 1059, 1082, 1102, 1123, 1141, 1154, 1166} func (i RuleName) String() string { if i >= RuleName(len(_RuleName_index)-1) { diff --git a/pkg/sql/opt/testutils/create_table.go b/pkg/sql/opt/testutils/create_table.go index 09737a9528ea..3291f3472389 100644 --- a/pkg/sql/opt/testutils/create_table.go +++ b/pkg/sql/opt/testutils/create_table.go @@ -186,7 +186,10 @@ func (ti *TestIndex) addColumn( } func (tt *TestTable) addPrimaryColumnIndex(col *TestColumn) { - idxCol := opt.IndexColumn{Column: col} + idxCol := opt.IndexColumn{ + Column: col, + Ordinal: tt.FindOrdinal(col.Name), + } tt.Indexes = append( tt.Indexes, &TestIndex{ diff --git a/pkg/sql/opt/testutils/test_catalog.go b/pkg/sql/opt/testutils/test_catalog.go index d5d953399ed5..ec6f3f0b45dc 100644 --- a/pkg/sql/opt/testutils/test_catalog.go +++ b/pkg/sql/opt/testutils/test_catalog.go @@ -37,9 +37,7 @@ func NewTestCatalog() *TestCatalog { } // FindTable is part of the opt.Catalog interface. -func (tc *TestCatalog) FindTable( - ctx context.Context, name *tree.TableName, -) (opt.Table, error) { +func (tc *TestCatalog) FindTable(ctx context.Context, name *tree.TableName) (opt.Table, error) { if table, ok := tc.tables[name.Table()]; ok { return table, nil } diff --git a/pkg/sql/opt/xform/coster.go b/pkg/sql/opt/xform/coster.go index 8f7c5bcf448d..a1ca14050d7f 100644 --- a/pkg/sql/opt/xform/coster.go +++ b/pkg/sql/opt/xform/coster.go @@ -47,6 +47,9 @@ func (c *coster) computeCost(candidate *memo.BestExpr, props *memo.LogicalProps) case opt.ScanOp: cost = c.computeScanCost(candidate, props) + case opt.SelectOp: + cost = c.computeSelectCost(candidate, props) + case opt.ValuesOp: cost = c.computeValuesCost(candidate, props) @@ -67,6 +70,13 @@ func (c *coster) computeScanCost(candidate *memo.BestExpr, props *memo.LogicalPr return memo.Cost(props.Relational.Stats.RowCount) } +func (c *coster) computeSelectCost(candidate *memo.BestExpr, props *memo.LogicalProps) memo.Cost { + // The filter has to be evaluated on each input row. + inputRowCount := c.mem.BestExprLogical(candidate.Child(0)).Relational.Stats.RowCount + cost := memo.Cost(inputRowCount) * 0.1 + return cost + c.computeChildrenCost(candidate) +} + func (c *coster) computeValuesCost(candidate *memo.BestExpr, props *memo.LogicalProps) memo.Cost { return memo.Cost(props.Relational.Stats.RowCount) } diff --git a/pkg/sql/opt/xform/explorer.go b/pkg/sql/opt/xform/explorer.go index fe57d2e21f81..91ce5b9ed0a1 100644 --- a/pkg/sql/opt/xform/explorer.go +++ b/pkg/sql/opt/xform/explorer.go @@ -16,8 +16,10 @@ package xform import ( "github.com/cockroachdb/cockroach/pkg/sql/opt" + "github.com/cockroachdb/cockroach/pkg/sql/opt/idxconstraint" "github.com/cockroachdb/cockroach/pkg/sql/opt/memo" "github.com/cockroachdb/cockroach/pkg/sql/opt/norm" + "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/util" ) @@ -85,9 +87,12 @@ import ( // themselves match this same rule. However, adding their replace expressions to // the memo group will be a no-op, because they're already present. type explorer struct { - o *Optimizer - mem *memo.Memo - f *norm.Factory + o *Optimizer + mem *memo.Memo + f *norm.Factory + evalCtx *tree.EvalContext + + // exprs is a buffer reused by custom replace functions. exprs []memo.Expr } @@ -95,6 +100,7 @@ func (e *explorer) init(o *Optimizer) { e.o = o e.mem = o.mem e.f = o.f + e.evalCtx = o.evalCtx } // exploreGroup generates alternate expressions that are logically equivalent @@ -202,11 +208,11 @@ func (e *explorer) ensureExploreState(group memo.GroupID) *exploreState { // // ---------------------------------------------------------------------- -// isPrimaryScan returns true if the given expression is scanning a primary -// index rather than a secondary index. -func (e *explorer) isPrimaryScan(def memo.PrivateID) bool { +// isUnconstrainedPrimaryScan returns true if the given expression is scanning a +// primary index rather than a secondary index. +func (e *explorer) isUnconstrainedPrimaryScan(def memo.PrivateID) bool { scanOpDef := e.mem.LookupPrivate(def).(*memo.ScanOpDef) - return scanOpDef.Index == opt.PrimaryIndex + return scanOpDef.Index == opt.PrimaryIndex && scanOpDef.Constraint == nil } // generateIndexScans enumerates all indexes on the scan operator's table and @@ -232,6 +238,82 @@ func (e *explorer) generateIndexScans(def memo.PrivateID) []memo.Expr { return e.exprs } +// ---------------------------------------------------------------------- +// +// Select Rules +// Custom match and replace functions used with select.opt rules. +// +// ---------------------------------------------------------------------- + +// isUnconstrainedScan returns true if thegiven expression is a Scan +// without any constraints. +func (e *explorer) isUnconstrainedScan(def memo.PrivateID) bool { + scanOpDef := e.mem.LookupPrivate(def).(*memo.ScanOpDef) + return scanOpDef.Constraint == nil +} + +// constrainScan tries to push filters into Scan operations as constraints. It +// is applied on a Select -> Scan pattern. The scan operation is assumed to have +// no constraints. +// +// There are three cases: +// +// - if the filter can be completely converted to constraints, we return a +// constrained scan expression (to be added to the same group as the select +// operator). +// +// - if the filter can be partially converted to constraints, we construct the +// constrained scan and we return a select expression with the remaining +// filter (to be added to the same group as the select operator). +// +// - if the filter cannot be converted to constraints, does and returns +// nothing. +// +func (e *explorer) constrainScan(filterGroup memo.GroupID, scanDef memo.PrivateID) []memo.Expr { + e.exprs = e.exprs[:0] + + scanOpDef := e.mem.LookupPrivate(scanDef).(*memo.ScanOpDef) + + // Fill out data structures needed to initialize the idxconstraint library. + md := e.mem.Metadata() + index := md.Table(scanOpDef.Table).Index(scanOpDef.Index) + columns := make([]opt.OrderingColumn, index.UniqueColumnCount()) + var notNullCols opt.ColSet + for i := range columns { + col := index.Column(i) + colID := md.TableColumn(scanOpDef.Table, col.Ordinal) + columns[i] = opt.MakeOrderingColumn(colID, col.Descending) + if !col.Column.IsNullable() { + notNullCols.Add(int(colID)) + } + } + + // Generate index constraints. + var ic idxconstraint.Instance + filter := memo.MakeNormExprView(e.mem, filterGroup) + ic.Init(filter, columns, notNullCols, false /* isInverted */, e.evalCtx, e.f) + constraint := ic.Constraint() + if constraint.IsUnconstrained() { + return nil + } + newDef := *scanOpDef + newDef.Constraint = constraint + + remainingFilter := ic.RemainingFilter() + if e.mem.NormExpr(remainingFilter).Operator() == opt.TrueOp { + // No remaining filter. Add the constrained scan node to select's group. + constrainedScan := memo.MakeScanExpr(e.mem.InternScanOpDef(&newDef)) + e.exprs = append(e.exprs, memo.Expr(constrainedScan)) + } else { + // We have a remaining filter. We create the constrained scan in a new group + // and create a select node in the same group with the original select. + constrainedScan := e.f.ConstructScan(e.mem.InternScanOpDef(&newDef)) + newSelect := memo.MakeSelectExpr(constrainedScan, remainingFilter) + e.exprs = append(e.exprs, memo.Expr(newSelect)) + } + return e.exprs +} + // ---------------------------------------------------------------------- // // Exploration state diff --git a/pkg/sql/opt/xform/explorer.og.go b/pkg/sql/opt/xform/explorer.og.go index 53b3ec1bf32d..b97337caf206 100644 --- a/pkg/sql/opt/xform/explorer.og.go +++ b/pkg/sql/opt/xform/explorer.og.go @@ -12,25 +12,65 @@ func (_e *explorer) exploreExpr(_state *exploreState, _eid memo.ExprID) (_fullyE switch _expr.Operator() { case opt.ScanOp: return _e.exploreScan(_state, _eid) + case opt.SelectOp: + return _e.exploreSelect(_state, _eid) } // No rules for other operator types. return true } -func (_e *explorer) exploreScan(_state *exploreState, _eid memo.ExprID) (_fullyExplored bool) { - _scanExpr := _e.mem.Expr(_eid).AsScan() +func (_e *explorer) exploreScan(_rootState *exploreState, _root memo.ExprID) (_fullyExplored bool) { + _rootExpr := _e.mem.Expr(_root).AsScan() _fullyExplored = true // [GenerateIndexScans] { - if _eid.Expr >= _state.start { - def := _scanExpr.Def() - if _e.isPrimaryScan(def) { + if _root.Expr >= _rootState.start { + def := _rootExpr.Def() + if _e.isUnconstrainedPrimaryScan(def) { if _e.o.onRuleMatch == nil || _e.o.onRuleMatch(opt.GenerateIndexScans) { exprs := _e.generateIndexScans(def) for i := range exprs { - _e.mem.MemoizeDenormExpr(_eid.Group, exprs[i]) + _e.mem.MemoizeDenormExpr(_root.Group, exprs[i]) + } + } + } + } + } + + return _fullyExplored +} + +func (_e *explorer) exploreSelect( + _rootState *exploreState, _root memo.ExprID, +) (_fullyExplored bool) { + _rootExpr := _e.mem.Expr(_root).AsSelect() + _fullyExplored = true + + // [ConstrainScan] + { + _partlyExplored := _root.Expr < _rootState.start + _state := _e.exploreGroup(_rootExpr.Input()) + if !_state.fullyExplored { + _fullyExplored = false + } + start := memo.ExprOrdinal(0) + if _partlyExplored { + start = _state.start + } + for _ord := start; _ord < _state.end; _ord++ { + _eid := memo.ExprID{Group: _rootExpr.Input(), Expr: _ord} + _scanExpr := _e.mem.Expr(_eid).AsScan() + if _scanExpr != nil { + def := _scanExpr.Def() + if _e.isUnconstrainedScan(def) { + filter := _rootExpr.Filter() + if _e.o.onRuleMatch == nil || _e.o.onRuleMatch(opt.ConstrainScan) { + exprs := _e.constrainScan(filter, def) + for i := range exprs { + _e.mem.MemoizeDenormExpr(_root.Group, exprs[i]) + } } } } diff --git a/pkg/sql/opt/xform/rules/scan.opt b/pkg/sql/opt/xform/rules/scan.opt index e67624b4bea4..167a2d848ef2 100644 --- a/pkg/sql/opt/xform/rules/scan.opt +++ b/pkg/sql/opt/xform/rules/scan.opt @@ -1,9 +1,9 @@ # ============================================================================= -# scan.opt contains rules for the Scan operator. +# scan.opt contains exploration rules for the Scan operator. # ============================================================================= # GenerateIndexScans creates alternate Scan expressions for each secondary index # on the scanned table. [GenerateIndexScans, Explore] -(Scan $def:* & (IsPrimaryScan $def)) => (GenerateIndexScans $def) +(Scan $def:* & (IsUnconstrainedPrimaryScan $def)) => (GenerateIndexScans $def) diff --git a/pkg/sql/opt/xform/rules/select.opt b/pkg/sql/opt/xform/rules/select.opt new file mode 100644 index 000000000000..d34d0304e495 --- /dev/null +++ b/pkg/sql/opt/xform/rules/select.opt @@ -0,0 +1,15 @@ +# ============================================================================= +# select.opt contains exploration rules for the Select operator. +# ============================================================================= + +# ConstrainScan matches a Select over an unconstrained Scan and tries to push +# down the filter (or part of it) as index constraints. The result is either +# a constrained Scan or a Select (with a remaining filter) on top of a +# constrained Scan. +[ConstrainScan, Explore] +(Select + (Scan $def:* & (IsUnconstrainedScan $def)) + $filter:* +) +=> +(ConstrainScan $filter $def) diff --git a/pkg/sql/opt/xform/testdata/coster/join b/pkg/sql/opt/xform/testdata/coster/join index c7a675d6dd57..08e925028c6d 100644 --- a/pkg/sql/opt/xform/testdata/coster/join +++ b/pkg/sql/opt/xform/testdata/coster/join @@ -25,15 +25,15 @@ SELECT k, x FROM a INNER JOIN b ON k=x WHERE d=1.0 inner-join ├── columns: k:1(int!null) x:5(int) ├── stats: [rows=10000] - ├── cost: 2000.00 + ├── cost: 2100.00 ├── project │ ├── columns: a.k:1(int!null) │ ├── stats: [rows=100] - │ ├── cost: 1000.00 + │ ├── cost: 1100.00 │ ├── select │ │ ├── columns: a.k:1(int!null) a.d:4(decimal!null) │ │ ├── stats: [rows=100] - │ │ ├── cost: 1000.00 + │ │ ├── cost: 1100.00 │ │ ├── scan a │ │ │ ├── columns: a.k:1(int!null) a.d:4(decimal!null) │ │ │ ├── stats: [rows=1000] diff --git a/pkg/sql/opt/xform/testdata/coster/select b/pkg/sql/opt/xform/testdata/coster/select new file mode 100644 index 000000000000..aaea5e7f5e07 --- /dev/null +++ b/pkg/sql/opt/xform/testdata/coster/select @@ -0,0 +1,26 @@ +exec-ddl +CREATE TABLE a (k INT PRIMARY KEY, i INT, s STRING, d DECIMAL NOT NULL) +---- +TABLE a + ├── k int not null + ├── i int + ├── s string + ├── d decimal not null + └── INDEX primary + └── k int not null + +opt +SELECT k, s FROM a WHERE s >= 'foo' +---- +select + ├── columns: k:1(int!null) s:3(string) + ├── stats: [rows=100] + ├── cost: 1100.00 + ├── scan a + │ ├── columns: a.k:1(int!null) a.s:3(string) + │ ├── stats: [rows=1000] + │ └── cost: 1000.00 + └── 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)] + └── const: 'foo' [type=string] diff --git a/pkg/sql/opt/xform/testdata/physprops/ordering b/pkg/sql/opt/xform/testdata/physprops/ordering index 26c55dc56189..4c1a4685ee29 100644 --- a/pkg/sql/opt/xform/testdata/physprops/ordering +++ b/pkg/sql/opt/xform/testdata/physprops/ordering @@ -71,7 +71,7 @@ sort # Pass through ordering to scan operator that can support it. opt -SELECT * FROM a WHERE x=1 ORDER BY x, y DESC +SELECT * FROM a WHERE x>y ORDER BY x, y DESC ---- select ├── columns: x:1(int!null) y:2(float!null) z:3(decimal) s:4(string!null) @@ -80,13 +80,13 @@ select │ ├── columns: a.x:1(int!null) a.y:2(float!null) a.z:3(decimal) a.s:4(string!null) │ └── ordering: +1,-2 └── filters [type=bool] - └── eq [type=bool] + └── gt [type=bool] ├── variable: a.x [type=int] - └── const: 1 [type=int] + └── variable: a.y [type=float] # Pass through ordering to scan operator that can't support it. opt -SELECT * FROM a WHERE x=1 ORDER BY z DESC +SELECT * FROM a WHERE x>y ORDER BY z DESC ---- sort ├── columns: x:1(int!null) y:2(float!null) z:3(decimal) s:4(string!null) @@ -96,9 +96,9 @@ sort ├── scan a │ └── columns: a.x:1(int!null) a.y:2(float!null) a.z:3(decimal) a.s:4(string!null) └── filters [type=bool] - └── eq [type=bool] + └── gt [type=bool] ├── variable: a.x [type=int] - └── const: 1 [type=int] + └── variable: a.y [type=float] # -------------------------------------------------- # Project operator (pass through). @@ -161,7 +161,7 @@ sort # Pass through ordering to scan operator that can support it. opt -SELECT y, x-1 FROM a WHERE x=1 ORDER BY x, y DESC +SELECT y, x-1 FROM a WHERE x>y ORDER BY x, y DESC ---- project ├── columns: y:2(float!null) column5:5(int) @@ -173,9 +173,9 @@ project │ │ ├── columns: a.x:1(int!null) a.y:2(float!null) │ │ └── ordering: +1,-2 │ └── filters [type=bool] - │ └── eq [type=bool] + │ └── gt [type=bool] │ ├── variable: a.x [type=int] - │ └── const: 1 [type=int] + │ └── variable: a.y [type=float] └── projections ├── variable: a.y [type=float] ├── minus [type=int] @@ -184,38 +184,38 @@ project └── variable: a.x [type=int] memo -SELECT y, x-1 FROM a WHERE x=1 ORDER BY x, y DESC +SELECT y, x-1 FROM a WHERE x>y ORDER BY x, y DESC ---- [12: "p:y:2,column5:5 o:+1,-2"] memo ├── 12: (project 11 9) - │ ├── "" [cost=1000.00] + │ ├── "" [cost=1100.00] │ │ └── best: (project 11 9) - │ └── "p:y:2,column5:5 o:+1,-2" [cost=1000.00] + │ └── "p:y:2,column5:5 o:+1,-2" [cost=1100.00] │ └── best: (project 11="o:+1,-2" 9) ├── 11: (select 10 5) - │ ├── "" [cost=1000.00] + │ ├── "" [cost=1100.00] │ │ └── best: (select 10 5) - │ └── "o:+1,-2" [cost=1000.00] + │ └── "o:+1,-2" [cost=1100.00] │ └── best: (select 10="o:+1,-2" 5) ├── 10: (scan a) │ ├── "" [cost=1000.00] │ │ └── best: (scan a) │ └── "o:+1,-2" [cost=1000.00] │ └── best: (scan a) - ├── 9: (projections 7 8 2) - ├── 8: (minus 2 3) - ├── 7: (variable a.y) + ├── 9: (projections 3 8 2) + ├── 8: (minus 2 7) + ├── 7: (const 1) ├── 6: (select 1 5) ├── 5: (filters 4) - ├── 4: (eq 2 3) - ├── 3: (const 1) + ├── 4: (gt 2 3) + ├── 3: (variable a.y) ├── 2: (variable a.x) └── 1: (scan a) # Pass through ordering to scan operator that can't support it. opt -SELECT y, z FROM a WHERE x=1 ORDER BY y +SELECT y, z FROM a WHERE x>y ORDER BY y ---- sort ├── columns: y:2(float!null) z:3(decimal) @@ -227,39 +227,38 @@ sort │ ├── scan a │ │ └── columns: a.x:1(int!null) a.y:2(float!null) a.z:3(decimal) │ └── filters [type=bool] - │ └── eq [type=bool] + │ └── gt [type=bool] │ ├── variable: a.x [type=int] - │ └── const: 1 [type=int] + │ └── variable: a.y [type=float] └── projections ├── variable: a.y [type=float] └── variable: a.z [type=decimal] memo -SELECT y, z FROM a WHERE x=1 ORDER BY y +SELECT y, z FROM a WHERE x>y ORDER BY y ---- -[12: "p:y:2,z:3 o:+2"] +[11: "p:y:2,z:3 o:+2"] memo - ├── 12: (project 11 9) - │ ├── "" [cost=1000.00] - │ │ └── best: (project 11 9) - │ └── "p:y:2,z:3 o:+2" [cost=1025.00] - │ └── best: (sort 12) - ├── 11: (select 10 5) - │ ├── "" [cost=1000.00] - │ │ └── best: (select 10 5) - │ └── "o:+2" [cost=1025.00] + ├── 11: (project 10 8) + │ ├── "" [cost=1100.00] + │ │ └── best: (project 10 8) + │ └── "p:y:2,z:3 o:+2" [cost=1125.00] │ └── best: (sort 11) - ├── 10: (scan a) + ├── 10: (select 9 5) + │ ├── "" [cost=1100.00] + │ │ └── best: (select 9 5) + │ └── "o:+2" [cost=1125.00] + │ └── best: (sort 10) + ├── 9: (scan a) │ ├── "" [cost=1000.00] │ │ └── best: (scan a) │ └── "o:+2" [cost=1250.00] - │ └── best: (sort 10) - ├── 9: (projections 7 8) - ├── 8: (variable a.z) - ├── 7: (variable a.y) + │ └── best: (sort 9) + ├── 8: (projections 3 7) + ├── 7: (variable a.z) ├── 6: (select 1 5) ├── 5: (filters 4) - ├── 4: (eq 2 3) - ├── 3: (const 1) + ├── 4: (gt 2 3) + ├── 3: (variable a.y) ├── 2: (variable a.x) └── 1: (scan a) diff --git a/pkg/sql/opt/xform/testdata/physprops/presentation b/pkg/sql/opt/xform/testdata/physprops/presentation index a6a3e17b36b8..1b002bda3e9f 100644 --- a/pkg/sql/opt/xform/testdata/physprops/presentation +++ b/pkg/sql/opt/xform/testdata/physprops/presentation @@ -26,7 +26,7 @@ scan a # Select operator. opt -SELECT a.y, a.x, a.y y2 FROM a WHERE x=1 +SELECT a.y, a.x, a.y y2 FROM a WHERE y=1 ---- select ├── columns: y:2(int) x:1(int!null) y2:2(int) @@ -34,7 +34,7 @@ select │ └── columns: a.x:1(int!null) a.y:2(int) └── filters [type=bool] └── eq [type=bool] - ├── variable: a.x [type=int] + ├── variable: a.y [type=int] └── const: 1 [type=int] # Project operator. diff --git a/pkg/sql/opt/xform/testdata/rules/scan b/pkg/sql/opt/xform/testdata/rules/scan index f64c71f5fdc4..c6b5453e4ddd 100644 --- a/pkg/sql/opt/xform/testdata/rules/scan +++ b/pkg/sql/opt/xform/testdata/rules/scan @@ -131,3 +131,28 @@ memo ├── 3: (variable a.k) ├── 2: (variable a.i) └── 1: (scan a) + +memo +SELECT i, k FROM a WHERE s >= 'foo' +---- +[12: "p:i:2,k:1"] +memo + ├── 13: (true) + ├── 12: (project 11 9) + │ └── "p:i:2,k:1" [cost=100.00] + │ └── best: (project 11 9) + ├── 11: (select 10 5) (scan a@s_idx,constrained) (scan a@si_idx,constrained) + │ └── "" [cost=100.00] + │ └── best: (scan a@s_idx,constrained) + ├── 10: (scan a) (scan a@s_idx) (scan a@si_idx) + │ └── "" [cost=1000.00] + │ └── best: (scan a) + ├── 9: (projections 7 8) + ├── 8: (variable a.k) + ├── 7: (variable a.i) + ├── 6: (select 1 5) + ├── 5: (filters 4) + ├── 4: (ge 2 3) + ├── 3: (const 'foo') + ├── 2: (variable a.s) + └── 1: (scan a) diff --git a/pkg/sql/opt/xform/testdata/rules/select b/pkg/sql/opt/xform/testdata/rules/select new file mode 100644 index 000000000000..347ad6418cdf --- /dev/null +++ b/pkg/sql/opt/xform/testdata/rules/select @@ -0,0 +1,130 @@ +exec-ddl +CREATE TABLE a +( + k INT PRIMARY KEY, + u INT, + v INT, + INDEX u(u), + UNIQUE INDEX v(v) +) +---- +TABLE a + ├── k int not null + ├── u int + ├── v int + ├── INDEX primary + │ └── k int not null + ├── INDEX u + │ ├── u int + │ └── k int not null + └── INDEX v + ├── v int + └── k int not null (storing) + +# -------------------------------------------------- +# ConstrainScan +# -------------------------------------------------- + +opt +SELECT k FROM a WHERE k = 1 +---- +scan a,constrained + ├── columns: k:1(int!null) + └── constraint: /1: [/1 - /1] + +memo +SELECT k FROM a WHERE k = 1 +---- +[9: "p:k:1"] +memo + ├── 10: (true) + ├── 9: (select 8 5) (scan a,constrained) + │ └── "p:k:1" [cost=100.00] + │ └── best: (scan a,constrained) + ├── 8: (scan a) (scan a@u) (scan a@v) + │ └── "" [cost=1000.00] + │ └── best: (scan a) + ├── 7: (projections 2) + ├── 6: (select 1 5) + ├── 5: (filters 4) + ├── 4: (eq 2 3) + ├── 3: (const 1) + ├── 2: (variable a.k) + └── 1: (scan a) + +opt +SELECT k FROM a WHERE v > 1 +---- +project + ├── columns: k:1(int!null) + ├── scan a@v,constrained + │ ├── columns: a.k:1(int!null) a.v:3(int) + │ └── constraint: /3: [/2 - ] + └── projections [outer=(1)] + └── variable: a.k [type=int, outer=(1)] + +memo +SELECT k FROM a WHERE v > 1 +---- +[11: "p:k:1"] +memo + ├── 12: (true) + ├── 11: (project 10 8) + │ └── "p:k:1" [cost=100.00] + │ └── best: (project 10 8) + ├── 10: (select 9 5) (scan a@v,constrained) + │ └── "" [cost=100.00] + │ └── best: (scan a@v,constrained) + ├── 9: (scan a) (scan a@v) + │ └── "" [cost=1000.00] + │ └── best: (scan a) + ├── 8: (projections 7) + ├── 7: (variable a.k) + ├── 6: (select 1 5) + ├── 5: (filters 4) + ├── 4: (gt 2 3) + ├── 3: (const 1) + ├── 2: (variable a.v) + └── 1: (scan a) + +opt +SELECT k FROM a WHERE u = 1 AND k = 5 +---- +project + ├── columns: k:1(int!null) + ├── scan a@u,constrained + │ ├── columns: a.k:1(int!null) a.u:2(int) + │ └── constraint: /2/1: [/1/5 - /1/5] + └── projections [outer=(1)] + └── variable: a.k [type=int, outer=(1)] + +memo +SELECT k FROM a WHERE u = 1 AND k = 5 +---- +[14: "p:k:1"] +memo + ├── 17: (scan a,constrained) + │ └── "" [cost=100.00] + │ └── best: (scan a,constrained) + ├── 16: (filters 4) + ├── 15: (true) + ├── 14: (project 13 11) + │ └── "p:k:1" [cost=100.00] + │ └── best: (project 13 11) + ├── 13: (select 12 9) (select 17 16) (scan a@u,constrained) + │ └── "" [cost=100.00] + │ └── best: (scan a@u,constrained) + ├── 12: (scan a) (scan a@u) + │ └── "" [cost=1000.00] + │ └── best: (scan a) + ├── 11: (projections 5) + ├── 10: (select 1 9) + ├── 9: (filters 4 7) + ├── 8: (and 4 7) + ├── 7: (eq 5 6) + ├── 6: (const 5) + ├── 5: (variable a.k) + ├── 4: (eq 2 3) + ├── 3: (const 1) + ├── 2: (variable a.u) + └── 1: (scan a) diff --git a/pkg/sql/opt_exec_engine.go b/pkg/sql/opt_exec_engine.go index 74653e52a1e5..c4f268f0fc1c 100644 --- a/pkg/sql/opt_exec_engine.go +++ b/pkg/sql/opt_exec_engine.go @@ -23,6 +23,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/internal/client" "github.com/cockroachdb/cockroach/pkg/sql/opt" + "github.com/cockroachdb/cockroach/pkg/sql/opt/constraint" "github.com/cockroachdb/cockroach/pkg/sql/opt/exec" "github.com/cockroachdb/cockroach/pkg/sql/sem/builtins" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" @@ -139,7 +140,10 @@ func (ee *execEngine) ConstructValues( // ConstructScan is part of the exec.Factory interface. func (ee *execEngine) ConstructScan( - table opt.Table, index opt.Index, cols exec.ColumnOrdinalSet, + table opt.Table, + index opt.Index, + cols exec.ColumnOrdinalSet, + indexConstraint *constraint.Constraint, ) (exec.Node, error) { tabDesc := table.(*optTable).desc indexDesc := index.(*optIndex).desc @@ -157,7 +161,7 @@ func (ee *execEngine) ConstructScan( scan.index = indexDesc scan.run.isSecondaryIndex = (indexDesc != &tabDesc.PrimaryIndex) var err error - scan.spans, err = unconstrainedSpans(tabDesc, indexDesc) + scan.spans, err = spansFromConstraint(tabDesc, indexDesc, indexConstraint) if err != nil { return nil, err }