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 17, 2021
1 parent 1892d2d commit 99c3af7
Show file tree
Hide file tree
Showing 11 changed files with 308 additions and 54 deletions.
23 changes: 13 additions & 10 deletions pkg/ccl/logictestccl/testdata/logic_test/partitioning_implicit
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ vectorized: true
│ │
│ └── • hash join (right anti)
│ │ equality: (pk) = (column2)
│ │ left cols are key
│ │ right cols are key
│ │
│ ├── • scan
Expand Down Expand Up @@ -631,25 +632,26 @@ 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
│ │
│ └── • error if rows
│ │
│ └── • hash join (right semi)
│ │ equality: (pk) = (upsert_pk)
│ │ right cols are key
│ │ pred: column3 != partition_by
│ │
│ ├── • scan
Expand All @@ -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
Expand Down
64 changes: 33 additions & 31 deletions pkg/ccl/logictestccl/testdata/logic_test/regional_by_row
Original file line number Diff line number Diff line change
Expand Up @@ -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
│ │
Expand All @@ -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
│ │
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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
│ │
Expand All @@ -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
│ │
Expand All @@ -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
Expand All @@ -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)
Expand Down
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 @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1614,6 +1691,7 @@ vectorized: true
│ │
│ └── • hash join (right anti)
│ │ equality: (b, c) = (b_new, c)
│ │ right cols are key
│ │
│ ├── • scan
│ │ missing stats
Expand Down Expand Up @@ -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
Expand Down
46 changes: 46 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,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
}
Expand Down
Loading

0 comments on commit 99c3af7

Please sign in to comment.