mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-04 06:18:17 +00:00
Replace Method
and CompositeAccess
with CompoundFieldAccess
(#1716)
This commit is contained in:
parent
c75a992621
commit
3e90a18f6d
9 changed files with 348 additions and 264 deletions
|
@ -661,11 +661,6 @@ pub enum Expr {
|
||||||
/// The path to the data to extract.
|
/// The path to the data to extract.
|
||||||
path: JsonPath,
|
path: JsonPath,
|
||||||
},
|
},
|
||||||
/// CompositeAccess eg: SELECT foo(bar).z, (information_schema._pg_expandarray(array['i','i'])).n
|
|
||||||
CompositeAccess {
|
|
||||||
expr: Box<Expr>,
|
|
||||||
key: Ident,
|
|
||||||
},
|
|
||||||
/// `IS FALSE` operator
|
/// `IS FALSE` operator
|
||||||
IsFalse(Box<Expr>),
|
IsFalse(Box<Expr>),
|
||||||
/// `IS NOT FALSE` operator
|
/// `IS NOT FALSE` operator
|
||||||
|
@ -915,23 +910,6 @@ pub enum Expr {
|
||||||
},
|
},
|
||||||
/// Scalar function call e.g. `LEFT(foo, 5)`
|
/// Scalar function call e.g. `LEFT(foo, 5)`
|
||||||
Function(Function),
|
Function(Function),
|
||||||
/// Arbitrary expr method call
|
|
||||||
///
|
|
||||||
/// Syntax:
|
|
||||||
///
|
|
||||||
/// `<arbitrary-expr>.<function-call>.<function-call-expr>...`
|
|
||||||
///
|
|
||||||
/// > `arbitrary-expr` can be any expression including a function call.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```sql
|
|
||||||
/// SELECT (SELECT ',' + name FROM sys.objects FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)')
|
|
||||||
/// SELECT CONVERT(XML,'<Book>abc</Book>').value('.','NVARCHAR(MAX)').value('.','NVARCHAR(MAX)')
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// (mssql): <https://learn.microsoft.com/en-us/sql/t-sql/xml/xml-data-type-methods?view=sql-server-ver16>
|
|
||||||
Method(Method),
|
|
||||||
/// `CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END`
|
/// `CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END`
|
||||||
///
|
///
|
||||||
/// Note we only recognize a complete single expression as `<condition>`,
|
/// Note we only recognize a complete single expression as `<condition>`,
|
||||||
|
@ -1631,7 +1609,6 @@ impl fmt::Display for Expr {
|
||||||
write!(f, " {value}")
|
write!(f, " {value}")
|
||||||
}
|
}
|
||||||
Expr::Function(fun) => write!(f, "{fun}"),
|
Expr::Function(fun) => write!(f, "{fun}"),
|
||||||
Expr::Method(method) => write!(f, "{method}"),
|
|
||||||
Expr::Case {
|
Expr::Case {
|
||||||
operand,
|
operand,
|
||||||
conditions,
|
conditions,
|
||||||
|
@ -1789,9 +1766,6 @@ impl fmt::Display for Expr {
|
||||||
Expr::JsonAccess { value, path } => {
|
Expr::JsonAccess { value, path } => {
|
||||||
write!(f, "{value}{path}")
|
write!(f, "{value}{path}")
|
||||||
}
|
}
|
||||||
Expr::CompositeAccess { expr, key } => {
|
|
||||||
write!(f, "{expr}.{key}")
|
|
||||||
}
|
|
||||||
Expr::AtTimeZone {
|
Expr::AtTimeZone {
|
||||||
timestamp,
|
timestamp,
|
||||||
time_zone,
|
time_zone,
|
||||||
|
|
|
@ -1288,7 +1288,6 @@ impl Spanned for Expr {
|
||||||
match self {
|
match self {
|
||||||
Expr::Identifier(ident) => ident.span,
|
Expr::Identifier(ident) => ident.span,
|
||||||
Expr::CompoundIdentifier(vec) => union_spans(vec.iter().map(|i| i.span)),
|
Expr::CompoundIdentifier(vec) => union_spans(vec.iter().map(|i| i.span)),
|
||||||
Expr::CompositeAccess { expr, key } => expr.span().union(&key.span),
|
|
||||||
Expr::CompoundFieldAccess { root, access_chain } => {
|
Expr::CompoundFieldAccess { root, access_chain } => {
|
||||||
union_spans(iter::once(root.span()).chain(access_chain.iter().map(|i| i.span())))
|
union_spans(iter::once(root.span()).chain(access_chain.iter().map(|i| i.span())))
|
||||||
}
|
}
|
||||||
|
@ -1478,7 +1477,6 @@ impl Spanned for Expr {
|
||||||
Expr::OuterJoin(expr) => expr.span(),
|
Expr::OuterJoin(expr) => expr.span(),
|
||||||
Expr::Prior(expr) => expr.span(),
|
Expr::Prior(expr) => expr.span(),
|
||||||
Expr::Lambda(_) => Span::empty(),
|
Expr::Lambda(_) => Span::empty(),
|
||||||
Expr::Method(_) => Span::empty(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -251,6 +251,11 @@ pub trait Dialect: Debug + Any {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the dialect supports the `(+)` syntax for OUTER JOIN.
|
||||||
|
fn supports_outer_join_operator(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the dialect supports CONNECT BY.
|
/// Returns true if the dialect supports CONNECT BY.
|
||||||
fn supports_connect_by(&self) -> bool {
|
fn supports_connect_by(&self) -> bool {
|
||||||
false
|
false
|
||||||
|
@ -352,15 +357,6 @@ pub trait Dialect: Debug + Any {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the dialect supports method calls, for example:
|
|
||||||
///
|
|
||||||
/// ```sql
|
|
||||||
/// SELECT (SELECT ',' + name FROM sys.objects FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)')
|
|
||||||
/// ```
|
|
||||||
fn supports_methods(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the dialect supports multiple variable assignment
|
/// Returns true if the dialect supports multiple variable assignment
|
||||||
/// using parentheses in a `SET` variable declaration.
|
/// using parentheses in a `SET` variable declaration.
|
||||||
///
|
///
|
||||||
|
@ -581,6 +577,7 @@ pub trait Dialect: Debug + Any {
|
||||||
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)),
|
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)),
|
||||||
Token::Word(w) if w.keyword == Keyword::OPERATOR => Ok(p!(Between)),
|
Token::Word(w) if w.keyword == Keyword::OPERATOR => Ok(p!(Between)),
|
||||||
Token::Word(w) if w.keyword == Keyword::DIV => Ok(p!(MulDivModOp)),
|
Token::Word(w) if w.keyword == Keyword::DIV => Ok(p!(MulDivModOp)),
|
||||||
|
Token::Period => Ok(p!(Period)),
|
||||||
Token::Eq
|
Token::Eq
|
||||||
| Token::Lt
|
| Token::Lt
|
||||||
| Token::LtEq
|
| Token::LtEq
|
||||||
|
@ -654,6 +651,7 @@ pub trait Dialect: Debug + Any {
|
||||||
/// Uses (APPROXIMATELY) <https://www.postgresql.org/docs/7.0/operators.htm#AEN2026> as a reference
|
/// Uses (APPROXIMATELY) <https://www.postgresql.org/docs/7.0/operators.htm#AEN2026> as a reference
|
||||||
fn prec_value(&self, prec: Precedence) -> u8 {
|
fn prec_value(&self, prec: Precedence) -> u8 {
|
||||||
match prec {
|
match prec {
|
||||||
|
Precedence::Period => 100,
|
||||||
Precedence::DoubleColon => 50,
|
Precedence::DoubleColon => 50,
|
||||||
Precedence::AtTz => 41,
|
Precedence::AtTz => 41,
|
||||||
Precedence::MulDivModOp => 40,
|
Precedence::MulDivModOp => 40,
|
||||||
|
@ -925,6 +923,7 @@ pub trait Dialect: Debug + Any {
|
||||||
/// higher number -> higher precedence
|
/// higher number -> higher precedence
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Precedence {
|
pub enum Precedence {
|
||||||
|
Period,
|
||||||
DoubleColon,
|
DoubleColon,
|
||||||
AtTz,
|
AtTz,
|
||||||
MulDivModOp,
|
MulDivModOp,
|
||||||
|
|
|
@ -46,6 +46,10 @@ impl Dialect for MsSqlDialect {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_outer_join_operator(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn supports_connect_by(&self) -> bool {
|
fn supports_connect_by(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -63,10 +67,6 @@ impl Dialect for MsSqlDialect {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports_methods(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn supports_named_fn_args_with_colon_operator(&self) -> bool {
|
fn supports_named_fn_args_with_colon_operator(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ use crate::tokenizer::Token;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PostgreSqlDialect {}
|
pub struct PostgreSqlDialect {}
|
||||||
|
|
||||||
|
const PERIOD_PREC: u8 = 200;
|
||||||
const DOUBLE_COLON_PREC: u8 = 140;
|
const DOUBLE_COLON_PREC: u8 = 140;
|
||||||
const BRACKET_PREC: u8 = 130;
|
const BRACKET_PREC: u8 = 130;
|
||||||
const COLLATE_PREC: u8 = 120;
|
const COLLATE_PREC: u8 = 120;
|
||||||
|
@ -144,6 +145,7 @@ impl Dialect for PostgreSqlDialect {
|
||||||
|
|
||||||
fn prec_value(&self, prec: Precedence) -> u8 {
|
fn prec_value(&self, prec: Precedence) -> u8 {
|
||||||
match prec {
|
match prec {
|
||||||
|
Precedence::Period => PERIOD_PREC,
|
||||||
Precedence::DoubleColon => DOUBLE_COLON_PREC,
|
Precedence::DoubleColon => DOUBLE_COLON_PREC,
|
||||||
Precedence::AtTz => AT_TZ_PREC,
|
Precedence::AtTz => AT_TZ_PREC,
|
||||||
Precedence::MulDivModOp => MUL_DIV_MOD_OP_PREC,
|
Precedence::MulDivModOp => MUL_DIV_MOD_OP_PREC,
|
||||||
|
|
|
@ -87,6 +87,11 @@ impl Dialect for SnowflakeDialect {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See <https://docs.snowflake.com/en/sql-reference/constructs/where#joins-in-the-where-clause>
|
||||||
|
fn supports_outer_join_operator(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn supports_connect_by(&self) -> bool {
|
fn supports_connect_by(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1021,6 +1021,8 @@ impl<'a> Parser<'a> {
|
||||||
debug!("parsing expr");
|
debug!("parsing expr");
|
||||||
let mut expr = self.parse_prefix()?;
|
let mut expr = self.parse_prefix()?;
|
||||||
|
|
||||||
|
expr = self.parse_compound_expr(expr, vec![])?;
|
||||||
|
|
||||||
debug!("prefix: {:?}", expr);
|
debug!("prefix: {:?}", expr);
|
||||||
loop {
|
loop {
|
||||||
let next_precedence = self.get_next_precedence()?;
|
let next_precedence = self.get_next_precedence()?;
|
||||||
|
@ -1030,6 +1032,12 @@ impl<'a> Parser<'a> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The period operator is handled exclusively by the
|
||||||
|
// compound field access parsing.
|
||||||
|
if Token::Period == self.peek_token_ref().token {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
expr = self.parse_infix(expr, next_precedence)?;
|
expr = self.parse_infix(expr, next_precedence)?;
|
||||||
}
|
}
|
||||||
Ok(expr)
|
Ok(expr)
|
||||||
|
@ -1105,8 +1113,8 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tries to parse an expression by matching the specified word to known keywords that have a special meaning in the dialect.
|
/// Tries to parse an expression by matching the specified word to known keywords that have a special meaning in the dialect.
|
||||||
// Returns `None if no match is found.
|
/// Returns `None if no match is found.
|
||||||
fn parse_expr_prefix_by_reserved_word(
|
fn parse_expr_prefix_by_reserved_word(
|
||||||
&mut self,
|
&mut self,
|
||||||
w: &Word,
|
w: &Word,
|
||||||
|
@ -1203,7 +1211,7 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
Keyword::STRUCT if self.dialect.supports_struct_literal() => {
|
Keyword::STRUCT if self.dialect.supports_struct_literal() => {
|
||||||
let struct_expr = self.parse_struct_literal()?;
|
let struct_expr = self.parse_struct_literal()?;
|
||||||
Ok(Some(self.parse_compound_field_access(struct_expr, vec![])?))
|
Ok(Some(struct_expr))
|
||||||
}
|
}
|
||||||
Keyword::PRIOR if matches!(self.state, ParserState::ConnectBy) => {
|
Keyword::PRIOR if matches!(self.state, ParserState::ConnectBy) => {
|
||||||
let expr = self.parse_subexpr(self.dialect.prec_value(Precedence::PlusMinus))?;
|
let expr = self.parse_subexpr(self.dialect.prec_value(Precedence::PlusMinus))?;
|
||||||
|
@ -1216,35 +1224,16 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tries to parse an expression by a word that is not known to have a special meaning in the dialect.
|
/// Tries to parse an expression by a word that is not known to have a special meaning in the dialect.
|
||||||
fn parse_expr_prefix_by_unreserved_word(
|
fn parse_expr_prefix_by_unreserved_word(
|
||||||
&mut self,
|
&mut self,
|
||||||
w: &Word,
|
w: &Word,
|
||||||
w_span: Span,
|
w_span: Span,
|
||||||
) -> Result<Expr, ParserError> {
|
) -> Result<Expr, ParserError> {
|
||||||
match self.peek_token().token {
|
match self.peek_token().token {
|
||||||
Token::Period => self.parse_compound_field_access(
|
Token::LParen if !self.peek_outer_join_operator() => {
|
||||||
Expr::Identifier(w.clone().into_ident(w_span)),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
Token::LParen => {
|
|
||||||
let id_parts = vec![w.clone().into_ident(w_span)];
|
let id_parts = vec![w.clone().into_ident(w_span)];
|
||||||
if let Some(expr) = self.parse_outer_join_expr(&id_parts) {
|
self.parse_function(ObjectName::from(id_parts))
|
||||||
Ok(expr)
|
|
||||||
} else {
|
|
||||||
let mut expr = self.parse_function(ObjectName::from(id_parts))?;
|
|
||||||
// consume all period if it's a method chain
|
|
||||||
expr = self.try_parse_method(expr)?;
|
|
||||||
let fields = vec![];
|
|
||||||
self.parse_compound_field_access(expr, fields)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Token::LBracket if dialect_of!(self is PostgreSqlDialect | DuckDbDialect | GenericDialect | ClickHouseDialect | BigQueryDialect) =>
|
|
||||||
{
|
|
||||||
let ident = Expr::Identifier(w.clone().into_ident(w_span));
|
|
||||||
let mut fields = vec![];
|
|
||||||
self.parse_multi_dim_subscript(&mut fields)?;
|
|
||||||
self.parse_compound_field_access(ident, fields)
|
|
||||||
}
|
}
|
||||||
// string introducer https://dev.mysql.com/doc/refman/8.0/en/charset-introducer.html
|
// string introducer https://dev.mysql.com/doc/refman/8.0/en/charset-introducer.html
|
||||||
Token::SingleQuotedString(_)
|
Token::SingleQuotedString(_)
|
||||||
|
@ -1453,25 +1442,7 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.expect_token(&Token::RParen)?;
|
self.expect_token(&Token::RParen)?;
|
||||||
let expr = self.try_parse_method(expr)?;
|
Ok(expr)
|
||||||
if !self.consume_token(&Token::Period) {
|
|
||||||
Ok(expr)
|
|
||||||
} else {
|
|
||||||
let tok = self.next_token();
|
|
||||||
let key = match tok.token {
|
|
||||||
Token::Word(word) => word.into_ident(tok.span),
|
|
||||||
_ => {
|
|
||||||
return parser_err!(
|
|
||||||
format!("Expected identifier, found: {tok}"),
|
|
||||||
tok.span.start
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(Expr::CompositeAccess {
|
|
||||||
expr: Box::new(expr),
|
|
||||||
key,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Token::Placeholder(_) | Token::Colon | Token::AtSign => {
|
Token::Placeholder(_) | Token::Colon | Token::AtSign => {
|
||||||
self.prev_token();
|
self.prev_token();
|
||||||
|
@ -1484,8 +1455,6 @@ impl<'a> Parser<'a> {
|
||||||
_ => self.expected_at("an expression", next_token_index),
|
_ => self.expected_at("an expression", next_token_index),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
let expr = self.try_parse_method(expr)?;
|
|
||||||
|
|
||||||
if self.parse_keyword(Keyword::COLLATE) {
|
if self.parse_keyword(Keyword::COLLATE) {
|
||||||
Ok(Expr::Collate {
|
Ok(Expr::Collate {
|
||||||
expr: Box::new(expr),
|
expr: Box::new(expr),
|
||||||
|
@ -1499,62 +1468,72 @@ impl<'a> Parser<'a> {
|
||||||
/// Try to parse an [Expr::CompoundFieldAccess] like `a.b.c` or `a.b[1].c`.
|
/// Try to parse an [Expr::CompoundFieldAccess] like `a.b.c` or `a.b[1].c`.
|
||||||
/// If all the fields are `Expr::Identifier`s, return an [Expr::CompoundIdentifier] instead.
|
/// If all the fields are `Expr::Identifier`s, return an [Expr::CompoundIdentifier] instead.
|
||||||
/// If only the root exists, return the root.
|
/// If only the root exists, return the root.
|
||||||
/// If self supports [Dialect::supports_partiql], it will fall back when occurs [Token::LBracket] for JsonAccess parsing.
|
/// Parses compound expressions which may be delimited by period
|
||||||
pub fn parse_compound_field_access(
|
/// or bracket notation.
|
||||||
|
/// For example: `a.b.c`, `a.b[1]`.
|
||||||
|
pub fn parse_compound_expr(
|
||||||
&mut self,
|
&mut self,
|
||||||
root: Expr,
|
root: Expr,
|
||||||
mut chain: Vec<AccessExpr>,
|
mut chain: Vec<AccessExpr>,
|
||||||
) -> Result<Expr, ParserError> {
|
) -> Result<Expr, ParserError> {
|
||||||
let mut ending_wildcard: Option<TokenWithSpan> = None;
|
let mut ending_wildcard: Option<TokenWithSpan> = None;
|
||||||
let mut ending_lbracket = false;
|
loop {
|
||||||
while self.consume_token(&Token::Period) {
|
if self.consume_token(&Token::Period) {
|
||||||
let next_token = self.next_token();
|
let next_token = self.peek_token_ref();
|
||||||
match next_token.token {
|
match &next_token.token {
|
||||||
Token::Word(w) => {
|
Token::Mul => {
|
||||||
let expr = Expr::Identifier(w.into_ident(next_token.span));
|
// Postgres explicitly allows funcnm(tablenm.*) and the
|
||||||
chain.push(AccessExpr::Dot(expr));
|
// function array_agg traverses this control flow
|
||||||
if self.peek_token().token == Token::LBracket {
|
if dialect_of!(self is PostgreSqlDialect) {
|
||||||
if self.dialect.supports_partiql() {
|
ending_wildcard = Some(self.next_token());
|
||||||
self.next_token();
|
|
||||||
ending_lbracket = true;
|
|
||||||
break;
|
|
||||||
} else {
|
} else {
|
||||||
self.parse_multi_dim_subscript(&mut chain)?
|
// Put back the consumed `.` tokens before exiting.
|
||||||
|
// If this expression is being parsed in the
|
||||||
|
// context of a projection, then the `.*` could imply
|
||||||
|
// a wildcard expansion. For example:
|
||||||
|
// `SELECT STRUCT('foo').* FROM T`
|
||||||
|
self.prev_token(); // .
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
Token::Mul => {
|
|
||||||
// Postgres explicitly allows funcnm(tablenm.*) and the
|
|
||||||
// function array_agg traverses this control flow
|
|
||||||
if dialect_of!(self is PostgreSqlDialect) {
|
|
||||||
ending_wildcard = Some(next_token);
|
|
||||||
} else {
|
|
||||||
// Put back the consumed .* tokens before exiting.
|
|
||||||
// If this expression is being parsed in the
|
|
||||||
// context of a projection, then this could imply
|
|
||||||
// a wildcard expansion. For example:
|
|
||||||
// `SELECT STRUCT('foo').* FROM T`
|
|
||||||
self.prev_token(); // *
|
|
||||||
self.prev_token(); // .
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Token::SingleQuotedString(s) => {
|
Token::SingleQuotedString(s) => {
|
||||||
let expr = Expr::Identifier(Ident::with_quote('\'', s));
|
let expr =
|
||||||
chain.push(AccessExpr::Dot(expr));
|
Expr::Identifier(Ident::with_quote_and_span('\'', next_token.span, s));
|
||||||
}
|
chain.push(AccessExpr::Dot(expr));
|
||||||
_ => {
|
self.advance_token(); // The consumed string
|
||||||
return self.expected("an identifier or a '*' after '.'", next_token);
|
}
|
||||||
|
// Fallback to parsing an arbitrary expression.
|
||||||
|
_ => match self.parse_subexpr(self.dialect.prec_value(Precedence::Period))? {
|
||||||
|
// If we get back a compound field access or identifier,
|
||||||
|
// we flatten the nested expression.
|
||||||
|
// For example if the current root is `foo`
|
||||||
|
// and we get back a compound identifier expression `bar.baz`
|
||||||
|
// The full expression should be `foo.bar.baz` (i.e.
|
||||||
|
// a root with an access chain with 2 entries) and not
|
||||||
|
// `foo.(bar.baz)` (i.e. a root with an access chain with
|
||||||
|
// 1 entry`).
|
||||||
|
Expr::CompoundFieldAccess { root, access_chain } => {
|
||||||
|
chain.push(AccessExpr::Dot(*root));
|
||||||
|
chain.extend(access_chain);
|
||||||
|
}
|
||||||
|
Expr::CompoundIdentifier(parts) => chain
|
||||||
|
.extend(parts.into_iter().map(Expr::Identifier).map(AccessExpr::Dot)),
|
||||||
|
expr => {
|
||||||
|
chain.push(AccessExpr::Dot(expr));
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
} else if !self.dialect.supports_partiql()
|
||||||
|
&& self.peek_token_ref().token == Token::LBracket
|
||||||
|
{
|
||||||
|
self.parse_multi_dim_subscript(&mut chain)?;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if dialect supports partiql, we need to go back one Token::LBracket for the JsonAccess parsing
|
let tok_index = self.get_current_index();
|
||||||
if self.dialect.supports_partiql() && ending_lbracket {
|
|
||||||
self.prev_token();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(wildcard_token) = ending_wildcard {
|
if let Some(wildcard_token) = ending_wildcard {
|
||||||
if !Self::is_all_ident(&root, &chain) {
|
if !Self::is_all_ident(&root, &chain) {
|
||||||
return self.expected("an identifier or a '*' after '.'", self.peek_token());
|
return self.expected("an identifier or a '*' after '.'", self.peek_token());
|
||||||
|
@ -1563,32 +1542,112 @@ impl<'a> Parser<'a> {
|
||||||
ObjectName::from(Self::exprs_to_idents(root, chain)?),
|
ObjectName::from(Self::exprs_to_idents(root, chain)?),
|
||||||
AttachedToken(wildcard_token),
|
AttachedToken(wildcard_token),
|
||||||
))
|
))
|
||||||
} else if self.peek_token().token == Token::LParen {
|
} else if self.maybe_parse_outer_join_operator() {
|
||||||
if !Self::is_all_ident(&root, &chain) {
|
if !Self::is_all_ident(&root, &chain) {
|
||||||
// consume LParen
|
return self.expected_at("column identifier before (+)", tok_index);
|
||||||
self.next_token();
|
|
||||||
return self.expected("an identifier or a '*' after '.'", self.peek_token());
|
|
||||||
};
|
};
|
||||||
let id_parts = Self::exprs_to_idents(root, chain)?;
|
let expr = if chain.is_empty() {
|
||||||
if let Some(expr) = self.parse_outer_join_expr(&id_parts) {
|
root
|
||||||
Ok(expr)
|
|
||||||
} else {
|
} else {
|
||||||
self.parse_function(ObjectName::from(id_parts))
|
Expr::CompoundIdentifier(Self::exprs_to_idents(root, chain)?)
|
||||||
}
|
};
|
||||||
} else if chain.is_empty() {
|
Ok(Expr::OuterJoin(expr.into()))
|
||||||
Ok(root)
|
|
||||||
} else {
|
} else {
|
||||||
if Self::is_all_ident(&root, &chain) {
|
Self::build_compound_expr(root, chain)
|
||||||
return Ok(Expr::CompoundIdentifier(Self::exprs_to_idents(
|
}
|
||||||
root, chain,
|
}
|
||||||
)?));
|
|
||||||
|
/// Combines a root expression and access chain to form
|
||||||
|
/// a compound expression. Which may be a [Expr::CompoundFieldAccess]
|
||||||
|
/// or other special cased expressions like [Expr::CompoundIdentifier],
|
||||||
|
/// [Expr::OuterJoin].
|
||||||
|
fn build_compound_expr(
|
||||||
|
root: Expr,
|
||||||
|
mut access_chain: Vec<AccessExpr>,
|
||||||
|
) -> Result<Expr, ParserError> {
|
||||||
|
if access_chain.is_empty() {
|
||||||
|
return Ok(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
if Self::is_all_ident(&root, &access_chain) {
|
||||||
|
return Ok(Expr::CompoundIdentifier(Self::exprs_to_idents(
|
||||||
|
root,
|
||||||
|
access_chain,
|
||||||
|
)?));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flatten qualified function calls.
|
||||||
|
// For example, the expression `a.b.c.foo(1,2,3)` should
|
||||||
|
// represent a function called `a.b.c.foo`, rather than
|
||||||
|
// a composite expression.
|
||||||
|
if matches!(root, Expr::Identifier(_))
|
||||||
|
&& matches!(
|
||||||
|
access_chain.last(),
|
||||||
|
Some(AccessExpr::Dot(Expr::Function(_)))
|
||||||
|
)
|
||||||
|
&& access_chain
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.skip(1) // All except the Function
|
||||||
|
.all(|access| matches!(access, AccessExpr::Dot(Expr::Identifier(_))))
|
||||||
|
{
|
||||||
|
let Some(AccessExpr::Dot(Expr::Function(mut func))) = access_chain.pop() else {
|
||||||
|
return parser_err!("expected function expression", root.span().start);
|
||||||
|
};
|
||||||
|
|
||||||
|
let compound_func_name = [root]
|
||||||
|
.into_iter()
|
||||||
|
.chain(access_chain.into_iter().flat_map(|access| match access {
|
||||||
|
AccessExpr::Dot(expr) => Some(expr),
|
||||||
|
_ => None,
|
||||||
|
}))
|
||||||
|
.flat_map(|expr| match expr {
|
||||||
|
Expr::Identifier(ident) => Some(ident),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.map(ObjectNamePart::Identifier)
|
||||||
|
.chain(func.name.0)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
func.name = ObjectName(compound_func_name);
|
||||||
|
|
||||||
|
return Ok(Expr::Function(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flatten qualified outer join expressions.
|
||||||
|
// For example, the expression `T.foo(+)` should
|
||||||
|
// represent an outer join on the column name `T.foo`
|
||||||
|
// rather than a composite expression.
|
||||||
|
if access_chain.len() == 1
|
||||||
|
&& matches!(
|
||||||
|
access_chain.last(),
|
||||||
|
Some(AccessExpr::Dot(Expr::OuterJoin(_)))
|
||||||
|
)
|
||||||
|
{
|
||||||
|
let Some(AccessExpr::Dot(Expr::OuterJoin(inner_expr))) = access_chain.pop() else {
|
||||||
|
return parser_err!("expected (+) expression", root.span().start);
|
||||||
|
};
|
||||||
|
|
||||||
|
if !Self::is_all_ident(&root, &[]) {
|
||||||
|
return parser_err!("column identifier before (+)", root.span().start);
|
||||||
|
};
|
||||||
|
|
||||||
|
let token_start = root.span().start;
|
||||||
|
let mut idents = Self::exprs_to_idents(root, vec![])?;
|
||||||
|
match *inner_expr {
|
||||||
|
Expr::CompoundIdentifier(suffix) => idents.extend(suffix),
|
||||||
|
Expr::Identifier(suffix) => idents.push(suffix),
|
||||||
|
_ => {
|
||||||
|
return parser_err!("column identifier before (+)", token_start);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Expr::CompoundFieldAccess {
|
return Ok(Expr::OuterJoin(Expr::CompoundIdentifier(idents).into()));
|
||||||
root: Box::new(root),
|
|
||||||
access_chain: chain,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(Expr::CompoundFieldAccess {
|
||||||
|
root: Box::new(root),
|
||||||
|
access_chain,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the root is an identifier and all fields are identifiers.
|
/// Check if the root is an identifier and all fields are identifiers.
|
||||||
|
@ -1625,20 +1684,23 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to parse OuterJoin expression `(+)`
|
/// Returns true if the next tokens indicate the outer join operator `(+)`.
|
||||||
fn parse_outer_join_expr(&mut self, id_parts: &[Ident]) -> Option<Expr> {
|
fn peek_outer_join_operator(&mut self) -> bool {
|
||||||
if dialect_of!(self is SnowflakeDialect | MsSqlDialect)
|
if !self.dialect.supports_outer_join_operator() {
|
||||||
&& self.consume_tokens(&[Token::LParen, Token::Plus, Token::RParen])
|
return false;
|
||||||
{
|
|
||||||
Some(Expr::OuterJoin(Box::new(
|
|
||||||
match <[Ident; 1]>::try_from(id_parts.to_vec()) {
|
|
||||||
Ok([ident]) => Expr::Identifier(ident),
|
|
||||||
Err(parts) => Expr::CompoundIdentifier(parts),
|
|
||||||
},
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let [maybe_lparen, maybe_plus, maybe_rparen] = self.peek_tokens_ref();
|
||||||
|
Token::LParen == maybe_lparen.token
|
||||||
|
&& Token::Plus == maybe_plus.token
|
||||||
|
&& Token::RParen == maybe_rparen.token
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the next tokens indicates the outer join operator `(+)`, consume
|
||||||
|
/// the tokens and return true.
|
||||||
|
fn maybe_parse_outer_join_operator(&mut self) -> bool {
|
||||||
|
self.dialect.supports_outer_join_operator()
|
||||||
|
&& self.consume_tokens(&[Token::LParen, Token::Plus, Token::RParen])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_utility_options(&mut self) -> Result<Vec<UtilityOption>, ParserError> {
|
pub fn parse_utility_options(&mut self) -> Result<Vec<UtilityOption>, ParserError> {
|
||||||
|
@ -1688,41 +1750,6 @@ impl<'a> Parser<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses method call expression
|
|
||||||
fn try_parse_method(&mut self, expr: Expr) -> Result<Expr, ParserError> {
|
|
||||||
if !self.dialect.supports_methods() {
|
|
||||||
return Ok(expr);
|
|
||||||
}
|
|
||||||
let method_chain = self.maybe_parse(|p| {
|
|
||||||
let mut method_chain = Vec::new();
|
|
||||||
while p.consume_token(&Token::Period) {
|
|
||||||
let tok = p.next_token();
|
|
||||||
let name = match tok.token {
|
|
||||||
Token::Word(word) => word.into_ident(tok.span),
|
|
||||||
_ => return p.expected("identifier", tok),
|
|
||||||
};
|
|
||||||
let func = match p.parse_function(ObjectName::from(vec![name]))? {
|
|
||||||
Expr::Function(func) => func,
|
|
||||||
_ => return p.expected("function", p.peek_token()),
|
|
||||||
};
|
|
||||||
method_chain.push(func);
|
|
||||||
}
|
|
||||||
if !method_chain.is_empty() {
|
|
||||||
Ok(method_chain)
|
|
||||||
} else {
|
|
||||||
p.expected("function", p.peek_token())
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
if let Some(method_chain) = method_chain {
|
|
||||||
Ok(Expr::Method(Method {
|
|
||||||
expr: Box::new(expr),
|
|
||||||
method_chain,
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
Ok(expr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tries to parse the body of an [ODBC function] call.
|
/// Tries to parse the body of an [ODBC function] call.
|
||||||
/// i.e. without the enclosing braces
|
/// i.e. without the enclosing braces
|
||||||
///
|
///
|
||||||
|
@ -3281,21 +3308,9 @@ impl<'a> Parser<'a> {
|
||||||
op: UnaryOperator::PGPostfixFactorial,
|
op: UnaryOperator::PGPostfixFactorial,
|
||||||
expr: Box::new(expr),
|
expr: Box::new(expr),
|
||||||
})
|
})
|
||||||
} else if Token::LBracket == *tok {
|
} else if Token::LBracket == *tok && self.dialect.supports_partiql()
|
||||||
if dialect_of!(self is PostgreSqlDialect | DuckDbDialect | GenericDialect | ClickHouseDialect | BigQueryDialect)
|
|| (dialect_of!(self is SnowflakeDialect | GenericDialect) && Token::Colon == *tok)
|
||||||
{
|
{
|
||||||
let mut chain = vec![];
|
|
||||||
// back to LBracket
|
|
||||||
self.prev_token();
|
|
||||||
self.parse_multi_dim_subscript(&mut chain)?;
|
|
||||||
self.parse_compound_field_access(expr, chain)
|
|
||||||
} else if self.dialect.supports_partiql() {
|
|
||||||
self.prev_token();
|
|
||||||
self.parse_json_access(expr)
|
|
||||||
} else {
|
|
||||||
parser_err!("Array subscripting is not supported", tok.span.start)
|
|
||||||
}
|
|
||||||
} else if dialect_of!(self is SnowflakeDialect | GenericDialect) && Token::Colon == *tok {
|
|
||||||
self.prev_token();
|
self.prev_token();
|
||||||
self.parse_json_access(expr)
|
self.parse_json_access(expr)
|
||||||
} else {
|
} else {
|
||||||
|
@ -3605,6 +3620,26 @@ impl<'a> Parser<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns references to the `N` next non-whitespace tokens
|
||||||
|
/// that have not yet been processed.
|
||||||
|
///
|
||||||
|
/// See [`Self::peek_tokens`] for an example.
|
||||||
|
pub fn peek_tokens_ref<const N: usize>(&self) -> [&TokenWithSpan; N] {
|
||||||
|
let mut index = self.index;
|
||||||
|
core::array::from_fn(|_| loop {
|
||||||
|
let token = self.tokens.get(index);
|
||||||
|
index += 1;
|
||||||
|
if let Some(TokenWithSpan {
|
||||||
|
token: Token::Whitespace(_),
|
||||||
|
span: _,
|
||||||
|
}) = token
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break token.unwrap_or(&EOF_TOKEN);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Return nth non-whitespace token that has not yet been processed
|
/// Return nth non-whitespace token that has not yet been processed
|
||||||
pub fn peek_nth_token(&self, n: usize) -> TokenWithSpan {
|
pub fn peek_nth_token(&self, n: usize) -> TokenWithSpan {
|
||||||
self.peek_nth_token_ref(n).clone()
|
self.peek_nth_token_ref(n).clone()
|
||||||
|
|
|
@ -73,6 +73,23 @@ fn parse_numeric_literal_underscore() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_function_object_name() {
|
||||||
|
let select = verified_only_select("SELECT a.b.c.d(1, 2, 3) FROM T");
|
||||||
|
let Expr::Function(func) = expr_from_projection(&select.projection[0]) else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
ObjectName::from(
|
||||||
|
["a", "b", "c", "d"]
|
||||||
|
.into_iter()
|
||||||
|
.map(Ident::new)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
),
|
||||||
|
func.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_insert_values() {
|
fn parse_insert_values() {
|
||||||
let row = vec![
|
let row = vec![
|
||||||
|
@ -936,6 +953,44 @@ fn parse_select_distinct_tuple() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_outer_join_operator() {
|
||||||
|
let dialects = all_dialects_where(|d| d.supports_outer_join_operator());
|
||||||
|
|
||||||
|
let select = dialects.verified_only_select("SELECT 1 FROM T WHERE a = b (+)");
|
||||||
|
assert_eq!(
|
||||||
|
select.selection,
|
||||||
|
Some(Expr::BinaryOp {
|
||||||
|
left: Box::new(Expr::Identifier(Ident::new("a"))),
|
||||||
|
op: BinaryOperator::Eq,
|
||||||
|
right: Box::new(Expr::OuterJoin(Box::new(Expr::Identifier(Ident::new("b")))))
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let select = dialects.verified_only_select("SELECT 1 FROM T WHERE t1.c1 = t2.c2.d3 (+)");
|
||||||
|
assert_eq!(
|
||||||
|
select.selection,
|
||||||
|
Some(Expr::BinaryOp {
|
||||||
|
left: Box::new(Expr::CompoundIdentifier(vec![
|
||||||
|
Ident::new("t1"),
|
||||||
|
Ident::new("c1")
|
||||||
|
])),
|
||||||
|
op: BinaryOperator::Eq,
|
||||||
|
right: Box::new(Expr::OuterJoin(Box::new(Expr::CompoundIdentifier(vec![
|
||||||
|
Ident::new("t2"),
|
||||||
|
Ident::new("c2"),
|
||||||
|
Ident::new("d3"),
|
||||||
|
]))))
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = dialects.parse_sql_statements("SELECT 1 FROM T WHERE 1 = 2 (+)");
|
||||||
|
assert_eq!(
|
||||||
|
ParserError::ParserError("Expected: column identifier before (+), found: 2".to_string()),
|
||||||
|
res.unwrap_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_select_distinct_on() {
|
fn parse_select_distinct_on() {
|
||||||
let sql = "SELECT DISTINCT ON (album_id) name FROM track ORDER BY album_id, milliseconds";
|
let sql = "SELECT DISTINCT ON (album_id) name FROM track ORDER BY album_id, milliseconds";
|
||||||
|
@ -12623,68 +12678,76 @@ fn test_try_convert() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_method_select() {
|
fn parse_method_select() {
|
||||||
let dialects = all_dialects_where(|d| d.supports_methods());
|
let _ = verified_only_select(
|
||||||
let _ = dialects.verified_only_select(
|
|
||||||
"SELECT LEFT('abc', 1).value('.', 'NVARCHAR(MAX)').value('.', 'NVARCHAR(MAX)') AS T",
|
"SELECT LEFT('abc', 1).value('.', 'NVARCHAR(MAX)').value('.', 'NVARCHAR(MAX)') AS T",
|
||||||
);
|
);
|
||||||
let _ = dialects.verified_only_select("SELECT STUFF((SELECT ',' + name FROM sys.objects FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS T");
|
let _ = verified_only_select("SELECT STUFF((SELECT ',' + name FROM sys.objects FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS T");
|
||||||
let _ = dialects
|
let _ = verified_only_select("SELECT CAST(column AS XML).value('.', 'NVARCHAR(MAX)') AS T");
|
||||||
.verified_only_select("SELECT CAST(column AS XML).value('.', 'NVARCHAR(MAX)') AS T");
|
|
||||||
|
|
||||||
// `CONVERT` support
|
// `CONVERT` support
|
||||||
let dialects = all_dialects_where(|d| {
|
let dialects =
|
||||||
d.supports_methods() && d.supports_try_convert() && d.convert_type_before_value()
|
all_dialects_where(|d| d.supports_try_convert() && d.convert_type_before_value());
|
||||||
});
|
|
||||||
let _ = dialects.verified_only_select("SELECT CONVERT(XML, '<Book>abc</Book>').value('.', 'NVARCHAR(MAX)').value('.', 'NVARCHAR(MAX)') AS T");
|
let _ = dialects.verified_only_select("SELECT CONVERT(XML, '<Book>abc</Book>').value('.', 'NVARCHAR(MAX)').value('.', 'NVARCHAR(MAX)') AS T");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_method_expr() {
|
fn parse_method_expr() {
|
||||||
let dialects = all_dialects_where(|d| d.supports_methods());
|
let expr =
|
||||||
let expr = dialects
|
verified_expr("LEFT('abc', 1).value('.', 'NVARCHAR(MAX)').value('.', 'NVARCHAR(MAX)')");
|
||||||
.verified_expr("LEFT('abc', 1).value('.', 'NVARCHAR(MAX)').value('.', 'NVARCHAR(MAX)')");
|
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Method(Method { expr, method_chain }) => {
|
Expr::CompoundFieldAccess { root, access_chain } => {
|
||||||
assert!(matches!(*expr, Expr::Function(_)));
|
assert!(matches!(*root, Expr::Function(_)));
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
method_chain[..],
|
access_chain[..],
|
||||||
[Function { .. }, Function { .. }]
|
[
|
||||||
|
AccessExpr::Dot(Expr::Function(_)),
|
||||||
|
AccessExpr::Dot(Expr::Function(_))
|
||||||
|
]
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
let expr = dialects.verified_expr(
|
|
||||||
|
let expr = verified_expr(
|
||||||
"(SELECT ',' + name FROM sys.objects FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')",
|
"(SELECT ',' + name FROM sys.objects FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')",
|
||||||
);
|
);
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Method(Method { expr, method_chain }) => {
|
Expr::CompoundFieldAccess { root, access_chain } => {
|
||||||
assert!(matches!(*expr, Expr::Subquery(_)));
|
assert!(matches!(*root, Expr::Subquery(_)));
|
||||||
assert!(matches!(method_chain[..], [Function { .. }]));
|
assert!(matches!(
|
||||||
|
access_chain[..],
|
||||||
|
[AccessExpr::Dot(Expr::Function(_))]
|
||||||
|
));
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
let expr = dialects.verified_expr("CAST(column AS XML).value('.', 'NVARCHAR(MAX)')");
|
let expr = verified_expr("CAST(column AS XML).value('.', 'NVARCHAR(MAX)')");
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Method(Method { expr, method_chain }) => {
|
Expr::CompoundFieldAccess { root, access_chain } => {
|
||||||
assert!(matches!(*expr, Expr::Cast { .. }));
|
assert!(matches!(*root, Expr::Cast { .. }));
|
||||||
assert!(matches!(method_chain[..], [Function { .. }]));
|
assert!(matches!(
|
||||||
|
access_chain[..],
|
||||||
|
[AccessExpr::Dot(Expr::Function(_))]
|
||||||
|
));
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// `CONVERT` support
|
// `CONVERT` support
|
||||||
let dialects = all_dialects_where(|d| {
|
let dialects =
|
||||||
d.supports_methods() && d.supports_try_convert() && d.convert_type_before_value()
|
all_dialects_where(|d| d.supports_try_convert() && d.convert_type_before_value());
|
||||||
});
|
|
||||||
let expr = dialects.verified_expr(
|
let expr = dialects.verified_expr(
|
||||||
"CONVERT(XML, '<Book>abc</Book>').value('.', 'NVARCHAR(MAX)').value('.', 'NVARCHAR(MAX)')",
|
"CONVERT(XML, '<Book>abc</Book>').value('.', 'NVARCHAR(MAX)').value('.', 'NVARCHAR(MAX)')",
|
||||||
);
|
);
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Method(Method { expr, method_chain }) => {
|
Expr::CompoundFieldAccess { root, access_chain } => {
|
||||||
assert!(matches!(*expr, Expr::Convert { .. }));
|
assert!(matches!(*root, Expr::Convert { .. }));
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
method_chain[..],
|
access_chain[..],
|
||||||
[Function { .. }, Function { .. }]
|
[
|
||||||
|
AccessExpr::Dot(Expr::Function(_)),
|
||||||
|
AccessExpr::Dot(Expr::Function(_))
|
||||||
|
]
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|
|
@ -2991,38 +2991,45 @@ fn parse_json_table_is_not_reserved() {
|
||||||
fn test_composite_value() {
|
fn test_composite_value() {
|
||||||
let sql = "SELECT (on_hand.item).name FROM on_hand WHERE (on_hand.item).price > 9";
|
let sql = "SELECT (on_hand.item).name FROM on_hand WHERE (on_hand.item).price > 9";
|
||||||
let select = pg().verified_only_select(sql);
|
let select = pg().verified_only_select(sql);
|
||||||
|
|
||||||
|
let Expr::CompoundFieldAccess { root, access_chain } =
|
||||||
|
expr_from_projection(&select.projection[0])
|
||||||
|
else {
|
||||||
|
unreachable!("expected projection: got {:?}", &select.projection[0]);
|
||||||
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
SelectItem::UnnamedExpr(Expr::CompositeAccess {
|
root.as_ref(),
|
||||||
key: Ident::new("name"),
|
&Expr::Nested(Box::new(Expr::CompoundIdentifier(vec![
|
||||||
expr: Box::new(Expr::Nested(Box::new(Expr::CompoundIdentifier(vec![
|
Ident::new("on_hand"),
|
||||||
Ident::new("on_hand"),
|
Ident::new("item")
|
||||||
Ident::new("item")
|
])))
|
||||||
]))))
|
);
|
||||||
}),
|
assert_eq!(
|
||||||
select.projection[0]
|
access_chain.as_slice(),
|
||||||
|
&[AccessExpr::Dot(Expr::Identifier(Ident::new("name")))]
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
select.selection,
|
select.selection.as_ref().unwrap(),
|
||||||
Some(Expr::BinaryOp {
|
&Expr::BinaryOp {
|
||||||
left: Box::new(Expr::CompositeAccess {
|
left: Box::new(Expr::CompoundFieldAccess {
|
||||||
key: Ident::new("price"),
|
root: Expr::Nested(Box::new(Expr::CompoundIdentifier(vec![
|
||||||
expr: Box::new(Expr::Nested(Box::new(Expr::CompoundIdentifier(vec![
|
|
||||||
Ident::new("on_hand"),
|
Ident::new("on_hand"),
|
||||||
Ident::new("item")
|
Ident::new("item")
|
||||||
]))))
|
])))
|
||||||
|
.into(),
|
||||||
|
access_chain: vec![AccessExpr::Dot(Expr::Identifier(Ident::new("price")))]
|
||||||
}),
|
}),
|
||||||
op: BinaryOperator::Gt,
|
op: BinaryOperator::Gt,
|
||||||
right: Box::new(Expr::Value(number("9")))
|
right: Box::new(Expr::Value(number("9")))
|
||||||
})
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let sql = "SELECT (information_schema._pg_expandarray(ARRAY['i', 'i'])).n";
|
let sql = "SELECT (information_schema._pg_expandarray(ARRAY['i', 'i'])).n";
|
||||||
let select = pg().verified_only_select(sql);
|
let select = pg().verified_only_select(sql);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
SelectItem::UnnamedExpr(Expr::CompositeAccess {
|
&Expr::CompoundFieldAccess {
|
||||||
key: Ident::new("n"),
|
root: Box::new(Expr::Nested(Box::new(Expr::Function(Function {
|
||||||
expr: Box::new(Expr::Nested(Box::new(Expr::Function(Function {
|
|
||||||
name: ObjectName::from(vec![
|
name: ObjectName::from(vec![
|
||||||
Ident::new("information_schema"),
|
Ident::new("information_schema"),
|
||||||
Ident::new("_pg_expandarray")
|
Ident::new("_pg_expandarray")
|
||||||
|
@ -3046,9 +3053,10 @@ fn test_composite_value() {
|
||||||
filter: None,
|
filter: None,
|
||||||
over: None,
|
over: None,
|
||||||
within_group: vec![],
|
within_group: vec![],
|
||||||
}))))
|
})))),
|
||||||
}),
|
access_chain: vec![AccessExpr::Dot(Expr::Identifier(Ident::new("n")))],
|
||||||
select.projection[0]
|
},
|
||||||
|
expr_from_projection(&select.projection[0])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue