diff --git a/docs/generated/sql/bnf/stmt_block.bnf b/docs/generated/sql/bnf/stmt_block.bnf index 30ad49f4d6c9..05e206b2a8d4 100644 --- a/docs/generated/sql/bnf/stmt_block.bnf +++ b/docs/generated/sql/bnf/stmt_block.bnf @@ -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 ::= diff --git a/docs/generated/sql/bnf/table_constraint.bnf b/docs/generated/sql/bnf/table_constraint.bnf index 2b8830c5bc7f..589d00fdb105 100644 --- a/docs/generated/sql/bnf/table_constraint.bnf +++ b/docs/generated/sql/bnf/table_constraint.bnf @@ -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 diff --git a/pkg/sql/alter_table.go b/pkg/sql/alter_table.go index 37adda4d8a0f..47dc2429f7ca 100644 --- a/pkg/sql/alter_table.go +++ b/pkg/sql/alter_table.go @@ -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 { @@ -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. @@ -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 @@ -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 @@ -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 } } diff --git a/pkg/sql/logictest/testdata/logic_test/alter_table b/pkg/sql/logictest/testdata/logic_test/alter_table index 184f4ac2c9be..41db2386136c 100644 --- a/pkg/sql/logictest/testdata/logic_test/alter_table +++ b/pkg/sql/logictest/testdata/logic_test/alter_table @@ -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')); diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index e4ad8096907c..8922922720b2 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -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() diff --git a/pkg/sql/sem/tree/create.go b/pkg/sql/sem/tree/create.go index c9fc6fc90339..3a2cca412dc9 100644 --- a/pkg/sql/sem/tree/create.go +++ b/pkg/sql/sem/tree/create.go @@ -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. @@ -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(' ') } @@ -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(' ') } @@ -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. @@ -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(' ') }