Skip to content

Commit

Permalink
opt: create key for UNIQUE WITHOUT INDEX constraints
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rytaft committed Feb 15, 2021
1 parent 3ae8b84 commit e3dd20a
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 14 deletions.
85 changes: 82 additions & 3 deletions pkg/sql/opt/exec/execbuilder/testdata/unique
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -841,6 +842,7 @@ vectorized: true
│ │ columns: (column1, column2, column3, column4)
│ │ estimated row count: 0 (missing stats)
│ │ equality: (i) = (column3)
│ │ left cols are key
│ │
│ ├── • scan
│ │ columns: (i)
Expand Down Expand Up @@ -1144,6 +1146,81 @@ 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.
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)
│ │ equality cols are key
│ │ 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.
Expand Down Expand Up @@ -1282,6 +1359,7 @@ vectorized: true
│ │
│ └── • hash join (right anti)
│ │ equality: (b, c) = (b_new, c)
│ │ right cols are key
│ │
│ ├── • scan
│ │ missing stats
Expand Down Expand Up @@ -1915,6 +1993,7 @@ vectorized: true
└── • hash join (semi)
│ equality: (k) = (a)
│ right cols are key
│ pred: v != b
├── • scan buffer
Expand Down
44 changes: 44 additions & 0 deletions pkg/sql/opt/memo/logical_props_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -1689,6 +1689,50 @@ func MakeTableFuncDep(md *opt.Metadata, tabID opt.TableID) *props.FuncDepSet {
fd.AddStrictKey(keyCols, allCols)
}
}

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
}
Expand Down
96 changes: 96 additions & 0 deletions pkg/sql/opt/memo/testdata/logprops/unique
Original file line number Diff line number Diff line change
@@ -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]
24 changes: 13 additions & 11 deletions pkg/sql/opt/norm/testdata/rules/prune_cols
Original file line number Diff line number Diff line change
Expand Up @@ -3603,7 +3603,7 @@ update uniq
│ │ ├── scan uniq
│ │ │ ├── columns: uniq.k:8!null w:10 x:11 uniq.y:12
│ │ │ ├── key: (8)
│ │ │ └── fd: (8)-->(10-12)
│ │ │ └── fd: (8)-->(10-12), (10)~~>(8,11,12), (11,12)~~>(8,10)
│ │ └── filters
│ │ └── uniq.k:8 = 3 [outer=(8), constraints=(/8: [/3 - /3]; tight), fd=()-->(8)]
│ └── projections
Expand All @@ -3627,7 +3627,7 @@ update uniq
│ ├── scan uniq
│ │ ├── columns: uniq.k:19!null w:21
│ │ ├── key: (19)
│ │ └── fd: (19)-->(21)
│ │ └── fd: (19)-->(21), (21)~~>(19)
│ └── filters
│ ├── w_new:17 = w:21 [outer=(17,21), constraints=(/17: (/NULL - ]; /21: (/NULL - ]), fd=(17)==(21), (21)==(17)]
│ └── k:18 != uniq.k:19 [outer=(18,19), constraints=(/18: (/NULL - ]; /19: (/NULL - ])]
Expand All @@ -3649,7 +3649,7 @@ update uniq
├── scan uniq
│ ├── columns: uniq.k:29!null x:32 uniq.y:33
│ ├── key: (29)
│ └── fd: (29)-->(32,33)
│ └── fd: (29)-->(32,33), (32,33)~~>(29)
└── filters
├── x_new:26 = x:32 [outer=(26,32), constraints=(/26: (/NULL - ]; /32: (/NULL - ]), fd=(26)==(32), (32)==(26)]
├── y:27 = uniq.y:33 [outer=(27,33), constraints=(/27: (/NULL - ]; /33: (/NULL - ]), fd=(27)==(33), (33)==(27)]
Expand Down Expand Up @@ -3702,7 +3702,7 @@ upsert uniq_fk_parent
│ │ │ ├── scan uniq_fk_parent
│ │ │ │ ├── columns: k:10!null a:11 b:12 c:13
│ │ │ │ ├── key: (10)
│ │ │ │ └── fd: (10)-->(11-13)
│ │ │ │ └── fd: (10)-->(11-13), (11)~~>(10,12,13), (12,13)~~>(10,11)
│ │ │ └── filters
│ │ │ └── k:10 = 2 [outer=(10), constraints=(/10: [/2 - /2]; tight), fd=()-->(10)]
│ │ └── filters (true)
Expand All @@ -3729,7 +3729,7 @@ upsert uniq_fk_parent
│ ├── scan uniq_fk_parent
│ │ ├── columns: k:24!null a:25
│ │ ├── key: (24)
│ │ └── fd: (24)-->(25)
│ │ └── fd: (24)-->(25), (25)~~>(24)
│ └── filters
│ ├── upsert_a:22 = a:25 [outer=(22,25), constraints=(/22: (/NULL - ]; /25: (/NULL - ]), fd=(22)==(25), (25)==(22)]
│ └── upsert_k:23 != k:24 [outer=(23,24), constraints=(/23: (/NULL - ]; /24: (/NULL - ])]
Expand All @@ -3751,7 +3751,7 @@ upsert uniq_fk_parent
├── scan uniq_fk_parent
│ ├── columns: k:33!null b:35 c:36
│ ├── key: (33)
│ └── fd: (33)-->(35,36)
│ └── fd: (33)-->(35,36), (35,36)~~>(33)
└── filters
├── upsert_b:30 = b:35 [outer=(30,35), constraints=(/30: (/NULL - ]; /35: (/NULL - ]), fd=(30)==(35), (35)==(30)]
├── upsert_c:31 = c:36 [outer=(31,36), constraints=(/31: (/NULL - ]; /36: (/NULL - ]), fd=(31)==(36), (36)==(31)]
Expand Down Expand Up @@ -3802,7 +3802,7 @@ upsert uniq_fk_parent
│ │ │ ├── scan uniq_fk_parent
│ │ │ │ ├── columns: k:9!null a:10 b:11 c:12 d:13
│ │ │ │ ├── key: (9)
│ │ │ │ └── fd: (9)-->(10-13)
│ │ │ │ └── fd: (9)-->(10-13), (10)~~>(9,11-13), (11,12)~~>(9,10,13)
│ │ │ └── filters
│ │ │ └── k:9 = 1 [outer=(9), constraints=(/9: [/1 - /1]; tight), fd=()-->(9)]
│ │ └── filters (true)
Expand Down Expand Up @@ -3830,7 +3830,7 @@ upsert uniq_fk_parent
│ ├── scan uniq_fk_parent
│ │ ├── columns: k:23!null a:24
│ │ ├── key: (23)
│ │ └── fd: (23)-->(24)
│ │ └── fd: (23)-->(24), (24)~~>(23)
│ └── filters
│ ├── upsert_a:21 = a:24 [outer=(21,24), constraints=(/21: (/NULL - ]; /24: (/NULL - ]), fd=(21)==(24), (24)==(21)]
│ └── upsert_k:22 != k:23 [outer=(22,23), constraints=(/22: (/NULL - ]; /23: (/NULL - ])]
Expand All @@ -3852,7 +3852,7 @@ upsert uniq_fk_parent
├── scan uniq_fk_parent
│ ├── columns: k:32!null b:34 c:35
│ ├── key: (32)
│ └── fd: (32)-->(34,35)
│ └── fd: (32)-->(34,35), (34,35)~~>(32)
└── filters
├── upsert_b:29 = b:34 [outer=(29,34), constraints=(/29: (/NULL - ]; /34: (/NULL - ]), fd=(29)==(34), (34)==(29)]
├── upsert_c:30 = c:35 [outer=(30,35), constraints=(/30: (/NULL - ]; /35: (/NULL - ]), fd=(30)==(35), (35)==(30)]
Expand All @@ -3876,7 +3876,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
Expand All @@ -3895,7 +3895,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)]
Expand Down

0 comments on commit e3dd20a

Please sign in to comment.