Skip to content
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
1 change: 1 addition & 0 deletions docs/generated/sql/session_vars.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ SHOW application_name;
<tr><td><code>optimizer_prove_implication_with_virtual_computed_columns</code></td><td>Controls whether the optimizer should use virtual computed columns to prove partial index implication.</td><td><code>on</code></td><td>No</td><td>-</td></tr>
<tr><td><code>optimizer_push_limit_into_project_filtered_scan</code></td><td>Controls whether the optimizer should push limit expressions into projects of filtered scans.</td><td><code>on</code></td><td>No</td><td>-</td></tr>
<tr><td><code>optimizer_push_offset_into_index_join</code></td><td>Controls whether the optimizer should push offset expressions into index joins.</td><td><code>on</code></td><td>No</td><td>-</td></tr>
<tr><td><code>optimizer_span_limit</code></td><td>Sets the maximum number of constraint spans allowed in a scan during query optimization. 0 means no limit.</td><td><code>0</code></td><td>No</td><td>-</td></tr>
<tr><td><code>optimizer_use_conditional_hoist_fix</code></td><td>Prevents the optimizer from hoisting volatile expressions that are conditionally executed by CASE, COALESCE, or IFERR expressions.</td><td><code>on</code></td><td>No</td><td>-</td></tr>
<tr><td><code>optimizer_use_delete_range_fast_path</code></td><td>Controls whether the optimizer uses the fast path for DELETE operations using range deletions.</td><td><code>on</code></td><td>No</td><td>-</td></tr>
<tr><td><code>optimizer_use_exists_filter_hoist_rule</code></td><td>Controls whether the optimizer hoists filters out of EXISTS subqueries.</td><td><code>on</code></td><td>No</td><td>-</td></tr>
Expand Down
1 change: 1 addition & 0 deletions pkg/sql/logictest/testdata/logic_test/information_schema
Original file line number Diff line number Diff line change
Expand Up @@ -3914,6 +3914,7 @@ optimizer_prefer_bounded_cardinality on
optimizer_prove_implication_with_virtual_computed_columns on
optimizer_push_limit_into_project_filtered_scan on
optimizer_push_offset_into_index_join on
optimizer_span_limit 0
optimizer_use_conditional_hoist_fix on
optimizer_use_delete_range_fast_path on
optimizer_use_exists_filter_hoist_rule on
Expand Down
3 changes: 3 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/pg_catalog
Original file line number Diff line number Diff line change
Expand Up @@ -3155,6 +3155,7 @@ optimizer_prefer_bounded_cardinality on
optimizer_prove_implication_with_virtual_computed_columns on NULL NULL NULL string
optimizer_push_limit_into_project_filtered_scan on NULL NULL NULL string
optimizer_push_offset_into_index_join on NULL NULL NULL string
optimizer_span_limit 0 NULL NULL NULL string
optimizer_use_conditional_hoist_fix on NULL NULL NULL string
optimizer_use_delete_range_fast_path on NULL NULL NULL string
optimizer_use_exists_filter_hoist_rule on NULL NULL NULL string
Expand Down Expand Up @@ -3413,6 +3414,7 @@ optimizer_prefer_bounded_cardinality on
optimizer_prove_implication_with_virtual_computed_columns on NULL user NULL on on
optimizer_push_limit_into_project_filtered_scan on NULL user NULL on on
optimizer_push_offset_into_index_join on NULL user NULL on on
optimizer_span_limit 0 NULL user NULL 0 0
optimizer_use_conditional_hoist_fix on NULL user NULL on on
optimizer_use_delete_range_fast_path on NULL user NULL on on
optimizer_use_exists_filter_hoist_rule on NULL user NULL on on
Expand Down Expand Up @@ -3662,6 +3664,7 @@ optimizer_prefer_bounded_cardinality NULL NULL
optimizer_prove_implication_with_virtual_computed_columns NULL NULL NULL NULL NULL
optimizer_push_limit_into_project_filtered_scan NULL NULL NULL NULL NULL
optimizer_push_offset_into_index_join NULL NULL NULL NULL NULL
optimizer_span_limit NULL NULL NULL NULL NULL
optimizer_use_conditional_hoist_fix NULL NULL NULL NULL NULL
optimizer_use_delete_range_fast_path NULL NULL NULL NULL NULL
optimizer_use_exists_filter_hoist_rule NULL NULL NULL NULL NULL
Expand Down
56 changes: 55 additions & 1 deletion pkg/sql/logictest/testdata/logic_test/select_index
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

