Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sql: add support for IF NOT EXISTS for ALTER TABLE ADD CONSTRAINT #53095

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/generated/sql/bnf/stmt_block.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -2101,7 +2101,8 @@ family_def ::=
'FAMILY' opt_family_name '(' name_list ')'

table_constraint ::=
'CONSTRAINT' constraint_name constraint_elem
'CONSTRAINT' 'IF' 'NOT' 'EXISTS' constraint_name constraint_elem
| 'CONSTRAINT' constraint_name constraint_elem
| constraint_elem

opt_validate_behavior ::=
Expand Down
9 changes: 8 additions & 1 deletion docs/generated/sql/bnf/table_constraint.bnf
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
table_constraint ::=
'CONSTRAINT' constraint_name 'CHECK' '(' a_expr ')'
'CONSTRAINT' 'IF' 'NOT' 'EXISTS' constraint_name 'CHECK' '(' a_expr ')'
| 'CONSTRAINT' 'IF' 'NOT' 'EXISTS' constraint_name 'UNIQUE' '(' index_params ')' 'COVERING' '(' name_list ')' opt_interleave opt_partition_by opt_where_clause
| 'CONSTRAINT' 'IF' 'NOT' 'EXISTS' constraint_name 'UNIQUE' '(' index_params ')' 'STORING' '(' name_list ')' opt_interleave opt_partition_by opt_where_clause
| 'CONSTRAINT' 'IF' 'NOT' 'EXISTS' constraint_name 'UNIQUE' '(' index_params ')' 'INCLUDE' '(' name_list ')' opt_interleave opt_partition_by opt_where_clause
| 'CONSTRAINT' 'IF' 'NOT' 'EXISTS' constraint_name 'UNIQUE' '(' index_params ')' opt_interleave opt_partition_by opt_where_clause
| 'CONSTRAINT' 'IF' 'NOT' 'EXISTS' constraint_name 'PRIMARY' 'KEY' '(' index_params ')' opt_hash_sharded opt_interleave
| 'CONSTRAINT' 'IF' 'NOT' 'EXISTS' constraint_name 'FOREIGN' 'KEY' '(' name_list ')' 'REFERENCES' table_name opt_column_list key_match reference_actions
| 'CONSTRAINT' constraint_name 'CHECK' '(' a_expr ')'
| 'CONSTRAINT' constraint_name 'UNIQUE' '(' index_params ')' 'COVERING' '(' name_list ')' opt_interleave opt_partition_by_index opt_where_clause
| 'CONSTRAINT' constraint_name 'UNIQUE' '(' index_params ')' 'STORING' '(' name_list ')' opt_interleave opt_partition_by_index opt_where_clause
| 'CONSTRAINT' constraint_name 'UNIQUE' '(' index_params ')' 'INCLUDE' '(' name_list ')' opt_interleave opt_partition_by_index opt_where_clause
Expand Down
8 changes: 8 additions & 0 deletions pkg/sql/alter_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ func (n *alterTableNode) startExec(params runParams) error {
descriptorChanged := false
origNumMutations := len(n.tableDesc.Mutations)
var droppedViews []string
skipIfExists := false
resolved := params.p.ResolvedName(n.n.Table)
tn, ok := resolved.(*tree.TableName)
if !ok {
Expand Down Expand Up @@ -320,6 +321,7 @@ func (n *alterTableNode) startExec(params runParams) error {
if err := n.tableDesc.AddIndexMutation(&idx, descpb.DescriptorMutation_ADD); err != nil {
return err
}
skipIfExists = d.IfNotExists

// We need to allocate IDs upfront in the event we need to update the zone config
// in the same transaction.
Expand Down Expand Up @@ -370,6 +372,7 @@ func (n *alterTableNode) startExec(params runParams) error {
if err != nil {
return err
}
skipIfExists = d.IfNotExists

case *tree.ForeignKeyConstraintTableDef:
// We want to reject uses of FK ON UPDATE actions where there is already
Expand Down Expand Up @@ -437,6 +440,8 @@ func (n *alterTableNode) startExec(params runParams) error {
return err
}
}
skipIfExists = d.IfNotExists

// TODO(lucy): Validate() can't be called here because it reads the
// referenced table descs, which may have to be upgraded to the new FK
// representation. That requires reading the original table descriptor
Expand Down Expand Up @@ -1040,6 +1045,9 @@ func (n *alterTableNode) startExec(params runParams) error {

// Allocate IDs now, so new IDs are available to subsequent commands
if err := n.tableDesc.AllocateIDs(params.ctx); err != nil {
if skipIfExists && pgerror.GetPGCode(err) == pgcode.DuplicateObject {
return nil
}
return err
}
}
Expand Down
21 changes: 21 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/alter_table
Original file line number Diff line number Diff line change
Expand Up @@ -1997,3 +1997,24 @@ ALTER TABLE t ALTER COLUMN b SET DEFAULT 10

statement error identity column type must be INT, INT2, INT4 or INT8
ALTER TABLE t ALTER COLUMN b TYPE numeric(10,2)

statement ok
CREATE TABLE test_if_not_exists (id uuid primary key, hash string)

statement ok
ALTER TABLE test_if_not_exists ADD CONSTRAINT test_if_not_exists_hash UNIQUE (hash)

statement error duplicate constraint name: "test_if_not_exists_hash"
ALTER TABLE test_if_not_exists ADD CONSTRAINT test_if_not_exists_hash UNIQUE (hash);

statement ok
ALTER TABLE test_if_not_exists ADD CONSTRAINT IF NOT EXISTS test_if_not_exists_hash UNIQUE (hash);

statement ok
ALTER TABLE test_if_not_exists ADD CONSTRAINT test_if_not_exists_check CHECK (hash in ('1', '2', '3'));

statement error duplicate constraint name: "test_if_not_exists_check"
ALTER TABLE test_if_not_exists ADD CONSTRAINT test_if_not_exists_check CHECK (hash in ('1', '2', '3'));

statement ok
ALTER TABLE test_if_not_exists ADD CONSTRAINT IF NOT EXISTS test_if_not_exists_check CHECK (hash in ('1', '2', '3'));
7 changes: 7 additions & 0 deletions pkg/sql/parser/sql.y
Original file line number Diff line number Diff line change
Expand Up @@ -6975,6 +6975,13 @@ family_def:
// column definition. col_qualification_elem specifies the embedded form.
// - thomas 1997-12-03
table_constraint:
CONSTRAINT IF NOT EXISTS constraint_name constraint_elem
{
$$.val = $6.constraintDef()
$$.val.(tree.ConstraintTableDef).SetName(tree.Name($5))
$$.val.(tree.ConstraintTableDef).SetIfNotExists()
}
|
CONSTRAINT constraint_name constraint_elem
{
$$.val = $3.constraintDef()
Expand Down
42 changes: 33 additions & 9 deletions pkg/sql/sem/tree/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -993,18 +993,31 @@ type ConstraintTableDef interface {

// SetName replaces the name of the definition in-place. Used in the parser.
SetName(name Name)

// SetIfNotExists sets the IfNotExists flag (do not raise an error if constraint exists). Used in the parser.
SetIfNotExists()
}

func (*UniqueConstraintTableDef) constraintTableDef() {}
func (*ForeignKeyConstraintTableDef) constraintTableDef() {}
func (*CheckConstraintTableDef) constraintTableDef() {}

// SetIfNotExists implements the TableDef interface.
func (node *UniqueConstraintTableDef) SetIfNotExists() { node.IfNotExists = true }

// SetIfNotExists implements the TableDef interface.
func (node *ForeignKeyConstraintTableDef) SetIfNotExists() { node.IfNotExists = true }

// SetIfNotExists implements the TableDef interface.
func (node *CheckConstraintTableDef) SetIfNotExists() { node.IfNotExists = true }

// UniqueConstraintTableDef represents a unique constraint within a CREATE
// TABLE statement.
type UniqueConstraintTableDef struct {
IndexTableDef
PrimaryKey bool
WithoutIndex bool
IfNotExists bool
}

// SetName implements the TableDef interface.
Expand All @@ -1016,6 +1029,9 @@ func (node *UniqueConstraintTableDef) SetName(name Name) {
func (node *UniqueConstraintTableDef) Format(ctx *FmtCtx) {
if node.Name != "" {
ctx.WriteString("CONSTRAINT ")
if node.IfNotExists {
ctx.WriteString("IF NOT EXISTS ")
}
ctx.FormatNode(&node.Name)
ctx.WriteByte(' ')
}
Expand Down Expand Up @@ -1119,18 +1135,22 @@ func (c CompositeKeyMatchMethod) String() string {

// ForeignKeyConstraintTableDef represents a FOREIGN KEY constraint in the AST.
type ForeignKeyConstraintTableDef struct {
Name Name
Table TableName
FromCols NameList
ToCols NameList
Actions ReferenceActions
Match CompositeKeyMatchMethod
Name Name
Table TableName
FromCols NameList
ToCols NameList
Actions ReferenceActions
Match CompositeKeyMatchMethod
IfNotExists bool
}

// Format implements the NodeFormatter interface.
func (node *ForeignKeyConstraintTableDef) Format(ctx *FmtCtx) {
if node.Name != "" {
ctx.WriteString("CONSTRAINT ")
if node.IfNotExists {
ctx.WriteString("IF NOT EXISTS ")
}
ctx.FormatNode(&node.Name)
ctx.WriteByte(' ')
}
Expand Down Expand Up @@ -1162,9 +1182,10 @@ func (node *ForeignKeyConstraintTableDef) SetName(name Name) {
// CheckConstraintTableDef represents a check constraint within a CREATE
// TABLE statement.
type CheckConstraintTableDef struct {
Name Name
Expr Expr
Hidden bool
Name Name
Expr Expr
Hidden bool
IfNotExists bool
}

// SetName implements the ConstraintTableDef interface.
Expand All @@ -1176,6 +1197,9 @@ func (node *CheckConstraintTableDef) SetName(name Name) {
func (node *CheckConstraintTableDef) Format(ctx *FmtCtx) {
if node.Name != "" {
ctx.WriteString("CONSTRAINT ")
if node.IfNotExists {
ctx.WriteString("IF NOT EXISTS ")
}
ctx.FormatNode(&node.Name)
ctx.WriteByte(' ')
}
Expand Down