From 99c3af7afba5a7b0682a09f4e9e8dea43fc26722 Mon Sep 17 00:00:00 2001 From: Rebecca Taft Date: Mon, 15 Feb 2021 10:59:00 -0700 Subject: [PATCH] opt: create key for UNIQUE WITHOUT INDEX constraints This commit teaches the optimizer that columns with a valid UNIQUE WITHOUT INDEX constraint form a key, and the functional dependencies should reflect that. This will be necessary to support locality optimized search. Fixes #58944 Informs #55185 Release note (performance improvement): The optimizer now knows that the unique columns in an implicitly partitioned unique index form a key. This can be used to enable certain optimizations and may result in better plans. --- .../testdata/logic_test/partitioning_implicit | 23 +++-- .../testdata/logic_test/regional_by_row | 64 +++++++------ pkg/sql/opt/exec/execbuilder/testdata/unique | 85 +++++++++++++++- pkg/sql/opt/memo/logical_props_builder.go | 46 +++++++++ pkg/sql/opt/memo/testdata/logprops/unique | 96 +++++++++++++++++++ pkg/sql/opt/norm/testdata/rules/prune_cols | 12 ++- pkg/sql/opt/optbuilder/mutation_builder.go | 3 +- .../opt/optbuilder/mutation_builder_unique.go | 4 +- pkg/sql/opt/optbuilder/select.go | 9 +- pkg/sql/opt/table_meta.go | 4 + pkg/sql/sem/tree/select.go | 16 +++- 11 files changed, 308 insertions(+), 54 deletions(-) create mode 100644 pkg/sql/opt/memo/testdata/logprops/unique diff --git a/pkg/ccl/logictestccl/testdata/logic_test/partitioning_implicit b/pkg/ccl/logictestccl/testdata/logic_test/partitioning_implicit index 8da1688ff0aa..c1cdf2d3a50e 100644 --- a/pkg/ccl/logictestccl/testdata/logic_test/partitioning_implicit +++ b/pkg/ccl/logictestccl/testdata/logic_test/partitioning_implicit @@ -183,6 +183,7 @@ vectorized: true │ │ │ └── • hash join (right anti) │ │ equality: (pk) = (column2) +│ │ left cols are key │ │ right cols are key │ │ │ ├── • scan @@ -631,18 +632,18 @@ vectorized: true │ │ │ └── • render │ │ -│ └── • cross join (right outer) +│ └── • cross join (left outer) │ │ -│ ├── • filter -│ │ │ filter: pk = 3 -│ │ │ -│ │ └── • scan -│ │ missing stats -│ │ table: t@primary -│ │ spans: FULL SCAN +│ ├── • values +│ │ size: 7 columns, 1 row │ │ -│ └── • values -│ size: 7 columns, 1 row +│ └── • filter +│ │ filter: pk = 3 +│ │ +│ └── • scan +│ missing stats +│ table: t@primary +│ spans: FULL SCAN │ ├── • constraint-check │ │ @@ -650,6 +651,7 @@ vectorized: true │ │ │ └── • hash join (right semi) │ │ equality: (pk) = (upsert_pk) +│ │ right cols are key │ │ pred: column3 != partition_by │ │ │ ├── • scan @@ -666,6 +668,7 @@ vectorized: true │ └── • hash join (right semi) │ equality: (b) = (column5) + │ right cols are key │ pred: (upsert_pk != pk) OR (column3 != partition_by) │ ├── • scan diff --git a/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row b/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row index 8d5d1dfa8290..0155bef99665 100644 --- a/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row +++ b/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row @@ -367,15 +367,16 @@ vectorized: true │ │ │ └── • render │ │ -│ └── • cross join (right outer) +│ └── • cross join (left outer) │ │ -│ ├── • scan -│ │ missing stats -│ │ table: regional_by_row_table@primary -│ │ spans: [/'ap-southeast-2'/2 - /'ap-southeast-2'/2] [/'ca-central-1'/2 - /'ca-central-1'/2] [/'us-east-1'/2 - /'us-east-1'/2] +│ ├── • values +│ │ size: 6 columns, 1 row │ │ -│ └── • values -│ size: 6 columns, 1 row +│ └── • scan +│ missing stats +│ table: regional_by_row_table@primary +│ spans: [/'ap-southeast-2'/2 - /'ap-southeast-2'/2] [/'ca-central-1'/2 - /'ca-central-1'/2] [/'us-east-1'/2 - /'us-east-1'/2] +│ locking strength: for update │ ├── • constraint-check │ │ @@ -389,11 +390,11 @@ vectorized: true │ │ │ └── • cross join │ │ -│ ├── • scan buffer -│ │ label: buffer 1 +│ ├── • values +│ │ size: 1 column, 3 rows │ │ -│ └── • values -│ size: 1 column, 3 rows +│ └── • scan buffer +│ label: buffer 1 │ ├── • constraint-check │ │ @@ -407,11 +408,11 @@ vectorized: true │ │ │ └── • cross join │ │ -│ ├── • scan buffer -│ │ label: buffer 1 +│ ├── • values +│ │ size: 1 column, 3 rows │ │ -│ └── • values -│ size: 1 column, 3 rows +│ └── • scan buffer +│ label: buffer 1 │ └── • constraint-check │ @@ -425,11 +426,11 @@ vectorized: true │ └── • cross join │ - ├── • scan buffer - │ label: buffer 1 + ├── • values + │ size: 1 column, 3 rows │ - └── • values - size: 1 column, 3 rows + └── • scan buffer + label: buffer 1 query T EXPLAIN UPSERT INTO regional_by_row_table (crdb_region, pk, pk2, a, b) @@ -451,6 +452,7 @@ vectorized: true │ │ │ └── • lookup join (left outer) │ │ table: regional_by_row_table@primary +│ │ equality cols are key │ │ lookup condition: (column2 = pk) AND (crdb_region IN ('ap-southeast-2', 'ca-central-1', 'us-east-1')) │ │ locking strength: for update │ │ @@ -471,11 +473,11 @@ vectorized: true │ │ │ └── • cross join │ │ -│ ├── • scan buffer -│ │ label: buffer 1 +│ ├── • values +│ │ size: 1 column, 3 rows │ │ -│ └── • values -│ size: 1 column, 3 rows +│ └── • scan buffer +│ label: buffer 1 │ ├── • constraint-check │ │ @@ -489,11 +491,11 @@ vectorized: true │ │ │ └── • cross join │ │ -│ ├── • scan buffer -│ │ label: buffer 1 +│ ├── • values +│ │ size: 1 column, 3 rows │ │ -│ └── • values -│ size: 1 column, 3 rows +│ └── • scan buffer +│ label: buffer 1 │ └── • constraint-check │ @@ -507,11 +509,11 @@ vectorized: true │ └── • cross join │ - ├── • scan buffer - │ label: buffer 1 + ├── • values + │ size: 1 column, 3 rows │ - └── • values - size: 1 column, 3 rows + └── • scan buffer + label: buffer 1 query TIIIIIIIIT colnames SELECT * FROM (VALUES ('us-east-1', 23, 24, 25, 26), ('ca-central-1', 30, 30, 31, 32)) AS v(crdb_region, pk, pk2, a, b) diff --git a/pkg/sql/opt/exec/execbuilder/testdata/unique b/pkg/sql/opt/exec/execbuilder/testdata/unique index a9cc49842b92..4fb2e7c4db0c 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/unique +++ b/pkg/sql/opt/exec/execbuilder/testdata/unique @@ -27,13 +27,14 @@ CREATE TABLE uniq_overlaps_pk ( PRIMARY KEY (a, b), UNIQUE WITHOUT INDEX (b, c), UNIQUE WITHOUT INDEX (a, b, d), - UNIQUE WITHOUT INDEX (a), - UNIQUE WITHOUT INDEX (c, d), FAMILY (a), FAMILY (b), FAMILY (c), FAMILY (d) -) +); +ALTER TABLE uniq_overlaps_pk ADD CONSTRAINT unique_a UNIQUE WITHOUT INDEX (a) NOT VALID; +ALTER TABLE uniq_overlaps_pk ADD CONSTRAINT unique_c_d UNIQUE WITHOUT INDEX (c, d) NOT VALID + statement ok CREATE TABLE uniq_hidden_pk ( @@ -881,12 +882,14 @@ vectorized: true │ │ columns: (column1, column2, column3, column4) │ │ estimated row count: 0 (missing stats) │ │ table: uniq_enum@uniq_enum_r_s_j_key +│ │ equality cols are key │ │ lookup condition: ((column2 = s) AND (column4 = j)) AND (r IN ('us-east', 'us-west', 'eu-west')) │ │ │ └── • lookup join (anti) │ │ columns: (column1, column2, column3, column4) │ │ estimated row count: 0 (missing stats) │ │ table: uniq_enum@primary +│ │ equality cols are key │ │ lookup condition: (column3 = i) AND (r IN ('us-east', 'us-west', 'eu-west')) │ │ │ └── • lookup join (anti) @@ -1476,6 +1479,80 @@ vectorized: true └── • scan buffer label: buffer 1 +statement ok +ALTER TABLE uniq_overlaps_pk VALIDATE CONSTRAINT unique_a + +# Same test as the previous, but now that the constraint has been validated, it +# can be treated as a key. This allows the joins to be more efficient. +query T +EXPLAIN UPDATE uniq_overlaps_pk SET a = 1, b = 2, c = 3, d = 4 WHERE a = 5 +---- +distribution: local +vectorized: true +· +• root +│ +├── • update +│ │ table: uniq_overlaps_pk +│ │ set: a, b, c, d +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • render +│ │ +│ └── • scan +│ missing stats +│ table: uniq_overlaps_pk@primary +│ spans: [/5 - /5] +│ locking strength: for update +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • hash join (right semi) +│ │ equality: (b, c) = (b_new, c_new) +│ │ right cols are key +│ │ pred: a_new != a +│ │ +│ ├── • scan +│ │ missing stats +│ │ table: uniq_overlaps_pk@primary +│ │ spans: FULL SCAN +│ │ +│ └── • scan buffer +│ label: buffer 1 +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • lookup join (semi) +│ │ table: uniq_overlaps_pk@primary +│ │ equality: (a_new) = (a) +│ │ pred: b_new != b +│ │ +│ └── • scan buffer +│ label: buffer 1 +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (right semi) + │ equality: (c, d) = (c_new, d_new) + │ right cols are key + │ pred: (a_new != a) OR (b_new != b) + │ + ├── • scan + │ missing stats + │ table: uniq_overlaps_pk@primary + │ spans: FULL SCAN + │ + └── • scan buffer + label: buffer 1 + # Update with non-constant input. # No need to add a check for b,c since those columns weren't updated. # Add inequality filters for the hidden primary key column. @@ -1614,6 +1691,7 @@ vectorized: true │ │ │ └── • hash join (right anti) │ │ equality: (b, c) = (b_new, c) +│ │ right cols are key │ │ │ ├── • scan │ │ missing stats @@ -2688,6 +2766,7 @@ vectorized: true │ │ columns: (column1, column2, column3, column4, r, s, i, j) │ │ estimated row count: 2 (missing stats) │ │ table: uniq_enum@uniq_enum_r_s_j_key +│ │ equality cols are key │ │ lookup condition: ((column2 = s) AND (column4 = j)) AND (r IN ('us-east', 'us-west', 'eu-west')) │ │ │ └── • values diff --git a/pkg/sql/opt/memo/logical_props_builder.go b/pkg/sql/opt/memo/logical_props_builder.go index 98dbcca01fcc..95dd0540a1ab 100644 --- a/pkg/sql/opt/memo/logical_props_builder.go +++ b/pkg/sql/opt/memo/logical_props_builder.go @@ -1689,6 +1689,52 @@ func MakeTableFuncDep(md *opt.Metadata, tabID opt.TableID) *props.FuncDepSet { fd.AddStrictKey(keyCols, allCols) } } + + if !md.TableMeta(tabID).IgnoreUniqueWithoutIndexKeys { + for i := 0; i < tab.UniqueCount(); i++ { + unique := tab.Unique(i) + + if !unique.Validated() { + // This unique constraint has not been validated, so we cannot use it + // as a key. + continue + } + + if _, isPartial := unique.Predicate(); isPartial { + // Partial constraints cannot be considered while building functional + // dependency keys for the table because their keys are only unique + // for a subset of the rows in the table. + continue + } + + // If any of the columns are nullable, add a lax key FD. Otherwise, add a + // strict key. + var keyCols opt.ColSet + hasNulls := false + for i := 0; i < unique.ColumnCount(); i++ { + ord := unique.ColumnOrdinal(tab, i) + keyCols.Add(tabID.ColumnID(ord)) + if tab.Column(ord).IsNullable() { + hasNulls = true + } + } + + if excludeColumn != 0 && keyCols.Contains(excludeColumn) { + // See comment above where excludeColumn is set. + // (Virtual tables currently do not have UNIQUE WITHOUT INDEX constraints + // or implicitly partitioned UNIQUE indexes, but we add this check in case + // of future changes.) + continue + } + + if hasNulls { + fd.AddLaxKey(keyCols, allCols) + } else { + fd.AddStrictKey(keyCols, allCols) + } + } + } + md.SetTableAnnotation(tabID, fdAnnID, fd) return fd } diff --git a/pkg/sql/opt/memo/testdata/logprops/unique b/pkg/sql/opt/memo/testdata/logprops/unique new file mode 100644 index 000000000000..8da1300b246b --- /dev/null +++ b/pkg/sql/opt/memo/testdata/logprops/unique @@ -0,0 +1,96 @@ +exec-ddl +CREATE TABLE t ( + x INT PRIMARY KEY, + y INT UNIQUE WITHOUT INDEX, + z INT NOT NULL UNIQUE WITHOUT INDEX, + a INT, + b INT NOT NULL, + c INT NOT NULL, + UNIQUE WITHOUT INDEX (a, b), + UNIQUE WITHOUT INDEX (b, c), + UNIQUE WITHOUT INDEX (c) WHERE a > 5 +) +---- + +# Test that we build appropriate strict or lax keys for each of the UNIQUE +# WITHOUT INDEX constraints depending on whether or not the columns allow +# NULL values. We should not build a key for the partial constraint. +build +SELECT * FROM t +---- +project + ├── columns: x:1(int!null) y:2(int) z:3(int!null) a:4(int) b:5(int!null) c:6(int!null) + ├── key: (1) + ├── fd: (1)-->(2-6), (2)~~>(1,3-6), (3)-->(1,2,4-6), (4,5)~~>(1-3,6), (5,6)-->(1-4) + ├── prune: (1-6) + ├── interesting orderings: (+1) + └── scan t + ├── columns: x:1(int!null) y:2(int) z:3(int!null) a:4(int) b:5(int!null) c:6(int!null) crdb_internal_mvcc_timestamp:7(decimal) + ├── key: (1) + ├── fd: (1)-->(2-7), (2)~~>(1,3-7), (3)-->(1,2,4-7), (4,5)~~>(1-3,6,7), (5,6)-->(1-4,7) + ├── prune: (1-7) + └── interesting orderings: (+1) + +# Because we're constraining a key to a constant, the resulting FDs should +# show that all columns are now constant, and cardinality is at most 1. +build +SELECT * FROM t WHERE y = 5 +---- +project + ├── columns: x:1(int!null) y:2(int!null) z:3(int!null) a:4(int) b:5(int!null) c:6(int!null) + ├── cardinality: [0 - 1] + ├── key: () + ├── fd: ()-->(1-6) + ├── prune: (1-6) + ├── interesting orderings: (+1) + └── select + ├── columns: x:1(int!null) y:2(int!null) z:3(int!null) a:4(int) b:5(int!null) c:6(int!null) crdb_internal_mvcc_timestamp:7(decimal) + ├── cardinality: [0 - 1] + ├── key: () + ├── fd: ()-->(1-7) + ├── prune: (1,3-7) + ├── interesting orderings: (+1) + ├── scan t + │ ├── columns: x:1(int!null) y:2(int) z:3(int!null) a:4(int) b:5(int!null) c:6(int!null) crdb_internal_mvcc_timestamp:7(decimal) + │ ├── key: (1) + │ ├── fd: (1)-->(2-7), (2)~~>(1,3-7), (3)-->(1,2,4-7), (4,5)~~>(1-3,6,7), (5,6)-->(1-4,7) + │ ├── prune: (1-7) + │ └── interesting orderings: (+1) + └── filters + └── eq [type=bool, outer=(2), constraints=(/2: [/5 - /5]; tight), fd=()-->(2)] + ├── variable: y:2 [type=int] + └── const: 5 [type=int] + +# Because we're constraining a key to a constant, the resulting FDs should +# show that all columns are now constant, and cardinality is at most 1. +build +SELECT * FROM t WHERE a = 1 AND b = 1 +---- +project + ├── columns: x:1(int!null) y:2(int) z:3(int!null) a:4(int!null) b:5(int!null) c:6(int!null) + ├── cardinality: [0 - 1] + ├── key: () + ├── fd: ()-->(1-6) + ├── prune: (1-6) + ├── interesting orderings: (+1) + └── select + ├── columns: x:1(int!null) y:2(int) z:3(int!null) a:4(int!null) b:5(int!null) c:6(int!null) crdb_internal_mvcc_timestamp:7(decimal) + ├── cardinality: [0 - 1] + ├── key: () + ├── fd: ()-->(1-7) + ├── prune: (1-3,6,7) + ├── interesting orderings: (+1) + ├── scan t + │ ├── columns: x:1(int!null) y:2(int) z:3(int!null) a:4(int) b:5(int!null) c:6(int!null) crdb_internal_mvcc_timestamp:7(decimal) + │ ├── key: (1) + │ ├── fd: (1)-->(2-7), (2)~~>(1,3-7), (3)-->(1,2,4-7), (4,5)~~>(1-3,6,7), (5,6)-->(1-4,7) + │ ├── prune: (1-7) + │ └── interesting orderings: (+1) + └── filters + └── and [type=bool, outer=(4,5), constraints=(/4: [/1 - /1]; /5: [/1 - /1]; tight), fd=()-->(4,5)] + ├── eq [type=bool] + │ ├── variable: a:4 [type=int] + │ └── const: 1 [type=int] + └── eq [type=bool] + ├── variable: b:5 [type=int] + └── const: 1 [type=int] diff --git a/pkg/sql/opt/norm/testdata/rules/prune_cols b/pkg/sql/opt/norm/testdata/rules/prune_cols index 922dfec7bffd..6431de0c0f3f 100644 --- a/pkg/sql/opt/norm/testdata/rules/prune_cols +++ b/pkg/sql/opt/norm/testdata/rules/prune_cols @@ -3603,7 +3603,7 @@ update uniq │ │ ├── scan uniq │ │ │ ├── columns: uniq.k:8!null uniq.v:9 uniq.w:10 uniq.x:11 uniq.y:12 uniq.z:13 │ │ │ ├── key: (8) - │ │ │ └── fd: (8)-->(9-13), (13)~~>(8-12) + │ │ │ └── fd: (8)-->(9-13), (13)~~>(8-12), (10)~~>(8,9,11-13), (11,12)~~>(8-10,13) │ │ └── filters │ │ └── uniq.k:8 = 3 [outer=(8), constraints=(/8: [/3 - /3]; tight), fd=()-->(8)] │ └── projections @@ -3709,7 +3709,7 @@ upsert uniq_fk_parent │ │ │ ├── scan uniq_fk_parent │ │ │ │ ├── columns: uniq_fk_parent.k:10!null uniq_fk_parent.a:11 uniq_fk_parent.b:12 uniq_fk_parent.c:13 uniq_fk_parent.d:14 │ │ │ │ ├── key: (10) - │ │ │ │ └── fd: (10)-->(11-14) + │ │ │ │ └── fd: (10)-->(11-14), (11)~~>(10,12-14), (12,13)~~>(10,11,14) │ │ │ └── filters │ │ │ └── uniq_fk_parent.k:10 = 2 [outer=(10), constraints=(/10: [/2 - /2]; tight), fd=()-->(10)] │ │ └── filters (true) @@ -3815,7 +3815,7 @@ upsert uniq_fk_parent │ │ │ ├── scan uniq_fk_parent │ │ │ │ ├── columns: uniq_fk_parent.k:9!null uniq_fk_parent.a:10 uniq_fk_parent.b:11 uniq_fk_parent.c:12 uniq_fk_parent.d:13 │ │ │ │ ├── key: (9) - │ │ │ │ └── fd: (9)-->(10-13) + │ │ │ │ └── fd: (9)-->(10-13), (10)~~>(9,11-13), (11,12)~~>(9,10,13) │ │ │ └── filters │ │ │ └── uniq_fk_parent.k:9 = 1 [outer=(9), constraints=(/9: [/1 - /1]; tight), fd=()-->(9)] │ │ └── filters (true) @@ -3894,7 +3894,7 @@ delete uniq_fk_parent │ ├── scan uniq_fk_parent │ │ ├── columns: k:7!null uniq_fk_parent.a:8 uniq_fk_parent.b:9 uniq_fk_parent.c:10 │ │ ├── key: (7) - │ │ └── fd: (7)-->(8-10) + │ │ └── fd: (7)-->(8-10), (8)~~>(7,9,10), (9,10)~~>(7,8) │ └── filters │ └── k:7 = 1 [outer=(7), constraints=(/7: [/1 - /1]; tight), fd=()-->(7)] └── f-k-checks @@ -3913,7 +3913,9 @@ delete uniq_fk_parent │ │ ├── key: () │ │ └── fd: ()-->(13,14) │ ├── scan uniq_fk_child - │ │ └── columns: uniq_fk_child.b:16 uniq_fk_child.c:17 + │ │ ├── columns: uniq_fk_child.b:16 uniq_fk_child.c:17 + │ │ ├── lax-key: (16,17) + │ │ └── fd: (17)~~>(16) │ └── filters │ ├── b:13 = uniq_fk_child.b:16 [outer=(13,16), constraints=(/13: (/NULL - ]; /16: (/NULL - ]), fd=(13)==(16), (16)==(13)] │ └── c:14 = uniq_fk_child.c:17 [outer=(14,17), constraints=(/14: (/NULL - ]; /17: (/NULL - ]), fd=(14)==(17), (17)==(14)] diff --git a/pkg/sql/opt/optbuilder/mutation_builder.go b/pkg/sql/opt/optbuilder/mutation_builder.go index 7fbb810b04fd..71a65c0fcb23 100644 --- a/pkg/sql/opt/optbuilder/mutation_builder.go +++ b/pkg/sql/opt/optbuilder/mutation_builder.go @@ -173,7 +173,8 @@ type mutationBuilder struct { // cascades contains foreign key check cascades; see buildFK* methods. cascades memo.FKCascades - // withID is nonzero if we need to buffer the input for FK checks. + // withID is nonzero if we need to buffer the input for FK or uniqueness + // checks. withID opt.WithID // extraAccessibleCols stores all the columns that are available to the diff --git a/pkg/sql/opt/optbuilder/mutation_builder_unique.go b/pkg/sql/opt/optbuilder/mutation_builder_unique.go index 8d38b126152c..5cedbbc68253 100644 --- a/pkg/sql/opt/optbuilder/mutation_builder_unique.go +++ b/pkg/sql/opt/optbuilder/mutation_builder_unique.go @@ -290,7 +290,9 @@ func (h *uniqueCheckHelper) buildTableScan() (outScope *scope, ordinals []int) { return h.mb.b.buildScan( tabMeta, ordinals, - nil, /* indexFlags */ + // After the update we can't guarantee that the constraints are unique + // (which is why we need the uniqueness checks in the first place). + &tree.IndexFlags{IgnoreUniqueWithoutIndexKeys: true}, noRowLocking, h.mb.b.allocScope(), ), ordinals diff --git a/pkg/sql/opt/optbuilder/select.go b/pkg/sql/opt/optbuilder/select.go index eb5eb01221dc..81ad30714d48 100644 --- a/pkg/sql/opt/optbuilder/select.go +++ b/pkg/sql/opt/optbuilder/select.go @@ -454,8 +454,13 @@ func (b *Builder) buildScan( tab := tabMeta.Table tabID := tabMeta.MetaID - if indexFlags != nil && indexFlags.IgnoreForeignKeys { - tabMeta.IgnoreForeignKeys = true + if indexFlags != nil { + if indexFlags.IgnoreForeignKeys { + tabMeta.IgnoreForeignKeys = true + } + if indexFlags.IgnoreUniqueWithoutIndexKeys { + tabMeta.IgnoreUniqueWithoutIndexKeys = true + } } outScope = inScope.push() diff --git a/pkg/sql/opt/table_meta.go b/pkg/sql/opt/table_meta.go index 9182144f25bf..50baed52dbbc 100644 --- a/pkg/sql/opt/table_meta.go +++ b/pkg/sql/opt/table_meta.go @@ -135,6 +135,10 @@ type TableMeta struct { // the consistency of foreign keys. IgnoreForeignKeys bool + // IgnoreUniqueWithoutIndexKeys is true if we should disable any rules that + // depend on the consistency of unique without index constraints. + IgnoreUniqueWithoutIndexKeys bool + // Constraints stores a *FiltersExpr containing filters that are known to // evaluate to true on the table data. This list is extracted from validated // check constraints; specifically, those check constraints that we can prove diff --git a/pkg/sql/sem/tree/select.go b/pkg/sql/sem/tree/select.go index f05888fbcc0f..9af7231e4736 100644 --- a/pkg/sql/sem/tree/select.go +++ b/pkg/sql/sem/tree/select.go @@ -281,6 +281,9 @@ type IndexFlags struct { // references from this table. This is useful in particular for scrub queries // used to verify the consistency of foreign key relations. IgnoreForeignKeys bool + // IgnoreUniqueWithoutIndexKeys disables optimizations based on unique without + // index constraints. + IgnoreUniqueWithoutIndexKeys bool } // ForceIndex returns true if a forced index was specified, either using a name @@ -298,9 +301,14 @@ func (ih *IndexFlags) CombineWith(other *IndexFlags) error { if ih.IgnoreForeignKeys && other.IgnoreForeignKeys { return errors.New("IGNORE_FOREIGN_KEYS specified multiple times") } + if ih.IgnoreUniqueWithoutIndexKeys && other.IgnoreUniqueWithoutIndexKeys { + return errors.New("IGNORE_UNIQUE_WITHOUT_INDEX_KEYS specified multiple times") + } result := *ih result.NoIndexJoin = ih.NoIndexJoin || other.NoIndexJoin result.IgnoreForeignKeys = ih.IgnoreForeignKeys || other.IgnoreForeignKeys + result.IgnoreUniqueWithoutIndexKeys = ih.IgnoreUniqueWithoutIndexKeys || + other.IgnoreUniqueWithoutIndexKeys if other.Direction != 0 { if ih.Direction != 0 { @@ -339,7 +347,8 @@ func (ih *IndexFlags) Check() error { // Format implements the NodeFormatter interface. func (ih *IndexFlags) Format(ctx *FmtCtx) { ctx.WriteByte('@') - if !ih.NoIndexJoin && !ih.IgnoreForeignKeys && ih.Direction == 0 { + if !ih.NoIndexJoin && !ih.IgnoreForeignKeys && !ih.IgnoreUniqueWithoutIndexKeys && + ih.Direction == 0 { if ih.Index != "" { ctx.FormatNode(&ih.Index) } else { @@ -373,6 +382,11 @@ func (ih *IndexFlags) Format(ctx *FmtCtx) { sep() ctx.WriteString("IGNORE_FOREIGN_KEYS") } + + if ih.IgnoreUniqueWithoutIndexKeys { + sep() + ctx.WriteString("IGNORE_UNIQUE_WITHOUT_INDEX_KEYS") + } ctx.WriteString("}") } }