From f14bb2ebb9ab3639b81abff26bd01676b89f4033 Mon Sep 17 00:00:00 2001 From: Govind Agarwal Date: Wed, 22 Jul 2026 21:51:30 +0000 Subject: [PATCH] feat(dsql): parse ALTER TABLE ASYNC Aurora DSQL runs certain ALTER TABLE operations as asynchronous DDL jobs via the ALTER TABLE ASYNC form, e.g. ALTER TABLE ASYNC t VALIDATE CONSTRAINT c which returns a job_id and validates existing rows in the background (same async DDL model as CREATE INDEX ASYNC). Add an `async` flag to the AlterTable AST node, parse an optional ASYNC keyword immediately after TABLE (before IF EXISTS / ONLY), and render it in Display. Non-async ALTER TABLE is unchanged. - ast/ddl.rs: AlterTable.async field + Display - parser/mod.rs: parse_alter_table parses optional ASYNC - dialect/snowflake.rs: set async: false at the two AlterTable sites - tests/sqlparser_postgres.rs: async VALIDATE CONSTRAINT round-trip, incl. ASYNC + IF EXISTS + ONLY Exact round-trip verified; full postgres (180) and common (471) suites pass. --- Cargo.toml | 4 ++-- src/ast/ddl.rs | 9 +++++++++ src/dialect/snowflake.rs | 2 ++ src/parser/mod.rs | 2 ++ tests/sqlparser_postgres.rs | 33 ++++++++++++++++++++++++++++++++- 5 files changed, 47 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f55a7201f1..7476521fc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,8 @@ [package] name = "sqlparser-dsql" -description = "SQL parser fork with Aurora DSQL extensions (CREATE INDEX ASYNC, order-independent CREATE SEQUENCE, INCLUDE on table constraints). Based on sqlparser 0.62.0." -version = "0.62.1" +description = "SQL parser fork with Aurora DSQL extensions (CREATE INDEX ASYNC, ALTER TABLE ASYNC, order-independent CREATE SEQUENCE, INCLUDE on table constraints). Based on sqlparser 0.62.0." +version = "0.62.2" authors = [ "Apache DataFusion ", "Amazon Web Services", diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index da348e3a0d..de216400ae 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -4730,6 +4730,12 @@ pub struct AlterTable { /// Table name #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))] pub name: ObjectName, + /// Whether the `ASYNC` keyword was specified ([DSQL]). `ALTER TABLE ASYNC` + /// runs the operation as an asynchronous DDL job, e.g. + /// `ALTER TABLE ASYNC t VALIDATE CONSTRAINT c`. + /// + /// [DSQL]: https://docs.aws.amazon.com/aurora-dsql/latest/userguide/working-with-postgresql-compatibility.html + pub r#async: bool, /// Whether `IF EXISTS` was specified for the `ALTER TABLE`. pub if_exists: bool, /// Whether the `ONLY` keyword was used (restrict scope to the named table). @@ -4757,6 +4763,9 @@ impl fmt::Display for AlterTable { None => write!(f, "ALTER TABLE ")?, } + if self.r#async { + write!(f, "ASYNC ")?; + } if self.if_exists { write!(f, "IF EXISTS ")?; } diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index fda5b7b976..3bd2079a5a 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -744,6 +744,7 @@ fn parse_alter_dynamic_table(parser: &mut Parser) -> Result Result Parser<'a> { /// Parse a [Statement::AlterTable] pub fn parse_alter_table(&mut self, iceberg: bool) -> Result { + let r#async = self.parse_keyword(Keyword::ASYNC); // [ ASYNC ] (DSQL) let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); let only = self.parse_keyword(Keyword::ONLY); // [ ONLY ] let table_name = self.parse_object_name(false)?; @@ -11138,6 +11139,7 @@ impl<'a> Parser<'a> { Ok(AlterTable { name: table_name, + r#async, if_exists, only, operations, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 92b62f02f7..9458dab3e4 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -7214,7 +7214,12 @@ fn parse_alter_table_constraint_not_valid() { #[test] fn parse_alter_table_validate_constraint() { match pg_and_generic().verified_stmt("ALTER TABLE foo VALIDATE CONSTRAINT bar") { - Statement::AlterTable(AlterTable { operations, .. }) => { + Statement::AlterTable(AlterTable { + operations, + r#async, + .. + }) => { + assert!(!r#async); assert_eq!( operations, vec![AlterTableOperation::ValidateConstraint { name: "bar".into() }] @@ -7224,6 +7229,32 @@ fn parse_alter_table_validate_constraint() { } } +#[test] +fn parse_alter_table_async_validate_constraint() { + // Aurora DSQL runs VALIDATE CONSTRAINT as an asynchronous DDL job via the + // `ALTER TABLE ASYNC` form. + match pg_and_generic().verified_stmt("ALTER TABLE ASYNC foo VALIDATE CONSTRAINT bar") { + Statement::AlterTable(AlterTable { + name, + operations, + r#async, + .. + }) => { + assert!(r#async); + assert_eq!(name.to_string(), "foo"); + assert_eq!( + operations, + vec![AlterTableOperation::ValidateConstraint { name: "bar".into() }] + ); + } + _ => unreachable!(), + } + + // ASYNC composes with IF EXISTS and ONLY. + pg_and_generic() + .verified_stmt("ALTER TABLE ASYNC IF EXISTS ONLY foo VALIDATE CONSTRAINT bar"); +} + #[test] fn parse_create_server() { let test_cases = vec![