Skip to content

Commit

Permalink
opt: Inline constant values
Browse files Browse the repository at this point in the history
Inline constants in expressions like:

  SELECT x, x+1 FROM (VALUES (1)) AS t(x) ;

with the new inlining rules, this becomes:

  VALUES (1, 2)

The new inlining rules are useful for mutation expressions (e.g. UPDATE),
which can nest multiple Project and Values expressions that often use
constant values. For example:

  CREATE TABLE ab (a INT PRIMARY KEY, b INT AS (a + 1) STORED);
  UPDATE ab SET a=1

This now gets mapped by the optimizer to this internal equivalent:

  UPDATE ab SET a=1, b=2

Release note: None
  • Loading branch information
andy-kimball committed Jan 1, 2019
1 parent 3b0ea90 commit 4e55f39
Show file tree
Hide file tree
Showing 9 changed files with 543 additions and 89 deletions.
8 changes: 3 additions & 5 deletions pkg/sql/opt/exec/execbuilder/testdata/explain
Original file line number Diff line number Diff line change
Expand Up @@ -601,11 +601,9 @@ values · · (a int) ·
query TTTTT
EXPLAIN (TYPES) SELECT x[1] FROM (SELECT ARRAY[1,2,3] AS x)
----
render · · (x int) ·
│ render 0 ((x)[int[]][(1)[int]])[int] · ·
└── values · · (x int[]) ·
· size 1 column, 1 row · ·
· row 0, expr 0 (ARRAY[(1)[int],(2)[int],(3)[int]])[int[]] · ·
values · · (x int) ·
· size 1 column, 1 row · ·
· row 0, expr 0 (1)[int] · ·

query T
EXPLAIN (OPT) SELECT 1 AS r
Expand Down
29 changes: 18 additions & 11 deletions pkg/sql/opt/exec/execbuilder/testdata/orderby
Original file line number Diff line number Diff line change
Expand Up @@ -730,17 +730,24 @@ render · · (k) ·
#

query TTTTT
EXPLAIN (VERBOSE) SELECT k FROM kv JOIN (VALUES (1,2)) AS z(a,b) ON kv.k = z.a ORDER BY INDEX kv@foo
----
render · · (k) ·
│ render 0 k · ·
└── lookup-join · · (column1, k) ·
│ type inner · ·
├── values · · (column1) ·
│ size 1 column, 1 row · ·
│ row 0, expr 0 1 · ·
└── scan · · (k) ·
· table kv@primary · ·
EXPLAIN (VERBOSE)
SELECT k FROM kv JOIN (VALUES (1,2), (3,4)) AS z(a,b) ON kv.k = z.a ORDER BY INDEX kv@foo
----
render · · (k) ·
│ render 0 k · ·
└── sort · · (k, v) -v,+k
│ order -v,+k · ·
└── render · · (k, v) ·
│ render 0 k · ·
│ render 1 v · ·
└── lookup-join · · (column1, k, v) ·
│ type inner · ·
├── values · · (column1) ·
│ size 1 column, 2 rows · ·
│ row 0, expr 0 1 · ·
│ row 1, expr 0 3 · ·
└── scan · · (k, v) ·
· table kv@primary · ·

query TTTTT
EXPLAIN (VERBOSE) SELECT k FROM kv a NATURAL JOIN kv ORDER BY INDEX kv@foo
Expand Down
14 changes: 6 additions & 8 deletions pkg/sql/opt/memo/testdata/logprops/join
Original file line number Diff line number Diff line change
Expand Up @@ -838,21 +838,19 @@ semi-join-apply

