Skip to content

Commit 777cee2

Browse files
Support IS [NOT] JSON predicate in all dialects
1 parent 09827e1 commit 777cee2

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,
@@ -8437,6 +8467,52 @@ pub enum AnalyzeFormat {
84378467
TREE,
84388468
}
84398469

8470+
/// Optional type constraint for `IS JSON`.
8471+
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
8472+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8473+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
8474+
pub enum JsonPredicateType {
8475+
/// `VALUE` form.
8476+
Value,
8477+
/// `SCALAR` form.
8478+
Scalar,
8479+
/// `ARRAY` form.
8480+
Array,
8481+
/// `OBJECT` form.
8482+
Object,
8483+
}
8484+
8485+
impl fmt::Display for JsonPredicateType {
8486+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8487+
match self {
8488+
JsonPredicateType::Value => write!(f, "VALUE"),
8489+
JsonPredicateType::Scalar => write!(f, "SCALAR"),
8490+
JsonPredicateType::Array => write!(f, "ARRAY"),
8491+
JsonPredicateType::Object => write!(f, "OBJECT"),
8492+
}
8493+
}
8494+
}
8495+
8496+
/// Optional duplicate-key handling for `IS JSON`.
8497+
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
8498+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8499+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
8500+
pub enum JsonKeyUniqueness {
8501+
/// `WITH UNIQUE KEYS` form.
8502+
WithUniqueKeys,
8503+
/// `WITHOUT UNIQUE KEYS` form.
8504+
WithoutUniqueKeys,
8505+
}
8506+
8507+
impl fmt::Display for JsonKeyUniqueness {
8508+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8509+
match self {
8510+
JsonKeyUniqueness::WithUniqueKeys => write!(f, "WITH UNIQUE KEYS"),
8511+
JsonKeyUniqueness::WithoutUniqueKeys => write!(f, "WITHOUT UNIQUE KEYS"),
8512+
}
8513+
}
8514+
}
8515+
84408516
impl fmt::Display for AnalyzeFormat {
84418517
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
84428518
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
}
@@ -8657,6 +8660,7 @@ impl<'a> Parser<'a> {
86578660
char: self.parse_identifier()?,
86588661
});
86598662
}
8663+
Some(Keyword::NULL) => break,
86608664
_ => {
86618665
break;
86628666
}
@@ -12641,6 +12645,43 @@ impl<'a> Parser<'a> {
1264112645
}
1264212646
}
1264312647

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

tests/sqlparser_common.rs

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

10999-
let sql = "SELECT f from foo where field is 0";
11000-
let res = parse_sql_statements(sql);
11001-
assert_eq!(
11002-
ParserError::ParserError(
11003-
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [form] NORMALIZED FROM after IS, found: 0"
11004-
.to_string()
11005-
),
11006-
res.unwrap_err()
11007-
);
10999+
for sql in [
11000+
"SELECT f from foo where field is 0",
11001+
"SELECT s, s IS XYZ NORMALIZED FROM foo",
11002+
"SELECT s, s IS NFKC FROM foo",
11003+
"SELECT s, s IS TRIM(' NFKC ') FROM foo",
11004+
] {
11005+
assert!(
11006+
parse_sql_statements(sql).is_err(),
11007+
"expected a parse failure for `{sql}`"
11008+
);
11009+
}
11010+
}
1100811011

