Skip to content

Commit

Permalink
Snowflake: Support semi-structured data (#693)
Browse files Browse the repository at this point in the history
* Support parse json in snowflake

* MR Review

* Lint

* Try to fix right as value

* Fix tests

* Fix lint

* Add generic dialect

* Add support in key location
  • Loading branch information
yuval-illumex committed Nov 2, 2022
1 parent 27c3ec8 commit 93a050e
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 1 deletion.
11 changes: 10 additions & 1 deletion src/ast/mod.rs
Expand Up @@ -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 {
Expand All @@ -204,6 +206,9 @@ impl fmt::Display for JsonOperator {
JsonOperator::HashLongArrow => {
write!(f, "#>>")
}
JsonOperator::Colon => {
write!(f, ":")
}
}
}
}
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions src/ast/value.rs
Expand Up @@ -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 {
Expand All @@ -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),
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/parser.rs
Expand Up @@ -1427,6 +1427,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
Expand Down Expand Up @@ -1628,6 +1634,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
Expand Down Expand Up @@ -3446,6 +3453,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
Expand Down
16 changes: 16 additions & 0 deletions tests/sqlparser_snowflake.rs
Expand Up @@ -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 {})],
Expand Down

0 comments on commit 93a050e

Please sign in to comment.