This is an automated email from the ASF dual-hosted git repository.
iffyio pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new be2d2f14 Add support for MySQL MEMBER OF (#1917)
be2d2f14 is described below
commit be2d2f14e740ac753d51609b026dae012b697353
Author: Yoav Cohen <[email protected]>
AuthorDate: Thu Jul 3 18:22:17 2025 +0200
Add support for MySQL MEMBER OF (#1917)
---
src/ast/mod.rs | 24 ++++++++++++++++++++++++
src/ast/spans.rs | 1 +
src/dialect/mod.rs | 2 ++
src/parser/mod.rs | 13 +++++++++++++
tests/sqlparser_mysql.rs | 25 +++++++++++++++++++++++++
5 files changed, 65 insertions(+)
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index 19966d21..9e502260 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -1124,6 +1124,8 @@ pub enum Expr {
///
[Databricks](https://docs.databricks.com/en/sql/language-manual/sql-ref-lambda-functions.html)
/// [DuckDb](https://duckdb.org/docs/sql/functions/lambda.html)
Lambda(LambdaFunction),
+ /// Checks membership of a value in a JSON array
+ MemberOf(MemberOf),
}
impl Expr {
@@ -1912,6 +1914,7 @@ impl fmt::Display for Expr {
}
Expr::Prior(expr) => write!(f, "PRIOR {expr}"),
Expr::Lambda(lambda) => write!(f, "{lambda}"),
+ Expr::MemberOf(member_of) => write!(f, "{member_of}"),
}
}
}
@@ -9831,6 +9834,27 @@ impl fmt::Display for NullInclusion {
}
}
+/// Checks membership of a value in a JSON array
+///
+/// Syntax:
+/// ```sql
+/// <value> MEMBER OF(<array>)
+/// ```
+///
[MySQL](https://dev.mysql.com/doc/refman/8.4/en/json-search-functions.html#operator_member-of)
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct MemberOf {
+ pub value: Box<Expr>,
+ pub array: Box<Expr>,
+}
+
+impl fmt::Display for MemberOf {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{} MEMBER OF({})", self.value, self.array)
+ }
+}
+
#[cfg(test)]
mod tests {
use crate::tokenizer::Location;
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index 26205496..f8ffeb54 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -1624,6 +1624,7 @@ impl Spanned for Expr {
Expr::OuterJoin(expr) => expr.span(),
Expr::Prior(expr) => expr.span(),
Expr::Lambda(_) => Span::empty(),
+ Expr::MemberOf(member_of) =>
member_of.value.span().union(&member_of.array.span()),
}
}
}
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index b214699c..3345380c 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -649,6 +649,7 @@ pub trait Dialect: Debug + Any {
Token::Word(w) if w.keyword == Keyword::REGEXP => Ok(p!(Like)),
Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)),
Token::Word(w) if w.keyword == Keyword::SIMILAR =>
Ok(p!(Like)),
+ Token::Word(w) if w.keyword == Keyword::MEMBER => Ok(p!(Like)),
_ => Ok(self.prec_unknown()),
},
Token::Word(w) if w.keyword == Keyword::IS => Ok(p!(Is)),
@@ -661,6 +662,7 @@ pub trait Dialect: Debug + Any {
Token::Word(w) if w.keyword == Keyword::REGEXP => Ok(p!(Like)),
Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)),
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)),
+ Token::Word(w) if w.keyword == Keyword::MEMBER => Ok(p!(Like)),
Token::Word(w) if w.keyword == Keyword::OPERATOR =>
Ok(p!(Between)),
Token::Word(w) if w.keyword == Keyword::DIV => Ok(p!(MulDivModOp)),
Token::Period => Ok(p!(Period)),
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 1c40a964..bfd75385 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -3609,6 +3609,19 @@ impl<'a> Parser<'a> {
self.expected("IN or BETWEEN after NOT",
self.peek_token())
}
}
+ Keyword::MEMBER => {
+ if self.parse_keyword(Keyword::OF) {
+ self.expect_token(&Token::LParen)?;
+ let array = self.parse_expr()?;
+ self.expect_token(&Token::RParen)?;
+ Ok(Expr::MemberOf(MemberOf {
+ value: Box::new(expr),
+ array: Box::new(array),
+ }))
+ } else {
+ self.expected("OF after MEMBER", self.peek_token())
+ }
+ }
// Can only happen if `get_next_precedence` got out of sync
with this function
_ => parser_err!(
format!("No infix parser for token {:?}", tok.token),
diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs
index d2feee03..9224a003 100644
--- a/tests/sqlparser_mysql.rs
+++ b/tests/sqlparser_mysql.rs
@@ -4109,3 +4109,28 @@ fn parse_alter_table_drop_index() {
AlterTableOperation::DropIndex { name } if name.value == "idx_index"
);
}
+
+#[test]
+fn parse_json_member_of() {
+ mysql().verified_stmt(r#"SELECT 17 MEMBER OF('[23, "abc", 17, "ab",
10]')"#);
+ let sql = r#"SELECT 'ab' MEMBER OF('[23, "abc", 17, "ab", 10]')"#;
+ let stmt = mysql().verified_stmt(sql);
+ match stmt {
+ Statement::Query(query) => {
+ let select = query.body.as_select().unwrap();
+ assert_eq!(
+ select.projection,
+ vec![SelectItem::UnnamedExpr(Expr::MemberOf(MemberOf {
+ value: Box::new(Expr::Value(
+ Value::SingleQuotedString("ab".to_string()).into()
+ )),
+ array: Box::new(Expr::Value(
+ Value::SingleQuotedString(r#"[23, "abc", 17, "ab",
10]"#.to_string())
+ .into()
+ )),
+ }))]
+ );
+ }
+ _ => panic!("Unexpected statement {stmt}"),
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]