Consolidate MapAccess, and Subscript into CompoundExpr to handle the complex field access chain (#1551)

This commit is contained in:
Jax Liu 2024-12-22 22:28:44 +08:00 committed by GitHub
parent cd898cb6a4
commit 0647a4aa82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 455 additions and 287 deletions

View file

@ -23,7 +23,7 @@ use std::ops::Deref;
use sqlparser::ast::*;
use sqlparser::dialect::{BigQueryDialect, GenericDialect};
use sqlparser::parser::{ParserError, ParserOptions};
use sqlparser::tokenizer::Span;
use sqlparser::tokenizer::{Location, Span};
use test_utils::*;
#[test]
@ -1965,27 +1965,47 @@ fn parse_map_access_expr() {
let sql = "users[-1][safe_offset(2)].a.b";
let expr = bigquery().verified_expr(sql);
fn map_access_key(key: Expr, syntax: MapAccessSyntax) -> MapAccessKey {
MapAccessKey { key, syntax }
}
let expected = Expr::MapAccess {
column: Expr::Identifier(Ident::new("users")).into(),
keys: vec![
map_access_key(
Expr::UnaryOp {
let expected = Expr::CompoundFieldAccess {
root: Box::new(Expr::Identifier(Ident::with_span(
Span::new(Location::of(1, 1), Location::of(1, 6)),
"users",
))),
access_chain: vec![
AccessExpr::Subscript(Subscript::Index {
index: Expr::UnaryOp {
op: UnaryOperator::Minus,
expr: Expr::Value(number("1")).into(),
},
MapAccessSyntax::Bracket,
),
map_access_key(
call("safe_offset", [Expr::Value(number("2"))]),
MapAccessSyntax::Bracket,
),
map_access_key(
Expr::CompoundIdentifier(vec![Ident::new("a"), Ident::new("b")]),
MapAccessSyntax::Period,
),
}),
AccessExpr::Subscript(Subscript::Index {
index: Expr::Function(Function {
name: ObjectName(vec![Ident::with_span(
Span::new(Location::of(1, 11), Location::of(1, 22)),
"safe_offset",
)]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
number("2"),
)))],
clauses: vec![],
}),
filter: None,
null_treatment: None,
over: None,
within_group: vec![],
uses_odbc_syntax: false,
}),
}),
AccessExpr::Dot(Expr::Identifier(Ident::with_span(
Span::new(Location::of(1, 24), Location::of(1, 25)),
"a",
))),
AccessExpr::Dot(Expr::Identifier(Ident::with_span(
Span::new(Location::of(1, 26), Location::of(1, 27)),
"b",
))),
],
};
assert_eq!(expr, expected);

View file

