Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1512,6 +1512,15 @@ pub enum TableFactor {
/// Optional alias for the table function result.
alias: Option<TableAlias>,
},
/// `ROWS FROM(function_call [, ...]) [WITH ORDINALITY][ AS <alias> ]`
RowsFrom {
/// Table functions combined by `ROWS FROM`.
table_functions: Vec<TableFactor>,
/// Whether `WITH ORDINALITY` was specified to include ordinality.
with_ordinality: bool,
/// Optional alias for the `ROWS FROM` result.
alias: Option<TableAlias>,
},
/// `e.g. LATERAL FLATTEN(<args>)[ AS <alias> ]`
Function {
/// Whether the function is LATERAL.
Expand Down Expand Up @@ -2315,6 +2324,20 @@ impl fmt::Display for TableFactor {
}
Ok(())
}
TableFactor::RowsFrom {
table_functions,
with_ordinality,
alias,
} => {
write!(f, "ROWS FROM({})", display_comma_separated(table_functions))?;
if *with_ordinality {
write!(f, " WITH ORDINALITY")?;
}
if let Some(alias) = alias {
write!(f, " {alias}")?;
}
Ok(())
}
TableFactor::UNNEST {
alias,
array_exprs,
Expand Down
10 changes: 10 additions & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2001,6 +2001,16 @@ impl Spanned for TableFactor {
TableFactor::TableFunction { expr, alias } => expr
.span()
.union_opt(&alias.as_ref().map(|alias| alias.span())),
TableFactor::RowsFrom {
table_functions,
with_ordinality: _,
alias,
} => union_spans(
table_functions
.iter()
.map(|i| i.span())
.chain(alias.as_ref().map(|alias| alias.span())),
),
TableFactor::UNNEST {
alias,
with_offset: _,
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ impl Dialect for GenericDialect {
true
}

fn supports_rows_from_table_factor(&self) -> bool {
true
}

fn supports_start_transaction_modifier(&self) -> bool {
true
}
Expand Down
7 changes: 7 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,13 @@ pub trait Dialect: Debug + Any {
false
}

/// Return true if the dialect supports `ROWS FROM` in table functions.
///
/// See <https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-TABLEFUNCTIONS>
fn supports_rows_from_table_factor(&self) -> bool {
false
}

/// Does the dialect support MySQL-style `'user'@'host'` grantee syntax?
fn supports_user_host_grantee(&self) -> bool {
false
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/postgresql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ impl Dialect for PostgreSqlDialect {
true
}

fn supports_rows_from_table_factor(&self) -> bool {
true
}

/// See <https://www.postgresql.org/docs/current/functions-json.html>
///
/// Required to support the colon in:
Expand Down
103 changes: 73 additions & 30 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16583,6 +16583,7 @@ impl<'a> Parser<'a> {
| TableFactor::XmlTable { alias, .. }
| TableFactor::OpenJsonTable { alias, .. }
| TableFactor::TableFunction { alias, .. }
| TableFactor::RowsFrom { alias, .. }
| TableFactor::Pivot { alias, .. }
| TableFactor::Unpivot { alias, .. }
| TableFactor::MatchRecognize { alias, .. }
Expand Down Expand Up @@ -16650,42 +16651,23 @@ impl<'a> Parser<'a> {
alias,
sample: None,
})
} else if dialect_of!(self is BigQueryDialect | PostgreSqlDialect | GenericDialect)
&& self.parse_keyword(Keyword::UNNEST)
} else if self.dialect.supports_rows_from_table_factor()
&& self.parse_keywords(&[Keyword::ROWS, Keyword::FROM])
{
self.expect_token(&Token::LParen)?;
let array_exprs = self.parse_comma_separated(Parser::parse_expr)?;
let table_functions = self.parse_comma_separated(Parser::parse_rows_from_function)?;
self.expect_token(&Token::RParen)?;

let with_ordinality = self.parse_keywords(&[Keyword::WITH, Keyword::ORDINALITY]);
let alias = match self.maybe_parse_table_alias() {
Ok(Some(alias)) => Some(alias),
Ok(None) => None,
Err(e) => return Err(e),
};

let with_offset = match self.expect_keywords(&[Keyword::WITH, Keyword::OFFSET]) {
Ok(()) => true,
Err(_) => false,
};

let with_offset_alias = if with_offset {
match self.parse_optional_alias(keywords::RESERVED_FOR_COLUMN_ALIAS) {
Ok(Some(alias)) => Some(alias),
Ok(None) => None,
Err(e) => return Err(e),
}
} else {
None
};

Ok(TableFactor::UNNEST {
alias,
array_exprs,
with_offset,
with_offset_alias,
let alias = self.maybe_parse_table_alias()?;
Ok(TableFactor::RowsFrom {
table_functions,
with_ordinality,
alias,
})
} else if dialect_of!(self is BigQueryDialect | PostgreSqlDialect | GenericDialect)
&& self.parse_keyword(Keyword::UNNEST)
{
self.parse_unnest_table_factor()
} else if self.dialect.supports_unpivot_expr() && self.peek_keyword(Keyword::UNPIVOT) {
self.parse_unpivot_expr_table_factor()
} else if self.parse_keyword_with_tokens(Keyword::JSON_TABLE, &[Token::LParen]) {
Expand Down Expand Up @@ -16813,6 +16795,67 @@ impl<'a> Parser<'a> {
}
}

fn parse_rows_from_function(&mut self) -> Result<TableFactor, ParserError> {
if self.parse_keyword(Keyword::UNNEST) {
return self.parse_unnest_table_factor();
}

let name = self.parse_object_name(true)?;
self.expect_token(&Token::LParen)?;
let args = Some(self.parse_table_function_args()?);
let with_ordinality = self.parse_keywords(&[Keyword::WITH, Keyword::ORDINALITY]);
let alias = self.maybe_parse_table_alias()?;

Ok(TableFactor::Table {
name,
alias,
args,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality,
json_path: None,
sample: None,
index_hints: vec![],
})
}
Comment on lines +16798 to +16821

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should parse each ROWS FROM item as a bare function call. I will provide some example red tests shortly.

Suggested change
fn parse_rows_from_function(&mut self) -> Result<TableFactor, ParserError> {
if self.parse_keyword(Keyword::UNNEST) {
return self.parse_unnest_table_factor();
}
let name = self.parse_object_name(true)?;
self.expect_token(&Token::LParen)?;
let args = Some(self.parse_table_function_args()?);
let with_ordinality = self.parse_keywords(&[Keyword::WITH, Keyword::ORDINALITY]);
let alias = self.maybe_parse_table_alias()?;
Ok(TableFactor::Table {
name,
alias,
args,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality,
json_path: None,
sample: None,
index_hints: vec![],
})
}
fn parse_rows_from_function(&mut self) -> Result<TableFactor, ParserError> {
if self.parse_keyword(Keyword::UNNEST) {
self.expect_token(&Token::LParen)?;
let array_exprs = self.parse_comma_separated(Parser::parse_expr)?;
self.expect_token(&Token::RParen)?;
return Ok(TableFactor::UNNEST {
alias: None,
array_exprs,
with_offset: false,
with_offset_alias: None,
with_ordinality: false,
});
}
let name = self.parse_object_name(true)?;
self.expect_token(&Token::LParen)?;
let args = Some(self.parse_table_function_args()?);
Ok(TableFactor::Table {
name,
alias: None,
args,
with_hints: vec![],
version: None,
partitions: vec![],
with_ordinality: false,
json_path: None,
sample: None,
index_hints: vec![],
})
}


fn parse_unnest_table_factor(&mut self) -> Result<TableFactor, ParserError> {
self.expect_token(&Token::LParen)?;
let array_exprs = self.parse_comma_separated(Parser::parse_expr)?;
self.expect_token(&Token::RParen)?;

let with_ordinality = self.parse_keywords(&[Keyword::WITH, Keyword::ORDINALITY]);
let alias = match self.maybe_parse_table_alias() {
Ok(Some(alias)) => Some(alias),
Ok(None) => None,
Err(e) => return Err(e),
};

let with_offset = match self.expect_keywords(&[Keyword::WITH, Keyword::OFFSET]) {
Ok(()) => true,
Err(_) => false,
};

let with_offset_alias = if with_offset {
match self.parse_optional_alias(keywords::RESERVED_FOR_COLUMN_ALIAS) {
Ok(Some(alias)) => Some(alias),
Ok(None) => None,
Err(e) => return Err(e),
}
} else {
None
};

Ok(TableFactor::UNNEST {
alias,
array_exprs,
with_offset,
with_offset_alias,
with_ordinality,
})
}

/// Parse a Snowflake stage reference as a table factor.
/// Handles syntax like: `@mystage1 (file_format => 'myformat', pattern => '...')`
///
Expand Down
20 changes: 20 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10037,3 +10037,23 @@ fn parse_bitstring_literal_escaping() {
pg_and_generic().verified_stmt("SELECT B''''");
pg_and_generic().verified_stmt("SELECT B'it''s'");
}

#[test]
fn parse_rows_from_with_ordinality() {
pg_and_generic().verified_stmt(
"SELECT * FROM ROWS FROM(UNNEST(ARRAY[1, 2]) WITH ORDINALITY) AS r (val, ord)",
);
Comment on lines +10043 to +10045

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may want to turn this case into a negative one, since PostgreSQL rejects it with syntax error at or near "WITH". The loop below fails on the current parser and passes with the change above.

Suggested change
pg_and_generic().verified_stmt(
"SELECT * FROM ROWS FROM(UNNEST(ARRAY[1, 2]) WITH ORDINALITY) AS r (val, ord)",
);
for sql in [
"SELECT * FROM ROWS FROM(UNNEST(ARRAY[1, 2]) WITH ORDINALITY) AS r",
"SELECT * FROM ROWS FROM(UNNEST(ARRAY[1]) WITH OFFSET) AS r",
"SELECT * FROM ROWS FROM(generate_series(1, 2) WITH ORDINALITY) AS r",
"SELECT * FROM ROWS FROM(generate_series(1, 2) AS g) AS r",
"SELECT * FROM ROWS FROM(generate_series(1, 2) g) AS r",
] {
assert!(pg_and_generic().parse_sql_statements(sql).is_err());
}


pg_and_generic().verified_stmt(
"SELECT * FROM ROWS FROM(UNNEST(ARRAY[1, 2])) WITH ORDINALITY AS r (val, ord)",
);

pg_and_generic().verified_stmt(
"SELECT * FROM ROWS FROM(UNNEST(ARRAY[1]), generate_series(1, 2)) AS r (a, b)",
);

let err = pg_and_generic()
.parse_sql_statements("SELECT * FROM ROWS FROM(foo)")
.unwrap_err();
assert_eq!("sql parser error: Expected: (, found: )", err.to_string());
}
Loading