From 3475d4bf5a1626fae9652c32cd22bf31765274cd Mon Sep 17 00:00:00 2001 From: Andrew Harper Date: Tue, 6 May 2025 14:18:15 -0400 Subject: [PATCH 01/14] Add support for inline table valued functions for SQL Server --- src/ast/data_type.rs | 8 +++++++- src/ast/ddl.rs | 3 +++ src/ast/mod.rs | 12 ++++++++++++ src/parser/mod.rs | 35 +++++++++++++++++++++++++---------- tests/sqlparser_mssql.rs | 8 ++++++++ 5 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index 52919de8a..7212ddc9f 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -48,6 +48,7 @@ pub enum DataType { /// Table type in [PostgreSQL], e.g. CREATE FUNCTION RETURNS TABLE(...). /// /// [PostgreSQL]: https://www.postgresql.org/docs/15/sql-createfunction.html + /// [MsSQL]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql?view=sql-server-ver16#c-create-a-multi-statement-table-valued-function Table(Vec), /// Fixed-length character type, e.g. CHARACTER(10). Character(Option), @@ -716,7 +717,12 @@ 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)), + DataType::Table(fields) => { + if fields.is_empty() { + return write!(f, "TABLE"); + } + write!(f, "TABLE({})", display_comma_separated(fields)) + } DataType::GeometricType(kind) => write!(f, "{}", kind), } } diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 270897130..7ddbf5e5a 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -2346,6 +2346,9 @@ impl fmt::Display for CreateFunction { if let Some(CreateFunctionBody::Return(function_body)) = &self.function_body { write!(f, " RETURN {function_body}")?; } + if let Some(CreateFunctionBody::AsReturn(function_body)) = &self.function_body { + write!(f, " AS RETURN {function_body}")?; + } if let Some(using) = &self.using { write!(f, " {using}")?; } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index d711a1062..2ef12915a 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -8778,6 +8778,18 @@ pub enum CreateFunctionBody { /// /// [PostgreSQL]: https://www.postgresql.org/docs/current/sql-createfunction.html Return(Expr), + + /// Function body expression using the 'AS RETURN' keywords + /// + /// Example: + /// ```sql + /// CREATE FUNCTION myfunc(a INT, b INT) + /// RETURNS TABLE + /// AS RETURN (SELECT a + b AS sum); + /// ``` + /// + /// [MsSql]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql + AsReturn(Expr), } #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 838996130..176bb77b0 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5211,15 +5211,26 @@ impl<'a> Parser<'a> { self.expect_keyword_is(Keyword::AS)?; - let begin_token = self.expect_keyword(Keyword::BEGIN)?; - let statements = self.parse_statement_list(&[Keyword::END])?; - let end_token = self.expect_keyword(Keyword::END)?; + let function_body = if self.peek_keyword(Keyword::BEGIN) { + let begin_token = self.expect_keyword(Keyword::BEGIN)?; + let statements = self.parse_statement_list(&[Keyword::END])?; + let end_token = self.expect_keyword(Keyword::END)?; - let function_body = Some(CreateFunctionBody::AsBeginEnd(BeginEndStatements { - begin_token: AttachedToken(begin_token), - statements, - end_token: AttachedToken(end_token), - })); + Some(CreateFunctionBody::AsBeginEnd(BeginEndStatements { + begin_token: AttachedToken(begin_token), + statements, + end_token: AttachedToken(end_token), + })) + } else if self.peek_keyword(Keyword::RETURN) { + self.expect_keyword(Keyword::RETURN)?; + let expr = self.parse_expr()?; + if !matches!(expr, Expr::Subquery(_)) { + parser_err!("Expected a subquery after RETURN", expr.span().start)?; + } + Some(CreateFunctionBody::AsReturn(expr)) + } else { + parser_err!("Unparsable function body", self.peek_token().span.start)? + }; Ok(Statement::CreateFunction(CreateFunction { or_alter, @@ -9787,8 +9798,12 @@ impl<'a> Parser<'a> { Ok(DataType::AnyType) } Keyword::TABLE => { - let columns = self.parse_returns_table_columns()?; - Ok(DataType::Table(columns)) + if self.peek_keyword(Keyword::AS) { + Ok(DataType::Table(Vec::::new())) + } else { + let columns = self.parse_returns_table_columns()?; + Ok(DataType::Table(columns)) + } } Keyword::SIGNED => { if self.parse_keyword(Keyword::INTEGER) { diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 7b3769ec8..7f8e4baf1 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -288,6 +288,14 @@ fn parse_create_function() { END\ "; let _ = ms().verified_stmt(create_function_with_return_expression); + + let create_inline_table_value_function = "\ + CREATE FUNCTION some_inline_tvf(@foo INT, @bar VARCHAR(256)) \ + RETURNS TABLE \ + AS \ + RETURN (SELECT 1 AS col_1)\ + "; + let _ = ms().verified_stmt(create_inline_table_value_function); } #[test] From a4bbd6872dc1aca90bb3a02358c73681ccaeb732 Mon Sep 17 00:00:00 2001 From: Andrew Harper Date: Tue, 6 May 2025 15:12:21 -0400 Subject: [PATCH 02/14] Add multi-statement table valued function support for SQL Server --- src/ast/data_type.rs | 10 ++++++++++ src/parser/mod.rs | 26 +++++++++++++++++++++++++- tests/sqlparser_mssql.rs | 11 +++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index 7212ddc9f..0f1cafe7a 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -50,6 +50,13 @@ pub enum DataType { /// [PostgreSQL]: https://www.postgresql.org/docs/15/sql-createfunction.html /// [MsSQL]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql?view=sql-server-ver16#c-create-a-multi-statement-table-valued-function Table(Vec), + /// Table type with a name, e.g. CREATE FUNCTION RETURNS @result TABLE(...). + NamedTable( + /// Table name. + ObjectName, + /// Table columns. + Vec, + ), /// Fixed-length character type, e.g. CHARACTER(10). Character(Option), /// Fixed-length char type, e.g. CHAR(10). @@ -723,6 +730,9 @@ impl fmt::Display for DataType { } write!(f, "TABLE({})", display_comma_separated(fields)) } + DataType::NamedTable(name, fields) => { + write!(f, "{} TABLE ({})", name, display_comma_separated(fields)) + } DataType::GeometricType(kind) => write!(f, "{}", kind), } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 176bb77b0..e405c18ec 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5207,7 +5207,31 @@ impl<'a> Parser<'a> { let (name, args) = self.parse_create_function_name_and_params()?; self.expect_keyword(Keyword::RETURNS)?; - let return_type = Some(self.parse_data_type()?); + + let return_table = self.maybe_parse(|p| { + let return_table_name = p.parse_identifier()?; + let table_column_defs = if p.peek_keyword(Keyword::TABLE) { + match p.parse_data_type()? { + DataType::Table(t) => t, + _ => parser_err!( + "Expected table data type after TABLE keyword", + p.peek_token().span.start + )?, + } + } else { + parser_err!("Expected TABLE keyword after return type", p.peek_token().span.start)? + }; + Ok(DataType::NamedTable( + ObjectName(vec![ObjectNamePart::Identifier(return_table_name)]), + table_column_defs.clone(), + )) + })?; + + let return_type = if return_table.is_some() { + return_table + } else { + Some(self.parse_data_type()?) + }; self.expect_keyword_is(Keyword::AS)?; diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 7f8e4baf1..04dc50737 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -296,6 +296,17 @@ fn parse_create_function() { RETURN (SELECT 1 AS col_1)\ "; let _ = ms().verified_stmt(create_inline_table_value_function); + + let create_multi_statement_table_value_function = "\ + CREATE FUNCTION some_multi_statement_tvf(@foo INT, @bar VARCHAR(256)) \ + RETURNS @t TABLE (col_1 INT) \ + AS \ + BEGIN \ + INSERT INTO @t SELECT 1; \ + RETURN; \ + END\ + "; + let _ = ms().verified_stmt(create_multi_statement_table_value_function); } #[test] From f1215e693f34bbc66b9506ec819c0c73bfdab558 Mon Sep 17 00:00:00 2001 From: Andrew Harper Date: Tue, 6 May 2025 16:07:05 -0400 Subject: [PATCH 03/14] Enable parsing `CREATE FUNCTION` without `AS` --- src/parser/mod.rs | 6 ++++-- tests/sqlparser_mssql.rs | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e405c18ec..7e947dbb1 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5233,7 +5233,9 @@ impl<'a> Parser<'a> { Some(self.parse_data_type()?) }; - self.expect_keyword_is(Keyword::AS)?; + if self.peek_keyword(Keyword::AS) { + self.expect_keyword_is(Keyword::AS)?; + } let function_body = if self.peek_keyword(Keyword::BEGIN) { let begin_token = self.expect_keyword(Keyword::BEGIN)?; @@ -9822,7 +9824,7 @@ impl<'a> Parser<'a> { Ok(DataType::AnyType) } Keyword::TABLE => { - if self.peek_keyword(Keyword::AS) { + if self.peek_token() != Token::LParen { Ok(DataType::Table(Vec::::new())) } else { let columns = self.parse_returns_table_columns()?; diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 04dc50737..377bcbc18 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -254,6 +254,12 @@ fn parse_create_function() { "; let _ = ms().verified_stmt(multi_statement_function); + let multi_statement_function_without_as = multi_statement_function.replace(" AS", ""); + let _ = ms().one_statement_parses_to( + &multi_statement_function_without_as, + multi_statement_function, + ); + let create_function_with_conditional = "\ CREATE FUNCTION some_scalar_udf() \ RETURNS INT \ @@ -297,6 +303,13 @@ fn parse_create_function() { "; let _ = ms().verified_stmt(create_inline_table_value_function); + let create_inline_table_value_function_without_as = + create_inline_table_value_function.replace(" AS", ""); + let _ = ms().one_statement_parses_to( + &create_inline_table_value_function_without_as, + create_inline_table_value_function, + ); + let create_multi_statement_table_value_function = "\ CREATE FUNCTION some_multi_statement_tvf(@foo INT, @bar VARCHAR(256)) \ RETURNS @t TABLE (col_1 INT) \ @@ -307,6 +320,13 @@ fn parse_create_function() { END\ "; let _ = ms().verified_stmt(create_multi_statement_table_value_function); + + let create_multi_statement_table_value_function_without_as = + create_multi_statement_table_value_function.replace(" AS", ""); + let _ = ms().one_statement_parses_to( + &create_multi_statement_table_value_function_without_as, + create_multi_statement_table_value_function, + ); } #[test] From 3093506880d891297a82e6c0a54557fbdf07ea88 Mon Sep 17 00:00:00 2001 From: Andrew Harper Date: Tue, 6 May 2025 16:39:37 -0400 Subject: [PATCH 04/14] Add support for constraints in table valued function definitions --- src/parser/mod.rs | 8 +------- tests/sqlparser_mssql.rs | 11 +++++++++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 7e947dbb1..922237cea 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9870,13 +9870,7 @@ impl<'a> Parser<'a> { } 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, - options: Vec::new(), // No constraints expected here - }) + self.parse_column_def() } fn parse_returns_table_columns(&mut self) -> Result, ParserError> { diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 377bcbc18..221529cdc 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -327,6 +327,17 @@ fn parse_create_function() { &create_multi_statement_table_value_function_without_as, create_multi_statement_table_value_function, ); + + let create_multi_statement_table_value_function_with_constraints = "\ + CREATE FUNCTION some_multi_statement_tvf(@foo INT, @bar VARCHAR(256)) \ + RETURNS @t TABLE (col_1 INT NOT NULL) \ + AS \ + BEGIN \ + INSERT INTO @t SELECT 1; \ + RETURN @t; \ + END\ + "; + let _ = ms().verified_stmt(create_multi_statement_table_value_function_with_constraints); } #[test] From eccc1c5458b03ed29b96ab1a943d270f87bca8d6 Mon Sep 17 00:00:00 2001 From: Andrew Harper Date: Tue, 6 May 2025 19:38:52 -0400 Subject: [PATCH 05/14] Corrected syntax formatting --- src/parser/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 922237cea..2568a6f6f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5219,7 +5219,10 @@ impl<'a> Parser<'a> { )?, } } else { - parser_err!("Expected TABLE keyword after return type", p.peek_token().span.start)? + parser_err!( + "Expected TABLE keyword after return type", + p.peek_token().span.start + )? }; Ok(DataType::NamedTable( ObjectName(vec![ObjectNamePart::Identifier(return_table_name)]), From f1f4e246b7d4abd0ac317ca0815b87a6b47966fc Mon Sep 17 00:00:00 2001 From: Andrew Harper Date: Wed, 7 May 2025 16:39:32 -0400 Subject: [PATCH 06/14] Add support for un-parenthesized "RETURN SELECT" syntax - rename `AsReturn` to `AsReturnSubquery` for clarity between these two variants --- src/ast/ddl.rs | 5 ++++- src/ast/mod.rs | 12 +++++++++++- src/parser/mod.rs | 22 ++++++++++++++++++---- tests/sqlparser_mssql.rs | 8 ++++++++ 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 7ddbf5e5a..87d11b7d3 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -2346,7 +2346,10 @@ impl fmt::Display for CreateFunction { if let Some(CreateFunctionBody::Return(function_body)) = &self.function_body { write!(f, " RETURN {function_body}")?; } - if let Some(CreateFunctionBody::AsReturn(function_body)) = &self.function_body { + if let Some(CreateFunctionBody::AsReturnSubquery(function_body)) = &self.function_body { + write!(f, " AS RETURN {function_body}")?; + } + if let Some(CreateFunctionBody::AsReturnSelect(function_body)) = &self.function_body { write!(f, " AS RETURN {function_body}")?; } if let Some(using) = &self.using { diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 2ef12915a..5856c2afc 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -8789,7 +8789,17 @@ pub enum CreateFunctionBody { /// ``` /// /// [MsSql]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql - AsReturn(Expr), + AsReturnSubquery(Expr), + + /// Function body expression using the 'AS RETURN' keywords, with an un-parenthesized SELECT query + /// + /// Example: + /// ```sql + /// CREATE FUNCTION myfunc(a INT, b INT) + /// RETURNS TABLE + /// AS RETURN SELECT a + b AS sum; + /// ``` + AsReturnSelect(Select), } #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2568a6f6f..22eea149f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5252,11 +5252,25 @@ impl<'a> Parser<'a> { })) } else if self.peek_keyword(Keyword::RETURN) { self.expect_keyword(Keyword::RETURN)?; - let expr = self.parse_expr()?; - if !matches!(expr, Expr::Subquery(_)) { - parser_err!("Expected a subquery after RETURN", expr.span().start)?; + + if self.peek_token() == Token::LParen { + let expr = self.parse_expr()?; + if !matches!(expr, Expr::Subquery(_)) { + parser_err!( + "Expected a subquery after RETURN", + self.peek_token().span.start + )? + } + Some(CreateFunctionBody::AsReturnSubquery(expr)) + } else if self.peek_keyword(Keyword::SELECT) { + let select = self.parse_select()?; + Some(CreateFunctionBody::AsReturnSelect(select)) + } else { + parser_err!( + "Expected a subquery (or bare SELECT statement) after RETURN", + self.peek_token().span.start + )? } - Some(CreateFunctionBody::AsReturn(expr)) } else { parser_err!("Unparsable function body", self.peek_token().span.start)? }; diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 221529cdc..534d24681 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -303,6 +303,14 @@ fn parse_create_function() { "; let _ = ms().verified_stmt(create_inline_table_value_function); + let create_inline_table_value_function_without_parentheses = "\ + CREATE FUNCTION some_inline_tvf(@foo INT, @bar VARCHAR(256)) \ + RETURNS TABLE \ + AS \ + RETURN SELECT 1 AS col_1\ + "; + let _ = ms().verified_stmt(create_inline_table_value_function_without_parentheses); + let create_inline_table_value_function_without_as = create_inline_table_value_function.replace(" AS", ""); let _ = ms().one_statement_parses_to( From f38609aa43d0849fb10add46950d07d19e075a34 Mon Sep 17 00:00:00 2001 From: Andrew Harper Date: Fri, 9 May 2025 10:57:23 -0400 Subject: [PATCH 07/14] Simplify peek/expect with `parse_keyword` --- src/parser/mod.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 22eea149f..b52e6b419 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5236,9 +5236,7 @@ impl<'a> Parser<'a> { Some(self.parse_data_type()?) }; - if self.peek_keyword(Keyword::AS) { - self.expect_keyword_is(Keyword::AS)?; - } + let _ = self.parse_keyword(Keyword::AS); let function_body = if self.peek_keyword(Keyword::BEGIN) { let begin_token = self.expect_keyword(Keyword::BEGIN)?; @@ -5250,9 +5248,7 @@ impl<'a> Parser<'a> { statements, end_token: AttachedToken(end_token), })) - } else if self.peek_keyword(Keyword::RETURN) { - self.expect_keyword(Keyword::RETURN)?; - + } else if self.parse_keyword(Keyword::RETURN) { if self.peek_token() == Token::LParen { let expr = self.parse_expr()?; if !matches!(expr, Expr::Subquery(_)) { From 96d9e4402276e53a618899493493eb5117014614 Mon Sep 17 00:00:00 2001 From: Andrew Harper Date: Fri, 9 May 2025 11:10:01 -0400 Subject: [PATCH 08/14] Make `Table`'s ColumnDefs optional --- src/ast/data_type.rs | 14 ++++++++------ src/parser/mod.rs | 16 +++++++++++++--- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index 0f1cafe7a..b731057d0 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -49,7 +49,7 @@ pub enum DataType { /// /// [PostgreSQL]: https://www.postgresql.org/docs/15/sql-createfunction.html /// [MsSQL]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql?view=sql-server-ver16#c-create-a-multi-statement-table-valued-function - Table(Vec), + Table(Option>), /// Table type with a name, e.g. CREATE FUNCTION RETURNS @result TABLE(...). NamedTable( /// Table name. @@ -724,12 +724,14 @@ impl fmt::Display for DataType { DataType::Unspecified => Ok(()), DataType::Trigger => write!(f, "TRIGGER"), DataType::AnyType => write!(f, "ANY TYPE"), - DataType::Table(fields) => { - if fields.is_empty() { - return write!(f, "TABLE"); + DataType::Table(fields) => match fields { + Some(fields) => { + write!(f, "TABLE({})", display_comma_separated(fields)) } - write!(f, "TABLE({})", display_comma_separated(fields)) - } + None => { + write!(f, "TABLE") + } + }, DataType::NamedTable(name, fields) => { write!(f, "{} TABLE ({})", name, display_comma_separated(fields)) } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index b52e6b419..6a8fb2d22 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5224,9 +5224,19 @@ impl<'a> Parser<'a> { p.peek_token().span.start )? }; + + if table_column_defs.is_none() + || table_column_defs.clone().is_some_and(|tcd| tcd.is_empty()) + { + parser_err!( + "Expected table column definitions after TABLE keyword", + p.peek_token().span.start + )? + } + Ok(DataType::NamedTable( ObjectName(vec![ObjectNamePart::Identifier(return_table_name)]), - table_column_defs.clone(), + table_column_defs.clone().unwrap(), )) })?; @@ -9838,10 +9848,10 @@ impl<'a> Parser<'a> { } Keyword::TABLE => { if self.peek_token() != Token::LParen { - Ok(DataType::Table(Vec::::new())) + Ok(DataType::Table(None)) } else { let columns = self.parse_returns_table_columns()?; - Ok(DataType::Table(columns)) + Ok(DataType::Table(Some(columns))) } } Keyword::SIGNED => { From 9c428b129e3e180efb45051f6b6cba2d70def6de Mon Sep 17 00:00:00 2001 From: Andrew Harper Date: Tue, 13 May 2025 12:17:32 -0400 Subject: [PATCH 09/14] Refactor to avoid cloning/unwrapping --- src/parser/mod.rs | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6a8fb2d22..9811c76b6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5210,33 +5210,40 @@ impl<'a> Parser<'a> { let return_table = self.maybe_parse(|p| { let return_table_name = p.parse_identifier()?; - let table_column_defs = if p.peek_keyword(Keyword::TABLE) { - match p.parse_data_type()? { - DataType::Table(t) => t, - _ => parser_err!( - "Expected table data type after TABLE keyword", - p.peek_token().span.start - )?, - } - } else { + + if !p.peek_keyword(Keyword::TABLE) { parser_err!( "Expected TABLE keyword after return type", p.peek_token().span.start )? - }; + } - if table_column_defs.is_none() - || table_column_defs.clone().is_some_and(|tcd| tcd.is_empty()) - { - parser_err!( - "Expected table column definitions after TABLE keyword", + let table_column_defs = match p.parse_data_type()? { + DataType::Table(maybe_table_column_defs) => match maybe_table_column_defs { + Some(table_column_defs) => { + if table_column_defs.is_empty() { + parser_err!( + "Expected table column definitions after TABLE keyword", + p.peek_token().span.start + )? + } + + table_column_defs + } + None => parser_err!( + "Expected table column definitions after TABLE keyword", + p.peek_token().span.start + )?, + }, + _ => parser_err!( + "Expected table data type after TABLE keyword", p.peek_token().span.start - )? - } + )?, + }; Ok(DataType::NamedTable( ObjectName(vec![ObjectNamePart::Identifier(return_table_name)]), - table_column_defs.clone().unwrap(), + table_column_defs, )) })?; From 4827bf5b88f8dd39c0aee3a25f0ae427433ca4d0 Mon Sep 17 00:00:00 2001 From: Andrew Harper Date: Tue, 13 May 2025 12:21:40 -0400 Subject: [PATCH 10/14] Refactor NamedTable to be a regular struct --- src/ast/data_type.rs | 12 ++++++------ src/parser/mod.rs | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index b731057d0..1c9dfba6c 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -51,12 +51,12 @@ pub enum DataType { /// [MsSQL]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql?view=sql-server-ver16#c-create-a-multi-statement-table-valued-function Table(Option>), /// Table type with a name, e.g. CREATE FUNCTION RETURNS @result TABLE(...). - NamedTable( + NamedTable { /// Table name. - ObjectName, + name: ObjectName, /// Table columns. - Vec, - ), + columns: Vec, + }, /// Fixed-length character type, e.g. CHARACTER(10). Character(Option), /// Fixed-length char type, e.g. CHAR(10). @@ -732,8 +732,8 @@ impl fmt::Display for DataType { write!(f, "TABLE") } }, - DataType::NamedTable(name, fields) => { - write!(f, "{} TABLE ({})", name, display_comma_separated(fields)) + DataType::NamedTable { name, columns } => { + write!(f, "{} TABLE ({})", name, display_comma_separated(columns)) } DataType::GeometricType(kind) => write!(f, "{}", kind), } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 9811c76b6..1d30c77b6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5241,10 +5241,10 @@ impl<'a> Parser<'a> { )?, }; - Ok(DataType::NamedTable( - ObjectName(vec![ObjectNamePart::Identifier(return_table_name)]), - table_column_defs, - )) + Ok(DataType::NamedTable { + name: ObjectName(vec![ObjectNamePart::Identifier(return_table_name)]), + columns: table_column_defs, + }) })?; let return_type = if return_table.is_some() { From 75c2e624ffff7a618b44f4bfb0dcc7dfaa15a009 Mon Sep 17 00:00:00 2001 From: Andrew Harper Date: Tue, 13 May 2025 12:23:59 -0400 Subject: [PATCH 11/14] Add documentation link for named tables types --- src/ast/data_type.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index 1c9dfba6c..3a4958c9f 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -51,6 +51,8 @@ pub enum DataType { /// [MsSQL]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql?view=sql-server-ver16#c-create-a-multi-statement-table-valued-function Table(Option>), /// Table type with a name, e.g. CREATE FUNCTION RETURNS @result TABLE(...). + /// + /// [MsSQl]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql?view=sql-server-ver16#table NamedTable { /// Table name. name: ObjectName, From de0665c02766a14b96cdb34f833dbac8f1b49b95 Mon Sep 17 00:00:00 2001 From: Andrew Harper Date: Tue, 13 May 2025 12:25:42 -0400 Subject: [PATCH 12/14] Add documentation link for RETURN SELECT --- src/ast/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 5856c2afc..c9f0b3e9f 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -8799,6 +8799,8 @@ pub enum CreateFunctionBody { /// RETURNS TABLE /// AS RETURN SELECT a + b AS sum; /// ``` + /// + /// [MsSql]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql?view=sql-server-ver16#select_stmt AsReturnSelect(Select), } From cc748c340e34de2ffd3391a04b35de3c3b089c48 Mon Sep 17 00:00:00 2001 From: Andrew Harper Date: Thu, 15 May 2025 13:26:02 -0400 Subject: [PATCH 13/14] Add comment to explain LParen behavior - plus, flip the if/else to positive equality for simplicity --- src/parser/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1d30c77b6..d4f6ef85d 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9854,11 +9854,13 @@ impl<'a> Parser<'a> { Ok(DataType::AnyType) } Keyword::TABLE => { - if self.peek_token() != Token::LParen { - Ok(DataType::Table(None)) - } else { + // an LParen after the TABLE keyword indicates that table columns are being defined + // whereas no LParen indicates an anonymous table expression will be returned + if self.peek_token() == Token::LParen { let columns = self.parse_returns_table_columns()?; Ok(DataType::Table(Some(columns))) + } else { + Ok(DataType::Table(None)) } } Keyword::SIGNED => { From d509d4e470a5d63d621202da1c21eee3b179636e Mon Sep 17 00:00:00 2001 From: Andrew Harper Date: Thu, 15 May 2025 13:52:52 -0400 Subject: [PATCH 14/14] Rename `AsReturnSubquery` to `AsReturnExpr` & remove non-subquery error --- src/ast/ddl.rs | 2 +- src/ast/mod.rs | 2 +- src/parser/mod.rs | 9 +-------- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 87d11b7d3..6275a4201 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -2346,7 +2346,7 @@ impl fmt::Display for CreateFunction { if let Some(CreateFunctionBody::Return(function_body)) = &self.function_body { write!(f, " RETURN {function_body}")?; } - if let Some(CreateFunctionBody::AsReturnSubquery(function_body)) = &self.function_body { + if let Some(CreateFunctionBody::AsReturnExpr(function_body)) = &self.function_body { write!(f, " AS RETURN {function_body}")?; } if let Some(CreateFunctionBody::AsReturnSelect(function_body)) = &self.function_body { diff --git a/src/ast/mod.rs b/src/ast/mod.rs index c9f0b3e9f..cb297af63 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -8789,7 +8789,7 @@ pub enum CreateFunctionBody { /// ``` /// /// [MsSql]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql - AsReturnSubquery(Expr), + AsReturnExpr(Expr), /// Function body expression using the 'AS RETURN' keywords, with an un-parenthesized SELECT query /// diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d4f6ef85d..2c9ab61d3 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5267,14 +5267,7 @@ impl<'a> Parser<'a> { })) } else if self.parse_keyword(Keyword::RETURN) { if self.peek_token() == Token::LParen { - let expr = self.parse_expr()?; - if !matches!(expr, Expr::Subquery(_)) { - parser_err!( - "Expected a subquery after RETURN", - self.peek_token().span.start - )? - } - Some(CreateFunctionBody::AsReturnSubquery(expr)) + Some(CreateFunctionBody::AsReturnExpr(self.parse_expr()?)) } else if self.peek_keyword(Keyword::SELECT) { let select = self.parse_select()?; Some(CreateFunctionBody::AsReturnSelect(select))