@ -25,7 +25,7 @@ use helpers::attached_token::AttachedToken;
use sqlparser::tokenizer::Span;
use test_utils::*;
use sqlparser::ast::Expr::{BinaryOp, Identifier, MapAccess};
use sqlparser::ast::Expr::{BinaryOp, Identifier};
use sqlparser::ast::SelectItem::UnnamedExpr;
use sqlparser::ast::TableFactor::Table;
use sqlparser::ast::Value::Number;
@ -44,22 +44,21 @@ fn parse_map_access_expr() {
select_token: AttachedToken::empty(),
top: None,
top_before_distinct: false,
projection: vec![UnnamedExpr(MapAccess {
column: Box::new(Identifier(Ident {
projection: vec![UnnamedExpr(Expr::CompoundFieldAccess {
root: Box::new(Identifier(Ident {
value: "string_values".to_string(),
quote_style: None,
span: Span::empty(),
})),
keys: vec![MapAccessKey {
key: call(
access_chain: vec![AccessExpr::Subscript(Subscript::Index {
index: call(
"indexOf",
[
Expr::Identifier(Ident::new("string_names")),
Expr::Value(Value::SingleQuotedString("endpoint".to_string()))
]
),
syntax: MapAccessSyntax::Bracket
}],
})],
})],
into: None,
from: vec![TableWithJoins {
@ -76,18 +75,17 @@ fn parse_map_access_expr() {
}),
op: BinaryOperator::And,
right: Box::new(BinaryOp {
left: Box::new(MapAccess {
column: Box::new(Identifier(Ident::new("string_value"))),
keys: vec![MapAccessKey {
key: call(
left: Box::new(Expr::CompoundFieldAccess {
root: Box::new(Identifier(Ident::new("string_value"))),
access_chain: vec![AccessExpr::Subscript(Subscript::Index {
index: call(
"indexOf",
[
Expr::Identifier(Ident::new("string_name")),
Expr::Value(Value::SingleQuotedString("app".to_string()))
]
),
syntax: MapAccessSyntax::Bracket
}],
})],
}),
op: BinaryOperator::NotEq,
right: Box::new(Expr::Value(Value::SingleQuotedString("foo".to_string()))),

View file

@ -37,8 +37,8 @@ use sqlparser::dialect::{
};
use sqlparser::keywords::{Keyword, ALL_KEYWORDS};
use sqlparser::parser::{Parser, ParserError, ParserOptions};
use sqlparser::tokenizer::Span;
use sqlparser::tokenizer::Tokenizer;
use sqlparser::tokenizer::{Location, Span};
use test_utils::{
all_dialects, all_dialects_where, alter_table_op, assert_eq_vec, call, expr_from_projection,
join, number, only, table, table_alias, table_from_name, TestedDialects,
@ -2939,6 +2939,31 @@ fn parse_window_function_null_treatment_arg() {
);
}
#[test]
fn test_compound_expr() {
let supported_dialects = TestedDialects::new(vec![
Box::new(GenericDialect {}),
Box::new(DuckDbDialect {}),
Box::new(BigQueryDialect {}),
]);
let sqls = [
"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 named_struct('a', 1, 'b', 2).a",
"SELECT named_struct('a', 1, 'b', 2).a",
"SELECT make_array(1, 2, 3)[1]",
"SELECT make_array(named_struct('a', 1))[1].a",
"SELECT abc[1][-1].a.b FROM t",
"SELECT abc[1][-1].a.b[1] FROM t",
];
for sql in sqls {
supported_dialects.verified_stmt(sql);
}
}
#[test]
fn parse_negative_value() {
let sql1 = "SELECT -1";
@ -10174,20 +10199,39 @@ fn parse_map_access_expr() {
Box::new(ClickHouseDialect {}),
]);
let expr = dialects.verified_expr(sql);
let expected = Expr::MapAccess {
column: Expr::Identifier(Ident::new("users")).into(),
keys: vec![
MapAccessKey {
key: Expr::UnaryOp {
let expected = Expr::CompoundFieldAccess {
root: Box::new(Expr::Identifier(Ident::with_span(
Span::new(Location::of(1, 1), Location::of(1, 6)),
"users",
))),
access_chain: vec![
AccessExpr::Subscript(Subscript::Index {
index: Expr::UnaryOp {
op: UnaryOperator::Minus,
expr: Expr::Value(number("1")).into(),
},
syntax: MapAccessSyntax::Bracket,
},
MapAccessKey {
key: call("safe_offset", [Expr::Value(number("2"))]),
syntax: MapAccessSyntax::Bracket,
},
}),
AccessExpr::Subscript(Subscript::Index {
index: Expr::Function(Function {
name: ObjectName(vec![Ident::with_span(
Span::new(Location::of(1, 11), Location::of(1, 22)),
"safe_offset",
)]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
number("2"),
)))],
clauses: vec![],
}),
filter: None,
null_treatment: None,
over: None,
within_group: vec![],
uses_odbc_syntax: false,
}),
}),
],
};
assert_eq!(expr, expected);
@ -10977,8 +11021,8 @@ fn test_map_syntax() {
check(
"MAP {'a': 10, 'b': 20}['a']",
Expr::Subscript {
expr: Box::new(Expr::Map(Map {
Expr::CompoundFieldAccess {
root: Box::new(Expr::Map(Map {
entries: vec![
MapEntry {
key: Box::new(Expr::Value(Value::SingleQuotedString("a".to_owned()))),
@ -10990,9 +11034,9 @@ fn test_map_syntax() {
},
],
})),
subscript: Box::new(Subscript::Index {
access_chain: vec![AccessExpr::Subscript(Subscript::Index {
index: Expr::Value(Value::SingleQuotedString("a".to_owned())),
}),
})],
},
);

View file

@ -630,8 +630,8 @@ fn test_array_index() {
_ => panic!("Expected an expression with alias"),
};
assert_eq!(
&Expr::Subscript {
expr: Box::new(Expr::Array(Array {
&Expr::CompoundFieldAccess {
root: Box::new(Expr::Array(Array {
elem: vec![
Expr::Value(Value::SingleQuotedString("a".to_owned())),
Expr::Value(Value::SingleQuotedString("b".to_owned())),
@ -639,9 +639,9 @@ fn test_array_index() {
],
named: false
})),
subscript: Box::new(Subscript::Index {
access_chain: vec![AccessExpr::Subscript(Subscript::Index {
index: Expr::Value(number("3"))
})
})]
},
expr
);

View file

@ -2095,11 +2095,11 @@ fn parse_array_index_expr() {
let sql = "SELECT foo[0] FROM foos";
let select = pg_and_generic().verified_only_select(sql);
assert_eq!(
&Expr::Subscript {
expr: Box::new(Expr::Identifier(Ident::new("foo"))),
subscript: Box::new(Subscript::Index {
&Expr::CompoundFieldAccess {
root: Box::new(Expr::Identifier(Ident::new("foo"))),
access_chain: vec![AccessExpr::Subscript(Subscript::Index {
index: num[0].clone()
}),
})],
},
expr_from_projection(only(&select.projection)),
);
@ -2107,16 +2107,16 @@ fn parse_array_index_expr() {
let sql = "SELECT foo[0][0] FROM foos";
let select = pg_and_generic().verified_only_select(sql);
assert_eq!(
&Expr::Subscript {
expr: Box::new(Expr::Subscript {
expr: Box::new(Expr::Identifier(Ident::new("foo"))),
subscript: Box::new(Subscript::Index {
&Expr::CompoundFieldAccess {
root: Box::new(Expr::Identifier(Ident::new("foo"))),
access_chain: vec![
AccessExpr::Subscript(Subscript::Index {
index: num[0].clone()
}),
}),
subscript: Box::new(Subscript::Index {
index: num[0].clone()
}),
AccessExpr::Subscript(Subscript::Index {
index: num[0].clone()
})
],
},
expr_from_projection(only(&select.projection)),
);
@ -2124,29 +2124,27 @@ fn parse_array_index_expr() {
let sql = r#"SELECT bar[0]["baz"]["fooz"] FROM foos"#;
let select = pg_and_generic().verified_only_select(sql);
assert_eq!(
&Expr::Subscript {
expr: Box::new(Expr::Subscript {
expr: Box::new(Expr::Subscript {
expr: Box::new(Expr::Identifier(Ident::new("bar"))),
subscript: Box::new(Subscript::Index {
index: num[0].clone()
})
&Expr::CompoundFieldAccess {
root: Box::new(Expr::Identifier(Ident::new("bar"))),
access_chain: vec![
AccessExpr::Subscript(Subscript::Index {
index: num[0].clone()
}),
subscript: Box::new(Subscript::Index {
AccessExpr::Subscript(Subscript::Index {
index: Expr::Identifier(Ident {
value: "baz".to_string(),
quote_style: Some('"'),
span: Span::empty(),
})
})
}),
subscript: Box::new(Subscript::Index {
index: Expr::Identifier(Ident {
value: "fooz".to_string(),
quote_style: Some('"'),
span: Span::empty(),
})
})
}),
AccessExpr::Subscript(Subscript::Index {
index: Expr::Identifier(Ident {
value: "fooz".to_string(),
quote_style: Some('"'),
span: Span::empty(),
})
}),
],
},
expr_from_projection(only(&select.projection)),
);
@ -2154,33 +2152,33 @@ fn parse_array_index_expr() {
let sql = "SELECT (CAST(ARRAY[ARRAY[2, 3]] AS INT[][]))[1][2]";
let select = pg_and_generic().verified_only_select(sql);
assert_eq!(
&Expr::Subscript {
expr: Box::new(Expr::Subscript {
expr: Box::new(Expr::Nested(Box::new(Expr::Cast {
kind: CastKind::Cast,
expr: Box::new(Expr::Array(Array {
elem: vec![Expr::Array(Array {
elem: vec![num[2].clone(), num[3].clone(),],
named: true,
})],
&Expr::CompoundFieldAccess {
root: Box::new(Expr::Nested(Box::new(Expr::Cast {
kind: CastKind::Cast,
expr: Box::new(Expr::Array(Array {
elem: vec![Expr::Array(Array {
elem: vec![num[2].clone(), num[3].clone(),],
named: true,
})),
data_type: DataType::Array(ArrayElemTypeDef::SquareBracket(
Box::new(DataType::Array(ArrayElemTypeDef::SquareBracket(
Box::new(DataType::Int(None)),
None
))),
})],
named: true,
})),
data_type: DataType::Array(ArrayElemTypeDef::SquareBracket(
Box::new(DataType::Array(ArrayElemTypeDef::SquareBracket(
Box::new(DataType::Int(None)),
None
)),
format: None,
}))),
subscript: Box::new(Subscript::Index {
))),
None
)),
format: None,
}))),
access_chain: vec![
AccessExpr::Subscript(Subscript::Index {
index: num[1].clone()
}),
}),
subscript: Box::new(Subscript::Index {
index: num[2].clone()
}),
AccessExpr::Subscript(Subscript::Index {
index: num[2].clone()
}),
],
},
expr_from_projection(only(&select.projection)),
);
@ -2269,9 +2267,13 @@ fn parse_array_subscript() {
),
];
for (sql, expect) in tests {
let Expr::Subscript { subscript, .. } = pg_and_generic().verified_expr(sql) else {
let Expr::CompoundFieldAccess { access_chain, .. } = pg_and_generic().verified_expr(sql)
else {
panic!("expected subscript expr");
};
let Some(AccessExpr::Subscript(subscript)) = access_chain.last() else {
panic!("expected subscript");
};
assert_eq!(expect, *subscript);
}
@ -2282,25 +2284,25 @@ fn parse_array_subscript() {
fn parse_array_multi_subscript() {
let expr = pg_and_generic().verified_expr("make_array(1, 2, 3)[1:2][2]");
assert_eq!(
Expr::Subscript {
expr: Box::new(Expr::Subscript {
expr: Box::new(call(
"make_array",
vec![
Expr::Value(number("1")),
Expr::Value(number("2")),
Expr::Value(number("3"))
]
)),
subscript: Box::new(Subscript::Slice {
Expr::CompoundFieldAccess {
root: Box::new(call(
"make_array",
vec![
Expr::Value(number("1")),
Expr::Value(number("2")),
Expr::Value(number("3"))
]
)),
access_chain: vec![
AccessExpr::Subscript(Subscript::Slice {
lower_bound: Some(Expr::Value(number("1"))),
upper_bound: Some(Expr::Value(number("2"))),
stride: None,
}),
}),
subscript: Box::new(Subscript::Index {
index: Expr::Value(number("2")),
}),
AccessExpr::Subscript(Subscript::Index {
index: Expr::Value(number("2")),
}),
],
},
expr,
);