This is an automated email from the ASF dual-hosted git repository.
iffyio pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new cd898cb6 Support arbitrary composite access expressions (#1600)
cd898cb6 is described below
commit cd898cb6a4e9d819f18649a4515bfb507678e64b
Author: Ayman Elkfrawy <[email protected]>
AuthorDate: Sun Dec 22 06:22:16 2024 -0800
Support arbitrary composite access expressions (#1600)
---
src/ast/mod.rs | 2 +-
src/parser/mod.rs | 28 +++++----------
tests/sqlparser_common.rs | 86 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 96 insertions(+), 20 deletions(-)
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index 3157a060..45dbba2a 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -640,7 +640,7 @@ pub enum Expr {
/// The path to the data to extract.
path: JsonPath,
},
- /// CompositeAccess (postgres) eg: SELECT
(information_schema._pg_expandarray(array['i','i'])).n
+ /// CompositeAccess eg: SELECT foo(bar).z,
(information_schema._pg_expandarray(array['i','i'])).n
CompositeAccess {
expr: Box<Expr>,
key: Ident,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index e809ffba..5ee8ae21 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -969,6 +969,14 @@ impl<'a> Parser<'a> {
let _guard = self.recursion_counter.try_decrease()?;
debug!("parsing expr");
let mut expr = self.parse_prefix()?;
+ // Attempt to parse composite access. Example `SELECT f(x).a`
+ while self.consume_token(&Token::Period) {
+ expr = Expr::CompositeAccess {
+ expr: Box::new(expr),
+ key: self.parse_identifier(false)?,
+ }
+ }
+
debug!("prefix: {:?}", expr);
loop {
let next_precedence = self.get_next_precedence()?;
@@ -1393,25 +1401,7 @@ impl<'a> Parser<'a> {
}
};
self.expect_token(&Token::RParen)?;
- let expr = self.try_parse_method(expr)?;
- if !self.consume_token(&Token::Period) {
- Ok(expr)
- } else {
- let tok = self.next_token();
- let key = match tok.token {
- Token::Word(word) => word.to_ident(tok.span),
- _ => {
- return parser_err!(
- format!("Expected identifier, found: {tok}"),
- tok.span.start
- )
- }
- };
- Ok(Expr::CompositeAccess {
- expr: Box::new(expr),
- key,
- })
- }
+ self.try_parse_method(expr)
}
Token::Placeholder(_) | Token::Colon | Token::AtSign => {
self.prev_token();
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index e7e2e3bc..8cc161f1 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -12341,6 +12341,92 @@ fn parse_create_table_with_bit_types() {
}
}
+#[test]
+fn parse_composite_access_expr() {
+ assert_eq!(
+ verified_expr("f(a).b"),
+ Expr::CompositeAccess {
+ expr: Box::new(Expr::Function(Function {
+ name: ObjectName(vec![Ident::new("f")]),
+ uses_odbc_syntax: false,
+ parameters: FunctionArguments::None,
+ args: FunctionArguments::List(FunctionArgumentList {
+ duplicate_treatment: None,
+ args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
+ Expr::Identifier(Ident::new("a"))
+ ))],
+ clauses: vec![],
+ }),
+ null_treatment: None,
+ filter: None,
+ over: None,
+ within_group: vec![]
+ })),
+ key: Ident::new("b")
+ }
+ );
+
+ // Nested Composite Access
+ assert_eq!(
+ verified_expr("f(a).b.c"),
+ Expr::CompositeAccess {
+ expr: Box::new(Expr::CompositeAccess {
+ expr: Box::new(Expr::Function(Function {
+ name: ObjectName(vec![Ident::new("f")]),
+ uses_odbc_syntax: false,
+ parameters: FunctionArguments::None,
+ args: FunctionArguments::List(FunctionArgumentList {
+ duplicate_treatment: None,
+ args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
+ Expr::Identifier(Ident::new("a"))
+ ))],
+ clauses: vec![],
+ }),
+ null_treatment: None,
+ filter: None,
+ over: None,
+ within_group: vec![]
+ })),
+ key: Ident::new("b")
+ }),
+ key: Ident::new("c")
+ }
+ );
+
+ // Composite Access in Select and Where Clauses
+ let stmt = verified_only_select("SELECT f(a).b FROM t WHERE f(a).b IS NOT
NULL");
+ let expr = Expr::CompositeAccess {
+ expr: Box::new(Expr::Function(Function {
+ name: ObjectName(vec![Ident::new("f")]),
+ uses_odbc_syntax: false,
+ parameters: FunctionArguments::None,
+ args: FunctionArguments::List(FunctionArgumentList {
+ duplicate_treatment: None,
+ args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
+ Expr::Identifier(Ident::new("a")),
+ ))],
+ clauses: vec![],
+ }),
+ null_treatment: None,
+ filter: None,
+ over: None,
+ within_group: vec![],
+ })),
+ key: Ident::new("b"),
+ };
+
+ assert_eq!(stmt.projection[0], SelectItem::UnnamedExpr(expr.clone()));
+ assert_eq!(stmt.selection.unwrap(), Expr::IsNotNull(Box::new(expr)));
+
+ // Composite Access with quoted identifier
+ verified_only_select("SELECT f(a).\"an id\"");
+
+ // Composite Access in struct literal
+ all_dialects_where(|d| d.supports_struct_literal()).verified_stmt(
+ "SELECT * FROM t WHERE STRUCT(STRUCT(1 AS a, NULL AS b) AS c, NULL AS
d).c.a IS NOT NULL",
+ );
+}
+
#[test]
fn parse_create_table_with_enum_types() {
let sql = "CREATE TABLE t0 (foo ENUM8('a' = 1, 'b' = 2), bar ENUM16('a' =
1, 'b' = 2), baz ENUM('a', 'b'))";
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]