diff --git a/src/dialect/bigquery.rs b/src/dialect/bigquery.rs index 96633552b..0e6514aee 100644 --- a/src/dialect/bigquery.rs +++ b/src/dialect/bigquery.rs @@ -72,4 +72,8 @@ impl Dialect for BigQueryDialect { fn require_interval_qualifier(&self) -> bool { true } + + fn support_period_map_access_key(&self) -> bool { + true + } } diff --git a/src/dialect/duckdb.rs b/src/dialect/duckdb.rs index a2699d850..5a5643113 100644 --- a/src/dialect/duckdb.rs +++ b/src/dialect/duckdb.rs @@ -75,4 +75,9 @@ impl Dialect for DuckDbDialect { fn supports_load_extension(&self) -> bool { true } + + /// See DuckDB + fn support_period_map_access_key(&self) -> bool { + true + } } diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index e3beeae7f..bece5f172 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -123,4 +123,8 @@ impl Dialect for GenericDialect { fn supports_named_fn_args_with_assignment_operator(&self) -> bool { true } + + fn support_period_map_access_key(&self) -> bool { + true + } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 985cad749..1d5b234be 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -675,6 +675,15 @@ pub trait Dialect: Debug + Any { fn supports_create_table_select(&self) -> bool { false } + + /// Return true if the dialect supports the period map access key + /// + /// Access on BigQuery nested and repeated expressions can + /// mix notations in the same expression. + /// + fn support_period_map_access_key(&self) -> bool { + false + } } /// This represents the operators for which precedence must be defined diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 35c763e93..cba0556b8 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2935,12 +2935,23 @@ impl<'a> Parser<'a> { }) } else if Token::LBracket == tok { if dialect_of!(self is PostgreSqlDialect | DuckDbDialect | GenericDialect) { - self.parse_subscript(expr) + let expr = self.parse_multi_dim_subscript(expr)?; + if self.dialect.support_period_map_access_key() { + self.parse_map_access(expr, vec![]) + } else { + Ok(expr) + } } else if dialect_of!(self is SnowflakeDialect) { self.prev_token(); self.parse_json_access(expr) } else { - self.parse_map_access(expr) + let key = self.parse_expr()?; + self.expect_token(&Token::RBracket)?; + let keys = vec![MapAccessKey { + key, + syntax: MapAccessSyntax::Bracket, + }]; + self.parse_map_access(expr, keys) } } else if dialect_of!(self is SnowflakeDialect | GenericDialect) && Token::Colon == tok { self.prev_token(); @@ -3036,6 +3047,19 @@ impl<'a> Parser<'a> { }) } + /// Parse an multi-dimension array accessing like `[1:3][1][1]` + /// + /// Parser is right after the first `[` + pub fn parse_multi_dim_subscript(&mut self, mut expr: Expr) -> Result { + loop { + expr = self.parse_subscript(expr)?; + if !self.consume_token(&Token::LBracket) { + break; + } + } + Ok(expr) + } + /// Parses an array subscript like `[1:3]` /// /// Parser is right after `[` @@ -3101,14 +3125,15 @@ impl<'a> Parser<'a> { }) } - pub fn parse_map_access(&mut self, expr: Expr) -> Result { - let key = self.parse_expr()?; - self.expect_token(&Token::RBracket)?; - - let mut keys = vec![MapAccessKey { - key, - syntax: MapAccessSyntax::Bracket, - }]; + /// Parse the map access like `[key]` or `.key` if [Dialect::support_period_map_access_key] is true + /// It could be an access-chain like `[key1][key2].key3` + /// + /// The parameter `keys` is an initialized buffer that could contain some keys parsed from other places. + pub fn parse_map_access( + &mut self, + expr: Expr, + mut keys: Vec, + ) -> Result { loop { let key = match self.peek_token().token { Token::LBracket => { @@ -3120,10 +3145,7 @@ impl<'a> Parser<'a> { syntax: MapAccessSyntax::Bracket, } } - // Access on BigQuery nested and repeated expressions can - // mix notations in the same expression. - // https://cloud.google.com/bigquery/docs/nested-repeated#query_nested_and_repeated_columns - Token::Period if dialect_of!(self is BigQueryDialect) => { + Token::Period if self.dialect.support_period_map_access_key() => { self.next_token(); // consume `.` MapAccessKey { key: self.parse_expr()?, @@ -3135,10 +3157,16 @@ impl<'a> Parser<'a> { keys.push(key); } - Ok(Expr::MapAccess { - column: Box::new(expr), - keys, - }) + // If no any key be collected, it means the elements have been parsed to [Subscript] + // e.g. `select abc[1]` or `select abc[1][2]` + if keys.is_empty() { + Ok(expr) + } else { + Ok(Expr::MapAccess { + column: Box::new(expr), + keys, + }) + } } /// Parses the parens following the `[ NOT ] IN` operator. diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 3d9ba5da2..975870b5b 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -12161,3 +12161,25 @@ fn parse_create_table_select() { ); } } + +#[test] +fn test_period_map_access() { + let supported_dialects = TestedDialects::new(vec![ + Box::new(GenericDialect {}), + Box::new(DuckDbDialect {}), + ]); + let sqls = [ + "SELECT abc[1] FROM t", + "SELECT abc[1].f1 FROM t", + "SELECT abc[1].f1.f2 FROM t", + "SELECT f1.abc[1] FROM t", + "SELECT f1.f2.abc[1] FROM t", + "SELECT f1.abc[1].f2 FROM t", + "SELECT abc['a'][1].f1 FROM t", + "SELECT abc['a'].f1[1].f2 FROM t", + "SELECT abc['a'].f1[1].f2[2] FROM t", + ]; + for sql in sqls { + supported_dialects.verified_stmt(sql); + } +}