This is an automated email from the ASF dual-hosted git repository. github-bot pushed a commit to branch gh-readonly-queue/main/pr-2181-153e7c57465476aa7350672c5cee4f1ef02b7365 in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
commit 802c7d3e03df900392a009ce60b9f30fd954ac4e Author: Michael Victor Zink <[email protected]> AuthorDate: Sun Jan 25 01:38:09 2026 -0800 PostgreSQL: Add support for `*` (descendant) option in TRUNCATE (#2181) --- src/ast/mod.rs | 18 ++++++++++--- src/parser/mod.rs | 17 ++++++++----- tests/sqlparser_common.rs | 2 ++ tests/sqlparser_postgres.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 10 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index fcfdf364..33f99bc2 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -6422,10 +6422,18 @@ pub struct TruncateTableTarget { /// name of the table being truncated #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))] pub name: ObjectName, - /// Postgres-specific option - /// [ TRUNCATE TABLE ONLY ] + /// Postgres-specific option: explicitly exclude descendants (also default without ONLY) + /// ```sql + /// TRUNCATE TABLE ONLY name + /// ``` /// <https://www.postgresql.org/docs/current/sql-truncate.html> pub only: bool, + /// Postgres-specific option: asterisk after table name to explicitly indicate descendants + /// ```sql + /// TRUNCATE TABLE name [ * ] + /// ``` + /// <https://www.postgresql.org/docs/current/sql-truncate.html> + pub has_asterisk: bool, } impl fmt::Display for TruncateTableTarget { @@ -6433,7 +6441,11 @@ impl fmt::Display for TruncateTableTarget { if self.only { write!(f, "ONLY ")?; }; - write!(f, "{}", self.name) + write!(f, "{}", self.name)?; + if self.has_asterisk { + write!(f, " *")?; + }; + Ok(()) } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 55fec678..e0712017 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1054,13 +1054,16 @@ impl<'a> Parser<'a> { let table = self.parse_keyword(Keyword::TABLE); let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); - let table_names = self - .parse_comma_separated(|p| { - Ok((p.parse_keyword(Keyword::ONLY), p.parse_object_name(false)?)) - })? - .into_iter() - .map(|(only, name)| TruncateTableTarget { name, only }) - .collect(); + let table_names = self.parse_comma_separated(|p| { + let only = p.parse_keyword(Keyword::ONLY); + let name = p.parse_object_name(false)?; + let has_asterisk = p.consume_token(&Token::Mul); + Ok(TruncateTableTarget { + name, + only, + has_asterisk, + }) + })?; let mut partitions = None; if self.parse_keyword(Keyword::PARTITION) { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index b2c41d97..6da4ea53 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -16828,10 +16828,12 @@ fn parse_truncate_only() { TruncateTableTarget { name: ObjectName::from(vec![Ident::new("employee")]), only: false, + has_asterisk: false, }, TruncateTableTarget { name: ObjectName::from(vec![Ident::new("dept")]), only: true, + has_asterisk: false, }, ]; diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 6a4b78b5..7bd7f43c 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -5088,6 +5088,7 @@ fn parse_truncate() { let table_names = vec![TruncateTableTarget { name: table_name.clone(), only: false, + has_asterisk: false, }]; assert_eq!( Statement::Truncate(Truncate { @@ -5112,6 +5113,7 @@ fn parse_truncate_with_options() { let table_names = vec![TruncateTableTarget { name: table_name.clone(), only: true, + has_asterisk: false, }]; assert_eq!( @@ -5141,10 +5143,12 @@ fn parse_truncate_with_table_list() { TruncateTableTarget { name: table_name_a.clone(), only: false, + has_asterisk: false, }, TruncateTableTarget { name: table_name_b.clone(), only: false, + has_asterisk: false, }, ]; @@ -5162,6 +5166,64 @@ fn parse_truncate_with_table_list() { ); } +#[test] +fn parse_truncate_with_descendant() { + let truncate = pg_and_generic().verified_stmt("TRUNCATE TABLE t *"); + + let table_names = vec![TruncateTableTarget { + name: ObjectName::from(vec![Ident::new("t")]), + only: false, + has_asterisk: true, + }]; + + assert_eq!( + Statement::Truncate(Truncate { + table_names, + partitions: None, + table: true, + if_exists: false, + identity: None, + cascade: None, + on_cluster: None, + }), + truncate + ); + + let truncate = pg_and_generic() + .verified_stmt("TRUNCATE TABLE ONLY parent, child *, grandchild RESTART IDENTITY"); + + let table_names = vec![ + TruncateTableTarget { + name: ObjectName::from(vec![Ident::new("parent")]), + only: true, + has_asterisk: false, + }, + TruncateTableTarget { + name: ObjectName::from(vec![Ident::new("child")]), + only: false, + has_asterisk: true, + }, + TruncateTableTarget { + name: ObjectName::from(vec![Ident::new("grandchild")]), + only: false, + has_asterisk: false, + }, + ]; + + assert_eq!( + Statement::Truncate(Truncate { + table_names, + partitions: None, + table: true, + if_exists: false, + identity: Some(TruncateIdentityOption::Restart), + cascade: None, + on_cluster: None, + }), + truncate + ); +} + #[test] fn parse_select_regexp_as_column_name() { pg_and_generic().verified_only_select( --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
