Skip to content

Commit 0adc48e

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

5 files changed

Lines changed: 197 additions & 7 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: 44 additions & 3 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);
@@ -21760,7 +21801,7 @@ mod tests {
2176021801
assert_eq!(
2176121802
ast,
2176221803
Err(ParserError::ParserError(
21763-
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [form] NORMALIZED FROM after IS, found: a at Line: 1, Column: 16"
21804+
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [NOT] JSON [VALUE | SCALAR | ARRAY | OBJECT] [WITH | WITHOUT UNIQUE [KEYS]] | [form] NORMALIZED FROM after IS, found: a at Line: 1, Column: 16"
2176421805
.to_string()
2176521806
))
2176621807
);

tests/sqlparser_common.rs

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11000,7 +11000,7 @@ fn parse_is_boolean() {
1100011000
let res = parse_sql_statements(sql);
1100111001
assert_eq!(
1100211002
ParserError::ParserError(
11003-
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [form] NORMALIZED FROM after IS, found: 0"
11003+
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [NOT] JSON [VALUE | SCALAR | ARRAY | OBJECT] [WITH | WITHOUT UNIQUE [KEYS]] | [form] NORMALIZED FROM after IS, found: 0"
1100411004
.to_string()
1100511005
),
1100611006
res.unwrap_err()
@@ -11010,7 +11010,7 @@ fn parse_is_boolean() {
1101011010
let res = parse_sql_statements(sql);
1101111011
assert_eq!(
1101211012
ParserError::ParserError(
11013-
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [form] NORMALIZED FROM after IS, found: XYZ"
11013+
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [NOT] JSON [VALUE | SCALAR | ARRAY | OBJECT] [WITH | WITHOUT UNIQUE [KEYS]] | [form] NORMALIZED FROM after IS, found: XYZ"
1101411014
.to_string()
1101511015
),
1101611016
res.unwrap_err()
@@ -11020,7 +11020,7 @@ fn parse_is_boolean() {
1102011020
let res = parse_sql_statements(sql);
1102111021
assert_eq!(
1102211022
ParserError::ParserError(
11023-
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [form] NORMALIZED FROM after IS, found: FROM"
11023+
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [NOT] JSON [VALUE | SCALAR | ARRAY | OBJECT] [WITH | WITHOUT UNIQUE [KEYS]] | [form] NORMALIZED FROM after IS, found: FROM"
1102411024
.to_string()
1102511025
),
1102611026
res.unwrap_err()
@@ -11030,13 +11030,79 @@ fn parse_is_boolean() {
1103011030
let res = parse_sql_statements(sql);
1103111031
assert_eq!(
1103211032
ParserError::ParserError(
11033-
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [form] NORMALIZED FROM after IS, found: TRIM"
11033+
"Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [NOT] JSON [VALUE | SCALAR | ARRAY | OBJECT] [WITH | WITHOUT UNIQUE [KEYS]] | [form] NORMALIZED FROM after IS, found: TRIM"
1103411034
.to_string()
1103511035
),
1103611036
res.unwrap_err()
1103711037
);
1103811038
}
1103911039

11040+
#[test]
11041+
fn parse_is_json_predicate() {
11042+
use self::Expr::*;
11043+
11044+
// Assert the full AST once for a case that exercises every field.
11045+
let sql = "a IS NOT JSON OBJECT WITHOUT UNIQUE KEYS";
11046+
assert_eq!(
11047+
IsJson {
11048+
expr: Box::new(Identifier(Ident::new("a"))),
11049+
kind: Some(JsonPredicateType::Object),
11050+
unique_keys: Some(JsonKeyUniqueness::WithoutUniqueKeys),
11051+
negated: true,
11052+
},
11053+
verified_expr(sql)
11054+
);
11055+
11056+
// The remaining forms only need to round-trip.
11057+
verified_expr("a IS JSON");
11058+
verified_expr("a IS NOT JSON");
11059+
verified_expr("a IS JSON VALUE");
11060+
verified_expr("a IS JSON SCALAR");
11061+
verified_expr("a IS JSON ARRAY");
11062+
verified_expr("a IS JSON OBJECT");
11063+
verified_expr("a IS JSON WITH UNIQUE KEYS");
11064+
verified_expr("a IS JSON WITHOUT UNIQUE KEYS");
11065+
11066+
all_dialects().expr_parses_to("a IS JSON WITH UNIQUE", "a IS JSON WITH UNIQUE KEYS");
11067+
all_dialects().expr_parses_to("a IS JSON WITHOUT UNIQUE", "a IS JSON WITHOUT UNIQUE KEYS");
11068+
11069+
assert_matches!(
11070+
verified_expr("NOT a IS JSON"),
11071+
Expr::UnaryOp {
11072+
op: UnaryOperator::Not,
11073+
expr
11074+
} if matches!(&*expr, Expr::IsJson { .. })
11075+
);
11076+
}
11077+
11078+
#[test]
11079+
fn parse_is_json_predicate_invalid() {
11080+
let dialects = all_dialects();
11081+
11082+
let invalid = [
11083+
"SELECT * FROM t WHERE a IS JSON WITH FROM",
11084+
"SELECT * FROM t WHERE a IS JSON WITH KEYS",
11085+
"SELECT * FROM t WHERE a IS JSON WITHOUT FROM",
11086+
"SELECT * FROM t WHERE a IS JSON WITHOUT KEYS",
11087+
"SELECT * FROM t WHERE a IS NOT JSON WITH FROM",
11088+
"SELECT * FROM t WHERE a IS JSON VALUE ARRAY",
11089+
"SELECT * FROM t WHERE a IS JSON OBJECT VALUE",
11090+
"SELECT * FROM t WHERE a IS JSON WITH UNIQUE EXTRA",
11091+
"SELECT * FROM t WHERE a IS JSON WITH UNIQUE KEYS EXTRA",
11092+
"SELECT * FROM t WHERE a IS JSON WITHOUT UNIQUE EXTRA",
11093+
"SELECT * FROM t WHERE a IS JSON WITHOUT UNIQUE KEYS EXTRA",
11094+
"SELECT * FROM t WHERE a IS JSON WITH UNIQUE KEYS WITH UNIQUE KEYS",
11095+
"SELECT * FROM t WHERE a IS JSON WITHOUT UNIQUE KEYS WITHOUT UNIQUE KEYS",
11096+
];
11097+
11098+
for sql in invalid {
11099+
assert!(
11100+
dialects.parse_sql_statements(sql).is_err(),
11101+
"expected a parse failure for `{sql}`"
11102+
);
11103+
}
11104+
}
11105+
1104011106
#[test]
1104111107
fn parse_discard() {
1104211108
let sql = "DISCARD ALL";

0 commit comments

Comments
 (0)