Skip to content

Commit 797fc9b

Browse files
Adding support for IS JSON & IS NOT JSON (#2256)
1 parent 952983b commit 797fc9b

5 files changed

Lines changed: 204 additions & 42 deletions

File tree

src/ast/mod.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,17 @@ pub enum Expr {
948948
IsDistinctFrom(Box<Expr>, Box<Expr>),
949949
/// `IS NOT DISTINCT FROM` operator
950950
IsNotDistinctFrom(Box<Expr>, Box<Expr>),
951+
/// `<expr> IS [NOT] JSON [VALUE|SCALAR|ARRAY|OBJECT] [WITH|WITHOUT UNIQUE [KEYS]]`
952+
IsJson {
953+
/// Expression being tested.
954+
expr: Box<Expr>,
955+
/// Optional JSON shape constraint.
956+
kind: Option<JsonPredicateType>,
957+
/// Optional duplicate-key handling constraint for JSON objects.
958+
unique_keys: Option<JsonKeyUniqueness>,
959+
/// `true` when `NOT` is present.
960+
negated: bool,
961+
},
951962
/// `<expr> IS [ NOT ] [ form ] NORMALIZED`
952963
IsNormalized {
953964
/// Expression being tested.
@@ -1750,6 +1761,25 @@ impl fmt::Display for Expr {
17501761
Expr::IsNotNull(ast) => write!(f, "{ast} IS NOT NULL"),
17511762
Expr::IsUnknown(ast) => write!(f, "{ast} IS UNKNOWN"),
17521763
Expr::IsNotUnknown(ast) => write!(f, "{ast} IS NOT UNKNOWN"),
1764+
Expr::IsJson {
1765+
expr,
1766+
kind,
1767+
unique_keys,
1768+
negated,
1769+
} => {
1770+
write!(f, "{expr} IS ")?;
1771+
if *negated {
1772+
write!(f, "NOT ")?;
1773+
}
1774+
write!(f, "JSON")?;
1775+
if let Some(kind) = kind {
1776+
write!(f, " {kind}")?;
1777+
}
1778+
if let Some(unique_keys) = unique_keys {
1779+
write!(f, " {unique_keys}")?;
1780+
}
1781+
Ok(())
1782+
}
17531783
Expr::InList {
17541784
expr,
17551785
list,
@@ -8441,6 +8471,52 @@ pub enum AnalyzeFormat {
84418471
TREE,
84428472
}
84438473

8474+
/// Optional type constraint for `IS JSON`.
8475+
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
8476+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8477+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
8478+
pub enum JsonPredicateType {
8479+
/// `VALUE` form.
8480+
Value,
8481+
/// `SCALAR` form.
8482+
Scalar,
8483+
/// `ARRAY` form.
8484+
Array,
8485+
/// `OBJECT` form.
8486+
Object,
8487+
}
8488+
8489+
impl fmt::Display for JsonPredicateType {
8490+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8491+
match self {
8492+
JsonPredicateType::Value => write!(f, "VALUE"),
8493+
JsonPredicateType::Scalar => write!(f, "SCALAR"),
8494+
JsonPredicateType::Array => write!(f, "ARRAY"),
8495+
JsonPredicateType::Object => write!(f, "OBJECT"),
8496+
}
8497+
}
8498+
}
8499+
8500+
/// Optional duplicate-key handling for `IS JSON`.
8501+
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
8502+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8503+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
8504+
pub enum JsonKeyUniqueness {
8505+
/// `WITH UNIQUE KEYS` form.
8506+
WithUniqueKeys,
8507+
/// `WITHOUT UNIQUE KEYS` form.
8508+
WithoutUniqueKeys,
8509+
}
8510+
8511+
impl fmt::Display for JsonKeyUniqueness {
8512+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8513+
match self {
8514+
JsonKeyUniqueness::WithUniqueKeys => write!(f, "WITH UNIQUE KEYS"),
8515+
JsonKeyUniqueness::WithoutUniqueKeys => write!(f, "WITHOUT UNIQUE KEYS"),
8516+
}
8517+
}
8518+
}
8519+
84448520
impl fmt::Display for AnalyzeFormat {
84458521
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
84468522
f.write_str(match self {

src/ast/spans.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,6 +1496,12 @@ impl Spanned for Expr {
14961496
Expr::IsNotNull(expr) => expr.span(),
14971497
Expr::IsUnknown(expr) => expr.span(),
14981498
Expr::IsNotUnknown(expr) => expr.span(),
1499+
Expr::IsJson {
1500+
expr,
1501+
kind: _,
1502+
unique_keys: _,
1503+
negated: _,
1504+
} => expr.span(),
14991505
Expr::IsDistinctFrom(lhs, rhs) => lhs.span().union(&rhs.span()),
15001506
Expr::IsNotDistinctFrom(lhs, rhs) => lhs.span().union(&rhs.span()),
15011507
Expr::InList {

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,7 @@ define_keywords!(
920920
SAFE_CAST,
921921
SAMPLE,
922922
SAVEPOINT,
923+
SCALAR,
923924
SCHEMA,
924925
SCHEMAS,
925926
SCOPE,

src/parser/mod.rs

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,6 @@ impl<'a> Parser<'a> {
543543

544544
match &self.peek_token_ref().token {
545545
Token::EOF => break,
546-
547546
// end of statement
548547
Token::Word(word)
549548
if expecting_statement_delimiter && word.keyword == Keyword::END =>
@@ -4075,11 +4074,15 @@ impl<'a> Parser<'a> {
40754074
{
40764075
let expr2 = self.parse_expr()?;
40774076
Ok(Expr::IsNotDistinctFrom(Box::new(expr), Box::new(expr2)))
4077+
} else if self.parse_keyword(Keyword::JSON) {
4078+
self.parse_is_json_predicate(expr, false)
4079+
} else if self.parse_keywords(&[Keyword::NOT, Keyword::JSON]) {
4080+
self.parse_is_json_predicate(expr, true)
40784081
} else if let Ok(is_normalized) = self.parse_unicode_is_normalized(expr) {
40794082
Ok(is_normalized)
40804083
} else {
40814084
self.expected_ref(
4082-
"[NOT] NULL | TRUE | FALSE | DISTINCT | [form] NORMALIZED FROM after IS",
4085+
"[NOT] NULL | TRUE | FALSE | DISTINCT | [NOT] JSON [VALUE | SCALAR | ARRAY | OBJECT] [WITH | WITHOUT UNIQUE [KEYS]] | [form] NORMALIZED FROM after IS",
40834086
self.peek_token_ref(),
40844087
)
40854088
}
@@ -8658,6 +8661,7 @@ impl<'a> Parser<'a> {
86588661
char: self.parse_identifier()?,
86598662
});
86608663
}
8664+
Some(Keyword::NULL) => break,
86618665
_ => {
86628666
break;
86638667
}
@@ -12642,6 +12646,43 @@ impl<'a> Parser<'a> {
1264212646
}
1264312647
}
1264412648

12649+
/// Parse the `IS [NOT] JSON` predicate after `JSON` (and optional `NOT`) was consumed.
12650+
fn parse_is_json_predicate(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParserError> {
12651+
let kind = match self.parse_one_of_keywords(&[
12652+
Keyword::VALUE,
12653+
Keyword::SCALAR,
12654+
Keyword::ARRAY,
12655+
Keyword::OBJECT,
12656+
]) {
12657+
Some(Keyword::VALUE) => Some(JsonPredicateType::Value),
12658+
Some(Keyword::SCALAR) => Some(JsonPredicateType::Scalar),
12659+
Some(Keyword::ARRAY) => Some(JsonPredicateType::Array),
12660+
Some(Keyword::OBJECT) => Some(JsonPredicateType::Object),
12661+
_ => None,
12662+
};
12663+
12664+
let unique_keys = match self.parse_one_of_keywords(&[Keyword::WITH, Keyword::WITHOUT]) {
12665+
Some(Keyword::WITH) => {
12666+
self.expect_keyword_is(Keyword::UNIQUE)?;
12667+
let _ = self.parse_keyword(Keyword::KEYS);
12668+
Some(JsonKeyUniqueness::WithUniqueKeys)
12669+
}
12670+
Some(Keyword::WITHOUT) => {
12671+
self.expect_keyword_is(Keyword::UNIQUE)?;
12672+
let _ = self.parse_keyword(Keyword::KEYS);
12673+
Some(JsonKeyUniqueness::WithoutUniqueKeys)
12674+
}
12675+
_ => None,
12676+
};
12677+
12678+
Ok(Expr::IsJson {
12679+
expr: Box::new(expr),
12680+
kind,
12681+
unique_keys,
12682+
negated,
12683+
})
12684+
}
12685+
1264512686
/// Parse a literal unicode normalization clause
1264612687
pub fn parse_unicode_is_normalized(&mut self, expr: Expr) -> Result<Expr, ParserError> {
1264712688
let neg = self.parse_keyword(Keyword::NOT);
@@ -21757,13 +21798,13 @@ mod tests {
2175721798
#[test]
2175821799
fn test_parser_error_loc() {
2175921800
let sql = "SELECT this is a syntax error";
21760-
let ast = Parser::parse_sql(&GenericDialect, sql);
21761-
assert_eq!(
21762-
ast,
21763-
Err(ParserError::ParserError(
21764-
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [form] NORMALIZED FROM after IS, found: a at Line: 1, Column: 16"
21765-
.to_string()
21766-
))
21801+
let ParserError::ParserError(msg) = Parser::parse_sql(&GenericDialect, sql).unwrap_err()
21802+
else {
21803+
panic!("expected ParserError::ParserError");
21804+
};
21805+
assert!(
21806+
msg.ends_with("found: a at Line: 1, Column: 16"),
21807+
"unexpected error message: {msg}"
2176721808
);
2176821809
}
2176921810

tests/sqlparser_common.rs

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11015,47 +11015,85 @@ fn parse_is_boolean() {
1101511015
verified_stmt("SELECT f FROM foo WHERE field IS UNKNOWN");
1101611016
verified_stmt("SELECT f FROM foo WHERE field IS NOT UNKNOWN");
1101711017

11018-
let sql = "SELECT f from foo where field is 0";
11019-
let res = parse_sql_statements(sql);
11020-
assert_eq!(
11021-
ParserError::ParserError(
11022-
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [form] NORMALIZED FROM after IS, found: 0"
11023-
.to_string()
11024-
),
11025-
res.unwrap_err()
11026-
);
11018+
for sql in [
11019+
"SELECT f from foo where field is 0",
11020+
"SELECT s, s IS XYZ NORMALIZED FROM foo",
11021+
"SELECT s, s IS NFKC FROM foo",
11022+
"SELECT s, s IS TRIM(' NFKC ') FROM foo",
11023+
] {
11024+
assert!(
11025+
parse_sql_statements(sql).is_err(),
11026+
"expected a parse failure for `{sql}`"
11027+
);
11028+
}
11029+
}
1102711030

11028-
let sql = "SELECT s, s IS XYZ NORMALIZED FROM foo";
11029-
let res = parse_sql_statements(sql);
11030-
assert_eq!(
11031-
ParserError::ParserError(
11032-
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [form] NORMALIZED FROM after IS, found: XYZ"
11033-
.to_string()
11034-
),
11035-
res.unwrap_err()
11036-
);
11031+
#[test]
11032+
fn parse_is_json_predicate() {
11033+
use self::Expr::*;
1103711034

11038-
let sql = "SELECT s, s IS NFKC FROM foo";
11039-
let res = parse_sql_statements(sql);
11035+
// Assert the full AST once for a case that exercises every field.
11036+
let sql = "a IS NOT JSON OBJECT WITHOUT UNIQUE KEYS";
1104011037
assert_eq!(
11041-
ParserError::ParserError(
11042-
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [form] NORMALIZED FROM after IS, found: FROM"
11043-
.to_string()
11044-
),
11045-
res.unwrap_err()
11038+
IsJson {
11039+
expr: Box::new(Identifier(Ident::new("a"))),
11040+
kind: Some(JsonPredicateType::Object),
11041+
unique_keys: Some(JsonKeyUniqueness::WithoutUniqueKeys),
11042+
negated: true,
11043+
},
11044+
verified_expr(sql)
1104611045
);
1104711046

11048-
let sql = "SELECT s, s IS TRIM(' NFKC ') FROM foo";
11049-
let res = parse_sql_statements(sql);
11050-
assert_eq!(
11051-
ParserError::ParserError(
11052-
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [form] NORMALIZED FROM after IS, found: TRIM"
11053-
.to_string()
11054-
),
11055-
res.unwrap_err()
11047+
// The remaining forms only need to round-trip.
11048+
verified_expr("a IS JSON");
11049+
verified_expr("a IS NOT JSON");
11050+
verified_expr("a IS JSON VALUE");
11051+
verified_expr("a IS JSON SCALAR");
11052+
verified_expr("a IS JSON ARRAY");
11053+
verified_expr("a IS JSON OBJECT");
11054+
verified_expr("a IS JSON WITH UNIQUE KEYS");
11055+
verified_expr("a IS JSON WITHOUT UNIQUE KEYS");
11056+
11057+
all_dialects().expr_parses_to("a IS JSON WITH UNIQUE", "a IS JSON WITH UNIQUE KEYS");
11058+
all_dialects().expr_parses_to("a IS JSON WITHOUT UNIQUE", "a IS JSON WITHOUT UNIQUE KEYS");
11059+
11060+
assert_matches!(
11061+
verified_expr("NOT a IS JSON"),
11062+
Expr::UnaryOp {
11063+
op: UnaryOperator::Not,
11064+
expr
11065+
} if matches!(&*expr, Expr::IsJson { .. })
1105611066
);
1105711067
}
1105811068

11069+
#[test]
11070+
fn parse_is_json_predicate_invalid() {
11071+
let dialects = all_dialects();
11072+
11073+
let invalid = [
11074+
"SELECT * FROM t WHERE a IS JSON WITH FROM",
11075+
"SELECT * FROM t WHERE a IS JSON WITH KEYS",
11076+
"SELECT * FROM t WHERE a IS JSON WITHOUT FROM",
11077+
"SELECT * FROM t WHERE a IS JSON WITHOUT KEYS",
11078+
"SELECT * FROM t WHERE a IS NOT JSON WITH FROM",
11079+
"SELECT * FROM t WHERE a IS JSON VALUE ARRAY",
11080+
"SELECT * FROM t WHERE a IS JSON OBJECT VALUE",
11081+
"SELECT * FROM t WHERE a IS JSON WITH UNIQUE EXTRA",
11082+
"SELECT * FROM t WHERE a IS JSON WITH UNIQUE KEYS EXTRA",
11083+
"SELECT * FROM t WHERE a IS JSON WITHOUT UNIQUE EXTRA",
11084+
"SELECT * FROM t WHERE a IS JSON WITHOUT UNIQUE KEYS EXTRA",
11085+
"SELECT * FROM t WHERE a IS JSON WITH UNIQUE KEYS WITH UNIQUE KEYS",
11086+
"SELECT * FROM t WHERE a IS JSON WITHOUT UNIQUE KEYS WITHOUT UNIQUE KEYS",
11087+
];
11088+
11089+
for sql in invalid {
11090+
assert!(
11091+
dialects.parse_sql_statements(sql).is_err(),
11092+
"expected a parse failure for `{sql}`"
11093+
);
11094+
}
11095+
}
11096+
1105911097
#[test]
1106011098
fn parse_discard() {
1106111099
let sql = "DISCARD ALL";

0 commit comments

Comments
 (0)