# Calculate anti-join cardinality when left side has non-zero cardinality.
opt
SELECT * FROM (SELECT * FROM (VALUES (1))) WHERE NOT EXISTS(SELECT * FROM uv WHERE u=column1)
SELECT * FROM (SELECT * FROM (VALUES (1), (2))) WHERE NOT EXISTS(SELECT * FROM uv WHERE u=column1)
----
anti-join
├── columns: column1:1(int)
├── cardinality: [0 - 1]
├── key: ()
├── fd: ()-->(1)
├── cardinality: [0 - 2]
├── values
│ ├── columns: column1:1(int)
│ ├── cardinality: [1 - 1]
│ ├── key: ()
│ ├── fd: ()-->(1)
│ ├── cardinality: [2 - 2]
│ ├── prune: (1)
│ ├── tuple [type=tuple{int}]
│ │ └── const: 1 [type=int]
│ └── tuple [type=tuple{int}]
│ └── const: 1 [type=int]
│ └── const: 2 [type=int]
├── scan uv
│ ├── columns: u:2(int) v:3(int!null)
│ └── prune: (2,3)
Expand Down
11 changes: 11 additions & 0 deletions pkg/sql/opt/norm/custom_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,11 @@ func (c *CustomFuncs) CanHaveZeroRows(input memo.RelExpr) bool {
return input.Relational().Cardinality.CanBeZero()
}

// ColsAreEmpty returns true if the column set is empty.
func (c *CustomFuncs) ColsAreEmpty(cols opt.ColSet) bool {
return cols.Empty()
}

