Skip to content

Commit 0647a4a

Browse files
authored
Consolidate MapAccess, and Subscript into CompoundExpr to handle the complex field access chain (#1551)
1 parent cd898cb commit 0647a4a

9 files changed

+455
-287
lines changed

src/ast/mod.rs

Lines changed: 50 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -459,40 +459,6 @@ pub enum CastFormat {
459459
ValueAtTimeZone(Value, Value),
460460
}
461461

462-
/// Represents the syntax/style used in a map access.
463-
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
464-
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
465-
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
466-
pub enum MapAccessSyntax {
467-
/// Access using bracket notation. `mymap[mykey]`
468-
Bracket,
469-
/// Access using period notation. `mymap.mykey`
470-
Period,
471-
}
472-
473-
/// Expression used to access a value in a nested structure.
474-
///
475-
/// Example: `SAFE_OFFSET(0)` in
476-
/// ```sql
477-
/// SELECT mymap[SAFE_OFFSET(0)];
478-
/// ```
479-
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
480-
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
481-
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
482-
pub struct MapAccessKey {
483-
pub key: Expr,
484-
pub syntax: MapAccessSyntax,
485-
}
486-
487-
impl fmt::Display for MapAccessKey {
488-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
489-
match self.syntax {
490-
MapAccessSyntax::Bracket => write!(f, "[{}]", self.key),
491-
MapAccessSyntax::Period => write!(f, ".{}", self.key),
492-
}
493-
}
494-
}
495-
496462
/// An element of a JSON path.
497463
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
498464
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -629,6 +595,28 @@ pub enum Expr {
629595
Identifier(Ident),
630596
/// Multi-part identifier, e.g. `table_alias.column` or `schema.table.col`
631597
CompoundIdentifier(Vec<Ident>),
598+
/// Multi-part expression access.
599+
///
600+
/// This structure represents an access chain in structured / nested types
601+
/// such as maps, arrays, and lists:
602+
/// - Array
603+
/// - A 1-dim array `a[1]` will be represented like:
604+
/// `CompoundFieldAccess(Ident('a'), vec![Subscript(1)]`
605+
/// - A 2-dim array `a[1][2]` will be represented like:
606+
/// `CompoundFieldAccess(Ident('a'), vec![Subscript(1), Subscript(2)]`
607+
/// - Map or Struct (Bracket-style)
608+
/// - A map `a['field1']` will be represented like:
609+
/// `CompoundFieldAccess(Ident('a'), vec![Subscript('field')]`
610+
/// - A 2-dim map `a['field1']['field2']` will be represented like:
611+
/// `CompoundFieldAccess(Ident('a'), vec![Subscript('field1'), Subscript('field2')]`
612+
/// - Struct (Dot-style) (only effect when the chain contains both subscript and expr)
613+
/// - A struct access `a[field1].field2` will be represented like:
614+
/// `CompoundFieldAccess(Ident('a'), vec![Subscript('field1'), Ident('field2')]`
615+
/// - If a struct access likes `a.field1.field2`, it will be represented by CompoundIdentifier([a, field1, field2])
616+
CompoundFieldAccess {
617+
root: Box<Expr>,
618+
access_chain: Vec<AccessExpr>,
619+
},
632620
/// Access data nested in a value containing semi-structured data, such as
633621
/// the `VARIANT` type on Snowflake. for example `src:customer[0].name`.
634622
///
@@ -882,14 +870,6 @@ pub enum Expr {
882870
data_type: DataType,
883871
value: String,
884872
},
885-
/// Access a map-like object by field (e.g. `column['field']` or `column[4]`
886-
/// Note that depending on the dialect, struct like accesses may be
887-
/// parsed as [`Subscript`](Self::Subscript) or [`MapAccess`](Self::MapAccess)
888-
/// <https://clickhouse.com/docs/en/sql-reference/data-types/map/>
889-
MapAccess {
890-
column: Box<Expr>,
891-
keys: Vec<MapAccessKey>,
892-
},
893873
/// Scalar function call e.g. `LEFT(foo, 5)`
894874
Function(Function),
895875
/// Arbitrary expr method call
@@ -978,11 +958,6 @@ pub enum Expr {
978958
/// ```
979959
/// [1]: https://duckdb.org/docs/sql/data_types/map#creating-maps
980960
Map(Map),
981-
/// An access of nested data using subscript syntax, for example `array[2]`.
982-
Subscript {
983-
expr: Box<Expr>,
984-
subscript: Box<Subscript>,
985-
},
986961
/// An array expression e.g. `ARRAY[1, 2]`
987962
Array(Array),
988963
/// An interval expression e.g. `INTERVAL '1' YEAR`
@@ -1099,6 +1074,27 @@ impl fmt::Display for Subscript {
10991074
}
11001075
}
11011076

1077+
/// An element of a [`Expr::CompoundFieldAccess`].
1078+
/// It can be an expression or a subscript.
1079+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1080+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1081+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1082+
pub enum AccessExpr {
1083+
/// Accesses a field using dot notation, e.g. `foo.bar.baz`.
1084+
Dot(Expr),
1085+
/// Accesses a field or array element using bracket notation, e.g. `foo['bar']`.
1086+
Subscript(Subscript),
1087+
}
1088+
1089+
impl fmt::Display for AccessExpr {
1090+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1091+
match self {
1092+
AccessExpr::Dot(expr) => write!(f, ".{}", expr),
1093+
AccessExpr::Subscript(subscript) => write!(f, "[{}]", subscript),
1094+
}
1095+
}
1096+
}
1097+
11021098
/// A lambda function.
11031099
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
11041100
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -1295,12 +1291,16 @@ impl fmt::Display for Expr {
12951291
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
12961292
match self {
12971293
Expr::Identifier(s) => write!(f, "{s}"),
1298-
Expr::MapAccess { column, keys } => {
1299-
write!(f, "{column}{}", display_separated(keys, ""))
1300-
}
13011294
Expr::Wildcard(_) => f.write_str("*"),
13021295
Expr::QualifiedWildcard(prefix, _) => write!(f, "{}.*", prefix),
13031296
Expr::CompoundIdentifier(s) => write!(f, "{}", display_separated(s, ".")),
1297+
Expr::CompoundFieldAccess { root, access_chain } => {
1298+
write!(f, "{}", root)?;
1299+
for field in access_chain {
1300+
write!(f, "{}", field)?;
1301+
}
1302+
Ok(())
1303+
}
13041304
Expr::IsTrue(ast) => write!(f, "{ast} IS TRUE"),
13051305
Expr::IsNotTrue(ast) => write!(f, "{ast} IS NOT TRUE"),
13061306
Expr::IsFalse(ast) => write!(f, "{ast} IS FALSE"),
@@ -1720,12 +1720,6 @@ impl fmt::Display for Expr {
17201720
Expr::Map(map) => {
17211721
write!(f, "{map}")
17221722
}
1723-
Expr::Subscript {
1724-
expr,
1725-
subscript: key,
1726-
} => {
1727-
write!(f, "{expr}[{key}]")
1728-
}
17291723
Expr::Array(set) => {
17301724
write!(f, "{set}")
17311725
}

src/ast/spans.rs

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,20 @@ use core::iter;
2020
use crate::tokenizer::Span;
2121

2222
use super::{
23-
dcl::SecondaryRoles, AlterColumnOperation, AlterIndexOperation, AlterTableOperation, Array,
24-
Assignment, AssignmentTarget, CloseCursor, ClusteredIndex, ColumnDef, ColumnOption,
25-
ColumnOptionDef, ConflictTarget, ConnectBy, ConstraintCharacteristics, CopySource, CreateIndex,
26-
CreateTable, CreateTableOptions, Cte, Delete, DoUpdate, ExceptSelectItem, ExcludeSelectItem,
27-
Expr, ExprWithAlias, Fetch, FromTable, Function, FunctionArg, FunctionArgExpr,
28-
FunctionArgumentClause, FunctionArgumentList, FunctionArguments, GroupByExpr, HavingBound,
29-
IlikeSelectItem, Insert, Interpolate, InterpolateExpr, Join, JoinConstraint, JoinOperator,
30-
JsonPath, JsonPathElem, LateralView, MatchRecognizePattern, Measure, NamedWindowDefinition,
31-
ObjectName, Offset, OnConflict, OnConflictAction, OnInsert, OrderBy, OrderByExpr, Partition,
32-
PivotValueSource, ProjectionSelect, Query, ReferentialAction, RenameSelectItem,
33-
ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SqlOption,
34-
Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint,
35-
TableFactor, TableOptionsClustered, TableWithJoins, Use, Value, Values, ViewColumnDef,
36-
WildcardAdditionalOptions, With, WithFill,
23+
dcl::SecondaryRoles, AccessExpr, AlterColumnOperation, AlterIndexOperation,
24+
AlterTableOperation, Array, Assignment, AssignmentTarget, CloseCursor, ClusteredIndex,
25+
ColumnDef, ColumnOption, ColumnOptionDef, ConflictTarget, ConnectBy, ConstraintCharacteristics,
26+
CopySource, CreateIndex, CreateTable, CreateTableOptions, Cte, Delete, DoUpdate,
27+
ExceptSelectItem, ExcludeSelectItem, Expr, ExprWithAlias, Fetch, FromTable, Function,
28+
FunctionArg, FunctionArgExpr, FunctionArgumentClause, FunctionArgumentList, FunctionArguments,
29+
GroupByExpr, HavingBound, IlikeSelectItem, Insert, Interpolate, InterpolateExpr, Join,
30+
JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, MatchRecognizePattern,
31+
Measure, NamedWindowDefinition, ObjectName, Offset, OnConflict, OnConflictAction, OnInsert,
32+
OrderBy, OrderByExpr, Partition, PivotValueSource, ProjectionSelect, Query, ReferentialAction,
33+
RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem,
34+
SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef,
35+
TableConstraint, TableFactor, TableOptionsClustered, TableWithJoins, Use, Value, Values,
36+
ViewColumnDef, WildcardAdditionalOptions, With, WithFill,
3737
};
3838

3939
/// Given an iterator of spans, return the [Span::union] of all spans.
@@ -1262,6 +1262,9 @@ impl Spanned for Expr {
12621262
Expr::Identifier(ident) => ident.span,
12631263
Expr::CompoundIdentifier(vec) => union_spans(vec.iter().map(|i| i.span)),
12641264
Expr::CompositeAccess { expr, key } => expr.span().union(&key.span),
1265+
Expr::CompoundFieldAccess { root, access_chain } => {
1266+
union_spans(iter::once(root.span()).chain(access_chain.iter().map(|i| i.span())))
1267+
}
12651268
Expr::IsFalse(expr) => expr.span(),
12661269
Expr::IsNotFalse(expr) => expr.span(),
12671270
Expr::IsTrue(expr) => expr.span(),
@@ -1336,9 +1339,6 @@ impl Spanned for Expr {
13361339
Expr::Nested(expr) => expr.span(),
13371340
Expr::Value(value) => value.span(),
13381341
Expr::TypedString { .. } => Span::empty(),
1339-
Expr::MapAccess { column, keys } => column
1340-
.span()
1341-
.union(&union_spans(keys.iter().map(|i| i.key.span()))),
13421342
Expr::Function(function) => function.span(),
13431343
Expr::GroupingSets(vec) => {
13441344
union_spans(vec.iter().flat_map(|i| i.iter().map(|k| k.span())))
@@ -1434,7 +1434,6 @@ impl Spanned for Expr {
14341434
Expr::Named { .. } => Span::empty(),
14351435
Expr::Dictionary(_) => Span::empty(),
14361436
Expr::Map(_) => Span::empty(),
1437-
Expr::Subscript { expr, subscript } => expr.span().union(&subscript.span()),
14381437
Expr::Interval(interval) => interval.value.span(),
14391438
Expr::Wildcard(token) => token.0.span,
14401439
Expr::QualifiedWildcard(object_name, token) => union_spans(
@@ -1473,6 +1472,15 @@ impl Spanned for Subscript {
14731472
}
14741473
}
14751474

1475+
impl Spanned for AccessExpr {
1476+
fn span(&self) -> Span {
1477+
match self {
1478+
AccessExpr::Dot(ident) => ident.span(),
1479+
AccessExpr::Subscript(subscript) => subscript.span(),
1480+
}
1481+
}
1482+
}
1483+
14761484
impl Spanned for ObjectName {
14771485
fn span(&self) -> Span {
14781486
let ObjectName(segments) = self;

src/dialect/snowflake.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,10 @@ impl Dialect for SnowflakeDialect {
234234
RESERVED_FOR_IDENTIFIER.contains(&kw)
235235
}
236236
}
237+
238+
fn supports_partiql(&self) -> bool {
239+
true
240+
}
237241
}
238242

239243
/// Parse snowflake create table statement.

0 commit comments

Comments
 (0)