diff --git a/pkg/sql/crdb_internal_test.go b/pkg/sql/crdb_internal_test.go index 4b1e33b1dff0..215fb9dd4c49 100644 --- a/pkg/sql/crdb_internal_test.go +++ b/pkg/sql/crdb_internal_test.go @@ -59,7 +59,6 @@ import ( "github.com/cockroachdb/cockroach/pkg/storage/enginepb" "github.com/cockroachdb/cockroach/pkg/testutils" "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" - "github.com/cockroachdb/cockroach/pkg/testutils/skip" "github.com/cockroachdb/cockroach/pkg/testutils/sqlutils" "github.com/cockroachdb/cockroach/pkg/testutils/testcluster" "github.com/cockroachdb/cockroach/pkg/upgrade/upgradebase" @@ -904,7 +903,6 @@ func TestIsAtLeastVersion(t *testing.T) { func TestTxnContentionEventsTable(t *testing.T) { defer leaktest.AfterTest(t)() defer log.Scope(t).Close(t) - skip.WithIssue(t, 102660) // Start the cluster. (One node is sufficient; the outliers system // is currently in-memory only.) diff --git a/pkg/sql/opt/indexrec/candidate.go b/pkg/sql/opt/indexrec/candidate.go index 6f57f8cb17b2..980f12e3a8f4 100644 --- a/pkg/sql/opt/indexrec/candidate.go +++ b/pkg/sql/opt/indexrec/candidate.go @@ -29,7 +29,7 @@ import ( // indexes by table. For Order By, the index column ordering and column // directions are the same as how it is in the Order By. // 2. Add a single-column index on any Range expression, comparison -// expression (=, <, >, <=, >=), and IS expression. +// expression (=, !=, <, >, <=, >=), IS and IS NOT expression. // 3. Add a single-column index on any column that appears in a JOIN predicate. // 4. If there exist multiple columns from the same table in a JOIN predicate, // create a single index on all such columns. @@ -123,8 +123,13 @@ func (ics *indexCandidateSet) categorizeIndexCandidates(expr opt.Expr) { case *memo.EqExpr: ics.addVariableExprIndex(expr.Left, ics.equalCandidates) ics.addVariableExprIndex(expr.Right, ics.equalCandidates) + case *memo.NeExpr: + ics.addVariableExprIndex(expr.Left, ics.rangeCandidates) + ics.addVariableExprIndex(expr.Right, ics.rangeCandidates) case *memo.IsExpr: ics.addVariableExprIndex(expr.Left, ics.equalCandidates) + case *memo.IsNotExpr: + ics.addVariableExprIndex(expr.Left, ics.rangeCandidates) case *memo.LtExpr: ics.addVariableExprIndex(expr.Left, ics.rangeCandidates) ics.addVariableExprIndex(expr.Right, ics.rangeCandidates) diff --git a/pkg/sql/opt/indexrec/testdata/index b/pkg/sql/opt/indexrec/testdata/index index cfa8266eb1eb..5247648fc17c 100644 --- a/pkg/sql/opt/indexrec/testdata/index +++ b/pkg/sql/opt/indexrec/testdata/index @@ -275,6 +275,42 @@ scan t1@_hyp_1 ├── cost: 28.6200001 └── fd: ()-->(4) +index-candidates +SELECT i FROM t1 WHERE i != 5 +---- +t1: + (i) + +index-recommendations +SELECT i FROM t1 WHERE i != 5 +---- +creation: CREATE INDEX ON t.public.t1 (i); +-- +optimal plan: +scan t1@_hyp_1 + ├── columns: i:2!null + ├── constraint: /2/5 + │ ├── (/NULL - /4] + │ └── [/6 - ] + └── cost: 375.353333 + +index-candidates +SELECT s FROM t1 WHERE s IS NOT NULL +---- +t1: + (s) + +index-recommendations +SELECT s FROM t1 WHERE s IS NOT NULL +---- +creation: CREATE INDEX ON t.public.t1 (s); +-- +optimal plan: +scan t1@_hyp_1 + ├── columns: s:4!null + ├── constraint: /4/5: (/NULL - ] + └── cost: 1067.42 + index-candidates SELECT t1.k FROM t1 JOIN t2 ON t1.k = t2.i ---- @@ -624,6 +660,32 @@ scan t1@_hyp_3 ├── cost: 28.1933333 └── fd: ()-->(1) +index-candidates +SELECT * FROM t1 WHERE k = 1 AND f != 0 +---- +t1: + (f) + (k) + (k, f) + +index-recommendations +SELECT * FROM t1 WHERE k = 1 AND f != 0 +---- +creation: CREATE INDEX ON t.public.t1 (k) STORING (i, f, s); +-- +optimal plan: +select + ├── columns: k:1!null i:2 f:3!null s:4 + ├── cost: 29.0500001 + ├── fd: ()-->(1) + ├── scan t1@_hyp_1 + │ ├── columns: k:1!null i:2 f:3 s:4 + │ ├── constraint: /1/5: [/1 - /1] + │ ├── cost: 28.9200001 + │ └── fd: ()-->(1) + └── filters + └── f:3 != 0.0 [outer=(3), constraints=(/3: (/NULL - /-5e-324] [/5e-324 - ]; tight)] + # Multi-column combinations used: EQ, EQ + R. index-candidates SELECT k, i, f FROM t1 WHERE k = 1 AND i = 2 AND f > 0 @@ -654,6 +716,28 @@ inner-join (zigzag t1@_hyp_1 t1@_hyp_2) ├── i:2 = 2 [outer=(2), constraints=(/2: [/2 - /2]; tight), fd=()-->(2)] └── f:3 > 0.0 [outer=(3), constraints=(/3: [/5e-324 - ]; tight)] +index-candidates +SELECT k, i, s FROM t1 WHERE k = 1 AND i = 2 AND s IS NOT NULL +---- +t1: + (i) + (k) + (k, i) + (k, i, s) + (s) + +index-recommendations +SELECT k, i, s FROM t1 WHERE k = 1 AND i = 2 AND s IS NOT NULL +---- +creation: CREATE INDEX ON t.public.t1 (k, i, s); +-- +optimal plan: +scan t1@_hyp_5 + ├── columns: k:1!null i:2!null s:4!null + ├── constraint: /1/2/4/5: (/1/2/NULL - /1/2] + ├── cost: 18.907147 + └── fd: ()-->(1,2) + # Multi-column combinations used: J + R. index-candidates SELECT t1.k, t1.f FROM t1 JOIN t2 ON t1.k != t2.k WHERE t1.f > 0 @@ -687,6 +771,39 @@ project └── filters └── t1.k:1 != t2.k:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ])] +index-candidates +SELECT t1.k, t1.s FROM t1 JOIN t2 ON t1.k = t2.k WHERE t1.s IS NOT NULL +---- +t1: + (k) + (k, s) + (s) +t2: + (k) + +index-recommendations +SELECT t1.k, t1.s FROM t1 JOIN t2 ON t1.k = t2.k WHERE t1.s IS NOT NULL +---- +creation: CREATE INDEX ON t.public.t1 (s) STORING (k); +-- +optimal plan: +project + ├── columns: k:1!null s:4!null + ├── cost: 2379.90804 + └── inner-join (hash) + ├── columns: t1.k:1!null t1.s:4!null t2.k:8!null + ├── cost: 2282.85814 + ├── fd: (1)==(8), (8)==(1) + ├── scan t2 + │ ├── columns: t2.k:8 + │ └── cost: 1078.52 + ├── scan t1@_hyp_1 + │ ├── columns: t1.k:1 t1.s:4!null + │ ├── constraint: /4/5: (/NULL - ] + │ └── cost: 1077.32 + └── filters + └── t1.k:1 = t2.k:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)] + # Multi-column combinations used: EQ, EQ + J. index-candidates SELECT t1.i, t1.s FROM t1 JOIN t2 ON t1.k != t2.k WHERE t1.i = 2 AND t1.s = 'NG' @@ -956,6 +1073,101 @@ union-all └── aggregations └── count-rows [as=count_rows:22] +index-candidates +SELECT count(*) +FROM t1 LEFT JOIN t2 +ON t1.k = t2.k +GROUP BY t2.s, t2.i +UNION ALL +SELECT count(*) +FROM ( + SELECT * + FROM t1 + WHERE t1.f != 0 + AND t1.s = 'NG' +) +---- +t1: + (f) + (k) + (k, f) + (s) + (s, f) + (s, k) + (s, k, f) +t2: + (i, s) + (k) + +index-recommendations +SELECT count(*) +FROM t1 LEFT JOIN t2 +ON t1.k = t2.k +GROUP BY t2.s, t2.i +UNION ALL +SELECT count(*) +FROM ( + SELECT * + FROM t1 + WHERE t1.f != 0 + AND t1.s = 'NG' +) +---- +creation: CREATE INDEX ON t.public.t1 (k); +creation: CREATE INDEX ON t.public.t1 (s) STORING (f); +creation: CREATE INDEX ON t.public.t2 (k) STORING (i, s); +-- +optimal plan: +union-all + ├── columns: count:23!null + ├── left columns: count_rows:14 + ├── right columns: count_rows:22 + ├── cardinality: [1 - ] + ├── cost: 2766.55828 + ├── project + │ ├── columns: count_rows:14!null + │ ├── cost: 2727.55537 + │ └── group-by (hash) + │ ├── columns: t2.i:9 t2.s:10 count_rows:14!null + │ ├── grouping columns: t2.i:9 t2.s:10 + │ ├── cost: 2717.53581 + │ ├── key: (9,10) + │ ├── fd: (9,10)-->(14) + │ ├── left-join (merge) + │ │ ├── columns: t1.k:1 t2.k:8 t2.i:9 t2.s:10 + │ │ ├── left ordering: +1 + │ │ ├── right ordering: +8 + │ │ ├── cost: 2307.36 + │ │ ├── scan t1@_hyp_3 + │ │ │ ├── columns: t1.k:1 + │ │ │ ├── cost: 1088.62 + │ │ │ └── ordering: +1 + │ │ ├── scan t2@_hyp_2 + │ │ │ ├── columns: t2.k:8 t2.i:9 t2.s:10 + │ │ │ ├── cost: 1098.72 + │ │ │ └── ordering: +8 + │ │ └── filters (true) + │ └── aggregations + │ └── count-rows [as=count_rows:14] + └── scalar-group-by + ├── columns: count_rows:22!null + ├── cardinality: [1 - 1] + ├── cost: 28.9733334 + ├── key: () + ├── fd: ()-->(22) + ├── select + │ ├── columns: f:17!null t1.s:18!null + │ ├── cost: 28.8500001 + │ ├── fd: ()-->(18) + │ ├── scan t1@_hyp_1 + │ │ ├── columns: f:17 t1.s:18!null + │ │ ├── constraint: /18/19: [/'NG' - /'NG'] + │ │ ├── cost: 28.7200001 + │ │ └── fd: ()-->(18) + │ └── filters + │ └── f:17 != 0.0 [outer=(17), constraints=(/17: (/NULL - /-5e-324] [/5e-324 - ]; tight)] + └── aggregations + └── count-rows [as=count_rows:22] # No rule 5 multi-column index combinations. index-candidates