Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Snowflake: Support semi-structured data #693

Merged
merged 8 commits into from Nov 2, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
10 changes: 10 additions & 0 deletions src/parser.rs
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -3441,6 +3448,9 @@ impl<'a> Parser<'a> {
Some('\'') => Ok(Value::SingleQuotedString(w.value)),
_ => self.expected("A value?", Token::Word(w))?,
},
Keyword::NoKeyword if dialect_of!(self is SnowflakeDialect) => {
yuval-illumex marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yuval-illumex adding the generic here is feasible?

I'd suggest adding it but, as identifiers have the same exact syntax, I don't know how that would work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AugustoFKL added :)

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
19 changes: 19 additions & 0 deletions tests/sqlparser_snowflake.rs
Expand Up @@ -143,6 +143,25 @@ fn test_single_table_in_parenthesis_with_alias() {
);
}

#[test]
fn parse_json_using_colon() {
let sql = "SELECT field:key FROM t";
let select = snowflake().verified_only_select(sql);
assert_eq!(
SelectItem::UnnamedExpr(Expr::JsonAccess {
left: Box::new(Expr::Identifier(Ident::new("field"))),
operator: JsonOperator::Colon,
right: Box::new(Expr::Identifier(Ident::new("key"))),
}),
select.projection[0]
);

snowflake().one_statement_parses_to(
"SELECT field:key::int FROM t",
"SELECT CAST(field:key AS INT) FROM t",
);
}

fn snowflake() -> TestedDialects {
TestedDialects {
dialects: vec![Box::new(SnowflakeDialect {})],
Expand Down