Skip to content

Commit

Permalink
sql: add syntax to create unique constraints without an index
Browse files Browse the repository at this point in the history
This commit adds support for the syntax `... UNIQUE WITHOUT INDEX ...`,
both when adding UNIQUE constraints and when adding UNIQUE columns. Using
this syntax will currently return the error "unique constraints without
an index are not yet supported", but support for the syntax serves as a
starting point for adding support for these unique constraints.

Informs cockroachdb#41535

Release note (sql change): Added support for using the syntax
`... UNIQUE WITHOUT INDEX ...` in CREATE TABLE and ALTER TABLE statements,
both when defining columns and unique constraints. Although this syntax
can now be parsed successfully, using this syntax currently returns an
error "unique constraints without an index are not yet supported".
  • Loading branch information
rytaft committed Oct 19, 2020
1 parent 3e3aaf3 commit 51387f6
Show file tree
Hide file tree
Showing 15 changed files with 136 additions and 35 deletions.
4 changes: 2 additions & 2 deletions docs/generated/sql/bnf/col_qualification.bnf
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
col_qualification ::=
'CONSTRAINT' constraint_name 'NOT' 'NULL'
| 'CONSTRAINT' constraint_name 'NULL'
| 'CONSTRAINT' constraint_name 'UNIQUE'
| 'CONSTRAINT' constraint_name 'UNIQUE' opt_without_index
| 'CONSTRAINT' constraint_name 'PRIMARY' 'KEY'
| 'CONSTRAINT' constraint_name 'PRIMARY' 'KEY' 'USING' 'HASH' 'WITH' 'BUCKET_COUNT' '=' n_buckets
| 'CONSTRAINT' constraint_name 'CHECK' '(' a_expr ')'
Expand All @@ -10,7 +10,7 @@ col_qualification ::=
| 'CONSTRAINT' constraint_name generated_as '(' a_expr ')' 'STORED'
| 'NOT' 'NULL'
| 'NULL'
| 'UNIQUE'
| 'UNIQUE' opt_without_index
| 'PRIMARY' 'KEY'
| 'PRIMARY' 'KEY' 'USING' 'HASH' 'WITH' 'BUCKET_COUNT' '=' n_buckets
| 'CHECK' '(' a_expr ')'
Expand Down
8 changes: 6 additions & 2 deletions docs/generated/sql/bnf/stmt_block.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -2198,7 +2198,7 @@ constraint_name ::=

constraint_elem ::=
'CHECK' '(' a_expr ')'
| 'UNIQUE' '(' index_params ')' opt_storing opt_interleave opt_partition_by opt_where_clause
| 'UNIQUE' opt_without_index '(' index_params ')' opt_storing opt_interleave opt_partition_by opt_where_clause
| 'PRIMARY' 'KEY' '(' index_params ')' opt_hash_sharded opt_interleave
| 'FOREIGN' 'KEY' '(' name_list ')' 'REFERENCES' table_name opt_column_list key_match reference_actions

Expand Down Expand Up @@ -2487,6 +2487,10 @@ col_qualification ::=
| 'CREATE' 'FAMILY'
| 'CREATE' 'IF' 'NOT' 'EXISTS' 'FAMILY' family_name

opt_without_index ::=
'WITHOUT' 'INDEX'
|

