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 @@ -1513,6 +1513,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 @@ -2316,6 +2325,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 @@ -732,6 +732,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 @@ -16611,6 +16611,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 @@ -16678,42 +16679,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 @@ -16841,6 +16823,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![],
})
}

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 @@ -9953,3 +9953,23 @@ fn parse_insert_by_name_keywords_as_table_and_alias() {
statement => panic!("Expected INSERT statement, got: {statement:?}"),
}
}

#[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)",
);

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