From 983f0259f32b7786513ee1f7cb1e0799aaba65c8 Mon Sep 17 00:00:00 2001 From: Rebecca Taft Date: Fri, 30 Oct 2020 08:17:48 -0500 Subject: [PATCH] opt: add support for unique constraints in the test catalog This commit adds support for unique constraints in the test catalog. This will allow us to begin work on supporting uniqueness checks in the optimizer. Release note: None --- pkg/sql/opt/cat/table.go | 41 +++++++ pkg/sql/opt/cat/utils.go | 12 ++ .../opt/optbuilder/testdata/inverted-indexes | 7 +- pkg/sql/opt/testutils/testcat/create_table.go | 70 ++++++++--- pkg/sql/opt/testutils/testcat/test_catalog.go | 60 ++++++++++ .../testutils/testcat/testdata/foreign_keys | 39 +++--- pkg/sql/opt/testutils/testcat/testdata/index | 44 +++---- pkg/sql/opt/testutils/testcat/testdata/table | 112 +++++++++++++----- pkg/sql/opt/testutils/testcat/testdata/zone | 76 +++++++----- pkg/sql/opt_catalog.go | 42 +++++-- 10 files changed, 374 insertions(+), 129 deletions(-) diff --git a/pkg/sql/opt/cat/table.go b/pkg/sql/opt/cat/table.go index 442818ecf98f..d1d4f84d98ba 100644 --- a/pkg/sql/opt/cat/table.go +++ b/pkg/sql/opt/cat/table.go @@ -123,6 +123,14 @@ type Table interface { // InboundForeignKey returns the ith inbound foreign key reference. InboundForeignKey(i int) ForeignKeyConstraint + + // UniqueCount returns the number of unique constraints defined on this table. + // Includes any unique constraints implied by unique indexes. + UniqueCount() int + + // Unique returns the ith unique constraint defined on this table, where + // i < UniqueCount. + Unique(i int) UniqueConstraint } // CheckConstraint contains the SQL text and the validity status for a check @@ -237,3 +245,36 @@ type ForeignKeyConstraint interface { // constraint would be violated by an update. UpdateReferenceAction() tree.ReferenceAction } + +// UniqueConstraint represents a uniqueness constraint. UniqueConstraints may +// or may not be enforced with a unique index. For example, the following +// statement creates a unique constraint on column a without a unique index: +// ALTER TABLE t ADD CONSTRAINT u UNIQUE WITHOUT INDEX (a); +// In order to enforce this uniqueness constraint, the optimizer must add +// a uniqueness check as a postquery to any query that inserts into or updates +// column a. +type UniqueConstraint interface { + // Name of the unique constraint. + Name() string + + // TableID returns the stable identifier of the table on which this unique + // constraint is defined. + TableID() StableID + + // ColumnCount returns the number of columns in this constraint. + ColumnCount() int + + // ColumnOrdinal returns the table column ordinal of the ith column in this + // constraint. + ColumnOrdinal(tab Table, i int) int + + // WithoutIndex is true if this unique constraint is not enforced by an index. + WithoutIndex() bool + + // Validated is true if the constraint is validated (i.e. we know that the + // existing data satisfies the constraint). It is possible to set up a unique + // constraint on existing tables without validating it, in which case we + // cannot make any assumptions about the data. An unvalidated constraint still + // needs to be enforced on new mutations. + Validated() bool +} diff --git a/pkg/sql/opt/cat/utils.go b/pkg/sql/opt/cat/utils.go index 58eb090e6311..b63d4e93cf82 100644 --- a/pkg/sql/opt/cat/utils.go +++ b/pkg/sql/opt/cat/utils.go @@ -161,6 +161,18 @@ func FormatTable(cat Catalog, tab Table, tp treeprinter.Node) { formatCatalogFKRef(cat, true /* inbound */, tab.InboundForeignKey(i), child) } + for i := 0; i < tab.UniqueCount(); i++ { + var withoutIndexStr string + if tab.Unique(i).WithoutIndex() { + withoutIndexStr = "WITHOUT INDEX " + } + child.Childf( + "UNIQUE %s%s", + withoutIndexStr, + formatCols(tab, tab.Unique(i).ColumnCount(), tab.Unique(i).ColumnOrdinal), + ) + } + // TODO(radu): show stats. } diff --git a/pkg/sql/opt/optbuilder/testdata/inverted-indexes b/pkg/sql/opt/optbuilder/testdata/inverted-indexes index 336b6cd0ef47..48792b322830 100644 --- a/pkg/sql/opt/optbuilder/testdata/inverted-indexes +++ b/pkg/sql/opt/optbuilder/testdata/inverted-indexes @@ -28,9 +28,10 @@ TABLE kj ├── j_inverted_key jsonb not null [hidden] [virtual-inverted] ├── INDEX primary │ └── k int not null - └── INVERTED INDEX secondary - ├── j_inverted_key jsonb not null [hidden] [virtual-inverted] - └── k int not null + ├── INVERTED INDEX secondary + │ ├── j_inverted_key jsonb not null [hidden] [virtual-inverted] + │ └── k int not null + └── UNIQUE (k) build SELECT j_inverted_key FROM kj diff --git a/pkg/sql/opt/testutils/testcat/create_table.go b/pkg/sql/opt/testutils/testcat/create_table.go index 7ead373ed712..52053f5d21e2 100644 --- a/pkg/sql/opt/testutils/testcat/create_table.go +++ b/pkg/sql/opt/testutils/testcat/create_table.go @@ -12,14 +12,14 @@ package testcat import ( "fmt" + "reflect" + "sort" "strings" "github.com/cockroachdb/cockroach/pkg/config/zonepb" "github.com/cockroachdb/cockroach/pkg/geo/geoindex" "github.com/cockroachdb/cockroach/pkg/sql/catalog/colinfo" "github.com/cockroachdb/cockroach/pkg/sql/opt/cat" - "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" - "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/types" "github.com/cockroachdb/cockroach/pkg/util" @@ -183,11 +183,8 @@ func (tc *Catalog) CreateTable(stmt *tree.CreateTable) *Table { switch def := def.(type) { case *tree.UniqueConstraintTableDef: if def.WithoutIndex { - panic(pgerror.New(pgcode.FeatureNotSupported, - "unique constraints without an index are not yet supported", - )) - } - if !def.PrimaryKey { + tab.addUniqueConstraint(def.Name, def.Columns, def.WithoutIndex) + } else if !def.PrimaryKey { tab.addIndex(&def.IndexTableDef, uniqueIndex) } @@ -198,19 +195,22 @@ func (tc *Catalog) CreateTable(stmt *tree.CreateTable) *Table { tab.addFamily(def) case *tree.ColumnTableDef: - if def.Unique.WithoutIndex { - panic(pgerror.New(pgcode.FeatureNotSupported, - "unique constraints without an index are not yet supported", - )) - } if def.Unique.IsUnique { - tab.addIndex( - &tree.IndexTableDef{ - Name: tree.Name(fmt.Sprintf("%s_%s_key", stmt.Table.ObjectName, def.Name)), - Columns: tree.IndexElemList{{Column: def.Name}}, - }, - uniqueIndex, - ) + if def.Unique.WithoutIndex { + tab.addUniqueConstraint( + def.Unique.ConstraintName, + tree.IndexElemList{{Column: def.Name}}, + def.Unique.WithoutIndex, + ) + } else { + tab.addIndex( + &tree.IndexTableDef{ + Name: tree.Name(fmt.Sprintf("%s_%s_key", stmt.Table.ObjectName, def.Name)), + Columns: tree.IndexElemList{{Column: def.Name}}, + }, + uniqueIndex, + ) + } } } } @@ -450,6 +450,33 @@ func (tc *Catalog) resolveFK(tab *Table, d *tree.ForeignKeyConstraintTableDef) { targetTable.inboundFKs = append(targetTable.inboundFKs, fk) } +func (tt *Table) addUniqueConstraint( + name tree.Name, columns tree.IndexElemList, withoutIndex bool, +) { + cols := make([]int, len(columns)) + for i, c := range columns { + cols[i] = tt.FindOrdinal(string(c.Column)) + } + sort.Ints(cols) + + // Don't add duplicate constraints. + for _, c := range tt.uniqueConstraints { + if reflect.DeepEqual(c.columnOrdinals, cols) && c.withoutIndex == withoutIndex { + return + } + } + + // We didn't find an existing constraint, so add a new one. + u := UniqueConstraint{ + name: string(name), + tabID: tt.TabID, + columnOrdinals: cols, + withoutIndex: withoutIndex, + validated: true, + } + tt.uniqueConstraints = append(tt.uniqueConstraints, u) +} + func (tt *Table) addColumn(def *tree.ColumnTableDef) { ordinal := len(tt.Columns) nullable := !def.PrimaryKey.IsPrimaryKey && def.Nullable.Nullability != tree.NotNull @@ -494,6 +521,11 @@ func (tt *Table) addColumn(def *tree.ColumnTableDef) { } func (tt *Table) addIndex(def *tree.IndexTableDef, typ indexType) *Index { + // Add a unique constraint if this is a primary or unique index. + if typ != nonUniqueIndex { + tt.addUniqueConstraint(def.Name, def.Columns, false /* withoutIndex */) + } + idx := &Index{ IdxName: tt.makeIndexName(def.Name, typ), Unique: typ != nonUniqueIndex, diff --git a/pkg/sql/opt/testutils/testcat/test_catalog.go b/pkg/sql/opt/testutils/testcat/test_catalog.go index 779480c16072..6bc5d672e6c5 100644 --- a/pkg/sql/opt/testutils/testcat/test_catalog.go +++ b/pkg/sql/opt/testutils/testcat/test_catalog.go @@ -582,6 +582,8 @@ type Table struct { outboundFKs []ForeignKeyConstraint inboundFKs []ForeignKeyConstraint + + uniqueConstraints []UniqueConstraint } var _ cat.Table = &Table{} @@ -711,6 +713,16 @@ func (tt *Table) InboundForeignKey(i int) cat.ForeignKeyConstraint { return &tt.inboundFKs[i] } +// UniqueCount is part of the cat.Table interface. +func (tt *Table) UniqueCount() int { + return len(tt.uniqueConstraints) +} + +// Unique is part of the cat.Table interface. +func (tt *Table) Unique(i int) cat.UniqueConstraint { + return &tt.uniqueConstraints[i] +} + // FindOrdinal returns the ordinal of the column with the given name. func (tt *Table) FindOrdinal(name string) int { for i, col := range tt.Columns { @@ -1114,6 +1126,54 @@ func (fk *ForeignKeyConstraint) UpdateReferenceAction() tree.ReferenceAction { return fk.updateAction } +// UniqueConstraint implements cat.UniqueConstraint. See that interface +// for more information on the fields. +type UniqueConstraint struct { + name string + tabID cat.StableID + columnOrdinals []int + withoutIndex bool + validated bool +} + +var _ cat.UniqueConstraint = &UniqueConstraint{} + +// Name is part of the cat.UniqueConstraint interface. +func (u *UniqueConstraint) Name() string { + return u.name +} + +// TableID is part of the cat.UniqueConstraint interface. +func (u *UniqueConstraint) TableID() cat.StableID { + return u.tabID +} + +// ColumnCount is part of the cat.UniqueConstraint interface. +func (u *UniqueConstraint) ColumnCount() int { + return len(u.columnOrdinals) +} + +// ColumnOrdinal is part of the cat.UniqueConstraint interface. +func (u *UniqueConstraint) ColumnOrdinal(tab cat.Table, i int) int { + if tab.ID() != u.tabID { + panic(errors.AssertionFailedf( + "invalid table %d passed to ColumnOrdinal (expected %d)", + tab.ID(), u.tabID, + )) + } + return u.columnOrdinals[i] +} + +// WithoutIndex is part of the cat.UniqueConstraint interface. +func (u *UniqueConstraint) WithoutIndex() bool { + return u.withoutIndex +} + +// Validated is part of the cat.UniqueConstraint interface. +func (u *UniqueConstraint) Validated() bool { + return u.validated +} + // Sequence implements the cat.Sequence interface for testing purposes. type Sequence struct { SeqID cat.StableID diff --git a/pkg/sql/opt/testutils/testcat/testdata/foreign_keys b/pkg/sql/opt/testutils/testcat/testdata/foreign_keys index f9c2912f85eb..17f5b305afe1 100644 --- a/pkg/sql/opt/testutils/testcat/testdata/foreign_keys +++ b/pkg/sql/opt/testutils/testcat/testdata/foreign_keys @@ -14,7 +14,8 @@ TABLE parent ├── crdb_internal_mvcc_timestamp decimal [hidden] [system] ├── INDEX primary │ └── p int not null - └── REFERENCED BY CONSTRAINT fk_p_ref_parent FOREIGN KEY child (p) REFERENCES parent (p) + ├── REFERENCED BY CONSTRAINT fk_p_ref_parent FOREIGN KEY child (p) REFERENCES parent (p) + └── UNIQUE (p) exec-ddl SHOW CREATE child @@ -25,7 +26,8 @@ TABLE child ├── crdb_internal_mvcc_timestamp decimal [hidden] [system] ├── INDEX primary │ └── c int not null - └── CONSTRAINT fk_p_ref_parent FOREIGN KEY child (p) REFERENCES parent (p) + ├── CONSTRAINT fk_p_ref_parent FOREIGN KEY child (p) REFERENCES parent (p) + └── UNIQUE (c) exec-ddl CREATE TABLE parent2 (p INT UNIQUE) @@ -47,7 +49,9 @@ TABLE parent2 ├── INDEX parent2_p_key │ ├── p int │ └── rowid int not null default (unique_rowid()) [hidden] (storing) - └── REFERENCED BY CONSTRAINT fk_p_ref_parent2 FOREIGN KEY child2 (p) REFERENCES parent2 (p) + ├── REFERENCED BY CONSTRAINT fk_p_ref_parent2 FOREIGN KEY child2 (p) REFERENCES parent2 (p) + ├── UNIQUE (rowid) + └── UNIQUE (p) exec-ddl SHOW CREATE child2 @@ -58,7 +62,8 @@ TABLE child2 ├── crdb_internal_mvcc_timestamp decimal [hidden] [system] ├── INDEX primary │ └── c int not null - └── CONSTRAINT fk_p_ref_parent2 FOREIGN KEY child2 (p) REFERENCES parent2 (p) + ├── CONSTRAINT fk_p_ref_parent2 FOREIGN KEY child2 (p) REFERENCES parent2 (p) + └── UNIQUE (c) exec-ddl CREATE TABLE parent_multicol (p INT, q INT, r INT, PRIMARY KEY (p,q,r)) @@ -97,7 +102,8 @@ TABLE parent_multicol │ ├── q int not null │ └── r int not null ├── REFERENCED BY CONSTRAINT fk FOREIGN KEY child_multicol (p, q, r) REFERENCES parent_multicol (p, q, r) - └── REFERENCED BY CONSTRAINT fk FOREIGN KEY child_multicol_full (p, q, r) REFERENCES parent_multicol (p, q, r) MATCH FULL + ├── REFERENCED BY CONSTRAINT fk FOREIGN KEY child_multicol_full (p, q, r) REFERENCES parent_multicol (p, q, r) MATCH FULL + └── UNIQUE (p, q, r) exec-ddl SHOW CREATE child_multicol @@ -111,7 +117,8 @@ TABLE child_multicol │ ├── p int not null │ ├── q int not null │ └── r int not null - └── CONSTRAINT fk FOREIGN KEY child_multicol (p, q, r) REFERENCES parent_multicol (p, q, r) + ├── CONSTRAINT fk FOREIGN KEY child_multicol (p, q, r) REFERENCES parent_multicol (p, q, r) + └── UNIQUE (p, q, r) exec-ddl SHOW CREATE child_multicol_full @@ -125,7 +132,8 @@ TABLE child_multicol_full │ ├── p int not null │ ├── q int not null │ └── r int not null - └── CONSTRAINT fk FOREIGN KEY child_multicol_full (p, q, r) REFERENCES parent_multicol (p, q, r) MATCH FULL + ├── CONSTRAINT fk FOREIGN KEY child_multicol_full (p, q, r) REFERENCES parent_multicol (p, q, r) MATCH FULL + └── UNIQUE (p, q, r) exec-ddl DROP TABLE child @@ -137,8 +145,9 @@ SHOW CREATE parent TABLE parent ├── p int not null ├── crdb_internal_mvcc_timestamp decimal [hidden] [system] - └── INDEX primary - └── p int not null + ├── INDEX primary + │ └── p int not null + └── UNIQUE (p) exec-ddl DROP TABLE child_multicol @@ -156,7 +165,8 @@ TABLE parent_multicol │ ├── p int not null │ ├── q int not null │ └── r int not null - └── REFERENCED BY CONSTRAINT fk FOREIGN KEY child_multicol_full (p, q, r) REFERENCES parent_multicol (p, q, r) MATCH FULL + ├── REFERENCED BY CONSTRAINT fk FOREIGN KEY child_multicol_full (p, q, r) REFERENCES parent_multicol (p, q, r) MATCH FULL + └── UNIQUE (p, q, r) exec-ddl DROP TABLE child_multicol_full @@ -170,10 +180,11 @@ TABLE parent_multicol ├── q int not null ├── r int not null ├── crdb_internal_mvcc_timestamp decimal [hidden] [system] - └── INDEX primary - ├── p int not null - ├── q int not null - └── r int not null + ├── INDEX primary + │ ├── p int not null + │ ├── q int not null + │ └── r int not null + └── UNIQUE (p, q, r) # Verify we can drop a self-referencing table. exec-ddl diff --git a/pkg/sql/opt/testutils/testcat/testdata/index b/pkg/sql/opt/testutils/testcat/testdata/index index 80053a80b563..adf0cc3dcc54 100644 --- a/pkg/sql/opt/testutils/testcat/testdata/index +++ b/pkg/sql/opt/testutils/testcat/testdata/index @@ -18,9 +18,10 @@ TABLE kv ├── crdb_internal_mvcc_timestamp decimal [hidden] [system] ├── INDEX primary │ └── k int not null - └── INDEX idx - ├── v int - └── k int not null + ├── INDEX idx + │ ├── v int + │ └── k int not null + └── UNIQUE (k) exec-ddl CREATE INDEX idx2 ON kv (v) @@ -55,11 +56,12 @@ TABLE kv ├── crdb_internal_mvcc_timestamp decimal [hidden] [system] ├── INDEX primary │ └── k int not null - └── INDEX idx - ├── v int - ├── k int not null - └── WHERE - └── v > 1 + ├── INDEX idx + │ ├── v int + │ ├── k int not null + │ └── WHERE + │ └── v > 1 + └── UNIQUE (k) exec-ddl CREATE TABLE g ( @@ -89,11 +91,12 @@ TABLE g │ ├── a int │ ├── geog_inverted_key geography not null [hidden] [virtual-inverted] │ └── k int not null - └── INVERTED INDEX secondary - ├── a int - ├── b int - ├── geog_inverted_key geography not null [hidden] [virtual-inverted] - └── k int not null + ├── INVERTED INDEX secondary + │ ├── a int + │ ├── b int + │ ├── geog_inverted_key geography not null [hidden] [virtual-inverted] + │ └── k int not null + └── UNIQUE (k) # Test for expression-based index columns. exec-ddl @@ -135,10 +138,11 @@ TABLE xyz │ ├── idx_expr_2 int as (y + 1) stored [hidden] [virtual-computed] │ ├── idx_expr_1 string as (lower(z)) stored [hidden] [virtual-computed] │ └── x int not null - └── INDEX idx4 - ├── idx_expr_3 int as (x + y) stored [hidden] [virtual-computed] - ├── y int - ├── x int not null - ├── z string (storing) - └── WHERE - └── v > 1 + ├── INDEX idx4 + │ ├── idx_expr_3 int as (x + y) stored [hidden] [virtual-computed] + │ ├── y int + │ ├── x int not null + │ ├── z string (storing) + │ └── WHERE + │ └── v > 1 + └── UNIQUE (x) diff --git a/pkg/sql/opt/testutils/testcat/testdata/table b/pkg/sql/opt/testutils/testcat/testdata/table index 4db10a0ad5c5..18e3f66eaa61 100644 --- a/pkg/sql/opt/testutils/testcat/testdata/table +++ b/pkg/sql/opt/testutils/testcat/testdata/table @@ -12,8 +12,9 @@ TABLE kv ├── k int not null ├── v int ├── crdb_internal_mvcc_timestamp decimal [hidden] [system] - └── INDEX primary - └── k int not null + ├── INDEX primary + │ └── k int not null + └── UNIQUE (k) exec-ddl CREATE TABLE abcdef ( @@ -39,8 +40,9 @@ TABLE abcdef ├── rowid int not null default (unique_rowid()) [hidden] ├── crdb_internal_mvcc_timestamp decimal [hidden] [system] ├── CHECK (f > 2) - └── INDEX primary - └── rowid int not null default (unique_rowid()) [hidden] + ├── INDEX primary + │ └── rowid int not null default (unique_rowid()) [hidden] + └── UNIQUE (rowid) exec-ddl CREATE TABLE uvwxy ( @@ -70,9 +72,10 @@ TABLE uvwxy ├── FAMILY family1 (u, v, w, crdb_internal_mvcc_timestamp) ├── FAMILY family2 (x) ├── FAMILY family3 (y) - └── INDEX primary - ├── u int not null - └── v int not null + ├── INDEX primary + │ ├── u int not null + │ └── v int not null + └── UNIQUE (u, v) exec-ddl CREATE TABLE a (a INT UNIQUE) @@ -87,9 +90,11 @@ TABLE a ├── crdb_internal_mvcc_timestamp decimal [hidden] [system] ├── INDEX primary │ └── rowid int not null default (unique_rowid()) [hidden] - └── INDEX a_a_key - ├── a int - └── rowid int not null default (unique_rowid()) [hidden] (storing) + ├── INDEX a_a_key + │ ├── a int + │ └── rowid int not null default (unique_rowid()) [hidden] (storing) + ├── UNIQUE (rowid) + └── UNIQUE (a) exec-ddl CREATE TABLE part1 (a INT PRIMARY KEY, b INT) PARTITION BY LIST (a) ( @@ -106,13 +111,14 @@ TABLE part1 ├── a int not null ├── b int ├── crdb_internal_mvcc_timestamp decimal [hidden] [system] - └── INDEX primary - ├── a int not null - └── partition by list prefixes - ├── (1) - ├── (3) - ├── (4) - └── (5) + ├── INDEX primary + │ ├── a int not null + │ └── partition by list prefixes + │ ├── (1) + │ ├── (3) + │ ├── (4) + │ └── (5) + └── UNIQUE (a) exec-ddl CREATE TABLE part2 ( @@ -148,14 +154,15 @@ TABLE part2 │ ├── ('foo', 'baz') │ ├── ('qux', 'qux') │ └── ('waldo') - └── INDEX secondary - ├── c int not null - ├── a string not null - ├── b string not null - └── partition by list prefixes - ├── (1) - ├── (3) - └── (4) + ├── INDEX secondary + │ ├── c int not null + │ ├── a string not null + │ ├── b string not null + │ └── partition by list prefixes + │ ├── (1) + │ ├── (3) + │ └── (4) + └── UNIQUE (a, b, c) exec-ddl CREATE TABLE inv ( @@ -184,9 +191,10 @@ TABLE inv ├── INVERTED INDEX secondary │ ├── j_inverted_key jsonb not null [hidden] [virtual-inverted] │ └── k int not null - └── INVERTED INDEX secondary - ├── g_inverted_key geometry not null [hidden] [virtual-inverted] - └── k int not null + ├── INVERTED INDEX secondary + │ ├── g_inverted_key geometry not null [hidden] [virtual-inverted] + │ └── k int not null + └── UNIQUE (k) # Table with inverted indexes and implicit primary index. exec-ddl @@ -215,6 +223,48 @@ TABLE inv2 ├── INVERTED INDEX secondary │ ├── j_inverted_key jsonb not null [hidden] [virtual-inverted] │ └── rowid int not null default (unique_rowid()) [hidden] - └── INVERTED INDEX secondary - ├── g_inverted_key geometry not null [hidden] [virtual-inverted] - └── rowid int not null default (unique_rowid()) [hidden] + ├── INVERTED INDEX secondary + │ ├── g_inverted_key geometry not null [hidden] [virtual-inverted] + │ └── rowid int not null default (unique_rowid()) [hidden] + └── UNIQUE (rowid) + +# Table with unique constraints. +exec-ddl +CREATE TABLE uniq ( + i INT UNIQUE, + j STRING UNIQUE WITHOUT INDEX, + k STRING CHECK (k IN ('foo', 'bar', 'baz')), + l INT, + m STRING, + UNIQUE INDEX (k, j), + UNIQUE WITHOUT INDEX (l, m), + UNIQUE WITHOUT INDEX (m, l) +) +---- + +exec-ddl +SHOW CREATE uniq +---- +TABLE uniq + ├── i int + ├── j string + ├── k string + ├── l int + ├── m string + ├── rowid int not null default (unique_rowid()) [hidden] + ├── crdb_internal_mvcc_timestamp decimal [hidden] [system] + ├── CHECK (k IN ('foo', 'bar', 'baz')) + ├── INDEX primary + │ └── rowid int not null default (unique_rowid()) [hidden] + ├── INDEX uniq_i_key + │ ├── i int + │ └── rowid int not null default (unique_rowid()) [hidden] (storing) + ├── INDEX secondary + │ ├── k string + │ ├── j string + │ └── rowid int not null default (unique_rowid()) [hidden] (storing) + ├── UNIQUE (rowid) + ├── UNIQUE (i) + ├── UNIQUE WITHOUT INDEX (j) + ├── UNIQUE (j, k) + └── UNIQUE WITHOUT INDEX (l, m) diff --git a/pkg/sql/opt/testutils/testcat/testdata/zone b/pkg/sql/opt/testutils/testcat/testdata/zone index 357bb9671155..15844e7009ab 100644 --- a/pkg/sql/opt/testutils/testcat/testdata/zone +++ b/pkg/sql/opt/testutils/testcat/testdata/zone @@ -28,10 +28,12 @@ TABLE abc │ ├── b int │ ├── c string │ └── a int not null (storing) - └── INDEX bc2 - ├── b int - ├── c string - └── a int not null (storing) + ├── INDEX bc2 + │ ├── b int + │ ├── c string + │ └── a int not null (storing) + ├── UNIQUE (a) + └── UNIQUE (b, c) exec-ddl ALTER INDEX abc@bc1 CONFIGURE ZONE USING constraints='[+region=east]' @@ -55,10 +57,12 @@ TABLE abc │ ├── a int not null (storing) │ └── ZONE │ └── constraints: [+region=east] - └── INDEX bc2 - ├── b int - ├── c string - └── a int not null (storing) + ├── INDEX bc2 + │ ├── b int + │ ├── c string + │ └── a int not null (storing) + ├── UNIQUE (a) + └── UNIQUE (b, c) exec-ddl ALTER INDEX abc@bc2 CONFIGURE ZONE USING constraints='[+region=west]' @@ -82,12 +86,14 @@ TABLE abc │ ├── a int not null (storing) │ └── ZONE │ └── constraints: [+region=east] - └── INDEX bc2 - ├── b int - ├── c string - ├── a int not null (storing) - └── ZONE - └── constraints: [+region=west] + ├── INDEX bc2 + │ ├── b int + │ ├── c string + │ ├── a int not null (storing) + │ └── ZONE + │ └── constraints: [+region=west] + ├── UNIQUE (a) + └── UNIQUE (b, c) exec-ddl ALTER TABLE abc CONFIGURE ZONE USING constraints='[+region=us,+dc=central,+rack=1]' @@ -111,12 +117,14 @@ TABLE abc │ ├── a int not null (storing) │ └── ZONE │ └── constraints: [+region=east] - └── INDEX bc2 - ├── b int - ├── c string - ├── a int not null (storing) - └── ZONE - └── constraints: [+region=west] + ├── INDEX bc2 + │ ├── b int + │ ├── c string + │ ├── a int not null (storing) + │ └── ZONE + │ └── constraints: [+region=west] + ├── UNIQUE (a) + └── UNIQUE (b, c) exec-ddl ALTER INDEX abc@bc1 CONFIGURE ZONE USING constraints='[+region=us,+dc=east,+rack=1]' @@ -140,12 +148,14 @@ TABLE abc │ ├── a int not null (storing) │ └── ZONE │ └── constraints: [+region=us,+dc=east,+rack=1] - └── INDEX bc2 - ├── b int - ├── c string - ├── a int not null (storing) - └── ZONE - └── constraints: [+region=west] + ├── INDEX bc2 + │ ├── b int + │ ├── c string + │ ├── a int not null (storing) + │ └── ZONE + │ └── constraints: [+region=west] + ├── UNIQUE (a) + └── UNIQUE (b, c) exec-ddl ALTER INDEX abc@bc2 CONFIGURE ZONE USING constraints='[+dc=west]' @@ -169,9 +179,11 @@ TABLE abc │ ├── a int not null (storing) │ └── ZONE │ └── constraints: [+region=us,+dc=east,+rack=1] - └── INDEX bc2 - ├── b int - ├── c string - ├── a int not null (storing) - └── ZONE - └── constraints: [+dc=west] + ├── INDEX bc2 + │ ├── b int + │ ├── c string + │ ├── a int not null (storing) + │ └── ZONE + │ └── constraints: [+dc=west] + ├── UNIQUE (a) + └── UNIQUE (b, c) diff --git a/pkg/sql/opt_catalog.go b/pkg/sql/opt_catalog.go index 815526db75fe..e4c96051566f 100644 --- a/pkg/sql/opt_catalog.go +++ b/pkg/sql/opt_catalog.go @@ -1038,6 +1038,18 @@ func (ot *optTable) InboundForeignKey(i int) cat.ForeignKeyConstraint { return &ot.inboundFKs[i] } +// UniqueCount is part of the cat.Table interface. +func (ot *optTable) UniqueCount() int { + // TODO(rytaft): return the number of unique constraints (both with and + // without indexes). + return 0 +} + +// Unique is part of the cat.Table interface. +func (ot *optTable) Unique(i int) cat.UniqueConstraint { + panic(errors.AssertionFailedf("unique constraint [%d] does not exist", i)) +} + // lookupColumnOrdinal returns the ordinal of the column with the given ID. A // cache makes the lookup O(1). func (ot *optTable) lookupColumnOrdinal(colID descpb.ColumnID) (int, error) { @@ -1213,7 +1225,7 @@ func (oi *optIndex) Column(i int) cat.IndexColumn { // VirtualInvertedColumn is part of the cat.Index interface. func (oi *optIndex) VirtualInvertedColumn() cat.IndexColumn { if !oi.IsInverted() { - panic("non-inverted indexes do not have inverted virtual columns") + panic(errors.AssertionFailedf("non-inverted indexes do not have inverted virtual columns")) } ord := len(oi.desc.ColumnIDs) - 1 return oi.Column(ord) @@ -1647,7 +1659,7 @@ func newOptVirtualTable( for i := range ot.desc.Indexes { idxDesc := &ot.desc.Indexes[i] if len(idxDesc.ColumnIDs) > 1 { - panic("virtual indexes with more than 1 col not supported") + panic(errors.AssertionFailedf("virtual indexes with more than 1 col not supported")) } // Add 1, since the 0th index will the the primary that we added above. @@ -1753,7 +1765,7 @@ func (ot *optVirtualTable) StatisticCount() int { // Statistic is part of the cat.Table interface. func (ot *optVirtualTable) Statistic(i int) cat.TableStatistic { - panic("no stats") + panic(errors.AssertionFailedf("no stats")) } // CheckCount is part of the cat.Table interface. @@ -1787,7 +1799,7 @@ func (ot *optVirtualTable) OutboundForeignKeyCount() int { // OutboundForeignKeyCount is part of the cat.Table interface. func (ot *optVirtualTable) OutboundForeignKey(i int) cat.ForeignKeyConstraint { - panic("no FKs") + panic(errors.AssertionFailedf("no FKs")) } // InboundForeignKeyCount is part of the cat.Table interface. @@ -1797,7 +1809,17 @@ func (ot *optVirtualTable) InboundForeignKeyCount() int { // InboundForeignKey is part of the cat.Table interface. func (ot *optVirtualTable) InboundForeignKey(i int) cat.ForeignKeyConstraint { - panic("no FKs") + panic(errors.AssertionFailedf("no FKs")) +} + +// UniqueCount is part of the cat.Table interface. +func (ot *optVirtualTable) UniqueCount() int { + return 0 +} + +// Unique is part of the cat.Table interface. +func (ot *optVirtualTable) Unique(i int) cat.UniqueConstraint { + panic(errors.AssertionFailedf("no unique constraints")) } // optVirtualIndex is a dummy implementation of cat.Index for the indexes @@ -1900,7 +1922,7 @@ func (oi *optVirtualIndex) Column(i int) cat.IndexColumn { // VirtualInvertedColumn is part of the cat.Index interface. func (oi *optVirtualIndex) VirtualInvertedColumn() cat.IndexColumn { - panic("virtual indexes are not inverted") + panic(errors.AssertionFailedf("virtual indexes are not inverted")) } // Predicate is part of the cat.Index interface. @@ -1910,12 +1932,12 @@ func (oi *optVirtualIndex) Predicate() (string, bool) { // Zone is part of the cat.Index interface. func (oi *optVirtualIndex) Zone() cat.Zone { - panic("no zone") + panic(errors.AssertionFailedf("no zone")) } // Span is part of the cat.Index interface. func (oi *optVirtualIndex) Span() roachpb.Span { - panic("no span") + panic(errors.AssertionFailedf("no span")) } // Table is part of the cat.Index interface. @@ -1940,7 +1962,7 @@ func (oi *optVirtualIndex) InterleaveAncestorCount() int { // InterleaveAncestor is part of the cat.Index interface. func (oi *optVirtualIndex) InterleaveAncestor(i int) (table, index cat.StableID, numKeyCols int) { - panic("no interleavings") + panic(errors.AssertionFailedf("no interleavings")) } // InterleavedByCount is part of the cat.Index interface. @@ -1950,7 +1972,7 @@ func (oi *optVirtualIndex) InterleavedByCount() int { // InterleavedBy is part of the cat.Index interface. func (oi *optVirtualIndex) InterleavedBy(i int) (table, index cat.StableID) { - panic("no interleavings") + panic(errors.AssertionFailedf("no interleavings")) } // GeoConfig is part of the cat.Index interface.