Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 2 additions & 2 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ pub use self::query::{
OrderBy, OrderByExpr, PivotValueSource, ProjectionSelect, Query, RenameSelectItem,
RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select,
SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Setting, SymbolDefinition, Table,
TableAlias, TableFactor, TableFunctionArgs, TableVersion, TableWithJoins, Top, TopQuantity,
ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
TableAlias, TableAliasColumnDef, TableFactor, TableFunctionArgs, TableVersion, TableWithJoins,
Top, TopQuantity, ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
};

pub use self::trigger::{
Expand Down
37 changes: 36 additions & 1 deletion src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1597,7 +1597,7 @@ impl fmt::Display for TableFactor {
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct TableAlias {
pub name: Ident,
pub columns: Vec<Ident>,
pub columns: Vec<TableAliasColumnDef>,
}

impl fmt::Display for TableAlias {
Expand All @@ -1610,6 +1610,41 @@ impl fmt::Display for TableAlias {
}
}

/// SQL column definition in a table expression alias.
/// Most of the time, the data type is not specified.
/// But some table-valued functions do require specifying the data type.
///
/// See <https://www.postgresql.org/docs/17/queries-table-expressions.html#QUERIES-TABLEFUNCTIONS>
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct TableAliasColumnDef {
/// Column name alias
pub name: Ident,
/// Some table-valued functions require specifying the data type in the alias.
pub data_type: Option<DataType>,
}

impl TableAliasColumnDef {
/// Create a new table alias column definition with only a name and no type
pub fn from_column_name(name: &str) -> Self {
TableAliasColumnDef {
name: Ident::new(name),
data_type: None,
}
}
}

impl fmt::Display for TableAliasColumnDef {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.name)?;
if let Some(ref data_type) = self.data_type {
write!(f, " {}", data_type)?;
}
Ok(())
}
}

#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
Expand Down
19 changes: 17 additions & 2 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8270,7 +8270,7 @@ impl<'a> Parser<'a> {
) -> Result<Option<TableAlias>, ParserError> {
match self.parse_optional_alias(reserved_kwds)? {
Some(name) => {
let columns = self.parse_parenthesized_column_list(Optional, false)?;
let columns = self.parse_table_alias_column_defs()?;
Ok(Some(TableAlias { name, columns }))
}
None => Ok(None),
Expand Down Expand Up @@ -8607,6 +8607,21 @@ impl<'a> Parser<'a> {
}
}

/// Parse a parenthesized comma-separated list of table alias column definitions.
fn parse_table_alias_column_defs(&mut self) -> Result<Vec<TableAliasColumnDef>, ParserError> {
if self.consume_token(&Token::LParen) {
let cols = self.parse_comma_separated(|p| {
let name = p.parse_identifier(false)?;
let data_type = p.maybe_parse(|p| p.parse_data_type())?;
Ok(TableAliasColumnDef { name, data_type })
})?;
self.expect_token(&Token::RParen)?;
Ok(cols)
} else {
Ok(vec![])
}
}

pub fn parse_precision(&mut self) -> Result<u64, ParserError> {
self.expect_token(&Token::LParen)?;
let n = self.parse_literal_uint()?;
Expand Down Expand Up @@ -9174,7 +9189,7 @@ impl<'a> Parser<'a> {
materialized: is_materialized,
}
} else {
let columns = self.parse_parenthesized_column_list(Optional, false)?;
let columns = self.parse_table_alias_column_defs()?;
self.expect_keyword(Keyword::AS)?;
let mut is_materialized = None;
if dialect_of!(self is PostgreSqlDialect) {
Expand Down
59 changes: 50 additions & 9 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,11 @@ fn parse_select_with_table_alias() {
name: ObjectName(vec![Ident::new("lineitem")]),
alias: Some(TableAlias {
name: Ident::new("l"),
columns: vec![Ident::new("A"), Ident::new("B"), Ident::new("C"),],
columns: vec![
TableAliasColumnDef::from_column_name("A"),
TableAliasColumnDef::from_column_name("B"),
TableAliasColumnDef::from_column_name("C"),
],
}),
args: None,
with_hints: vec![],
Expand Down Expand Up @@ -5576,6 +5580,40 @@ fn parse_table_function() {
);
}

#[test]
fn parse_select_with_alias_and_column_defs() {
let sql = r#"SELECT * FROM jsonb_to_record('{"a": "x", "b": 2}'::JSONB) AS x (a TEXT, b INT)"#;
let select = verified_only_select(sql);

match only(&select.from) {
TableWithJoins {
relation: TableFactor::Table {
alias: Some(alias), ..
},
..
} => {
assert_eq!(alias.name.value, "x");
assert_eq!(
alias.columns,
vec![
TableAliasColumnDef {
name: Ident::new("a"),
data_type: Some(DataType::Text),
},
TableAliasColumnDef {
name: Ident::new("b"),
data_type: Some(DataType::Int(None)),
},
]
);
}
_ => unreachable!(
"Expecting only TableWithJoins with TableFactor::Table, got {:#?}",
select.from
),
}
}

#[test]
fn parse_unnest() {
let sql = "SELECT UNNEST(make_array(1, 2, 3))";
Expand Down Expand Up @@ -6335,7 +6373,10 @@ fn parse_cte_renamed_columns() {
let sql = "WITH cte (col1, col2) AS (SELECT foo, bar FROM baz) SELECT * FROM cte";
let query = all_dialects().verified_query(sql);
assert_eq!(
vec![Ident::new("col1"), Ident::new("col2")],
vec![
TableAliasColumnDef::from_column_name("col1"),
TableAliasColumnDef::from_column_name("col2")
],
query
.with
.unwrap()
Expand Down Expand Up @@ -6364,10 +6405,7 @@ fn parse_recursive_cte() {
value: "nums".to_string(),
quote_style: None,
},
columns: vec![Ident {
value: "val".to_string(),
quote_style: None,
}],
columns: vec![TableAliasColumnDef::from_column_name("val")],
},
query: Box::new(cte_query),
from: None,
Expand Down Expand Up @@ -9310,7 +9348,10 @@ fn parse_pivot_table() {
value: "p".to_string(),
quote_style: None
},
columns: vec![Ident::new("c"), Ident::new("d")],
columns: vec![
TableAliasColumnDef::from_column_name("c"),
TableAliasColumnDef::from_column_name("d"),
],
}),
}
);
Expand Down Expand Up @@ -9371,8 +9412,8 @@ fn parse_unpivot_table() {
name: Ident::new("u"),
columns: ["product", "quarter", "quantity"]
.into_iter()
.map(Ident::new)
.collect()
.map(TableAliasColumnDef::from_column_name)
.collect(),
}),
}
);
Expand Down