Postgres: support ADD CONSTRAINT NOT VALID and VALIDATE CONSTRAINT (#1908)

This commit is contained in:
carl 2025-07-03 12:19:26 -04:00 committed by GitHub
parent 015caca611
commit 418b94227a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 99 additions and 22 deletions

View file

@ -67,8 +67,11 @@ impl fmt::Display for ReplicaIdentity {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum AlterTableOperation {
/// `ADD <table_constraint>`
AddConstraint(TableConstraint),
/// `ADD <table_constraint> [NOT VALID]`
AddConstraint {
constraint: TableConstraint,
not_valid: bool,
},
/// `ADD [COLUMN] [IF NOT EXISTS] <column_def>`
AddColumn {
/// `[COLUMN]`.
@ -344,6 +347,10 @@ pub enum AlterTableOperation {
equals: bool,
value: ValueWithSpan,
},
/// `VALIDATE CONSTRAINT <name>`
ValidateConstraint {
name: Ident,
},
}
/// An `ALTER Policy` (`Statement::AlterPolicy`) operation
@ -494,7 +501,16 @@ impl fmt::Display for AlterTableOperation {
display_separated(new_partitions, " "),
ine = if *if_not_exists { " IF NOT EXISTS" } else { "" }
),
AlterTableOperation::AddConstraint(c) => write!(f, "ADD {c}"),
AlterTableOperation::AddConstraint {
not_valid,
constraint,
} => {
write!(f, "ADD {constraint}")?;
if *not_valid {
write!(f, " NOT VALID")?;
}
Ok(())
}
AlterTableOperation::AddColumn {
column_keyword,
if_not_exists,
@ -772,6 +788,9 @@ impl fmt::Display for AlterTableOperation {
AlterTableOperation::ReplicaIdentity { identity } => {
write!(f, "REPLICA IDENTITY {identity}")
}
AlterTableOperation::ValidateConstraint { name } => {
write!(f, "VALIDATE CONSTRAINT {name}")
}
}
}
}

View file

@ -1075,7 +1075,10 @@ impl Spanned for CreateTableOptions {
impl Spanned for AlterTableOperation {
fn span(&self) -> Span {
match self {
AlterTableOperation::AddConstraint(table_constraint) => table_constraint.span(),
AlterTableOperation::AddConstraint {
constraint,
not_valid: _,
} => constraint.span(),
AlterTableOperation::AddColumn {
column_keyword: _,
if_not_exists: _,
@ -1196,6 +1199,7 @@ impl Spanned for AlterTableOperation {
AlterTableOperation::AutoIncrement { value, .. } => value.span(),
AlterTableOperation::Lock { .. } => Span::empty(),
AlterTableOperation::ReplicaIdentity { .. } => Span::empty(),
AlterTableOperation::ValidateConstraint { name } => name.span,
}
}
}

View file

@ -981,6 +981,7 @@ define_keywords!(
UUID,
VACUUM,
VALID,
VALIDATE,
VALIDATION_MODE,
VALUE,
VALUES,

View file

@ -8477,7 +8477,11 @@ impl<'a> Parser<'a> {
pub fn parse_alter_table_operation(&mut self) -> Result<AlterTableOperation, ParserError> {
let operation = if self.parse_keyword(Keyword::ADD) {
if let Some(constraint) = self.parse_optional_table_constraint()? {
AlterTableOperation::AddConstraint(constraint)
let not_valid = self.parse_keywords(&[Keyword::NOT, Keyword::VALID]);
AlterTableOperation::AddConstraint {
constraint,
not_valid,
}
} else if dialect_of!(self is ClickHouseDialect|GenericDialect)
&& self.parse_keyword(Keyword::PROJECTION)
{
@ -8886,6 +8890,9 @@ impl<'a> Parser<'a> {
};
AlterTableOperation::ReplicaIdentity { identity }
} else if self.parse_keywords(&[Keyword::VALIDATE, Keyword::CONSTRAINT]) {
let name = self.parse_identifier()?;
AlterTableOperation::ValidateConstraint { name }
} else {
let options: Vec<SqlOption> =
self.parse_options_with_keywords(&[Keyword::SET, Keyword::TBLPROPERTIES])?;

View file

@ -479,20 +479,25 @@ pub fn index_column(stmt: Statement) -> Expr {
}
}
Statement::AlterTable { operations, .. } => match operations.first().unwrap() {
AlterTableOperation::AddConstraint(TableConstraint::Index { columns, .. }) => {
columns.first().unwrap().column.expr.clone()
AlterTableOperation::AddConstraint { constraint, .. } => {
match constraint {
TableConstraint::Index { columns, .. } => {
columns.first().unwrap().column.expr.clone()
}
TableConstraint::Unique { columns, .. } => {
columns.first().unwrap().column.expr.clone()
}
TableConstraint::PrimaryKey { columns, .. } => {
columns.first().unwrap().column.expr.clone()
}
TableConstraint::FulltextOrSpatial {
columns,
..
} => columns.first().unwrap().column.expr.clone(),
_ => panic!("Expected an index, unique, primary, full text, or spatial constraint (foreign key does not support general key part expressions)"),
}
}
AlterTableOperation::AddConstraint(TableConstraint::Unique { columns, .. }) => {
columns.first().unwrap().column.expr.clone()
}
AlterTableOperation::AddConstraint(TableConstraint::PrimaryKey { columns, .. }) => {
columns.first().unwrap().column.expr.clone()
}
AlterTableOperation::AddConstraint(TableConstraint::FulltextOrSpatial {
columns,
..
}) => columns.first().unwrap().column.expr.clone(),
_ => panic!("Expected an index, unique, primary, full text, or spatial constraint (foreign key does not support general key part expressions)"),
_ => panic!("Expected a constraint"),
},
_ => panic!("Expected CREATE INDEX, ALTER TABLE, or CREATE TABLE, got: {stmt:?}"),
}

View file

@ -4956,7 +4956,7 @@ fn parse_alter_table_constraints() {
match alter_table_op(verified_stmt(&format!(
"ALTER TABLE tab ADD {constraint_text}"
))) {
AlterTableOperation::AddConstraint(constraint) => {
AlterTableOperation::AddConstraint { constraint, .. } => {
assert_eq!(constraint_text, constraint.to_string());
}
_ => unreachable!(),

View file

@ -606,9 +606,10 @@ fn parse_alter_table_constraints_unique_nulls_distinct() {
.verified_stmt("ALTER TABLE t ADD CONSTRAINT b UNIQUE NULLS NOT DISTINCT (c)")
{
Statement::AlterTable { operations, .. } => match &operations[0] {
AlterTableOperation::AddConstraint(TableConstraint::Unique {
nulls_distinct, ..
}) => {
AlterTableOperation::AddConstraint {
constraint: TableConstraint::Unique { nulls_distinct, .. },
..
} => {
assert_eq!(nulls_distinct, &NullsDistinctOption::NotDistinct)
}
_ => unreachable!(),
@ -6229,3 +6230,43 @@ fn parse_ts_datatypes() {
_ => unreachable!(),
}
}
#[test]
fn parse_alter_table_constraint_not_valid() {
match pg_and_generic().verified_stmt(
"ALTER TABLE foo ADD CONSTRAINT bar FOREIGN KEY (baz) REFERENCES other(ref) NOT VALID",
) {
Statement::AlterTable { operations, .. } => {
assert_eq!(
operations,
vec![AlterTableOperation::AddConstraint {
constraint: TableConstraint::ForeignKey {
name: Some("bar".into()),
index_name: None,
columns: vec!["baz".into()],
foreign_table: ObjectName::from(vec!["other".into()]),
referred_columns: vec!["ref".into()],
on_delete: None,
on_update: None,
characteristics: None,
},
not_valid: true,
}]
);
}
_ => unreachable!(),
}
}
#[test]
fn parse_alter_table_validate_constraint() {
match pg_and_generic().verified_stmt("ALTER TABLE foo VALIDATE CONSTRAINT bar") {
Statement::AlterTable { operations, .. } => {
assert_eq!(
operations,
vec![AlterTableOperation::ValidateConstraint { name: "bar".into() }]
);
}
_ => unreachable!(),
}
}