Skip to content

Commit 73d2001

Browse files
committed
support arbitrary composite access expressions
1 parent 5de5312 commit 73d2001

File tree

3 files changed

+96
-20
lines changed

3 files changed

+96
-20
lines changed

src/ast/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ pub enum Expr {
637637
/// The path to the data to extract.
638638
path: JsonPath,
639639
},
640-
/// CompositeAccess (postgres) eg: SELECT (information_schema._pg_expandarray(array['i','i'])).n
640+
/// CompositeAccess eg: SELECT foo(bar).z, (information_schema._pg_expandarray(array['i','i'])).n
641641
CompositeAccess {
642642
expr: Box<Expr>,
643643
key: Ident,

src/parser/mod.rs

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,14 @@ impl<'a> Parser<'a> {
962962
let _guard = self.recursion_counter.try_decrease()?;
963963
debug!("parsing expr");
964964
let mut expr = self.parse_prefix()?;
965+
// Attempt to parse composite access. Example `SELECT f(x).a`
966+
while self.consume_token(&Token::Period) {
967+
expr = Expr::CompositeAccess {
968+
expr: Box::new(expr),
969+
key: self.parse_identifier(false)?,
970+
}
971+
}
972+
965973
debug!("prefix: {:?}", expr);
966974
loop {
967975
let next_precedence = self.get_next_precedence()?;
@@ -1386,25 +1394,7 @@ impl<'a> Parser<'a> {
13861394
}
13871395
};
13881396
self.expect_token(&Token::RParen)?;
1389-
let expr = self.try_parse_method(expr)?;
1390-
if !self.consume_token(&Token::Period) {
1391-
Ok(expr)
1392-
} else {
1393-
let tok = self.next_token();
1394-
let key = match tok.token {
1395-
Token::Word(word) => word.to_ident(tok.span),
1396-
_ => {
1397-
return parser_err!(
1398-
format!("Expected identifier, found: {tok}"),
1399-
tok.span.start
1400-
)
1401-
}
1402-
};
1403-
Ok(Expr::CompositeAccess {
1404-
expr: Box::new(expr),
1405-
key,
1406-
})
1407-
}
1397+
self.try_parse_method(expr)
14081398
}
14091399
Token::Placeholder(_) | Token::Colon | Token::AtSign => {
14101400
self.prev_token();

tests/sqlparser_common.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12506,6 +12506,92 @@ fn parse_create_table_with_bit_types() {
1250612506
}
1250712507
}
1250812508

12509+
#[test]
12510+
fn parse_composed_access_expr() {
12511+
assert_eq!(
12512+
verified_expr("f(a).b"),
12513+
Expr::CompositeAccess {
12514+
expr: Box::new(Expr::Function(Function {
12515+
name: ObjectName(vec![Ident::new("f")]),
12516+
uses_odbc_syntax: false,
12517+
parameters: FunctionArguments::None,
12518+
args: FunctionArguments::List(FunctionArgumentList {
12519+
duplicate_treatment: None,
12520+
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
12521+
Expr::Identifier(Ident::new("a"))
12522+
))],
12523+
clauses: vec![],
12524+
}),
12525+
null_treatment: None,
12526+
filter: None,
12527+
over: None,
12528+
within_group: vec![]
12529+
})),
12530+
key: Ident::new("b")
12531+
}
12532+
);
12533+
12534+
// Nested Composite Access
12535+
assert_eq!(
12536+
verified_expr("f(a).b.c"),
12537+
Expr::CompositeAccess {
12538+
expr: Box::new(Expr::CompositeAccess {
12539+
expr: Box::new(Expr::Function(Function {
12540+
name: ObjectName(vec![Ident::new("f")]),
12541+
uses_odbc_syntax: false,
12542+
parameters: FunctionArguments::None,
12543+
args: FunctionArguments::List(FunctionArgumentList {
12544+
duplicate_treatment: None,
12545+
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
12546+
Expr::Identifier(Ident::new("a"))
12547+
))],
12548+
clauses: vec![],
12549+
}),
12550+
null_treatment: None,
12551+
filter: None,
12552+
over: None,
12553+
within_group: vec![]
12554+
})),
12555+
key: Ident::new("b")
12556+
}),
12557+
key: Ident::new("c")
12558+
}
12559+
);
12560+
12561+
// Composite Access in Select and Where Clauses
12562+
let stmt = verified_only_select("SELECT f(a).b FROM t WHERE f(a).b IS NOT NULL");
12563+
let expr = Expr::CompositeAccess {
12564+
expr: Box::new(Expr::Function(Function {
12565+
name: ObjectName(vec![Ident::new("f")]),
12566+
uses_odbc_syntax: false,
12567+
parameters: FunctionArguments::None,
12568+
args: FunctionArguments::List(FunctionArgumentList {
12569+
duplicate_treatment: None,
12570+
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
12571+
Expr::Identifier(Ident::new("a")),
12572+
))],
12573+
clauses: vec![],
12574+
}),
12575+
null_treatment: None,
12576+
filter: None,
12577+
over: None,
12578+
within_group: vec![],
12579+
})),
12580+
key: Ident::new("b"),
12581+
};
12582+
12583+
assert_eq!(stmt.projection[0], SelectItem::UnnamedExpr(expr.clone()));
12584+
assert_eq!(stmt.selection.unwrap(), Expr::IsNotNull(Box::new(expr)));
12585+
12586+
// Composite Access with quoted identifier
12587+
verified_only_select("SELECT f(a).\"an id\"");
12588+
12589+
// Composite Access in struct literal
12590+
all_dialects_where(|d| d.supports_struct_literal()).verified_stmt(
12591+
"SELECT * FROM t WHERE STRUCT(STRUCT(1 AS a, NULL AS b) AS c, NULL AS d).c.a IS NOT NULL",
12592+
);
12593+
}
12594+
1250912595
#[test]
1251012596
fn parse_create_table_with_enum_types() {
1251112597
let sql = "CREATE TABLE t0 (foo ENUM8('a' = 1, 'b' = 2), bar ENUM16('a' = 1, 'b' = 2), baz ENUM('a', 'b'))";

0 commit comments

Comments
 (0)