statement ok
CREATE TABLE t (
a INT PRIMARY KEY,
Expand Down Expand Up @@ -721,3 +720,58 @@ query I
SELECT DISTINCT _int FROM t88110 WHERE NOT _bool;
----
NULL

subtest optimizer_span_limit

# Correctness tests for the optimizer_span_limit session setting.

statement ok
CREATE TABLE t167620 (a INT, b INT, c INT, INDEX (a, b, c))

statement ok
INSERT INTO t167620 VALUES
(1, 2, 7), (1, 4, 9), (3, 6, 11),
(5, 2, 9), (3, 4, 7), (1, 6, 11),
(1, 2, 99), (1, 3, 7), (2, 2, 7)

# Verify that results are the same regardless of span limit.
query III rowsort
SELECT * FROM t167620@t167620_a_b_c_idx WHERE a IN (1, 3, 5) AND b IN (2, 4, 6) AND c IN (7, 9, 11)
----
1 2 7
1 4 9
1 6 11
3 4 7
3 6 11
5 2 9

statement ok
SET optimizer_span_limit = 10

query III rowsort
SELECT * FROM t167620@t167620_a_b_c_idx WHERE a IN (1, 3, 5) AND b IN (2, 4, 6) AND c IN (7, 9, 11)
----
1 2 7
1 4 9
1 6 11
3 4 7
3 6 11
5 2 9

statement ok
SET optimizer_span_limit = 8

query III rowsort
SELECT * FROM t167620@t167620_a_b_c_idx WHERE a IN (1, 3, 5) AND b IN (2, 4, 6) AND c IN (7, 9, 11)
----
1 2 7
1 4 9
1 6 11
3 4 7
3 6 11
5 2 9

statement ok
RESET optimizer_span_limit

subtest end
1 change: 1 addition & 0 deletions pkg/sql/logictest/testdata/logic_test/show_source
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ optimizer_prefer_bounded_cardinality on
optimizer_prove_implication_with_virtual_computed_columns on Controls whether the optimizer should use virtual computed columns to prove partial index implication.
optimizer_push_limit_into_project_filtered_scan on Controls whether the optimizer should push limit expressions into projects of filtered scans.
optimizer_push_offset_into_index_join on Controls whether the optimizer should push offset expressions into index joins.
optimizer_span_limit 0 Sets the maximum number of constraint spans allowed in a scan during query optimization. 0 means no limit.
optimizer_use_conditional_hoist_fix on Prevents the optimizer from hoisting volatile expressions that are conditionally executed by CASE, COALESCE, or IFERR expressions.
optimizer_use_delete_range_fast_path on Controls whether the optimizer uses the fast path for DELETE operations using range deletions.
optimizer_use_exists_filter_hoist_rule on Controls whether the optimizer hoists filters out of EXISTS subqueries.
Expand Down
97 changes: 97 additions & 0 deletions pkg/sql/opt/exec/execbuilder/testdata/select_index
Original file line number Diff line number Diff line change
Expand Up @@ -1925,3 +1925,100 @@ distribution: local
missing stats
table: t5@t5_a_idx
spans: [/'A.B.C' - /'A.B.C-')

subtest optimizer_span_limit

# Tests for the optimizer_span_limit session setting, which limits the number of
# index constraint spans generated during optimization. When the cross-product
# of spans across index columns would exceed the limit, the optimizer stops
# extending spans and adds remaining filters instead.

statement ok
CREATE TABLE t167620 (a INT, b INT, c INT, INDEX (a, b, c));
INSERT INTO t167620 VALUES
(1, 2, 7), (1, 4, 9), (3, 6, 11),
(5, 2, 9), (3, 4, 7), (1, 6, 11),
(1, 2, 99), (1, 3, 7), (2, 2, 7)

# No limit (default): 27 spans for the full 3x3x3 cross-product.
query T
EXPLAIN (OPT) SELECT * FROM t167620@t167620_a_b_c_idx WHERE a IN (1, 3, 5) AND b IN (2, 4, 6) AND c IN (7, 9, 11)
----
scan t167620@t167620_a_b_c_idx
├── constraint: /1/2/3/4
│ ├── [/1/2/7 - /1/2/7]
│ ├── [/1/2/9 - /1/2/9]
│ ├── [/1/2/11 - /1/2/11]
│ ├── [/1/4/7 - /1/4/7]
│ ├── [/1/4/9 - /1/4/9]
│ ├── [/1/4/11 - /1/4/11]
│ ├── [/1/6/7 - /1/6/7]
│ ├── [/1/6/9 - /1/6/9]
│ ├── [/1/6/11 - /1/6/11]
│ ├── [/3/2/7 - /3/2/7]
│ ├── [/3/2/9 - /3/2/9]
│ ├── [/3/2/11 - /3/2/11]
│ ├── [/3/4/7 - /3/4/7]
│ ├── [/3/4/9 - /3/4/9]
│ ├── [/3/4/11 - /3/4/11]
│ ├── [/3/6/7 - /3/6/7]
│ ├── [/3/6/9 - /3/6/9]
│ ├── [/3/6/11 - /3/6/11]
│ ├── [/5/2/7 - /5/2/7]
│ ├── [/5/2/9 - /5/2/9]
│ ├── [/5/2/11 - /5/2/11]
│ ├── [/5/4/7 - /5/4/7]
│ ├── [/5/4/9 - /5/4/9]
│ ├── [/5/4/11 - /5/4/11]
│ ├── [/5/6/7 - /5/6/7]
│ ├── [/5/6/9 - /5/6/9]
│ └── [/5/6/11 - /5/6/11]
└── flags: force-index=t167620_a_b_c_idx

# With limit=10, the final 9*3 cross-product is blocked. We get 9 (a, b) spans
# with a remaining filter on c.
statement ok
SET optimizer_span_limit = 10

query T
EXPLAIN (OPT) SELECT * FROM t167620@t167620_a_b_c_idx WHERE a IN (1, 3, 5) AND b IN (2, 4, 6) AND c IN (7, 9, 11)
----
select
├── scan t167620@t167620_a_b_c_idx
│ ├── constraint: /1/2/3/4
│ │ ├── [/1/2 - /1/2]
│ │ ├── [/1/4 - /1/4]
│ │ ├── [/1/6 - /1/6]
│ │ ├── [/3/2 - /3/2]
│ │ ├── [/3/4 - /3/4]
│ │ ├── [/3/6 - /3/6]
│ │ ├── [/5/2 - /5/2]
│ │ ├── [/5/4 - /5/4]
│ │ └── [/5/6 - /5/6]
│ └── flags: force-index=t167620_a_b_c_idx
└── filters
└── c IN (7, 9, 11)

# With limit=8, the 3*3 cross-product for b is also blocked. We get 3 (a) spans
# with remaining filters on b and c.
statement ok
SET optimizer_span_limit = 8

query T
EXPLAIN (OPT) SELECT * FROM t167620@t167620_a_b_c_idx WHERE a IN (1, 3, 5) AND b IN (2, 4, 6) AND c IN (7, 9, 11)
----
select
├── scan t167620@t167620_a_b_c_idx
│ ├── constraint: /1/2/3/4
│ │ ├── [/1 - /1]
│ │ ├── [/3 - /3]
│ │ └── [/5 - /5]
│ └── flags: force-index=t167620_a_b_c_idx
└── filters
├── b IN (2, 4, 6)
└── c IN (7, 9, 11)

statement ok
RESET optimizer_span_limit

subtest end
1 change: 1 addition & 0 deletions pkg/sql/opt/idxconstraint/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ go_library(
"//pkg/sql/sem/tree",
"//pkg/sql/types",
"//pkg/util",
"//pkg/util/log",
"//pkg/util/ltree",
"@com_github_cockroachdb_errors//:errors",
],
Expand Down
60 changes: 56 additions & 4 deletions pkg/sql/opt/idxconstraint/index_constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/types"
"github.com/cockroachdb/cockroach/pkg/util"
"github.com/cockroachdb/cockroach/pkg/util/log"
"github.com/cockroachdb/cockroach/pkg/util/ltree"
"github.com/cockroachdb/errors"
)
Expand Down Expand Up @@ -145,6 +146,16 @@ func (c *indexConstraintCtx) makeSpansForSingleColumn(
) (tight bool) {
if op == opt.InOp && memo.CanExtractConstTuple(val) {
tupVal := val.(*memo.TupleExpr)
// If the IN list has more elements than the span limit, return
// unconstrained.
if c.spanLimit > 0 && len(tupVal.Elems) > c.spanLimit {
log.VEventf(c.ctx, 2,
"not building spans for IN list: has %d elements, exceeds optimizer_span_limit of %d",
len(tupVal.Elems), c.spanLimit,
)
c.unconstrained(offset, out)
return false
}
keyCtx := &c.keyCtx[offset]
var spans constraint.Spans
spans.Alloc(len(tupVal.Elems))
Expand Down Expand Up @@ -608,6 +619,16 @@ func (c *indexConstraintCtx) makeSpansForTupleIn(
return false
}

// If the tuple has more elements than the span limit, return unconstrained.
if c.spanLimit > 0 && len(rhs.Elems) > c.spanLimit {
log.VEventf(c.ctx, 2,
"not building spans for tuple: has %d elements, exceeds optimizer_span_limit of %d",
len(rhs.Elems), c.spanLimit,
)
c.unconstrained(offset, out)
return false
}

// Create a span for each (tuple) value inside the right-hand side tuple.
keyCtx := &c.keyCtx[offset]
var spans constraint.Spans
Expand Down Expand Up @@ -891,17 +912,32 @@ func (c *indexConstraintCtx) makeSpansForAnd(
break
}

var tightFilters util.FastIntMap
tight := c.makeSpansForExpr(offset+delta, filters[0].Condition, &ofsC)
if tight {
tightDeltaMap.Set(0, delta)
tightFilters.Set(0, delta)
}
for j := 1; j < len(filters); j++ {
tight := c.makeSpansForExpr(offset+delta, filters[j].Condition, &exprConstraint)
if tight {
tightDeltaMap.Set(j, delta)
tightFilters.Set(j, delta)
}
ofsC.IntersectWith(c.ctx, c.evalCtx, &exprConstraint)
}
// If combining could produce more spans than the limit (e.g. via
// cross-product of exact-match prefix spans with suffix spans), skip the
// suffix extension.
if c.spanLimit > 0 && out.Spans.Count()*ofsC.Spans.Count() > c.spanLimit {
log.VEventf(c.ctx, 2,
"limiting index span tightness: combining %d spans with %d suffix spans could exceed optimizer_span_limit of %d",
out.Spans.Count(), ofsC.Spans.Count(), c.spanLimit,
)
break
}
// Record tightness after we've confirmed we'll actually combine.
tightFilters.ForEach(func(j, delta int) {
tightDeltaMap.Set(j, delta)
})
out.Combine(c.ctx, c.evalCtx, &ofsC, c.checkCancellation)
numIterations++
// In case we can't exit this loop, allow the cancel checker to cancel
Expand Down Expand Up @@ -1155,6 +1191,11 @@ type Instance struct {
// they need not generate remaining filters. This is e.g. used for check
// constraints that can help generate better spans but don't actually need to be
// enforced.
//
// spanLimit limits the number of spans that will be generated during constraint
// building. When a span-generating operation would produce more spans than this
// limit, the constraint builder returns a looser result instead (in some cases
// fully unconstrained). A value of 0 means no limit.
func (ic *Instance) Init(
ctx context.Context,
requiredFilters memo.FiltersExpr,
Expand All @@ -1167,6 +1208,7 @@ func (ic *Instance) Init(
evalCtx *eval.Context,
factory *norm.Factory,
ps partition.PrefixSorter,
spanLimit int,
checkCancellation func(),
) {
// This initialization pattern ensures that fields are not unwittingly
Expand All @@ -1183,7 +1225,7 @@ func (ic *Instance) Init(
ic.allFilters = requiredFilters[:len(requiredFilters):len(requiredFilters)]
ic.allFilters = append(ic.allFilters, optionalFilters...)
}
ic.indexConstraintCtx.init(ctx, columns, notNullCols, computedCols, colsInComputedColsExpressions, evalCtx, factory, checkCancellation)
ic.indexConstraintCtx.init(ctx, columns, notNullCols, computedCols, colsInComputedColsExpressions, evalCtx, factory, spanLimit, checkCancellation)
ic.tight = ic.makeSpansForExpr(0 /* offset */, &ic.allFilters, &ic.constraint)

// Note: If consolidate is true, we only consolidate spans at the
Expand Down Expand Up @@ -1295,6 +1337,10 @@ type indexConstraintCtx struct {
ctx context.Context
evalCtx *eval.Context

// spanLimit limits the number of spans that will be generated during
// constraint building. A value of 0 means no limit.
spanLimit int

// We pre-initialize the KeyContext for each suffix of the index columns.
keyCtx []constraint.KeyContext

Expand All @@ -1311,6 +1357,7 @@ func (c *indexConstraintCtx) init(
colsInComputedColsExpressions opt.ColSet,
evalCtx *eval.Context,
factory *norm.Factory,
spanLimit int,
checkCancellation func(),
) {
var keyCols, computedColSet opt.ColSet
Expand All @@ -1332,6 +1379,7 @@ func (c *indexConstraintCtx) init(
colsInComputedColsExpressions: colsInComputedColsExpressions,
ctx: ctx,
evalCtx: evalCtx,
spanLimit: spanLimit,
factory: factory,
keyCtx: make([]constraint.KeyContext, len(columns)),
checkCancellation: checkCancellation,
Expand Down Expand Up @@ -1422,6 +1470,8 @@ func IndexPrefixCols(
// spans have the same start and end keys for all prefix columns. This is
// required for building spans for scanning multi-column inverted/vector indexes
// (see span.Builder.SpansFromInvertedSpans).
//
// TODO(michae2): Accept and use optimizer_span_limit.
func ConstrainIndexPrefixCols(
ctx context.Context,
evalCtx *eval.Context,
Expand Down Expand Up @@ -1461,7 +1511,9 @@ func ConstrainIndexPrefixCols(
columns, notNullCols, tabMeta.ComputedCols,
tabMeta.ColsInComputedColsExpressions,
false, /* consolidate */
evalCtx, factory, ps, checkCancellation,
evalCtx, factory, ps,
0, /* spanLimit */
checkCancellation,
)
var c constraint.Constraint
ic.UnconsolidatedConstraint(&c)
Expand Down
Loading
Loading