diff --git a/src/ast/mod.rs b/src/ast/mod.rs index c83ead544..cda0d4e29 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -187,6 +187,8 @@ pub enum JsonOperator { HashArrow, /// #>> Extracts JSON sub-object at the specified path as text HashLongArrow, + /// : Colon is used by Snowflake (Which is similar to LongArrow) + Colon, } impl fmt::Display for JsonOperator { @@ -204,6 +206,9 @@ impl fmt::Display for JsonOperator { JsonOperator::HashLongArrow => { write!(f, "#>>") } + JsonOperator::Colon => { + write!(f, ":") + } } } } @@ -757,7 +762,11 @@ impl fmt::Display for Expr { operator, right, } => { - write!(f, "{} {} {}", left, operator, right) + if operator == &JsonOperator::Colon { + write!(f, "{}{}{}", left, operator, right) + } else { + write!(f, "{} {} {}", left, operator, right) + } } Expr::CompositeAccess { expr, key } => { write!(f, "{}.{}", expr, key) diff --git a/src/ast/value.rs b/src/ast/value.rs index 3861ab008..2162dd219 100644 --- a/src/ast/value.rs +++ b/src/ast/value.rs @@ -45,6 +45,8 @@ pub enum Value { Null, /// `?` or `$` Prepared statement arg placeholder Placeholder(String), + /// Add support of snowflake field:key - key should be a value + UnQuotedString(String), } impl fmt::Display for Value { @@ -59,6 +61,7 @@ impl fmt::Display for Value { Value::Boolean(v) => write!(f, "{}", v), Value::Null => write!(f, "NULL"), Value::Placeholder(v) => write!(f, "{}", v), + Value::UnQuotedString(v) => write!(f, "{}", v), } } } diff --git a/src/parser.rs b/src/parser.rs index 108427889..5621e67c6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1426,6 +1426,12 @@ impl<'a> Parser<'a> { return self.parse_array_index(expr); } self.parse_map_access(expr) + } else if Token::Colon == tok { + Ok(Expr::JsonAccess { + left: Box::new(expr), + operator: JsonOperator::Colon, + right: Box::new(Expr::Value(self.parse_value()?)), + }) } else if Token::Arrow == tok || Token::LongArrow == tok || Token::HashArrow == tok @@ -1627,6 +1633,7 @@ impl<'a> Parser<'a> { Token::Plus | Token::Minus => Ok(Self::PLUS_MINUS_PREC), Token::Mul | Token::Div | Token::Mod | Token::StringConcat => Ok(40), Token::DoubleColon => Ok(50), + Token::Colon => Ok(50), Token::ExclamationMark => Ok(50), Token::LBracket | Token::LongArrow @@ -3441,6 +3448,10 @@ impl<'a> Parser<'a> { Some('\'') => Ok(Value::SingleQuotedString(w.value)), _ => self.expected("A value?", Token::Word(w))?, }, + // Case when Snowflake Semi-structured data like key:value + Keyword::NoKeyword | Keyword::LOCATION if dialect_of!(self is SnowflakeDialect | GenericDialect) => { + Ok(Value::UnQuotedString(w.value)) + } _ => self.expected("a concrete value", Token::Word(w)), }, // The call to n.parse() returns a bigdecimal when the diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 7c089a935..64fff62f9 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -143,6 +143,22 @@ fn test_single_table_in_parenthesis_with_alias() { ); } +#[test] +fn parse_json_using_colon() { + let sql = "SELECT a:b FROM t"; + let select = snowflake().verified_only_select(sql); + assert_eq!( + SelectItem::UnnamedExpr(Expr::JsonAccess { + left: Box::new(Expr::Identifier(Ident::new("a"))), + operator: JsonOperator::Colon, + right: Box::new(Expr::Value(Value::UnQuotedString("b".to_string()))), + }), + select.projection[0] + ); + + snowflake().one_statement_parses_to("SELECT a:b::int FROM t", "SELECT CAST(a:b AS INT) FROM t"); +} + fn snowflake() -> TestedDialects { TestedDialects { dialects: vec![Box::new(SnowflakeDialect {})],