// ColsAreSubset returns true if the left columns are a subset of the right
// columns.
func (c *CustomFuncs) ColsAreSubset(left, right opt.ColSet) bool {
Expand All @@ -288,6 +293,12 @@ func (c *CustomFuncs) ColsAreEqual(left, right opt.ColSet) bool {
return left.Equals(right)
}

// ColsIntersect returns true if at least one column appears in both the left
// and right sets.
func (c *CustomFuncs) ColsIntersect(left, right opt.ColSet) bool {
return left.Intersects(right)
}

// UnionCols returns the union of the left and right column sets.
func (c *CustomFuncs) UnionCols(left, right opt.ColSet) opt.ColSet {
return left.Union(right)
Expand Down
94 changes: 94 additions & 0 deletions pkg/sql/opt/norm/inline.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,100 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/opt/memo"
)

// FindInlinableConstants returns the set of input columns that are synthesized
// constant value expressions: ConstOp, TrueOp, FalseOp, or NullOp. Constant
// value expressions can often be inlined into referencing expressions. Only
// Project and Values operators synthesize constant value expressions.
func (c *CustomFuncs) FindInlinableConstants(input memo.RelExpr) opt.ColSet {
var cols opt.ColSet
if project, ok := input.(*memo.ProjectExpr); ok {
for i := range project.Projections {
item := &project.Projections[i]
if opt.IsConstValueOp(item.Element) {
cols.Add(int(item.Col))
}
}
} else if values, ok := input.(*memo.ValuesExpr); ok && len(values.Rows) == 1 {
tup := values.Rows[0].(*memo.TupleExpr)
for i, scalar := range tup.Elems {
if opt.IsConstValueOp(scalar) {
cols.Add(int(values.Cols[i]))
}
}
}
return cols
}

// InlineProjectionConstants recursively searches each projection expression and
// replaces any references to input columns that are constant. It returns a new
// Projections list containing the replaced expressions.
func (c *CustomFuncs) InlineProjectionConstants(
projections memo.ProjectionsExpr, input memo.RelExpr, constCols opt.ColSet,
) memo.ProjectionsExpr {
newProjections := make(memo.ProjectionsExpr, len(projections))
for i := range projections {
item := &projections[i]
newProjections[i].Col = item.Col
newProjections[i].Element = c.inlineConstants(item.Element, input, constCols).(opt.ScalarExpr)
}
return newProjections
}

// InlineFilterConstants recursively searches each filter expression and
// replaces any references to input columns that are constant. It returns a new
// Filters list containing the replaced expressions.
func (c *CustomFuncs) InlineFilterConstants(
filters memo.FiltersExpr, input memo.RelExpr, constCols opt.ColSet,
) memo.FiltersExpr {
newFilters := make(memo.FiltersExpr, len(filters))
for i := range filters {
item := &filters[i]
newFilters[i].Condition = c.inlineConstants(item.Condition, input, constCols).(opt.ScalarExpr)
}
return newFilters
}

// inlineConstants recursively searches the given expression and replaces any
// references to input columns that are constant. It returns the replaced
// expression.
func (c *CustomFuncs) inlineConstants(
e opt.Expr, input memo.RelExpr, constCols opt.ColSet,
) opt.Expr {
var replace ReconstructFunc
replace = func(e opt.Expr) opt.Expr {
switch t := e.(type) {
case *memo.VariableExpr:
if constCols.Contains(int(t.Col)) {
return c.extractColumn(input, t.Col)
}
return t
}
return c.f.Reconstruct(e, replace)
}
return replace(e)
}

// extractColumn searches a Project or Values input expression for the column
// having the given id. It returns the expression for that column.
func (c *CustomFuncs) extractColumn(input memo.RelExpr, col opt.ColumnID) opt.ScalarExpr {
if project, ok := input.(*memo.ProjectExpr); ok {
for i := range project.Projections {
item := &project.Projections[i]
if item.Col == col {
return item.Element
}
}
} else if values, ok := input.(*memo.ValuesExpr); ok && len(values.Rows) == 1 {
tup := values.Rows[0].(*memo.TupleExpr)
for i, scalar := range tup.Elems {
if values.Cols[i] == col {
return scalar
}
}
}
panic("could not find column to extract")
}

// HasDuplicateRefs returns true if the target projection expressions or
// passthrough columns reference any outer column more than one time, or if the
// projection expressions contain a correlated subquery. For example:
Expand Down
77 changes: 73 additions & 4 deletions pkg/sql/opt/norm/rules/inline.opt
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,82 @@
#
# SELECT (x+1)+1 FROM a
#
# Inlining variables can result in the complete elimination of Project
# operators, or at least in the ability to more freely reorder them within the
# larger relational expression tree. This allows pushing filters further down
# the tree, as well as with pulling them up in the decorrelation case.
# Inlining variables can result in the simplification or even complete
# elimination of operators, or at least in the ability to more freely reorder
# them within the larger relational expression tree. This allows pushing filters
# further down the tree, as well as with pulling them up in the decorrelation
# case.
# =============================================================================


# InlineProjectConstants finds variable references in Projections expressions
# that refer to constant input values, and then inlines those constant values
# in place of the corresponding variable references. This sometimes allows
# further simplifications such as constant folding or Project merging.
[InlineProjectConstants, Normalize]
(Project
$input:* & ^(ColsAreEmpty $constCols:(FindInlinableConstants $input))
$projections:[ ... $item:* & (ColsIntersect (OuterCols $item) $constCols) ... ]
$passthrough:*
)
=>
(Project
$input
(InlineProjectionConstants $projections $input $constCols)
$passthrough
)

# InlineSelectConstants finds variable references in Filters expressions that
# refer to constant input values, and then inlines those constant values in
# place of the corresponding variable references. This sometimes allows further
# simplifications such as constant folding or generation of constrained scans.
[InlineSelectConstants, Normalize]
(Select
$input:* & ^(ColsAreEmpty $constCols:(FindInlinableConstants $input))
$filters:[ ... $item:* & (ColsIntersect (OuterCols $item) $constCols) ... ]
)
=>
(Select
$input
(InlineFilterConstants $filters $input $constCols)
)

# InlineJoinConstantsLeft finds variable references in a join condition that
# refers to constant values projected by the left input. It then inlines those
# constant values in place of the corresponding variable references. This
# sometimes allows further simplifications such as constant folding or filter
# pushdown.
[InlineJoinConstantsLeft, Normalize]
(Join
$left:* & ^(ColsAreEmpty $constCols:(FindInlinableConstants $left))
$right:*
$on:[ ... $item:* & (ColsIntersect (OuterCols $item) $constCols) ... ]
)
=>
((OpName)
$left
$right
(InlineFilterConstants $on $left $constCols)
)

# InlineJoinConstantsRight finds variable references in a join condition that
# refers to constant values projected by the right input. It then inlines those
# constant values in place of the corresponding variable references. This
# sometimes allows further simplifications such as constant folding or filter
# pushdown.
[InlineJoinConstantsRight, Normalize]
(Join
$left:*
$right:* & ^(ColsAreEmpty $constCols:(FindInlinableConstants $right))
$on:[ ... $item:* & (ColsIntersect (OuterCols $item) $constCols) ... ]
)
=>
((OpName)
$left
$right
(InlineFilterConstants $on $right $constCols)
)

# PushSelectIntoInlinableProject pushes the Select operator into a Project, even
# though the filter references it. This is made possible by inlining the
# references to projected columns so that the Select becomes independent of the
Expand Down
Loading

0 comments on commit 4e55f39

Please sign in to comment.