Add support for MySQL MEMBER OF

This commit is contained in:
Yoav Cohen 2025-07-01 13:25:59 +02:00
parent f32a41a004
commit f82b1e550a
5 changed files with 28 additions and 0 deletions

View file

@ -1124,6 +1124,14 @@ 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
///
/// Syntax:
/// ```sql
/// <value> MEMBER OF(<array>)
/// ```
/// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/json-search-functions.html#operator_member-of)
MemberOf(Box<Expr>, Box<Expr>),
}
impl Expr {
@ -1912,6 +1920,7 @@ impl fmt::Display for Expr {
}
Expr::Prior(expr) => write!(f, "PRIOR {expr}"),
Expr::Lambda(lambda) => write!(f, "{lambda}"),
Expr::MemberOf(value, array) => write!(f, "{} MEMBER OF({})", value, array),
}
}
}

View file

@ -1619,6 +1619,7 @@ impl Spanned for Expr {
Expr::OuterJoin(expr) => expr.span(),
Expr::Prior(expr) => expr.span(),
Expr::Lambda(_) => Span::empty(),
Expr::MemberOf(value, array) => value.span().union(&array.span()),
}
}
}

View file

@ -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)),

View file

@ -3609,6 +3609,16 @@ impl<'a> Parser<'a> {
self.expected("IN or BETWEEN after NOT", self.peek_token())
}
}
Keyword::MEMBER => {
if self.parse_keyword(Keyword::OF) {
let _ = self.expect_token(&Token::LParen);
let expr2 = self.parse_expr()?;
let _ = self.expect_token(&Token::RParen);
Ok(Expr::MemberOf(Box::new(expr), Box::new(expr2)))
} 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),

View file

@ -4109,3 +4109,9 @@ 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]')"#);
mysql().verified_stmt(r#"SELECT 'ab' MEMBER OF('[23, "abc", 17, "ab", 10]')"#);
}