Skip to content

Commit

Permalink
opt: add support for unique constraints in the test catalog
Browse files Browse the repository at this point in the history
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
  • Loading branch information
rytaft committed Nov 5, 2020
1 parent 9fd78ea commit 983f025
Show file tree
Hide file tree
Showing 10 changed files with 374 additions and 129 deletions.
41 changes: 41 additions & 0 deletions pkg/sql/opt/cat/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
12 changes: 12 additions & 0 deletions pkg/sql/opt/cat/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}

Expand Down
7 changes: 4 additions & 3 deletions pkg/sql/opt/optbuilder/testdata/inverted-indexes
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
70 changes: 51 additions & 19 deletions pkg/sql/opt/testutils/testcat/create_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}

Expand All @@ -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,
)
}
}
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
60 changes: 60 additions & 0 deletions pkg/sql/opt/testutils/testcat/test_catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,8 @@ type Table struct {

outboundFKs []ForeignKeyConstraint
inboundFKs []ForeignKeyConstraint

uniqueConstraints []UniqueConstraint
}

var _ cat.Table = &Table{}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
39 changes: 25 additions & 14 deletions pkg/sql/opt/testutils/testcat/testdata/foreign_keys
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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))
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
Loading

0 comments on commit 983f025

Please sign in to comment.