Skip to content

Commit 945f8e0

Browse files
authored
Add support for XMLTABLE (#1817)
1 parent 3ec80e1 commit 945f8e0

File tree

6 files changed

+327
-1
lines changed

6 files changed

+327
-1
lines changed

src/ast/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ pub use self::query::{
8181
TableSampleBucket, TableSampleKind, TableSampleMethod, TableSampleModifier,
8282
TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier, TableSampleUnit, TableVersion,
8383
TableWithJoins, Top, TopQuantity, UpdateTableFromKind, ValueTableMode, Values,
84-
WildcardAdditionalOptions, With, WithFill,
84+
WildcardAdditionalOptions, With, WithFill, XmlNamespaceDefinition, XmlPassingArgument,
85+
XmlPassingClause, XmlTableColumn, XmlTableColumnOption,
8586
};
8687

8788
pub use self::trigger::{

src/ast/query.rs

+186
Original file line numberDiff line numberDiff line change
@@ -1271,6 +1271,37 @@ pub enum TableFactor {
12711271
symbols: Vec<SymbolDefinition>,
12721272
alias: Option<TableAlias>,
12731273
},
1274+
/// The `XMLTABLE` table-valued function.
1275+
/// Part of the SQL standard, supported by PostgreSQL, Oracle, and DB2.
1276+
///
1277+
/// <https://www.postgresql.org/docs/15/functions-xml.html#FUNCTIONS-XML-PROCESSING>
1278+
///
1279+
/// ```sql
1280+
/// SELECT xmltable.*
1281+
/// FROM xmldata,
1282+
/// XMLTABLE('//ROWS/ROW'
1283+
/// PASSING data
1284+
/// COLUMNS id int PATH '@id',
1285+
/// ordinality FOR ORDINALITY,
1286+
/// "COUNTRY_NAME" text,
1287+
/// country_id text PATH 'COUNTRY_ID',
1288+
/// size_sq_km float PATH 'SIZE[@unit = "sq_km"]',
1289+
/// size_other text PATH 'concat(SIZE[@unit!="sq_km"], " ", SIZE[@unit!="sq_km"]/@unit)',
1290+
/// premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified'
1291+
/// );
1292+
/// ````
1293+
XmlTable {
1294+
/// Optional XMLNAMESPACES clause (empty if not present)
1295+
namespaces: Vec<XmlNamespaceDefinition>,
1296+
/// The row-generating XPath expression.
1297+
row_expression: Expr,
1298+
/// The PASSING clause specifying the document expression.
1299+
passing: XmlPassingClause,
1300+
/// The columns to be extracted from each generated row.
1301+
columns: Vec<XmlTableColumn>,
1302+
/// The alias for the table.
1303+
alias: Option<TableAlias>,
1304+
},
12741305
}
12751306

12761307
/// The table sample modifier options
@@ -1936,6 +1967,31 @@ impl fmt::Display for TableFactor {
19361967
}
19371968
Ok(())
19381969
}
1970+
TableFactor::XmlTable {
1971+
row_expression,
1972+
passing,
1973+
columns,
1974+
alias,
1975+
namespaces,
1976+
} => {
1977+
write!(f, "XMLTABLE(")?;
1978+
if !namespaces.is_empty() {
1979+
write!(
1980+
f,
1981+
"XMLNAMESPACES({}), ",
1982+
display_comma_separated(namespaces)
1983+
)?;
1984+
}
1985+
write!(
1986+
f,
1987+
"{row_expression}{passing} COLUMNS {columns})",
1988+
columns = display_comma_separated(columns)
1989+
)?;
1990+
if let Some(alias) = alias {
1991+
write!(f, " AS {alias}")?;
1992+
}
1993+
Ok(())
1994+
}
19391995
}
19401996
}
19411997
}
@@ -3082,3 +3138,133 @@ pub enum UpdateTableFromKind {
30823138
/// For Example: `UPDATE SET t1.name='aaa' FROM t1`
30833139
AfterSet(Vec<TableWithJoins>),
30843140
}
3141+
3142+
/// Defines the options for an XmlTable column: Named or ForOrdinality
3143+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
3144+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
3145+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
3146+
pub enum XmlTableColumnOption {
3147+
/// A named column with a type, optional path, and default value.
3148+
NamedInfo {
3149+
/// The type of the column to be extracted.
3150+
r#type: DataType,
3151+
/// The path to the column to be extracted. If None, defaults to the column name.
3152+
path: Option<Expr>,
3153+
/// Default value if path does not match
3154+
default: Option<Expr>,
3155+
/// Whether the column is nullable (NULL=true, NOT NULL=false)
3156+
nullable: bool,
3157+
},
3158+
/// The FOR ORDINALITY marker
3159+
ForOrdinality,
3160+
}
3161+
3162+
/// A single column definition in XMLTABLE
3163+
///
3164+
/// ```sql
3165+
/// COLUMNS
3166+
/// id int PATH '@id',
3167+
/// ordinality FOR ORDINALITY,
3168+
/// "COUNTRY_NAME" text,
3169+
/// country_id text PATH 'COUNTRY_ID',
3170+
/// size_sq_km float PATH 'SIZE[@unit = "sq_km"]',
3171+
/// size_other text PATH 'concat(SIZE[@unit!="sq_km"], " ", SIZE[@unit!="sq_km"]/@unit)',
3172+
/// premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified'
3173+
/// ```
3174+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
3175+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
3176+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
3177+
pub struct XmlTableColumn {
3178+
/// The name of the column.
3179+
pub name: Ident,
3180+
/// Column options: type/path/default or FOR ORDINALITY
3181+
pub option: XmlTableColumnOption,
3182+
}
3183+
3184+
impl fmt::Display for XmlTableColumn {
3185+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3186+
write!(f, "{}", self.name)?;
3187+
match &self.option {
3188+
XmlTableColumnOption::NamedInfo {
3189+
r#type,
3190+
path,
3191+
default,
3192+
nullable,
3193+
} => {
3194+
write!(f, " {}", r#type)?;
3195+
if let Some(p) = path {
3196+
write!(f, " PATH {}", p)?;
3197+
}
3198+
if let Some(d) = default {
3199+
write!(f, " DEFAULT {}", d)?;
3200+
}
3201+
if !*nullable {
3202+
write!(f, " NOT NULL")?;
3203+
}
3204+
Ok(())
3205+
}
3206+
XmlTableColumnOption::ForOrdinality => {
3207+
write!(f, " FOR ORDINALITY")
3208+
}
3209+
}
3210+
}
3211+
}
3212+
3213+
/// Argument passed in the XMLTABLE PASSING clause
3214+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
3215+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
3216+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
3217+
pub struct XmlPassingArgument {
3218+
pub expr: Expr,
3219+
pub alias: Option<Ident>,
3220+
pub by_value: bool, // True if BY VALUE is specified
3221+
}
3222+
3223+
impl fmt::Display for XmlPassingArgument {
3224+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3225+
if self.by_value {
3226+
write!(f, "BY VALUE ")?;
3227+
}
3228+
write!(f, "{}", self.expr)?;
3229+
if let Some(alias) = &self.alias {
3230+
write!(f, " AS {}", alias)?;
3231+
}
3232+
Ok(())
3233+
}
3234+
}
3235+
3236+
/// The PASSING clause for XMLTABLE
3237+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
3238+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
3239+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
3240+
pub struct XmlPassingClause {
3241+
pub arguments: Vec<XmlPassingArgument>,
3242+
}
3243+
3244+
impl fmt::Display for XmlPassingClause {
3245+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3246+
if !self.arguments.is_empty() {
3247+
write!(f, " PASSING {}", display_comma_separated(&self.arguments))?;
3248+
}
3249+
Ok(())
3250+
}
3251+
}
3252+
3253+
/// Represents a single XML namespace definition in the XMLNAMESPACES clause.
3254+
///
3255+
/// `namespace_uri AS namespace_name`
3256+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
3257+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
3258+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
3259+
pub struct XmlNamespaceDefinition {
3260+
/// The namespace URI (a text expression).
3261+
pub uri: Expr,
3262+
/// The alias for the namespace (a simple identifier).
3263+
pub name: Ident,
3264+
}
3265+
3266+
impl fmt::Display for XmlNamespaceDefinition {
3267+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3268+
write!(f, "{} AS {}", self.uri, self.name)
3269+
}
3270+
}

src/ast/spans.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1909,6 +1909,7 @@ impl Spanned for TableFactor {
19091909
.chain(alias.as_ref().map(|alias| alias.span())),
19101910
),
19111911
TableFactor::JsonTable { .. } => Span::empty(),
1912+
TableFactor::XmlTable { .. } => Span::empty(),
19121913
TableFactor::Pivot {
19131914
table,
19141915
aggregate_functions,

src/keywords.rs

+3
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,7 @@ define_keywords!(
654654
PARTITION,
655655
PARTITIONED,
656656
PARTITIONS,
657+
PASSING,
657658
PASSWORD,
658659
PAST,
659660
PATH,
@@ -989,6 +990,8 @@ define_keywords!(
989990
WORK,
990991
WRITE,
991992
XML,
993+
XMLNAMESPACES,
994+
XMLTABLE,
992995
XOR,
993996
YEAR,
994997
YEARS,

src/parser/mod.rs

+97
Original file line numberDiff line numberDiff line change
@@ -11992,6 +11992,7 @@ impl<'a> Parser<'a> {
1199211992
| TableFactor::Function { alias, .. }
1199311993
| TableFactor::UNNEST { alias, .. }
1199411994
| TableFactor::JsonTable { alias, .. }
11995+
| TableFactor::XmlTable { alias, .. }
1199511996
| TableFactor::OpenJsonTable { alias, .. }
1199611997
| TableFactor::TableFunction { alias, .. }
1199711998
| TableFactor::Pivot { alias, .. }
@@ -12107,6 +12108,9 @@ impl<'a> Parser<'a> {
1210712108
} else if self.parse_keyword_with_tokens(Keyword::OPENJSON, &[Token::LParen]) {
1210812109
self.prev_token();
1210912110
self.parse_open_json_table_factor()
12111+
} else if self.parse_keyword_with_tokens(Keyword::XMLTABLE, &[Token::LParen]) {
12112+
self.prev_token();
12113+
self.parse_xml_table_factor()
1211012114
} else {
1211112115
let name = self.parse_object_name(true)?;
1211212116

@@ -12339,6 +12343,99 @@ impl<'a> Parser<'a> {
1233912343
})
1234012344
}
1234112345

12346+
fn parse_xml_table_factor(&mut self) -> Result<TableFactor, ParserError> {
12347+
self.expect_token(&Token::LParen)?;
12348+
let namespaces = if self.parse_keyword(Keyword::XMLNAMESPACES) {
12349+
self.expect_token(&Token::LParen)?;
12350+
let namespaces = self.parse_comma_separated(Parser::parse_xml_namespace_definition)?;
12351+
self.expect_token(&Token::RParen)?;
12352+
self.expect_token(&Token::Comma)?;
12353+
namespaces
12354+
} else {
12355+
vec![]
12356+
};
12357+
let row_expression = self.parse_expr()?;
12358+
let passing = self.parse_xml_passing_clause()?;
12359+
self.expect_keyword_is(Keyword::COLUMNS)?;
12360+
let columns = self.parse_comma_separated(Parser::parse_xml_table_column)?;
12361+
self.expect_token(&Token::RParen)?;
12362+
let alias = self.maybe_parse_table_alias()?;
12363+
Ok(TableFactor::XmlTable {
12364+
namespaces,
12365+
row_expression,
12366+
passing,
12367+
columns,
12368+
alias,
12369+
})
12370+
}
12371+
12372+
fn parse_xml_namespace_definition(&mut self) -> Result<XmlNamespaceDefinition, ParserError> {
12373+
let uri = self.parse_expr()?;
12374+
self.expect_keyword_is(Keyword::AS)?;
12375+
let name = self.parse_identifier()?;
12376+
Ok(XmlNamespaceDefinition { uri, name })
12377+
}
12378+
12379+
fn parse_xml_table_column(&mut self) -> Result<XmlTableColumn, ParserError> {
12380+
let name = self.parse_identifier()?;
12381+
12382+
let option = if self.parse_keyword(Keyword::FOR) {
12383+
self.expect_keyword(Keyword::ORDINALITY)?;
12384+
XmlTableColumnOption::ForOrdinality
12385+
} else {
12386+
let r#type = self.parse_data_type()?;
12387+
let mut path = None;
12388+
let mut default = None;
12389+
12390+
if self.parse_keyword(Keyword::PATH) {
12391+
path = Some(self.parse_expr()?);
12392+
}
12393+
12394+
if self.parse_keyword(Keyword::DEFAULT) {
12395+
default = Some(self.parse_expr()?);
12396+
}
12397+
12398+
let not_null = self.parse_keywords(&[Keyword::NOT, Keyword::NULL]);
12399+
if !not_null {
12400+
// NULL is the default but can be specified explicitly
12401+
let _ = self.parse_keyword(Keyword::NULL);
12402+
}
12403+
12404+
XmlTableColumnOption::NamedInfo {
12405+
r#type,
12406+
path,
12407+
default,
12408+
nullable: !not_null,
12409+
}
12410+
};
12411+
Ok(XmlTableColumn { name, option })
12412+
}
12413+
12414+
fn parse_xml_passing_clause(&mut self) -> Result<XmlPassingClause, ParserError> {
12415+
let mut arguments = vec![];
12416+
if self.parse_keyword(Keyword::PASSING) {
12417+
loop {
12418+
let by_value =
12419+
self.parse_keyword(Keyword::BY) && self.expect_keyword(Keyword::VALUE).is_ok();
12420+
let expr = self.parse_expr()?;
12421+
let alias = if self.parse_keyword(Keyword::AS) {
12422+
Some(self.parse_identifier()?)
12423+
} else {
12424+
None
12425+
};
12426+
arguments.push(XmlPassingArgument {
12427+
expr,
12428+
alias,
12429+
by_value,
12430+
});
12431+
if !self.consume_token(&Token::Comma) {
12432+
break;
12433+
}
12434+
}
12435+
}
12436+
Ok(XmlPassingClause { arguments })
12437+
}
12438+
1234212439
fn parse_match_recognize(&mut self, table: TableFactor) -> Result<TableFactor, ParserError> {
1234312440
self.expect_token(&Token::LParen)?;
1234412441

0 commit comments

Comments
 (0)