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]

Reply via email to