Skip to content

Commit cd898cb

Browse files
authored
Support arbitrary composite access expressions (#1600)
1 parent 84e82e6 commit cd898cb

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
@@ -640,7 +640,7 @@ pub enum Expr {
640640
/// The path to the data to extract.
641641
path: JsonPath,
642642
},
643-
/// CompositeAccess (postgres) eg: SELECT (information_schema._pg_expandarray(array['i','i'])).n
643+
/// CompositeAccess eg: SELECT foo(bar).z, (information_schema._pg_expandarray(array['i','i'])).n
644644
CompositeAccess {
645645
expr: Box<Expr>,
646646
key: Ident,

src/parser/mod.rs

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,14 @@ impl<'a> Parser<'a> {
969969
let _guard = self.recursion_counter.try_decrease()?;
970970
debug!("parsing expr");
971971
let mut expr = self.parse_prefix()?;
972+
// Attempt to parse composite access. Example `SELECT f(x).a`
973+
while self.consume_token(&Token::Period) {
974+
expr = Expr::CompositeAccess {
975+
expr: Box::new(expr),
976+
key: self.parse_identifier(false)?,
977+
}
978+
}
979+
972980
debug!("prefix: {:?}", expr);
973981
loop {
974982
let next_precedence = self.get_next_precedence()?;
@@ -1393,25 +1401,7 @@ impl<'a> Parser<'a> {
13931401
}
13941402
};
13951403
self.expect_token(&Token::RParen)?;
1396-
let expr = self.try_parse_method(expr)?;
1397-
if !self.consume_token(&Token::Period) {
1398-
Ok(expr)
1399-
} else {
1400-
let tok = self.next_token();
1401-
let key = match tok.token {
1402-
Token::Word(word) => word.to_ident(tok.span),
1403-
_ => {
1404-
return parser_err!(
1405-
format!("Expected identifier, found: {tok}"),
1406-
tok.span.start
1407-
)
1408-
}
1409-
};
1410-
Ok(Expr::CompositeAccess {
1411-
expr: Box::new(expr),
1412-
key,
1413-
})
1414-
}
1404+
self.try_parse_method(expr)
14151405
}
14161406
Token::Placeholder(_) | Token::Colon | Token::AtSign => {
14171407
self.prev_token();

tests/sqlparser_common.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12341,6 +12341,92 @@ fn parse_create_table_with_bit_types() {
1234112341
}
1234212342
}
1234312343

12344+
#[test]
12345+
fn parse_composite_access_expr() {
12346+
assert_eq!(
12347+
verified_expr("f(a).b"),
12348+
Expr::CompositeAccess {
12349+
expr: Box::new(Expr::Function(Function {
12350+
name: ObjectName(vec![Ident::new("f")]),
12351+
uses_odbc_syntax: false,
12352+
parameters: FunctionArguments::None,
12353+
args: FunctionArguments::List(FunctionArgumentList {
12354+
duplicate_treatment: None,
12355+
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
12356+
Expr::Identifier(Ident::new("a"))
12357+
))],
12358+
clauses: vec![],
12359+
}),
12360+
null_treatment: None,
12361+
filter: None,
12362+
over: None,
12363+
within_group: vec![]
12364+
})),
12365+
key: Ident::new("b")
12366+
}
12367+
);
12368+
12369+
// Nested Composite Access
12370+
assert_eq!(
12371+
verified_expr("f(a).b.c"),
12372+
Expr::CompositeAccess {
12373+
expr: Box::new(Expr::CompositeAccess {
12374+
expr: Box::new(Expr::Function(Function {
12375+
name: ObjectName(vec![Ident::new("f")]),
12376+
uses_odbc_syntax: false,
12377+
parameters: FunctionArguments::None,
12378+
args: FunctionArguments::List(FunctionArgumentList {
12379+
duplicate_treatment: None,
12380+
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
12381+
Expr::Identifier(Ident::new("a"))
12382+
))],
12383+
clauses: vec![],
12384+
}),
12385+
null_treatment: None,
12386+
filter: None,
12387+
over: None,
12388+
within_group: vec![]
12389+
})),
12390+
key: Ident::new("b")
12391+
}),
12392+
key: Ident::new("c")
12393+
}
12394+
);
12395+
12396+
// Composite Access in Select and Where Clauses
12397+
let stmt = verified_only_select("SELECT f(a).b FROM t WHERE f(a).b IS NOT NULL");
12398+
let expr = Expr::CompositeAccess {
12399+
expr: Box::new(Expr::Function(Function {
12400+
name: ObjectName(vec![Ident::new("f")]),
12401+
uses_odbc_syntax: false,
12402+
parameters: FunctionArguments::None,
12403+
args: FunctionArguments::List(FunctionArgumentList {
12404+
duplicate_treatment: None,
12405+
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
12406+
Expr::Identifier(Ident::new("a")),
12407+
))],
12408+
clauses: vec![],
12409+
}),
12410+
null_treatment: None,
12411+
filter: None,
12412+
over: None,
12413+
within_group: vec![],
12414+
})),
12415+
key: Ident::new("b"),
12416+
};
12417+
12418+
assert_eq!(stmt.projection[0], SelectItem::UnnamedExpr(expr.clone()));
12419+
assert_eq!(stmt.selection.unwrap(), Expr::IsNotNull(Box::new(expr)));
12420+
12421+
// Composite Access with quoted identifier
12422+
verified_only_select("SELECT f(a).\"an id\"");
12423+
12424+
// Composite Access in struct literal
12425+
all_dialects_where(|d| d.supports_struct_literal()).verified_stmt(
12426+
"SELECT * FROM t WHERE STRUCT(STRUCT(1 AS a, NULL AS b) AS c, NULL AS d).c.a IS NOT NULL",
12427+
);
12428+
}
12429+
1234412430
#[test]
1234512431
fn parse_create_table_with_enum_types() {
1234612432
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)