Skip to content

Commit e807fb5

Browse files
yoavcloudayman-sigma
authored andcommitted
Add support for MySQL MEMBER OF (apache#1917)
1 parent f5a8d5a commit e807fb5

File tree

5 files changed

+65
-0
lines changed

5 files changed

+65
-0
lines changed

src/ast/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,8 @@ pub enum Expr {
11471147
/// [Databricks](https://docs.databricks.com/en/sql/language-manual/sql-ref-lambda-functions.html)
11481148
/// [DuckDb](https://duckdb.org/docs/sql/functions/lambda.html)
11491149
Lambda(LambdaFunction),
1150+
/// Checks membership of a value in a JSON array
1151+
MemberOf(MemberOf),
11501152
}
11511153

11521154
impl Expr {
@@ -1946,6 +1948,7 @@ impl fmt::Display for Expr {
19461948
}
19471949
Expr::Prior(expr) => write!(f, "PRIOR {expr}"),
19481950
Expr::Lambda(lambda) => write!(f, "{lambda}"),
1951+
Expr::MemberOf(member_of) => write!(f, "{member_of}"),
19491952
}
19501953
}
19511954
}
@@ -9860,6 +9863,27 @@ impl fmt::Display for NullInclusion {
98609863
}
98619864
}
98629865

9866+
/// Checks membership of a value in a JSON array
9867+
///
9868+
/// Syntax:
9869+
/// ```sql
9870+
/// <value> MEMBER OF(<array>)
9871+
/// ```
9872+
/// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/json-search-functions.html#operator_member-of)
9873+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
9874+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9875+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
9876+
pub struct MemberOf {
9877+
pub value: Box<Expr>,
9878+
pub array: Box<Expr>,
9879+
}
9880+
9881+
impl fmt::Display for MemberOf {
9882+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
9883+
write!(f, "{} MEMBER OF({})", self.value, self.array)
9884+
}
9885+
}
9886+
98639887
#[cfg(test)]
98649888
mod tests {
98659889
use crate::tokenizer::Location;

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,6 +1629,7 @@ impl Spanned for Expr {
16291629
Expr::OuterJoin(expr) => expr.span(),
16301630
Expr::Prior(expr) => expr.span(),
16311631
Expr::Lambda(_) => Span::empty(),
1632+
Expr::MemberOf(member_of) => member_of.value.span().union(&member_of.array.span()),
16321633
}
16331634
}
16341635
}

src/dialect/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,7 @@ pub trait Dialect: Debug + Any {
649649
Token::Word(w) if w.keyword == Keyword::REGEXP => Ok(p!(Like)),
650650
Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)),
651651
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)),
652+
Token::Word(w) if w.keyword == Keyword::MEMBER => Ok(p!(Like)),
652653
_ => Ok(self.prec_unknown()),
653654
},
654655
Token::Word(w) if w.keyword == Keyword::IS => Ok(p!(Is)),
@@ -661,6 +662,7 @@ pub trait Dialect: Debug + Any {
661662
Token::Word(w) if w.keyword == Keyword::REGEXP => Ok(p!(Like)),
662663
Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)),
663664
Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)),
665+
Token::Word(w) if w.keyword == Keyword::MEMBER => Ok(p!(Like)),
664666
Token::Word(w) if w.keyword == Keyword::OPERATOR => Ok(p!(Between)),
665667
Token::Word(w) if w.keyword == Keyword::DIV => Ok(p!(MulDivModOp)),
666668
Token::Period => Ok(p!(Period)),

src/parser/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3609,6 +3609,19 @@ impl<'a> Parser<'a> {
36093609
self.expected("IN or BETWEEN after NOT", self.peek_token())
36103610
}
36113611
}
3612+
Keyword::MEMBER => {
3613+
if self.parse_keyword(Keyword::OF) {
3614+
self.expect_token(&Token::LParen)?;
3615+
let array = self.parse_expr()?;
3616+
self.expect_token(&Token::RParen)?;
3617+
Ok(Expr::MemberOf(MemberOf {
3618+
value: Box::new(expr),
3619+
array: Box::new(array),
3620+
}))
3621+
} else {
3622+
self.expected("OF after MEMBER", self.peek_token())
3623+
}
3624+
}
36123625
// Can only happen if `get_next_precedence` got out of sync with this function
36133626
_ => parser_err!(
36143627
format!("No infix parser for token {:?}", tok.token),

tests/sqlparser_mysql.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4109,3 +4109,28 @@ fn parse_alter_table_drop_index() {
41094109
AlterTableOperation::DropIndex { name } if name.value == "idx_index"
41104110
);
41114111
}
4112+
4113+
#[test]
4114+
fn parse_json_member_of() {
4115+
mysql().verified_stmt(r#"SELECT 17 MEMBER OF('[23, "abc", 17, "ab", 10]')"#);
4116+
let sql = r#"SELECT 'ab' MEMBER OF('[23, "abc", 17, "ab", 10]')"#;
4117+
let stmt = mysql().verified_stmt(sql);
4118+
match stmt {
4119+
Statement::Query(query) => {
4120+
let select = query.body.as_select().unwrap();
4121+
assert_eq!(
4122+
select.projection,
4123+
vec![SelectItem::UnnamedExpr(Expr::MemberOf(MemberOf {
4124+
value: Box::new(Expr::Value(
4125+
Value::SingleQuotedString("ab".to_string()).into()
4126+
)),
4127+
array: Box::new(Expr::Value(
4128+
Value::SingleQuotedString(r#"[23, "abc", 17, "ab", 10]"#.to_string())
4129+
.into()
4130+
)),
4131+
}))]
4132+
);
4133+
}
4134+
_ => panic!("Unexpected statement {stmt}"),
4135+
}
4136+
}

0 commit comments

Comments
 (0)