Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

opt: generate spans for IN filters #26708

Merged
merged 1 commit into from
Jun 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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