Skip to content

Commit

Permalink
sql: Add IF NOT EXISTS modifier to ALTER TABLE ... ADD CONSTRAINT
Browse files Browse the repository at this point in the history
Previously, it was not possible to execute ALTER TABLE ... ADD
CONSTRAINT IF NOT EXISTS ... statements. This commit adds this IF NOT
EXISTS modifier to the ADD CONSTRAINT command of the ALTER TABLE
statement. When the modifier is present and the defined constraint name
already exists in the table, the statement no-ops instead of erroring.

Note that this syntax is not supported in PostgreSQL, however PostgreSQL
has transactional schema changes and PL/pgSQL which make it possible to
achieve the same thing. CockroachDB has none of that yet.

Fixes cockroachdb#53007.

Release note (sql change): added ALTER TABLE ... ADD CONSTRAINT IF NOT
EXISTS.
  • Loading branch information
Marius Posta committed Oct 7, 2021
1 parent d7d6a17 commit 5d2cebf
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 28 deletions.
4 changes: 2 additions & 2 deletions docs/generated/sql/bnf/alter_table.bnf
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
alter_onetable_stmt ::=
'ALTER' 'TABLE' table_name ( ( ( 'RENAME' ( 'COLUMN' | ) column_name 'TO' column_name | 'RENAME' 'CONSTRAINT' column_name 'TO' column_name | 'ADD' ( column_name typename col_qual_list ) | 'ADD' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DEFAULT' a_expr | 'DROP' 'DEFAULT' ) | 'ALTER' ( 'COLUMN' | ) column_name alter_column_on_update | 'ALTER' ( 'COLUMN' | ) column_name alter_column_visible | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'NOT' 'NULL' | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'STORED' | 'ALTER' ( 'COLUMN' | ) column_name 'SET' 'NOT' 'NULL' | 'DROP' ( 'COLUMN' | ) 'IF' 'EXISTS' column_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' ( 'COLUMN' | ) column_name ( 'CASCADE' | 'RESTRICT' | ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DATA' | ) 'TYPE' typename ( 'COLLATE' collation_name | ) ( 'USING' a_expr | ) | 'ADD' ( 'CONSTRAINT' constraint_name constraint_elem | constraint_elem ) | 'ALTER' 'PRIMARY' 'KEY' 'USING' 'COLUMNS' '(' index_params ')' opt_hash_sharded opt_interleave | 'VALIDATE' 'CONSTRAINT' constraint_name | 'DROP' 'CONSTRAINT' 'IF' 'EXISTS' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' 'CONSTRAINT' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'EXPERIMENTAL_AUDIT' 'SET' audit_mode | partition_by_table ) ) ( ( ',' ( 'RENAME' ( 'COLUMN' | ) column_name 'TO' column_name | 'RENAME' 'CONSTRAINT' column_name 'TO' column_name | 'ADD' ( column_name typename col_qual_list ) | 'ADD' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DEFAULT' a_expr | 'DROP' 'DEFAULT' ) | 'ALTER' ( 'COLUMN' | ) column_name alter_column_on_update | 'ALTER' ( 'COLUMN' | ) column_name alter_column_visible | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'NOT' 'NULL' | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'STORED' | 'ALTER' ( 'COLUMN' | ) column_name 'SET' 'NOT' 'NULL' | 'DROP' ( 'COLUMN' | ) 'IF' 'EXISTS' column_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' ( 'COLUMN' | ) column_name ( 'CASCADE' | 'RESTRICT' | ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DATA' | ) 'TYPE' typename ( 'COLLATE' collation_name | ) ( 'USING' a_expr | ) | 'ADD' ( 'CONSTRAINT' constraint_name constraint_elem | constraint_elem ) | 'ALTER' 'PRIMARY' 'KEY' 'USING' 'COLUMNS' '(' index_params ')' opt_hash_sharded opt_interleave | 'VALIDATE' 'CONSTRAINT' constraint_name | 'DROP' 'CONSTRAINT' 'IF' 'EXISTS' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' 'CONSTRAINT' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'EXPERIMENTAL_AUDIT' 'SET' audit_mode | partition_by_table ) ) )* )
| 'ALTER' 'TABLE' 'IF' 'EXISTS' table_name ( ( ( 'RENAME' ( 'COLUMN' | ) column_name 'TO' column_name | 'RENAME' 'CONSTRAINT' column_name 'TO' column_name | 'ADD' ( column_name typename col_qual_list ) | 'ADD' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DEFAULT' a_expr | 'DROP' 'DEFAULT' ) | 'ALTER' ( 'COLUMN' | ) column_name alter_column_on_update | 'ALTER' ( 'COLUMN' | ) column_name alter_column_visible | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'NOT' 'NULL' | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'STORED' | 'ALTER' ( 'COLUMN' | ) column_name 'SET' 'NOT' 'NULL' | 'DROP' ( 'COLUMN' | ) 'IF' 'EXISTS' column_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' ( 'COLUMN' | ) column_name ( 'CASCADE' | 'RESTRICT' | ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DATA' | ) 'TYPE' typename ( 'COLLATE' collation_name | ) ( 'USING' a_expr | ) | 'ADD' ( 'CONSTRAINT' constraint_name constraint_elem | constraint_elem ) | 'ALTER' 'PRIMARY' 'KEY' 'USING' 'COLUMNS' '(' index_params ')' opt_hash_sharded opt_interleave | 'VALIDATE' 'CONSTRAINT' constraint_name | 'DROP' 'CONSTRAINT' 'IF' 'EXISTS' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' 'CONSTRAINT' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'EXPERIMENTAL_AUDIT' 'SET' audit_mode | partition_by_table ) ) ( ( ',' ( 'RENAME' ( 'COLUMN' | ) column_name 'TO' column_name | 'RENAME' 'CONSTRAINT' column_name 'TO' column_name | 'ADD' ( column_name typename col_qual_list ) | 'ADD' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DEFAULT' a_expr | 'DROP' 'DEFAULT' ) | 'ALTER' ( 'COLUMN' | ) column_name alter_column_on_update | 'ALTER' ( 'COLUMN' | ) column_name alter_column_visible | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'NOT' 'NULL' | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'STORED' | 'ALTER' ( 'COLUMN' | ) column_name 'SET' 'NOT' 'NULL' | 'DROP' ( 'COLUMN' | ) 'IF' 'EXISTS' column_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' ( 'COLUMN' | ) column_name ( 'CASCADE' | 'RESTRICT' | ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DATA' | ) 'TYPE' typename ( 'COLLATE' collation_name | ) ( 'USING' a_expr | ) | 'ADD' ( 'CONSTRAINT' constraint_name constraint_elem | constraint_elem ) | 'ALTER' 'PRIMARY' 'KEY' 'USING' 'COLUMNS' '(' index_params ')' opt_hash_sharded opt_interleave | 'VALIDATE' 'CONSTRAINT' constraint_name | 'DROP' 'CONSTRAINT' 'IF' 'EXISTS' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' 'CONSTRAINT' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'EXPERIMENTAL_AUDIT' 'SET' audit_mode | partition_by_table ) ) )* )
'ALTER' 'TABLE' table_name ( ( ( 'RENAME' ( 'COLUMN' | ) column_name 'TO' column_name | 'RENAME' 'CONSTRAINT' column_name 'TO' column_name | 'ADD' ( column_name typename col_qual_list ) | 'ADD' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DEFAULT' a_expr | 'DROP' 'DEFAULT' ) | 'ALTER' ( 'COLUMN' | ) column_name alter_column_on_update | 'ALTER' ( 'COLUMN' | ) column_name alter_column_visible | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'NOT' 'NULL' | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'STORED' | 'ALTER' ( 'COLUMN' | ) column_name 'SET' 'NOT' 'NULL' | 'DROP' ( 'COLUMN' | ) 'IF' 'EXISTS' column_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' ( 'COLUMN' | ) column_name ( 'CASCADE' | 'RESTRICT' | ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DATA' | ) 'TYPE' typename ( 'COLLATE' collation_name | ) ( 'USING' a_expr | ) | 'ADD' ( 'CONSTRAINT' constraint_name constraint_elem | constraint_elem ) | 'ADD' 'CONSTRAINT' 'IF' 'NOT' 'EXISTS' constraint_name constraint_elem | 'ALTER' 'PRIMARY' 'KEY' 'USING' 'COLUMNS' '(' index_params ')' opt_hash_sharded opt_interleave | 'VALIDATE' 'CONSTRAINT' constraint_name | 'DROP' 'CONSTRAINT' 'IF' 'EXISTS' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' 'CONSTRAINT' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'EXPERIMENTAL_AUDIT' 'SET' audit_mode | partition_by_table ) ) ( ( ',' ( 'RENAME' ( 'COLUMN' | ) column_name 'TO' column_name | 'RENAME' 'CONSTRAINT' column_name 'TO' column_name | 'ADD' ( column_name typename col_qual_list ) | 'ADD' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DEFAULT' a_expr | 'DROP' 'DEFAULT' ) | 'ALTER' ( 'COLUMN' | ) column_name alter_column_on_update | 'ALTER' ( 'COLUMN' | ) column_name alter_column_visible | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'NOT' 'NULL' | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'STORED' | 'ALTER' ( 'COLUMN' | ) column_name 'SET' 'NOT' 'NULL' | 'DROP' ( 'COLUMN' | ) 'IF' 'EXISTS' column_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' ( 'COLUMN' | ) column_name ( 'CASCADE' | 'RESTRICT' | ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DATA' | ) 'TYPE' typename ( 'COLLATE' collation_name | ) ( 'USING' a_expr | ) | 'ADD' ( 'CONSTRAINT' constraint_name constraint_elem | constraint_elem ) | 'ADD' 'CONSTRAINT' 'IF' 'NOT' 'EXISTS' constraint_name constraint_elem | 'ALTER' 'PRIMARY' 'KEY' 'USING' 'COLUMNS' '(' index_params ')' opt_hash_sharded opt_interleave | 'VALIDATE' 'CONSTRAINT' constraint_name | 'DROP' 'CONSTRAINT' 'IF' 'EXISTS' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' 'CONSTRAINT' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'EXPERIMENTAL_AUDIT' 'SET' audit_mode | partition_by_table ) ) )* )
| 'ALTER' 'TABLE' 'IF' 'EXISTS' table_name ( ( ( 'RENAME' ( 'COLUMN' | ) column_name 'TO' column_name | 'RENAME' 'CONSTRAINT' column_name 'TO' column_name | 'ADD' ( column_name typename col_qual_list ) | 'ADD' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DEFAULT' a_expr | 'DROP' 'DEFAULT' ) | 'ALTER' ( 'COLUMN' | ) column_name alter_column_on_update | 'ALTER' ( 'COLUMN' | ) column_name alter_column_visible | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'NOT' 'NULL' | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'STORED' | 'ALTER' ( 'COLUMN' | ) column_name 'SET' 'NOT' 'NULL' | 'DROP' ( 'COLUMN' | ) 'IF' 'EXISTS' column_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' ( 'COLUMN' | ) column_name ( 'CASCADE' | 'RESTRICT' | ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DATA' | ) 'TYPE' typename ( 'COLLATE' collation_name | ) ( 'USING' a_expr | ) | 'ADD' ( 'CONSTRAINT' constraint_name constraint_elem | constraint_elem ) | 'ADD' 'CONSTRAINT' 'IF' 'NOT' 'EXISTS' constraint_name constraint_elem | 'ALTER' 'PRIMARY' 'KEY' 'USING' 'COLUMNS' '(' index_params ')' opt_hash_sharded opt_interleave | 'VALIDATE' 'CONSTRAINT' constraint_name | 'DROP' 'CONSTRAINT' 'IF' 'EXISTS' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' 'CONSTRAINT' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'EXPERIMENTAL_AUDIT' 'SET' audit_mode | partition_by_table ) ) ( ( ',' ( 'RENAME' ( 'COLUMN' | ) column_name 'TO' column_name | 'RENAME' 'CONSTRAINT' column_name 'TO' column_name | 'ADD' ( column_name typename col_qual_list ) | 'ADD' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' ( column_name typename col_qual_list ) | 'ADD' 'COLUMN' 'IF' 'NOT' 'EXISTS' ( column_name typename col_qual_list ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DEFAULT' a_expr | 'DROP' 'DEFAULT' ) | 'ALTER' ( 'COLUMN' | ) column_name alter_column_on_update | 'ALTER' ( 'COLUMN' | ) column_name alter_column_visible | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'NOT' 'NULL' | 'ALTER' ( 'COLUMN' | ) column_name 'DROP' 'STORED' | 'ALTER' ( 'COLUMN' | ) column_name 'SET' 'NOT' 'NULL' | 'DROP' ( 'COLUMN' | ) 'IF' 'EXISTS' column_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' ( 'COLUMN' | ) column_name ( 'CASCADE' | 'RESTRICT' | ) | 'ALTER' ( 'COLUMN' | ) column_name ( 'SET' 'DATA' | ) 'TYPE' typename ( 'COLLATE' collation_name | ) ( 'USING' a_expr | ) | 'ADD' ( 'CONSTRAINT' constraint_name constraint_elem | constraint_elem ) | 'ADD' 'CONSTRAINT' 'IF' 'NOT' 'EXISTS' constraint_name constraint_elem | 'ALTER' 'PRIMARY' 'KEY' 'USING' 'COLUMNS' '(' index_params ')' opt_hash_sharded opt_interleave | 'VALIDATE' 'CONSTRAINT' constraint_name | 'DROP' 'CONSTRAINT' 'IF' 'EXISTS' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'DROP' 'CONSTRAINT' constraint_name ( 'CASCADE' | 'RESTRICT' | ) | 'EXPERIMENTAL_AUDIT' 'SET' audit_mode | partition_by_table ) ) )* )
1 change: 1 addition & 0 deletions docs/generated/sql/bnf/stmt_block.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -2675,6 +2675,7 @@ alter_table_cmd ::=
| 'DROP' opt_column column_name opt_drop_behavior
| 'ALTER' opt_column column_name opt_set_data 'TYPE' typename opt_collate opt_alter_column_using
| 'ADD' table_constraint opt_validate_behavior
| 'ADD' 'CONSTRAINT' 'IF' 'NOT' 'EXISTS' constraint_name constraint_elem opt_validate_behavior
| 'ALTER' 'PRIMARY' 'KEY' 'USING' 'COLUMNS' '(' index_params ')' opt_hash_sharded opt_interleave
| 'VALIDATE' 'CONSTRAINT' constraint_name
| 'DROP' 'CONSTRAINT' 'IF' 'EXISTS' constraint_name opt_drop_behavior
Expand Down
90 changes: 76 additions & 14 deletions pkg/sql/alter_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,12 @@ func (n *alterTableNode) startExec(params runParams) error {
return err
}
case *tree.AlterTableAddConstraint:
skip, err := validateConstraintNameIsNotUsed(n.tableDesc, t)
if err != nil {
return err
} else if skip {
continue
}
switch d := t.ConstraintDef.(type) {
case *tree.UniqueConstraintTableDef:
if d.WithoutIndex {
Expand All @@ -214,15 +220,6 @@ func (n *alterTableNode) startExec(params runParams) error {
}

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
// was processed before this statement. If a DROP PRIMARY KEY
// statement was processed, then n.tableDesc.HasPrimaryKey() = false.
if n.tableDesc.HasPrimaryKey() && !n.tableDesc.IsPrimaryIndexDefaultRowID() {
return pgerror.Newf(pgcode.InvalidTableDefinition,
"multiple primary keys for table %q are not allowed", n.tableDesc.Name)
}

// Translate this operation into an ALTER PRIMARY KEY command.
alterPK := &tree.AlterTableAlterPrimaryKey{
Columns: d.Columns,
Expand Down Expand Up @@ -277,11 +274,6 @@ func (n *alterTableNode) startExec(params runParams) error {
return err
}
}
// If the index is named, ensure that the name is unique.
// Unnamed indexes will be given a unique auto-generated name later on.
if d.Name != "" && n.tableDesc.ValidateIndexNameIsUnique(d.Name.String()) != nil {
return pgerror.Newf(pgcode.DuplicateRelation, "duplicate index name: %q", d.Name)
}
idx := descpb.IndexDescriptor{
Name: string(d.Name),
Unique: true,
Expand Down Expand Up @@ -1564,6 +1556,76 @@ func (p *planner) removeColumnComment(
return err
}

// validateConstraintNameIsNotUsed checks that the name of the constraint we're
// trying to add isn't already used, and, if it is, whether the constraint
// addition should be skipped:
// - if the name is free to use, it returns false;
// - if it's already used but IF NOT EXISTS was specified, it returns true;
// - otherwise, it returns an error.
func validateConstraintNameIsNotUsed(
tableDesc *tabledesc.Mutable, cmd *tree.AlterTableAddConstraint,
) (skipAddConstraint bool, _ error) {
var name tree.Name
var hasIfNotExists bool
switch d := cmd.ConstraintDef.(type) {
case *tree.CheckConstraintTableDef:
name = d.Name
hasIfNotExists = d.IfNotExists
case *tree.ForeignKeyConstraintTableDef:
name = d.Name
hasIfNotExists = d.IfNotExists
case *tree.UniqueConstraintTableDef:
name = d.Name
hasIfNotExists = d.IfNotExists
if d.WithoutIndex {
break
}
// Handle edge cases specific to unique constraints with indexes.
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
// was processed before this statement. If a DROP PRIMARY KEY
// statement was processed, then n.tableDesc.HasPrimaryKey() = false.
if tableDesc.HasPrimaryKey() && !tableDesc.IsPrimaryIndexDefaultRowID() {
if d.IfNotExists {
return true, nil
}
return false, pgerror.Newf(pgcode.InvalidTableDefinition,
"multiple primary keys for table %q are not allowed", tableDesc.Name)
}
}
if name == "" || tableDesc.ValidateIndexNameIsUnique(name.String()) == nil {
return false, nil
}
if d.IfNotExists {
return true, nil
}
// A specific pgcode is returned in this edge case.
return false, pgerror.Newf(pgcode.DuplicateRelation, "duplicate index name: %q", name)

default:
return false, errors.AssertionFailedf(
"unsupported constraint: %T", cmd.ConstraintDef)
}

if name == "" {
return false, nil
}
info, err := tableDesc.GetConstraintInfo()
if err != nil {
// Unexpected error: table descriptor should be valid at this point.
return false, errors.WithAssertionFailure(err)
}
if _, isInUse := info[name.String()]; !isInUse {
return false, nil
}
if hasIfNotExists {
return true, nil
}
return false, pgerror.Newf(pgcode.DuplicateObject,
"duplicate constraint name: %q", name)
}

// updateFKBackReferenceName updates the name of a foreign key reference on
// the referenced table descriptor.
// TODO (lucy): This method is meant to be analogous to removeFKBackReference,
Expand Down
7 changes: 5 additions & 2 deletions pkg/sql/logictest/testdata/logic_test/alter_primary_key
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ t CREATE TABLE public.t (
statement ok
CREATE INDEX i ON t (x);

statement error pq: constraint with name i already exists
statement error pgcode 42P07 duplicate index name: "i"
ALTER TABLE t DROP CONSTRAINT "my_pk", ADD CONSTRAINT "i" PRIMARY KEY (x);

# Regression for #45362.
Expand Down Expand Up @@ -628,7 +628,10 @@ statement error pq: multiple primary keys for table "t" are not allowed
ALTER TABLE t ADD CONSTRAINT "primary" PRIMARY KEY (y)

statement ok
ALTER TABLE t DROP CONSTRAINT "primary", ADD CONSTRAINT "primary" PRIMARY KEY (y)
ALTER TABLE t DROP CONSTRAINT "primary", ADD CONSTRAINT IF NOT EXISTS "primary" PRIMARY KEY (y)

statement ok
ALTER TABLE t ADD CONSTRAINT IF NOT EXISTS "primary" PRIMARY KEY (x)

query TT
SHOW CREATE t
Expand Down
17 changes: 16 additions & 1 deletion pkg/sql/logictest/testdata/logic_test/alter_table
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ b INT8 true NULL · {pr
statement ok
ALTER TABLE t ADD CONSTRAINT foo UNIQUE (b)

statement ok
ALTER TABLE t ADD CONSTRAINT IF NOT EXISTS foo UNIQUE (b)

statement ok
ALTER TABLE t ADD CONSTRAINT IF NOT EXISTS foo UNIQUE (f)

query TTTTRT
SELECT job_type, description, user_name, status, fraction_completed, error
FROM crdb_internal.jobs
Expand Down Expand Up @@ -116,7 +122,7 @@ statement ok
DELETE FROM t WHERE a = -2

statement ok
ALTER TABLE t ADD CONSTRAINT check_a CHECK (a > 0)
ALTER TABLE t ADD CONSTRAINT IF NOT EXISTS check_a CHECK (a > 0)

statement error CHECK
INSERT INTO t (a) VALUES (-3)
Expand All @@ -132,9 +138,18 @@ t primary PRIMARY KEY PRIMARY KEY (a ASC) true
statement error duplicate constraint name
ALTER TABLE t ADD CONSTRAINT check_a CHECK (a > 0)

statement ok
ALTER TABLE t ADD CONSTRAINT IF NOT EXISTS check_a CHECK (a < 0)

statement error duplicate constraint name
ALTER TABLE t ADD CONSTRAINT fk_f_ref_other FOREIGN KEY (a) REFERENCES other (b)

statement ok
ALTER TABLE t ADD CONSTRAINT IF NOT EXISTS fk_f_ref_other FOREIGN KEY (a) REFERENCES other (b)

statement ok
ALTER TABLE t ADD CONSTRAINT IF NOT EXISTS fk_f_ref_other CHECK (a < 0)

# added constraints with generated names avoid name collisions.
statement ok
ALTER TABLE t ADD CHECK (a > 0)
Expand Down
11 changes: 11 additions & 0 deletions pkg/sql/parser/sql.y
Original file line number Diff line number Diff line change
Expand Up @@ -2218,6 +2218,17 @@ alter_table_cmd:
ValidationBehavior: $3.validationBehavior(),
}
}
// ALTER TABLE <name> ADD CONSTRAINT IF NOT EXISTS ...
| ADD CONSTRAINT IF NOT EXISTS constraint_name constraint_elem opt_validate_behavior
{
def := $7.constraintDef()
def.SetName(tree.Name($6))
def.SetIfNotExists()
$$.val = &tree.AlterTableAddConstraint{
ConstraintDef: def,
ValidationBehavior: $8.validationBehavior(),
}
}
// ALTER TABLE <name> ALTER CONSTRAINT ...
| ALTER CONSTRAINT constraint_name error { return unimplementedWithIssueDetail(sqllex, 31632, "alter constraint") }
// ALTER TABLE <name> INHERITS ....
Expand Down
Loading

0 comments on commit 5d2cebf

Please sign in to comment.