Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,13 @@ pub enum Expr {
IsUnknown(Box<Expr>),
/// `IS NOT UNKNOWN` operator
IsNotUnknown(Box<Expr>),
/// `<expr> IS [NOT] DOCUMENT`
IsDocument {
/// Expression being tested.
expr: Box<Expr>,
/// `true` when `NOT` is present.
negated: bool,
},
/// `IS DISTINCT FROM` operator
IsDistinctFrom(Box<Expr>, Box<Expr>),
/// `IS NOT DISTINCT FROM` operator
Expand Down Expand Up @@ -1764,6 +1771,13 @@ impl fmt::Display for Expr {
Expr::IsNotNull(ast) => write!(f, "{ast} IS NOT NULL"),
Expr::IsUnknown(ast) => write!(f, "{ast} IS UNKNOWN"),
Expr::IsNotUnknown(ast) => write!(f, "{ast} IS NOT UNKNOWN"),
Expr::IsDocument { expr, negated } => {
write!(f, "{expr} IS ")?;
if *negated {
write!(f, "NOT ")?;
}
write!(f, "DOCUMENT")
}
Expr::IsJson {
expr,
kind,
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1497,6 +1497,7 @@ impl Spanned for Expr {
Expr::IsNotNull(expr) => expr.span(),
Expr::IsUnknown(expr) => expr.span(),
Expr::IsNotUnknown(expr) => expr.span(),
Expr::IsDocument { expr, negated: _ } => expr.span(),
Expr::IsJson {
expr,
kind: _,
Expand Down
35 changes: 34 additions & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4091,6 +4091,15 @@ impl<'a> Parser<'a> {
Ok(Expr::IsUnknown(Box::new(expr)))
} else if self.parse_keywords(&[Keyword::NOT, Keyword::UNKNOWN]) {
Ok(Expr::IsNotUnknown(Box::new(expr)))
} else if let Some(negated) = if dialect.supports_xml_expressions() {
self.parse_is_document_predicate()
} else {
None
} {
Ok(Expr::IsDocument {
expr: Box::new(expr),
negated,
})
} else if self.parse_keywords(&[Keyword::DISTINCT, Keyword::FROM]) {
// The right operand binds no more loosely than `IS`
// itself, so that e.g. `a IS DISTINCT FROM b AND c`
Expand All @@ -4109,7 +4118,7 @@ impl<'a> Parser<'a> {
Ok(is_normalized)
} else {
self.expected_ref(
"[NOT] NULL | TRUE | FALSE | DISTINCT | [NOT] JSON [VALUE | SCALAR | ARRAY | OBJECT] [WITH | WITHOUT UNIQUE [KEYS]] | [form] NORMALIZED FROM after IS",
"[NOT] NULL | TRUE | FALSE | DISTINCT | [NOT] DOCUMENT | [NOT] JSON [VALUE | SCALAR | ARRAY | OBJECT] [WITH | WITHOUT UNIQUE [KEYS]] | [form] NORMALIZED FROM after IS",
self.peek_token_ref(),
)
}
Expand Down Expand Up @@ -4752,6 +4761,16 @@ impl<'a> Parser<'a> {
}
}

fn parse_unquoted_word(&mut self, expected: &str) -> bool {
match &self.peek_token_ref().token {
Token::Word(w) if w.quote_style.is_none() && w.value.eq_ignore_ascii_case(expected) => {
self.advance_token();
true
}
_ => false,
}
}

#[must_use]
/// Check if the current token is the expected keyword without consuming it.
///
Expand Down Expand Up @@ -12742,6 +12761,20 @@ impl<'a> Parser<'a> {
})
}

fn parse_is_document_predicate(&mut self) -> Option<bool> {
if self.parse_unquoted_word("document") {
return Some(false);
}

let start_index = self.index;
if self.parse_keyword(Keyword::NOT) && self.parse_unquoted_word("document") {
Some(true)
} else {
self.index = start_index;
None
}
}

/// Parse a literal unicode normalization clause
pub fn parse_unicode_is_normalized(&mut self, expr: Expr) -> Result<Expr, ParserError> {
let neg = self.parse_keyword(Keyword::NOT);
Expand Down
12 changes: 12 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19837,6 +19837,18 @@ fn parse_xmlparse() {
.is_err());
}

#[test]
fn parse_xml_is_document_predicate() {
let dialects = all_dialects_where(|d| d.supports_xml_expressions());
dialects.one_statement_parses_to("SELECT '<a/>' IS document", "SELECT '<a/>' IS DOCUMENT");
dialects.verified_stmt("SELECT '<a/>' IS NOT DOCUMENT");

let others = all_dialects_except(|d| d.supports_xml_expressions());
assert!(others
.parse_sql_statements("SELECT '<a/>' IS DOCUMENT")
.is_err());
}

/// Regression test for the 2^N parse-time blowup in `parse_compound_expr` on
/// inputs like `IF a0.a1...aN.#`. The parse is run on a worker thread and the
/// main thread asserts that it reports back within a generous timeout. Post-fix
Expand Down
Loading