From 364e0993ecfd158f81fe335d045e66ac1493d53f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Saissy?= Date: Wed, 29 Jan 2025 11:54:41 +0100 Subject: [PATCH 1/5] Add support for the TABLE returns datatype in create function for postgresql. Fixes #1687. # Conflicts: # tests/sqlparser_postgres.rs --- src/ast/data_type.rs | 5 +++++ src/parser/mod.rs | 27 ++++++++++++++++++++++++++- tests/sqlparser_postgres.rs | 5 +++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index 02aa6cc9fa..1f2b6be977 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -45,6 +45,10 @@ pub enum EnumMember { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum DataType { + /// Table type in [postgresql]. e.g. CREATE FUNCTION RETURNS TABLE(...) + /// + /// [postgresql]: https://www.postgresql.org/docs/15/sql-createfunction.html + Table(Vec), /// Fixed-length character type e.g. CHARACTER(10) Character(Option), /// Fixed-length char type e.g. CHAR(10) @@ -630,6 +634,7 @@ impl fmt::Display for DataType { DataType::Unspecified => Ok(()), DataType::Trigger => write!(f, "TRIGGER"), DataType::AnyType => write!(f, "ANY TYPE"), + DataType::Table(fields) => write!(f, "TABLE({})", display_comma_separated(fields)), } } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ca858c42eb..a961fc79ce 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4563,7 +4563,14 @@ impl<'a> Parser<'a> { self.expect_token(&Token::RParen)?; let return_type = if self.parse_keyword(Keyword::RETURNS) { - Some(self.parse_data_type()?) + if dialect_of!(self is PostgreSqlDialect | GenericDialect) + && self.parse_keyword(Keyword::TABLE) + { + let columns = self.parse_parenthesized_columns()?; + Some(DataType::Table(columns)) + } else { + Some(self.parse_data_type()?) + } } else { None }; @@ -8894,6 +8901,24 @@ impl<'a> Parser<'a> { Ok((data, trailing_bracket)) } + fn parse_returns_table_column(&mut self) -> Result { + let name = self.parse_identifier()?; + let data_type = self.parse_data_type()?; + Ok(ColumnDef { + name, + data_type, + collation: None, + options: Vec::new(), // No constraints expected here + }) + } + + fn parse_parenthesized_columns(&mut self) -> Result, ParserError> { + self.expect_token(&Token::LParen)?; + let columns = self.parse_comma_separated(Parser::parse_returns_table_column)?; + self.expect_token(&Token::RParen)?; + Ok(columns) + } + pub fn parse_string_values(&mut self) -> Result, ParserError> { self.expect_token(&Token::LParen)?; let mut values = Vec::new(); diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index ee4aa2a0af..097a80173c 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -3803,6 +3803,7 @@ fn parse_create_function_detailed() { pg_and_generic().verified_stmt("CREATE OR REPLACE FUNCTION add(a INTEGER, IN b INTEGER = 1) RETURNS INTEGER LANGUAGE SQL STABLE CALLED ON NULL INPUT PARALLEL UNSAFE RETURN a + b"); pg_and_generic().verified_stmt(r#"CREATE OR REPLACE FUNCTION increment(i INTEGER) RETURNS INTEGER LANGUAGE plpgsql AS $$ BEGIN RETURN i + 1; END; $$"#); pg_and_generic().verified_stmt(r#"CREATE OR REPLACE FUNCTION no_arg() RETURNS VOID LANGUAGE plpgsql AS $$ BEGIN DELETE FROM my_table; END; $$"#); + pg_and_generic().verified_stmt(r#"CREATE OR REPLACE FUNCTION return_table(i INTEGER) RETURNS TABLE(id UUID, is_active BOOLEAN) LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT NULL::UUID, NULL::BOOLEAN; END; $$"#); } #[test] fn parse_incorrect_create_function_parallel() { @@ -4372,7 +4373,7 @@ fn parse_join_constraint_unnest_alias() { with_ordinality: false, }, global: false, - join_operator: JoinOperator::Join(JoinConstraint::On(Expr::BinaryOp { + join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp { left: Box::new(Expr::Identifier("c1".into())), op: BinaryOperator::Eq, right: Box::new(Expr::Identifier("c2".into())), @@ -5147,7 +5148,7 @@ fn parse_trigger_related_functions() { temporary: false, if_not_exists: false, name: ObjectName::from(vec![Ident::new("emp_stamp")]), - args: Some(vec![]), + args: None, return_type: Some(DataType::Trigger), function_body: Some( CreateFunctionBody::AsBeforeOptions( From e35c3ebc1feefc6466dc8e95ef5173dcf8ffcbfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20SAISSY?= Date: Wed, 29 Jan 2025 21:09:21 +0100 Subject: [PATCH 2/5] Update src/parser/mod.rs Co-authored-by: Ifeanyi Ubah --- src/parser/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index a961fc79ce..e61e8d1a1c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8912,7 +8912,7 @@ impl<'a> Parser<'a> { }) } - fn parse_parenthesized_columns(&mut self) -> Result, ParserError> { + fn parse_returns_table_columns(&mut self) -> Result, ParserError> { self.expect_token(&Token::LParen)?; let columns = self.parse_comma_separated(Parser::parse_returns_table_column)?; self.expect_token(&Token::RParen)?; From abecd1c7789bd5fcab307006875276cd560a82ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Saissy?= Date: Wed, 29 Jan 2025 23:20:45 +0100 Subject: [PATCH 3/5] Moves return table columns parsing in parse_data_type. --- src/parser/mod.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e61e8d1a1c..4cb90b05e3 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4563,14 +4563,7 @@ impl<'a> Parser<'a> { self.expect_token(&Token::RParen)?; let return_type = if self.parse_keyword(Keyword::RETURNS) { - if dialect_of!(self is PostgreSqlDialect | GenericDialect) - && self.parse_keyword(Keyword::TABLE) - { - let columns = self.parse_parenthesized_columns()?; - Some(DataType::Table(columns)) - } else { - Some(self.parse_data_type()?) - } + Some(self.parse_data_type()?) } else { None }; @@ -8874,6 +8867,10 @@ impl<'a> Parser<'a> { let _ = self.parse_keyword(Keyword::TYPE); Ok(DataType::AnyType) } + Keyword::TABLE => { + let columns = self.parse_returns_table_columns()?; + Ok(DataType::Table(columns)) + } _ => { self.prev_token(); let type_name = self.parse_object_name(false)?; From ea9b8520112c8758031fe0dfc8eeedd2572442f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Saissy?= Date: Fri, 31 Jan 2025 14:51:18 +0100 Subject: [PATCH 4/5] Fix no_args evaluation in the AST. --- tests/sqlparser_postgres.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 097a80173c..59cb5fe54f 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -5148,7 +5148,7 @@ fn parse_trigger_related_functions() { temporary: false, if_not_exists: false, name: ObjectName::from(vec![Ident::new("emp_stamp")]), - args: None, + args: Some(vec![]), return_type: Some(DataType::Trigger), function_body: Some( CreateFunctionBody::AsBeforeOptions( From c50db37dd4870cd5c3993cdd6ab3f47baa9b3db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Saissy?= Date: Fri, 31 Jan 2025 15:29:56 +0100 Subject: [PATCH 5/5] Fix JOIN being parsed as JoinOperator::Join instead of JoinOperator::Inner. --- tests/sqlparser_postgres.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 59cb5fe54f..62da0f574f 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4373,7 +4373,7 @@ fn parse_join_constraint_unnest_alias() { with_ordinality: false, }, global: false, - join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp { + join_operator: JoinOperator::Join(JoinConstraint::On(Expr::BinaryOp { left: Box::new(Expr::Identifier("c1".into())), op: BinaryOperator::Eq, right: Box::new(Expr::Identifier("c2".into())),