diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 959b64f9a..9508f7015 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -231,6 +231,11 @@ pub enum Expr { operator: JsonOperator, right: Box, }, + /// CompositeAccess (postgres) eg: SELECT (information_schema._pg_expandarray(array['i','i'])).n + CompositeAccess { + expr: Box, + key: Ident, + }, /// `IS NULL` operator IsNull(Box), /// `IS NOT NULL` operator @@ -553,6 +558,9 @@ impl fmt::Display for Expr { } => { write!(f, "{} {} {}", left, operator, right) } + Expr::CompositeAccess { expr, key } => { + write!(f, "{}.{}", expr, key) + } } } } diff --git a/src/parser.rs b/src/parser.rs index 587e55b2d..3258ffa56 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -516,7 +516,18 @@ impl<'a> Parser<'a> { } }; self.expect_token(&Token::RParen)?; - Ok(expr) + if !self.consume_token(&Token::Period) { + return Ok(expr); + } + let tok = self.next_token(); + let key = match tok { + Token::Word(word) => word.to_ident(), + _ => return parser_err!(format!("Expected identifier, found: {}", tok)), + }; + Ok(Expr::CompositeAccess { + expr: Box::new(expr), + key, + }) } Token::Placeholder(_) => { self.prev_token(); diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index e3e2d087a..8768311b5 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -1301,6 +1301,67 @@ fn test_json() { ); } +#[test] +fn test_composite_value() { + let sql = "SELECT (on_hand.item).name FROM on_hand WHERE (on_hand.item).price > 9"; + let select = pg().verified_only_select(sql); + assert_eq!( + SelectItem::UnnamedExpr(Expr::CompositeAccess { + key: Ident::new("name"), + expr: Box::new(Expr::Nested(Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("on_hand"), + Ident::new("item") + ])))) + }), + select.projection[0] + ); + + #[cfg(feature = "bigdecimal")] + let num: Expr = Expr::Value(Value::Number(bigdecimal::BigDecimal::from(9), false)); + #[cfg(not(feature = "bigdecimal"))] + let num: Expr = Expr::Value(Value::Number("9".to_string(), false)); + assert_eq!( + select.selection, + Some(Expr::BinaryOp { + left: Box::new(Expr::CompositeAccess { + key: Ident::new("price"), + expr: Box::new(Expr::Nested(Box::new(Expr::CompoundIdentifier(vec![ + Ident::new("on_hand"), + Ident::new("item") + ])))) + }), + op: BinaryOperator::Gt, + right: Box::new(num) + }) + ); + + let sql = "SELECT (information_schema._pg_expandarray(ARRAY['i', 'i'])).n"; + let select = pg().verified_only_select(sql); + assert_eq!( + SelectItem::UnnamedExpr(Expr::CompositeAccess { + key: Ident::new("n"), + expr: Box::new(Expr::Nested(Box::new(Expr::Function(Function { + name: ObjectName(vec![ + Ident::new("information_schema"), + Ident::new("_pg_expandarray") + ]), + args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Array( + Array { + elem: vec![ + Expr::Value(Value::SingleQuotedString("i".to_string())), + Expr::Value(Value::SingleQuotedString("i".to_string())), + ], + named: true + } + )))], + over: None, + distinct: false, + })))) + }), + select.projection[0] + ); +} + #[test] fn parse_comments() { match pg().verified_stmt("COMMENT ON COLUMN tab.name IS 'comment'") {