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 }