Skip to content

Commit

Permalink
opt: generate spans for IN filters
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Justin Jaffray committed Jun 13, 2018
1 parent 529f626 commit 8b44756
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 58 deletions.
25 changes: 8 additions & 17 deletions pkg/sql/opt/exec/execbuilder/testdata/select_index
Original file line number Diff line number Diff line change
Expand Up @@ -471,12 +471,11 @@ render · · (b) ·
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 +884,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
71 changes: 70 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,74 @@ 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)

tight = true
var sp constraint.Span
cols := 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 {
cols = append(cols, opt.MakeOrderingColumn(colID, false /* descending */))
colIdxsInLHS = append(colIdxsInLHS, i)
} else {
// If one of the LHS entries is not a bare column then our constraints
// are no longer tight.
tight = false
}
}

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

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

hasNull := false
for j := range colIdxsInLHS {
elem := val.Child(colIdxsInLHS[j])
if !elem.IsConstValue() {
return unconstrained, false
}
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 {
continue
}

key := constraint.MakeCompositeKey(vals...)
sp.Init(key, constraint.IncludeBoundary, key, constraint.IncludeBoundary)
c = c.Union(cb.evalCtx, constraint.SingleSpanConstraint(&keyCtx, &sp))
}

return c, tight
}

func (cb *constraintsBuilder) buildConstraintForTupleInequality(
ev ExprView,
) (_ *constraint.Set, tight bool) {
Expand Down Expand Up @@ -301,7 +369,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) v:3(int)
├── stats: [rows=333]
├── keys: (1)
├── scan c
│ ├── columns: k:1(int!null) u:2(int) v:3(int)
│ ├── stats: [rows=1000]
│ └── keys: (1)
└── filters [type=bool, outer=(1-3)]
└── in [type=bool, outer=(1-3)]
├── 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 8b44756

Please sign in to comment.