key_match ::=
'MATCH' 'SIMPLE'
| 'MATCH' 'FULL'
Expand Down Expand Up @@ -2730,7 +2734,7 @@ create_as_constraint_elem ::=
col_qualification_elem ::=
'NOT' 'NULL'
| 'NULL'
| 'UNIQUE'
| 'UNIQUE' opt_without_index
| 'PRIMARY' 'KEY'
| 'PRIMARY' 'KEY' 'USING' 'HASH' 'WITH' 'BUCKET_COUNT' '=' a_expr
| 'CHECK' '(' a_expr ')'
Expand Down
16 changes: 8 additions & 8 deletions docs/generated/sql/bnf/table_constraint.bnf
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
table_constraint ::=
'CONSTRAINT' constraint_name 'CHECK' '(' a_expr ')'
| 'CONSTRAINT' constraint_name 'UNIQUE' '(' index_params ')' 'COVERING' '(' name_list ')' opt_interleave opt_partition_by opt_where_clause
| 'CONSTRAINT' constraint_name 'UNIQUE' '(' index_params ')' 'STORING' '(' name_list ')' opt_interleave opt_partition_by opt_where_clause
| 'CONSTRAINT' constraint_name 'UNIQUE' '(' index_params ')' 'INCLUDE' '(' name_list ')' opt_interleave opt_partition_by opt_where_clause
| 'CONSTRAINT' constraint_name 'UNIQUE' '(' index_params ')' opt_interleave opt_partition_by opt_where_clause
| 'CONSTRAINT' constraint_name 'UNIQUE' opt_without_index '(' index_params ')' 'COVERING' '(' name_list ')' opt_interleave opt_partition_by opt_where_clause
| 'CONSTRAINT' constraint_name 'UNIQUE' opt_without_index '(' index_params ')' 'STORING' '(' name_list ')' opt_interleave opt_partition_by opt_where_clause
| 'CONSTRAINT' constraint_name 'UNIQUE' opt_without_index '(' index_params ')' 'INCLUDE' '(' name_list ')' opt_interleave opt_partition_by opt_where_clause
| 'CONSTRAINT' constraint_name 'UNIQUE' opt_without_index '(' index_params ')' opt_interleave opt_partition_by opt_where_clause
| 'CONSTRAINT' constraint_name 'PRIMARY' 'KEY' '(' index_params ')' 'USING' 'HASH' 'WITH' 'BUCKET_COUNT' '=' n_buckets opt_interleave
| 'CONSTRAINT' constraint_name 'PRIMARY' 'KEY' '(' index_params ')' opt_interleave
| 'CONSTRAINT' constraint_name 'FOREIGN' 'KEY' '(' name_list ')' 'REFERENCES' table_name opt_column_list key_match reference_actions
| 'CHECK' '(' a_expr ')'
| 'UNIQUE' '(' index_params ')' 'COVERING' '(' name_list ')' opt_interleave opt_partition_by opt_where_clause
| 'UNIQUE' '(' index_params ')' 'STORING' '(' name_list ')' opt_interleave opt_partition_by opt_where_clause
| 'UNIQUE' '(' index_params ')' 'INCLUDE' '(' name_list ')' opt_interleave opt_partition_by opt_where_clause
| 'UNIQUE' '(' index_params ')' opt_interleave opt_partition_by opt_where_clause
| 'UNIQUE' opt_without_index '(' index_params ')' 'COVERING' '(' name_list ')' opt_interleave opt_partition_by opt_where_clause
| 'UNIQUE' opt_without_index '(' index_params ')' 'STORING' '(' name_list ')' opt_interleave opt_partition_by opt_where_clause
| 'UNIQUE' opt_without_index '(' index_params ')' 'INCLUDE' '(' name_list ')' opt_interleave opt_partition_by opt_where_clause
| 'UNIQUE' opt_without_index '(' index_params ')' opt_interleave opt_partition_by opt_where_clause
| 'PRIMARY' 'KEY' '(' index_params ')' 'USING' 'HASH' 'WITH' 'BUCKET_COUNT' '=' n_buckets opt_interleave
| 'PRIMARY' 'KEY' '(' index_params ')' opt_interleave
| 'FOREIGN' 'KEY' '(' name_list ')' 'REFERENCES' table_name opt_column_list key_match reference_actions
5 changes: 5 additions & 0 deletions pkg/sql/alter_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ func (n *alterTableNode) startExec(params runParams) error {
case *tree.AlterTableAddConstraint:
switch d := t.ConstraintDef.(type) {
case *tree.UniqueConstraintTableDef:
if d.NoIndex {
return pgerror.New(pgcode.FeatureNotSupported,
"unique constraints without an index are not yet supported",
)
}
if d.PrimaryKey {
// We only support "adding" a primary key when we are using the
// default rowid primary index or if a DROP PRIMARY KEY statement
Expand Down
7 changes: 6 additions & 1 deletion pkg/sql/catalog/tabledesc/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ func MakeColumnDefDescs(
// Should never happen since `HoistConstraints` moves these to table level
return nil, nil, nil, errors.New("unexpected column REFERENCED constraint")
}
if d.UniqueNoIndex {
return nil, nil, nil, pgerror.New(pgcode.FeatureNotSupported,
"unique constraints without an index are not yet supported",
)
}

col := &descpb.ColumnDescriptor{
Name: string(d.Name),
Expand Down Expand Up @@ -107,7 +112,7 @@ func MakeColumnDefDescs(
}

var idx *descpb.IndexDescriptor
if d.PrimaryKey.IsPrimaryKey || d.Unique {
if d.PrimaryKey.IsPrimaryKey || (d.Unique && !d.UniqueNoIndex) {
if !d.PrimaryKey.Sharded {
idx = &descpb.IndexDescriptor{
Unique: true,
Expand Down
11 changes: 10 additions & 1 deletion pkg/sql/create_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -1468,6 +1468,11 @@ func NewTableDesc(
return nil, unimplemented.NewWithIssue(9148, "use CREATE INDEX to make interleaved indexes")
}
case *tree.UniqueConstraintTableDef:
if d.NoIndex {
return nil, pgerror.New(pgcode.FeatureNotSupported,
"unique constraints without an index are not yet supported",
)
}
idx := descpb.IndexDescriptor{
Name: string(d.Name),
Unique: true,
Expand Down Expand Up @@ -2077,7 +2082,11 @@ func incTelemetryForNewColumn(def *tree.ColumnTableDef, desc *descpb.ColumnDescr
telemetry.Inc(sqltelemetry.SchemaNewColumnTypeQualificationCounter("default_expr"))
}
if def.Unique {
telemetry.Inc(sqltelemetry.SchemaNewColumnTypeQualificationCounter("unique"))
if def.UniqueNoIndex {
telemetry.Inc(sqltelemetry.SchemaNewColumnTypeQualificationCounter("unique_without_index"))
} else {
telemetry.Inc(sqltelemetry.SchemaNewColumnTypeQualificationCounter("unique"))
}
}
}

Expand Down
14 changes: 14 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/alter_table
Original file line number Diff line number Diff line change
Expand Up @@ -1401,3 +1401,17 @@ ALTER TABLE t_cannot_rename_constraint_over_index RENAME CONSTRAINT v_unique TO

statement error relation idx_v already exists
ALTER TABLE t_cannot_rename_constraint_over_index RENAME CONSTRAINT "primary" TO idx_v;

subtest unique_without_index

statement ok
CREATE TABLE unique_without_index (
a INT,
b INT
)

statement error unique constraints without an index are not yet supported
ALTER TABLE unique_without_index ADD COLUMN c INT UNIQUE WITHOUT INDEX

statement error unique constraints without an index are not yet supported
ALTER TABLE unique_without_index ADD CONSTRAINT ab UNIQUE WITHOUT INDEX (a, b)
8 changes: 8 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/create_table
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,11 @@ CREATE TABLE error (LIKE like_hash_base INCLUDING STATISTICS)

statement error unimplemented
CREATE TABLE error (LIKE like_hash_base INCLUDING STORAGE)

subtest unique_without_index

statement error unique constraints without an index are not yet supported
CREATE TABLE unique_without_index (a INT UNIQUE WITHOUT INDEX)

statement error unique constraints without an index are not yet supported
CREATE TABLE unique_without_index (a INT, b INT, CONSTRAINT ab UNIQUE WITHOUT INDEX (a, b))
14 changes: 12 additions & 2 deletions pkg/sql/mutations/mutations.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,13 +278,15 @@ func statisticsMutator(
DistinctCount: distinctCount,
NullCount: nullCount,
}
if def.Unique || def.PrimaryKey.IsPrimaryKey {
if (def.Unique && !def.UniqueNoIndex) || def.PrimaryKey.IsPrimaryKey {
makeHistogram(def)
}
case *tree.IndexTableDef:
makeHistogram(cols[def.Columns[0].Column])
case *tree.UniqueConstraintTableDef:
makeHistogram(cols[def.Columns[0].Column])
if !def.NoIndex {
makeHistogram(cols[def.Columns[0].Column])
}
}
}
if len(colStats) > 0 {
Expand Down Expand Up @@ -569,6 +571,10 @@ var postgresStatementMutator MultiStatementMutation = func(rng *rand.Rand, stmts
def.Family.Create = false
changed = true
}
if def.UniqueNoIndex {
def.UniqueNoIndex = false
changed = true
}
case *tree.UniqueConstraintTableDef:
if def.Interleave != nil {
def.Interleave = nil
Expand All @@ -578,6 +584,10 @@ var postgresStatementMutator MultiStatementMutation = func(rng *rand.Rand, stmts
def.PartitionBy = nil
changed = true
}
if def.NoIndex {
def.NoIndex = false
changed = true
}
}
}
case *tree.AlterTable:
Expand Down
12 changes: 12 additions & 0 deletions pkg/sql/opt/testutils/testcat/create_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"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 @@ -178,6 +180,11 @@ func (tc *Catalog) CreateTable(stmt *tree.CreateTable) *Table {
for _, def := range stmt.Defs {
switch def := def.(type) {
case *tree.UniqueConstraintTableDef:
if def.NoIndex {
panic(pgerror.New(pgcode.FeatureNotSupported,
"unique constraints without an index are not yet supported",
))
}
if !def.PrimaryKey {
tab.addIndex(&def.IndexTableDef, uniqueIndex)
}
Expand All @@ -189,6 +196,11 @@ func (tc *Catalog) CreateTable(stmt *tree.CreateTable) *Table {
tab.addFamily(def)

case *tree.ColumnTableDef:
if def.UniqueNoIndex {
panic(pgerror.New(pgcode.FeatureNotSupported,
"unique constraints without an index are not yet supported",
))
}
if def.Unique {
tab.addIndex(
&tree.IndexTableDef{
Expand Down
5 changes: 4 additions & 1 deletion pkg/sql/parser/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ func TestParse(t *testing.T) {
{`CREATE TABLE a (b INT8 CONSTRAINT always NOT NULL)`},
{`CREATE TABLE a (b INT8 PRIMARY KEY)`},
{`CREATE TABLE a (b INT8 UNIQUE)`},
{`CREATE TABLE a (b INT8 UNIQUE WITHOUT INDEX)`},
{`CREATE TABLE a (b INT8 NULL PRIMARY KEY)`},
{`CREATE TABLE a (b INT8 DEFAULT 1)`},
{`CREATE TABLE a (b INT8 CONSTRAINT one DEFAULT 1)`},
Expand Down Expand Up @@ -223,6 +224,7 @@ func TestParse(t *testing.T) {
{`CREATE TABLE a (b INT8, c STRING, INDEX (b, c))`},
{`CREATE TABLE a (b INT8, c STRING, INDEX d (b, c))`},
{`CREATE TABLE a (b INT8, c STRING, CONSTRAINT d UNIQUE (b, c))`},
{`CREATE TABLE a (b INT8, c STRING, CONSTRAINT d UNIQUE WITHOUT INDEX (b, c))`},
{`CREATE TABLE a (b INT8, c STRING, CONSTRAINT d UNIQUE (b, c) INTERLEAVE IN PARENT d (e, f))`},
{`CREATE TABLE a (b INT8, UNIQUE (b))`},
{`CREATE TABLE a (b INT8, UNIQUE (b) STORING (c))`},
Expand Down Expand Up @@ -271,6 +273,7 @@ func TestParse(t *testing.T) {
{`CREATE TABLE a (b INT8 CONSTRAINT c PRIMARY KEY)`},
{`CREATE TABLE a (b INT8 CONSTRAINT c NULL)`},
{`CREATE TABLE a (b INT8 CONSTRAINT c UNIQUE)`},
{`CREATE TABLE a (b INT8 CONSTRAINT c UNIQUE WITHOUT INDEX)`},
{`CREATE TABLE a (b INT8 CONSTRAINT c DEFAULT d)`},
{`CREATE TABLE a (b INT8 CONSTRAINT c CHECK (d))`},
{`CREATE TABLE a (b INT8 CONSTRAINT c REFERENCES d)`},
Expand Down Expand Up @@ -1351,7 +1354,7 @@ func TestParse(t *testing.T) {
{`ALTER TABLE a ADD COLUMN IF NOT EXISTS b INT8, ADD CONSTRAINT a_idx UNIQUE (a)`},
{`ALTER TABLE IF EXISTS a ADD COLUMN b INT8, ADD CONSTRAINT a_idx UNIQUE (a)`},
{`ALTER TABLE IF EXISTS a ADD COLUMN IF NOT EXISTS b INT8, ADD CONSTRAINT a_idx UNIQUE (a)`},
{`ALTER TABLE a ADD COLUMN b INT8, ADD CONSTRAINT a_idx UNIQUE (a)`},
{`ALTER TABLE a ADD COLUMN b INT8 UNIQUE WITHOUT INDEX, ADD CONSTRAINT a_no_idx UNIQUE WITHOUT INDEX (a)`},
{`ALTER TABLE a ADD COLUMN IF NOT EXISTS b INT8, ADD CONSTRAINT a_idx UNIQUE (a) NOT VALID`},
{`ALTER TABLE IF EXISTS a ADD COLUMN b INT8, ADD CONSTRAINT a_idx UNIQUE (a)`},
{`ALTER TABLE IF EXISTS a ADD COLUMN IF NOT EXISTS b INT8, ADD CONSTRAINT a_idx UNIQUE (a)`},
Expand Down
38 changes: 26 additions & 12 deletions pkg/sql/parser/sql.y
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,7 @@ func (u *sqlSymUnion) refreshDataOption() tree.RefreshDataOption {
%type <types.IntervalTypeMetadata> opt_interval_qualifier interval_qualifier interval_second
%type <tree.Expr> overlay_placing

%type <bool> opt_unique opt_concurrently opt_cluster
%type <bool> opt_unique opt_concurrently opt_cluster opt_without_index
%type <bool> opt_index_access_method

%type <*tree.Limit> limit_clause offset_clause opt_limit_clause
Expand Down Expand Up @@ -1317,7 +1317,7 @@ alter_ddl_stmt:
// ALTER TABLE ... SET SCHEMA <newschemaname>
//
// Column qualifiers:
// [CONSTRAINT <constraintname>] {NULL | NOT NULL | UNIQUE | PRIMARY KEY | CHECK (<expr>) | DEFAULT <expr>}
// [CONSTRAINT <constraintname>] {NULL | NOT NULL | UNIQUE [WITHOUT INDEX] | PRIMARY KEY | CHECK (<expr>) | DEFAULT <expr>}
// FAMILY <familyname>, CREATE [IF NOT EXISTS] FAMILY [<familyname>]
// REFERENCES <tablename> [( <colnames...> )]
// COLLATE <collationname>
Expand Down Expand Up @@ -5426,11 +5426,11 @@ alter_schema_stmt:
// Table constraints:
// PRIMARY KEY ( <colnames...> ) [USING HASH WITH BUCKET_COUNT = <shard_buckets>]
// FOREIGN KEY ( <colnames...> ) REFERENCES <tablename> [( <colnames...> )] [ON DELETE {NO ACTION | RESTRICT}] [ON UPDATE {NO ACTION | RESTRICT}]
// UNIQUE ( <colnames... ) [{STORING | INCLUDE | COVERING} ( <colnames...> )] [<interleave>]
// UNIQUE [WITHOUT INDEX] ( <colnames... ) [{STORING | INCLUDE | COVERING} ( <colnames...> )] [<interleave>]
// CHECK ( <expr> )
//
// Column qualifiers:
// [CONSTRAINT <constraintname>] {NULL | NOT NULL | UNIQUE | PRIMARY KEY | CHECK (<expr>) | DEFAULT <expr>}
// [CONSTRAINT <constraintname>] {NULL | NOT NULL | UNIQUE [WITHOUT INDEX] | PRIMARY KEY | CHECK (<expr>) | DEFAULT <expr>}
// FAMILY <familyname>, CREATE [IF NOT EXISTS] FAMILY [<familyname>]
// REFERENCES <tablename> [( <colnames...> )] [ON DELETE {NO ACTION | RESTRICT}] [ON UPDATE {NO ACTION | RESTRICT}]
// COLLATE <collationname>
Expand Down Expand Up @@ -5868,9 +5868,11 @@ col_qualification_elem:
{
$$.val = tree.NullConstraint{}
}
| UNIQUE
| UNIQUE opt_without_index
{
$$.val = tree.UniqueConstraint{}
$$.val = tree.UniqueConstraint{
NoIndex: $2.bool(),
}
}
| PRIMARY KEY
{
Expand Down Expand Up @@ -5915,6 +5917,16 @@ col_qualification_elem:
return 1
}

opt_without_index:
WITHOUT INDEX
{
$$.val = true
}
| /* EMPTY */
{
$$.val = false
}

// GENERATED ALWAYS is a noise word for compatibility with Postgres.
generated_as:
AS {}
Expand Down Expand Up @@ -5991,15 +6003,17 @@ constraint_elem:
Expr: $3.expr(),
}
}
| UNIQUE '(' index_params ')' opt_storing opt_interleave opt_partition_by opt_deferrable opt_where_clause
| UNIQUE opt_without_index '(' index_params ')'
opt_storing opt_interleave opt_partition_by opt_deferrable opt_where_clause
{
$$.val = &tree.UniqueConstraintTableDef{
NoIndex: $2.bool(),
IndexTableDef: tree.IndexTableDef{
Columns: $3.idxElems(),
Storing: $5.nameList(),
Interleave: $6.interleave(),
PartitionBy: $7.partitionBy(),
Predicate: $9.expr(),
Columns: $4.idxElems(),
Storing: $6.nameList(),
Interleave: $7.interleave(),
PartitionBy: $8.partitionBy(),
Predicate: $10.expr(),
},
}
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/sql/rowenc/testutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -1368,7 +1368,7 @@ func IndexStoringMutator(rng *rand.Rand, stmts []tree.Statement) ([]tree.Stateme
case *tree.IndexTableDef:
idx = defType
case *tree.UniqueConstraintTableDef:
if !defType.PrimaryKey {
if !defType.PrimaryKey && !defType.NoIndex {
idx = &defType.IndexTableDef
}
}
Expand Down Expand Up @@ -1422,7 +1422,7 @@ func PartialIndexMutator(rng *rand.Rand, stmts []tree.Statement) ([]tree.Stateme
case *tree.IndexTableDef:
idx = defType
case *tree.UniqueConstraintTableDef:
if !defType.PrimaryKey {
if !defType.PrimaryKey && !defType.NoIndex {
idx = &defType.IndexTableDef
}
}
Expand Down
Loading

0 comments on commit 51387f6

Please sign in to comment.