mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-07 17:04:59 +00:00
Snowflake: Add support for CONNECT_BY_ROOT
(#1780)
This commit is contained in:
parent
4e392f5c07
commit
2b5bdcef0b
8 changed files with 107 additions and 20 deletions
|
@ -930,12 +930,14 @@ pub enum Expr {
|
||||||
Nested(Box<Expr>),
|
Nested(Box<Expr>),
|
||||||
/// A literal value, such as string, number, date or NULL
|
/// A literal value, such as string, number, date or NULL
|
||||||
Value(ValueWithSpan),
|
Value(ValueWithSpan),
|
||||||
|
/// Prefixed expression, e.g. introducer strings, projection prefix
|
||||||
/// <https://dev.mysql.com/doc/refman/8.0/en/charset-introducer.html>
|
/// <https://dev.mysql.com/doc/refman/8.0/en/charset-introducer.html>
|
||||||
IntroducedString {
|
/// <https://docs.snowflake.com/en/sql-reference/constructs/connect-by>
|
||||||
introducer: String,
|
Prefixed {
|
||||||
|
prefix: Ident,
|
||||||
/// The value of the constant.
|
/// The value of the constant.
|
||||||
/// Hint: you can unwrap the string value using `value.into_string()`.
|
/// Hint: you can unwrap the string value using `value.into_string()`.
|
||||||
value: Value,
|
value: Box<Expr>,
|
||||||
},
|
},
|
||||||
/// A constant of form `<data_type> 'value'`.
|
/// A constant of form `<data_type> 'value'`.
|
||||||
/// This can represent ANSI SQL `DATE`, `TIME`, and `TIMESTAMP` literals (such as `DATE '2020-01-01'`),
|
/// This can represent ANSI SQL `DATE`, `TIME`, and `TIMESTAMP` literals (such as `DATE '2020-01-01'`),
|
||||||
|
@ -1655,7 +1657,7 @@ impl fmt::Display for Expr {
|
||||||
Expr::Collate { expr, collation } => write!(f, "{expr} COLLATE {collation}"),
|
Expr::Collate { expr, collation } => write!(f, "{expr} COLLATE {collation}"),
|
||||||
Expr::Nested(ast) => write!(f, "({ast})"),
|
Expr::Nested(ast) => write!(f, "({ast})"),
|
||||||
Expr::Value(v) => write!(f, "{v}"),
|
Expr::Value(v) => write!(f, "{v}"),
|
||||||
Expr::IntroducedString { introducer, value } => write!(f, "{introducer} {value}"),
|
Expr::Prefixed { prefix, value } => write!(f, "{prefix} {value}"),
|
||||||
Expr::TypedString { data_type, value } => {
|
Expr::TypedString { data_type, value } => {
|
||||||
write!(f, "{data_type}")?;
|
write!(f, "{data_type}")?;
|
||||||
write!(f, " {value}")
|
write!(f, " {value}")
|
||||||
|
|
|
@ -1543,7 +1543,7 @@ impl Spanned for Expr {
|
||||||
.map(|items| union_spans(items.iter().map(|i| i.span()))),
|
.map(|items| union_spans(items.iter().map(|i| i.span()))),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expr::IntroducedString { value, .. } => value.span(),
|
Expr::Prefixed { value, .. } => value.span(),
|
||||||
Expr::Case {
|
Expr::Case {
|
||||||
operand,
|
operand,
|
||||||
conditions,
|
conditions,
|
||||||
|
|
|
@ -888,6 +888,12 @@ pub trait Dialect: Debug + Any {
|
||||||
keywords::RESERVED_FOR_TABLE_FACTOR
|
keywords::RESERVED_FOR_TABLE_FACTOR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns reserved keywords that may prefix a select item expression
|
||||||
|
/// e.g. `SELECT CONNECT_BY_ROOT name FROM Tbl2` (Snowflake)
|
||||||
|
fn get_reserved_keywords_for_select_item_operator(&self) -> &[Keyword] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if this dialect supports the `TABLESAMPLE` option
|
/// Returns true if this dialect supports the `TABLESAMPLE` option
|
||||||
/// before the table alias option. For example:
|
/// before the table alias option. For example:
|
||||||
///
|
///
|
||||||
|
|
|
@ -44,6 +44,7 @@ use alloc::{format, vec};
|
||||||
use super::keywords::RESERVED_FOR_IDENTIFIER;
|
use super::keywords::RESERVED_FOR_IDENTIFIER;
|
||||||
use sqlparser::ast::StorageSerializationPolicy;
|
use sqlparser::ast::StorageSerializationPolicy;
|
||||||
|
|
||||||
|
const RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR: [Keyword; 1] = [Keyword::CONNECT_BY_ROOT];
|
||||||
/// A [`Dialect`] for [Snowflake](https://www.snowflake.com/)
|
/// A [`Dialect`] for [Snowflake](https://www.snowflake.com/)
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct SnowflakeDialect;
|
pub struct SnowflakeDialect;
|
||||||
|
@ -346,6 +347,11 @@ impl Dialect for SnowflakeDialect {
|
||||||
fn supports_group_by_expr(&self) -> bool {
|
fn supports_group_by_expr(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See: <https://docs.snowflake.com/en/sql-reference/constructs/connect-by>
|
||||||
|
fn get_reserved_keywords_for_select_item_operator(&self) -> &[Keyword] {
|
||||||
|
&RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result<Statement, ParserError> {
|
fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result<Statement, ParserError> {
|
||||||
|
|
|
@ -207,6 +207,7 @@ define_keywords!(
|
||||||
CONNECT,
|
CONNECT,
|
||||||
CONNECTION,
|
CONNECTION,
|
||||||
CONNECTOR,
|
CONNECTOR,
|
||||||
|
CONNECT_BY_ROOT,
|
||||||
CONSTRAINT,
|
CONSTRAINT,
|
||||||
CONTAINS,
|
CONTAINS,
|
||||||
CONTINUE,
|
CONTINUE,
|
||||||
|
|
|
@ -1388,9 +1388,9 @@ impl<'a> Parser<'a> {
|
||||||
| Token::HexStringLiteral(_)
|
| Token::HexStringLiteral(_)
|
||||||
if w.value.starts_with('_') =>
|
if w.value.starts_with('_') =>
|
||||||
{
|
{
|
||||||
Ok(Expr::IntroducedString {
|
Ok(Expr::Prefixed {
|
||||||
introducer: w.value.clone(),
|
prefix: w.clone().into_ident(w_span),
|
||||||
value: self.parse_introduced_string_value()?,
|
value: self.parse_introduced_string_expr()?.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 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
|
||||||
|
@ -1399,9 +1399,9 @@ impl<'a> Parser<'a> {
|
||||||
| Token::HexStringLiteral(_)
|
| Token::HexStringLiteral(_)
|
||||||
if w.value.starts_with('_') =>
|
if w.value.starts_with('_') =>
|
||||||
{
|
{
|
||||||
Ok(Expr::IntroducedString {
|
Ok(Expr::Prefixed {
|
||||||
introducer: w.value.clone(),
|
prefix: w.clone().into_ident(w_span),
|
||||||
value: self.parse_introduced_string_value()?,
|
value: self.parse_introduced_string_expr()?.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Token::Arrow if self.dialect.supports_lambda_functions() => {
|
Token::Arrow if self.dialect.supports_lambda_functions() => {
|
||||||
|
@ -9035,13 +9035,19 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_introduced_string_value(&mut self) -> Result<Value, ParserError> {
|
fn parse_introduced_string_expr(&mut self) -> Result<Expr, ParserError> {
|
||||||
let next_token = self.next_token();
|
let next_token = self.next_token();
|
||||||
let span = next_token.span;
|
let span = next_token.span;
|
||||||
match next_token.token {
|
match next_token.token {
|
||||||
Token::SingleQuotedString(ref s) => Ok(Value::SingleQuotedString(s.to_string())),
|
Token::SingleQuotedString(ref s) => Ok(Expr::Value(
|
||||||
Token::DoubleQuotedString(ref s) => Ok(Value::DoubleQuotedString(s.to_string())),
|
Value::SingleQuotedString(s.to_string()).with_span(span),
|
||||||
Token::HexStringLiteral(ref s) => Ok(Value::HexStringLiteral(s.to_string())),
|
)),
|
||||||
|
Token::DoubleQuotedString(ref s) => Ok(Expr::Value(
|
||||||
|
Value::DoubleQuotedString(s.to_string()).with_span(span),
|
||||||
|
)),
|
||||||
|
Token::HexStringLiteral(ref s) => Ok(Expr::Value(
|
||||||
|
Value::HexStringLiteral(s.to_string()).with_span(span),
|
||||||
|
)),
|
||||||
unexpected => self.expected(
|
unexpected => self.expected(
|
||||||
"a string value",
|
"a string value",
|
||||||
TokenWithSpan {
|
TokenWithSpan {
|
||||||
|
@ -13968,6 +13974,13 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
/// Parse a comma-delimited list of projections after SELECT
|
/// Parse a comma-delimited list of projections after SELECT
|
||||||
pub fn parse_select_item(&mut self) -> Result<SelectItem, ParserError> {
|
pub fn parse_select_item(&mut self) -> Result<SelectItem, ParserError> {
|
||||||
|
let prefix = self
|
||||||
|
.parse_one_of_keywords(
|
||||||
|
self.dialect
|
||||||
|
.get_reserved_keywords_for_select_item_operator(),
|
||||||
|
)
|
||||||
|
.map(|keyword| Ident::new(format!("{:?}", keyword)));
|
||||||
|
|
||||||
match self.parse_wildcard_expr()? {
|
match self.parse_wildcard_expr()? {
|
||||||
Expr::QualifiedWildcard(prefix, token) => Ok(SelectItem::QualifiedWildcard(
|
Expr::QualifiedWildcard(prefix, token) => Ok(SelectItem::QualifiedWildcard(
|
||||||
SelectItemQualifiedWildcardKind::ObjectName(prefix),
|
SelectItemQualifiedWildcardKind::ObjectName(prefix),
|
||||||
|
@ -14012,8 +14025,11 @@ impl<'a> Parser<'a> {
|
||||||
expr => self
|
expr => self
|
||||||
.maybe_parse_select_item_alias()
|
.maybe_parse_select_item_alias()
|
||||||
.map(|alias| match alias {
|
.map(|alias| match alias {
|
||||||
Some(alias) => SelectItem::ExprWithAlias { expr, alias },
|
Some(alias) => SelectItem::ExprWithAlias {
|
||||||
None => SelectItem::UnnamedExpr(expr),
|
expr: maybe_prefixed_expr(expr, prefix),
|
||||||
|
alias,
|
||||||
|
},
|
||||||
|
None => SelectItem::UnnamedExpr(maybe_prefixed_expr(expr, prefix)),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15375,6 +15391,17 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn maybe_prefixed_expr(expr: Expr, prefix: Option<Ident>) -> Expr {
|
||||||
|
if let Some(prefix) = prefix {
|
||||||
|
Expr::Prefixed {
|
||||||
|
prefix,
|
||||||
|
value: Box::new(expr),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Word {
|
impl Word {
|
||||||
#[deprecated(since = "0.54.0", note = "please use `into_ident` instead")]
|
#[deprecated(since = "0.54.0", note = "please use `into_ident` instead")]
|
||||||
pub fn to_ident(&self, span: Span) -> Ident {
|
pub fn to_ident(&self, span: Span) -> Ident {
|
||||||
|
|
|
@ -3020,9 +3020,12 @@ fn parse_hex_string_introducer() {
|
||||||
distinct: None,
|
distinct: None,
|
||||||
top: None,
|
top: None,
|
||||||
top_before_distinct: false,
|
top_before_distinct: false,
|
||||||
projection: vec![SelectItem::UnnamedExpr(Expr::IntroducedString {
|
projection: vec![SelectItem::UnnamedExpr(Expr::Prefixed {
|
||||||
introducer: "_latin1".to_string(),
|
prefix: Ident::from("_latin1"),
|
||||||
value: Value::HexStringLiteral("4D7953514C".to_string())
|
value: Expr::Value(
|
||||||
|
Value::HexStringLiteral("4D7953514C".to_string()).with_empty_span()
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
})],
|
})],
|
||||||
from: vec![],
|
from: vec![],
|
||||||
lateral_views: vec![],
|
lateral_views: vec![],
|
||||||
|
|
|
@ -3983,3 +3983,45 @@ fn test_nested_join_without_parentheses() {
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_connect_by_root_operator() {
|
||||||
|
let sql = "SELECT CONNECT_BY_ROOT name AS root_name FROM Tbl1";
|
||||||
|
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::Query(query) => {
|
||||||
|
assert_eq!(
|
||||||
|
query.body.as_select().unwrap().projection[0],
|
||||||
|
SelectItem::ExprWithAlias {
|
||||||
|
expr: Expr::Prefixed {
|
||||||
|
prefix: Ident::new("CONNECT_BY_ROOT"),
|
||||||
|
value: Box::new(Expr::Identifier(Ident::new("name")))
|
||||||
|
},
|
||||||
|
alias: Ident::new("root_name"),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let sql = "SELECT CONNECT_BY_ROOT name FROM Tbl2";
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::Query(query) => {
|
||||||
|
assert_eq!(
|
||||||
|
query.body.as_select().unwrap().projection[0],
|
||||||
|
SelectItem::UnnamedExpr(Expr::Prefixed {
|
||||||
|
prefix: Ident::new("CONNECT_BY_ROOT"),
|
||||||
|
value: Box::new(Expr::Identifier(Ident::new("name")))
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let sql = "SELECT CONNECT_BY_ROOT FROM Tbl2";
|
||||||
|
let res = snowflake().parse_sql_statements(sql);
|
||||||
|
assert_eq!(
|
||||||
|
res.unwrap_err().to_string(),
|
||||||
|
"sql parser error: Expected an expression, found: FROM"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue