Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
26708: opt: generate spans for IN filters r=justinj a=justinj

This code is largely based off of makeSpansForTupleIn, though I wasn't
able to reuse much of it since the context is slightly different.

Also fix up a CREATE STATISTICS statement and modify a TPCC query.

Release note: None

Co-authored-by: Justin Jaffray <[email protected]>
  • Loading branch information
craig[bot] and Justin Jaffray committed Jun 14, 2018
2 parents a49f75b + 55ac430 commit b3f1d17
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 59 deletions.
26 changes: 8 additions & 18 deletions pkg/sql/opt/exec/execbuilder/testdata/select_index
Original file line number Diff line number Diff line change
Expand Up @@ -467,16 +467,14 @@ render · · (b) ·
· table abcd@abcd · ·
· spans /1/4-/1/5 · ·

# TODO(radu): in this case, we're not preferring abcd, unlike 2.0
query TTTTT
EXPLAIN (VERBOSE) SELECT b FROM abcd WHERE (a, b) IN ((1, 4), (2, 9))
----
render · · (b) ·
│ render 0 b · ·
└── scan · · (a, b) ·
· table abcd@adb · ·
· spans /1-/3 · ·
· filter (a, b) IN ((1, 4), (2, 9)) · ·
render · · (b) ·
│ render 0 b · ·
└── scan · · (a, b) ·
· table abcd@abcd · ·
· spans /1/4-/1/5 /2/9-/2/10 · ·

statement ok
CREATE TABLE ab (
Expand Down Expand Up @@ -885,17 +883,9 @@ scan · · (x, y) ·
query TTTTT
EXPLAIN (VERBOSE) SELECT * FROM xy WHERE (x, y) IN ((NULL, NULL), (1, NULL), (NULL, 1), (1, 1), (1, 2))
----
render · · (x, y) ·
│ render 0 x · ·
│ render 1 y · ·
└── filter · · (x, y, rowid[hidden]) ·
│ filter (x, y) IN ((NULL, NULL), (1, NULL), (NULL, 1), (1, 1), (1, 2)) · ·
└── index-join · · (x, y, rowid[hidden]) ·
├── scan · · (y, rowid[hidden]) ·
│ table xy@xy_y_idx · ·
│ spans /1-/3 · ·
└── scan · · (x, y, rowid[hidden]) ·
· table xy@primary · ·
scan · · (x, y) ·
· table xy@xy_idx · ·
· spans /1/1-/1/3 · ·

# ------------------------------------------------------------------------------
# Non-covering index
Expand Down
104 changes: 103 additions & 1 deletion pkg/sql/opt/memo/constraint_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,107 @@ func (cb *constraintsBuilder) buildSingleColumnConstraintConst(
return unconstrained, false
}

// buildConstraintForTupleIn handles the case where we have a tuple IN another
// tuple, for instance:
//
// (a, b, c) IN ((1, 2, 3), (4, 5, 6))
//
// This function is a less powerful version of makeSpansForTupleIn, since it
// does not operate on a particular index. The <tight> return value indicates
// if the spans are exactly equivalent to the expression (and not weaker).
// Assumes that ev is an InOp and both children are TupleOps.
func (cb *constraintsBuilder) buildConstraintForTupleIn(
ev ExprView,
) (_ *constraint.Set, tight bool) {
lhs, rhs := ev.Child(0), ev.Child(1)

// We can only constrain here if every element of rhs is a TupleOp.
for i, n := 0, rhs.ChildCount(); i < n; i++ {
val := rhs.Child(i)
if val.Operator() != opt.TupleOp {
return unconstrained, false
}
}

var sp constraint.Span
constrainedCols := make([]opt.OrderingColumn, 0, lhs.ChildCount())
colIdxsInLHS := make([]int, 0, lhs.ChildCount())
for i, n := 0, lhs.ChildCount(); i < n; i++ {
if colID, ok := lhs.Child(i).Private().(opt.ColumnID); ok {
// We can't constrain a column if it's compared to anything besides a constant.
allConstant := true
for j, m := 0, rhs.ChildCount(); j < m; j++ {
val := rhs.Child(j)

if val.Operator() != opt.TupleOp {
return unconstrained, false
}

if !val.Child(i).IsConstValue() {
allConstant = false
break
}
}

if allConstant {
constrainedCols = append(
constrainedCols,
opt.MakeOrderingColumn(colID, false /* descending */),
)
colIdxsInLHS = append(colIdxsInLHS, i)
}
}
}

// If any of the LHS entries are not constrained then our constraints are not
// tight.
tight = (len(constrainedCols) == lhs.ChildCount())

keyCtx := constraint.KeyContext{EvalCtx: cb.evalCtx}
keyCtx.Columns.Init(constrainedCols)
var spans constraint.Spans
spans.Alloc(len(constrainedCols))
vals := make(tree.Datums, len(colIdxsInLHS))
for i, n := 0, rhs.ChildCount(); i < n; i++ {
val := rhs.Child(i)

hasNull := false
for j := range colIdxsInLHS {
elem := val.Child(colIdxsInLHS[j])
datum := ExtractConstDatum(elem)
if datum == tree.DNull {
hasNull = true
break
}
vals[j] = datum
}

// Nothing can match a tuple containing a NULL, so it introduces no
// constraints.
if hasNull {
// TODO(justin): consider redefining "tight" so that this is included in
// it. The spans are not "exactly equivalent" in the presence of NULLs,
// because of examples like the following:
// (x, y) IN ((1, 2), (NULL, 4))
// is not the same as
// (x, y) IN ((1, 2)),
// because the former is NULL (not false) on (3,4).
tight = false
continue
}

key := constraint.MakeCompositeKey(vals...)
sp.Init(key, constraint.IncludeBoundary, key, constraint.IncludeBoundary)
spans.Append(&sp)
}

spans.SortAndMerge(&keyCtx)

var c constraint.Constraint
c.Init(&keyCtx, &spans)
return constraint.SingleConstraint(&c), tight
}

func (cb *constraintsBuilder) buildConstraintForTupleInequality(
ev ExprView,
) (_ *constraint.Set, tight bool) {
Expand Down Expand Up @@ -301,7 +402,8 @@ func (cb *constraintsBuilder) buildConstraints(ev ExprView) (_ *constraint.Set,
// Tuple inequality.
return cb.buildConstraintForTupleInequality(ev)

//TODO(radu): case opt.InOp:
case opt.InOp:
return cb.buildConstraintForTupleIn(ev)
}
}
if child0.Operator() == opt.VariableOp {
Expand Down
129 changes: 129 additions & 0 deletions pkg/sql/opt/memo/testdata/logprops/constraints
Original file line number Diff line number Diff line change
Expand Up @@ -629,3 +629,132 @@ select
└── tuple [type=tuple{int, int}]
├── const: 1 [type=int]
└── const: 2 [type=int]

exec-ddl
CREATE TABLE c
(
k INT PRIMARY KEY,
u INT,
v INT,
INDEX v (v, u)
)
----
TABLE c
├── k int not null
├── u int
├── v int
├── INDEX primary
│ └── k int not null
└── INDEX v
├── v int
├── u int
└── k int not null

opt
SELECT * FROM c WHERE (v, u) IN ((1, 2), (3, 50), (5, 100))
----
scan c@v
├── columns: k:1(int!null) u:2(int) v:3(int!null)
├── constraint: /3/2/1: [/1/2 - /1/2] [/3/50 - /3/50] [/5/100 - /5/100]
├── stats: [rows=4, distinct(3)=3]
└── keys: (1)

# A tuple with NULL in it can't match anything, so it should be excluded from the constraints.
opt
SELECT * FROM c WHERE (v, u) IN ((1, 2), (3, 50), (5, NULL))
----
scan c@v
├── columns: k:1(int!null) u:2(int) v:3(int!null)
├── constraint: /3/2/1: [/1/2 - /1/2] [/3/50 - /3/50]
├── stats: [rows=2, distinct(3)=2]
└── keys: (1)

# TODO(justin): ideally we would be normalizing away the 2 on the LHS here to
# get v = 1 and tight spans.
opt
SELECT * FROM c WHERE (v, 2) IN ((1, 2), (3, 50), (5, 100))
----
select
├── columns: k:1(int!null) u:2(int) v:3(int!null)
├── stats: [rows=4, distinct(3)=3]
├── keys: (1)
├── scan c@v
│ ├── columns: k:1(int!null) u:2(int) v:3(int)
│ ├── constraint: /3/2/1: [/1 - /1] [/3 - /3] [/5 - /5]
│ ├── stats: [rows=4, distinct(3)=3]
│ └── keys: (1)
└── filters [type=bool, outer=(3), constraints=(/3: [/1 - /1] [/3 - /3] [/5 - /5])]
└── in [type=bool, outer=(3), constraints=(/3: [/1 - /1] [/3 - /3] [/5 - /5])]
├── tuple [type=tuple{int, int}, outer=(3)]
│ ├── variable: c.v [type=int, outer=(3)]
│ └── const: 2 [type=int]
└── tuple [type=tuple{tuple{int, int}, tuple{int, int}, tuple{int, int}}]
├── tuple [type=tuple{int, int}]
│ ├── const: 1 [type=int]
│ └── const: 2 [type=int]
├── tuple [type=tuple{int, int}]
│ ├── const: 3 [type=int]
│ └── const: 50 [type=int]
└── tuple [type=tuple{int, int}]
├── const: 5 [type=int]
└── const: 100 [type=int]

# TODO(justin): in a perfect world we would be able to somehow transform this
# filter to (v, u) IN ((1, 1), (3, 47), (5, 95)) in order to get tight spans.
# This could be achieved via row-reduction.
opt
SELECT * FROM c WHERE (v, u + v) IN ((1, 2), (3, 50), (5, 100))
----
select
├── columns: k:1(int!null) u:2(int) v:3(int!null)
├── stats: [rows=4, distinct(3)=3]
├── keys: (1)
├── scan c@v
│ ├── columns: k:1(int!null) u:2(int) v:3(int)
│ ├── constraint: /3/2/1: [/1 - /1] [/3 - /3] [/5 - /5]
│ ├── stats: [rows=4, distinct(3)=3]
│ └── keys: (1)
└── filters [type=bool, outer=(2,3), constraints=(/3: [/1 - /1] [/3 - /3] [/5 - /5])]
└── in [type=bool, outer=(2,3), constraints=(/3: [/1 - /1] [/3 - /3] [/5 - /5])]
├── tuple [type=tuple{int, int}, outer=(2,3)]
│ ├── variable: c.v [type=int, outer=(3)]
│ └── plus [type=int, outer=(2,3)]
│ ├── variable: c.u [type=int, outer=(2)]
│ └── variable: c.v [type=int, outer=(3)]
└── tuple [type=tuple{tuple{int, int}, tuple{int, int}, tuple{int, int}}]
├── tuple [type=tuple{int, int}]
│ ├── const: 1 [type=int]
│ └── const: 2 [type=int]
├── tuple [type=tuple{int, int}]
│ ├── const: 3 [type=int]
│ └── const: 50 [type=int]
└── tuple [type=tuple{int, int}]
├── const: 5 [type=int]
└── const: 100 [type=int]

opt
SELECT * FROM c WHERE (v, u) IN ((1, 2), (k, 50), (5, 100))
----
select
├── columns: k:1(int!null) u:2(int!null) v:3(int)
├── stats: [rows=4, distinct(2)=3]
├── keys: (1)
├── scan c
│ ├── columns: k:1(int!null) u:2(int) v:3(int)
│ ├── stats: [rows=1000, distinct(2)=700]
│ └── keys: (1)
└── filters [type=bool, outer=(1-3), constraints=(/2: [/2 - /2] [/50 - /50] [/100 - /100])]
└── in [type=bool, outer=(1-3), constraints=(/2: [/2 - /2] [/50 - /50] [/100 - /100])]
├── tuple [type=tuple{int, int}, outer=(2,3)]
│ ├── variable: c.v [type=int, outer=(3)]
│ └── variable: c.u [type=int, outer=(2)]
└── tuple [type=tuple{tuple{int, int}, tuple{int, int}, tuple{int, int}}, outer=(1)]
├── tuple [type=tuple{int, int}]
│ ├── const: 1 [type=int]
│ └── const: 2 [type=int]
├── tuple [type=tuple{int, int}, outer=(1)]
│ ├── variable: c.k [type=int, outer=(1)]
│ └── const: 50 [type=int]
└── tuple [type=tuple{int, int}]
├── const: 5 [type=int]
└── const: 100 [type=int]
8 changes: 4 additions & 4 deletions pkg/sql/opt/optbuilder/testdata/where
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,9 @@ build
SELECT * FROM ab WHERE (a, b) IN ((1, 10), (3, 30), (4, 40))
----
project
├── columns: a:1(int) b:2(int)
├── columns: a:1(int!null) b:2(int)
└── select
├── columns: a:1(int) b:2(int) rowid:3(int!null)
├── columns: a:1(int!null) b:2(int) rowid:3(int!null)
├── scan ab
│ └── columns: a:1(int) b:2(int) rowid:3(int!null)
└── filters [type=bool]
Expand All @@ -250,9 +250,9 @@ build
SELECT * FROM ab WHERE (a, b) IN ((1, 10), (4, NULL), (NULL, 50))
----
project
├── columns: a:1(int) b:2(int)
├── columns: a:1(int!null) b:2(int)
└── select
├── columns: a:1(int) b:2(int) rowid:3(int!null)
├── columns: a:1(int!null) b:2(int) rowid:3(int!null)
├── scan ab
│ └── columns: a:1(int) b:2(int) rowid:3(int!null)
└── filters [type=bool]
Expand Down
Loading

0 comments on commit b3f1d17

Please sign in to comment.