11009-
let sql = "SELECT s, s IS XYZ NORMALIZED FROM foo";
11010-
let res = parse_sql_statements(sql);
11011-
assert_eq!(
11012-
ParserError::ParserError(
11013-
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [form] NORMALIZED FROM after IS, found: XYZ"
11014-
.to_string()
11015-
),
11016-
res.unwrap_err()
11017-
);
11012+
#[test]
11013+
fn parse_is_json_predicate() {
11014+
use self::Expr::*;
1101811015

11019-
let sql = "SELECT s, s IS NFKC FROM foo";
11020-
let res = parse_sql_statements(sql);
11016+
// Assert the full AST once for a case that exercises every field.
11017+
let sql = "a IS NOT JSON OBJECT WITHOUT UNIQUE KEYS";
1102111018
assert_eq!(
11022-
ParserError::ParserError(
11023-
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [form] NORMALIZED FROM after IS, found: FROM"
11024-
.to_string()
11025-
),
11026-
res.unwrap_err()
11019+
IsJson {
11020+
expr: Box::new(Identifier(Ident::new("a"))),
11021+
kind: Some(JsonPredicateType::Object),
11022+
unique_keys: Some(JsonKeyUniqueness::WithoutUniqueKeys),
11023+
negated: true,
11024+
},
11025+
verified_expr(sql)
1102711026
);
1102811027

11029-
let sql = "SELECT s, s IS TRIM(' NFKC ') FROM foo";
11030-
let res = parse_sql_statements(sql);
11031-
assert_eq!(
11032-
ParserError::ParserError(
11033-
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [form] NORMALIZED FROM after IS, found: TRIM"
11034-
.to_string()
11035-
),
11036-
res.unwrap_err()
11028+
// The remaining forms only need to round-trip.
11029+
verified_expr("a IS JSON");
11030+
verified_expr("a IS NOT JSON");
11031+
verified_expr("a IS JSON VALUE");
11032+
verified_expr("a IS JSON SCALAR");
11033+
verified_expr("a IS JSON ARRAY");
11034+
verified_expr("a IS JSON OBJECT");
11035+
verified_expr("a IS JSON WITH UNIQUE KEYS");
11036+
verified_expr("a IS JSON WITHOUT UNIQUE KEYS");
11037+
11038+
all_dialects().expr_parses_to("a IS JSON WITH UNIQUE", "a IS JSON WITH UNIQUE KEYS");
11039+
all_dialects().expr_parses_to("a IS JSON WITHOUT UNIQUE", "a IS JSON WITHOUT UNIQUE KEYS");
11040+
11041+
assert_matches!(
11042+
verified_expr("NOT a IS JSON"),
11043+
Expr::UnaryOp {
11044+
op: UnaryOperator::Not,
11045+
expr
11046+
} if matches!(&*expr, Expr::IsJson { .. })
1103711047
);
1103811048
}
1103911049

11050+
#[test]
11051+
fn parse_is_json_predicate_invalid() {
11052+
let dialects = all_dialects();
11053+
11054+
let invalid = [
11055+
"SELECT * FROM t WHERE a IS JSON WITH FROM",
11056+
"SELECT * FROM t WHERE a IS JSON WITH KEYS",
11057+
"SELECT * FROM t WHERE a IS JSON WITHOUT FROM",
11058+
"SELECT * FROM t WHERE a IS JSON WITHOUT KEYS",
11059+
"SELECT * FROM t WHERE a IS NOT JSON WITH FROM",
11060+
"SELECT * FROM t WHERE a IS JSON VALUE ARRAY",
11061+
"SELECT * FROM t WHERE a IS JSON OBJECT VALUE",
11062+
"SELECT * FROM t WHERE a IS JSON WITH UNIQUE EXTRA",
11063+
"SELECT * FROM t WHERE a IS JSON WITH UNIQUE KEYS EXTRA",
11064+
"SELECT * FROM t WHERE a IS JSON WITHOUT UNIQUE EXTRA",
11065+
"SELECT * FROM t WHERE a IS JSON WITHOUT UNIQUE KEYS EXTRA",
11066+
"SELECT * FROM t WHERE a IS JSON WITH UNIQUE KEYS WITH UNIQUE KEYS",
11067+
"SELECT * FROM t WHERE a IS JSON WITHOUT UNIQUE KEYS WITHOUT UNIQUE KEYS",
11068+
];
11069+
11070+
for sql in invalid {
11071+
assert!(
11072+
dialects.parse_sql_statements(sql).is_err(),
11073+
"expected a parse failure for `{sql}`"
11074+
);
11075+
}
11076+
}
11077+
1104011078
#[test]
1104111079
fn parse_discard() {
1104211080
let sql = "DISCARD ALL";

0 commit comments

Comments
 (0)