Skip to content

Commit be2d2f1

Browse files
authored
Add support for MySQL MEMBER OF (#1917)
1 parent 418b942 commit be2d2f1

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
@@ -1124,6 +1124,8 @@ pub enum Expr {
11241124
/// [Databricks](https://docs.databricks.com/en/sql/language-manual/sql-ref-lambda-functions.html)
11251125
/// [DuckDb](https://duckdb.org/docs/sql/functions/lambda.html)
11261126
Lambda(LambdaFunction),
1127+
/// Checks membership of a value in a JSON array
1128+
MemberOf(MemberOf),
11271129
}
11281130

11291131
impl Expr {
@@ -1912,6 +1914,7 @@ impl fmt::Display for Expr {
19121914
}
19131915
Expr::Prior(expr) => write!(f, "PRIOR {expr}"),
19141916
Expr::Lambda(lambda) => write!(f, "{lambda}"),
1917+
Expr::MemberOf(member_of) => write!(f, "{member_of}"),
19151918
}
19161919
}
19171920
}
@@ -9831,6 +9834,27 @@ impl fmt::Display for NullInclusion {
98319834
}
98329835
}
98339836

9837+
/// Checks membership of a value in a JSON array
9838+
///
9839+
/// Syntax:
9840+
/// ```sql
9841+
/// <value> MEMBER OF(<array>)
9842+
/// ```
9843+
/// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/json-search-functions.html#operator_member-of)
9844+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
9845+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9846+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
9847+
pub struct MemberOf {
9848+
pub value: Box<Expr>,
9849+
pub array: Box<Expr>,
9850+
}
9851+
9852+
impl fmt::Display for MemberOf {
9853+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
9854+
write!(f, "{} MEMBER OF({})", self.value, self.array)
9855+
}
9856+
}
9857+
98349858
#[cfg(test)]
98359859
mod tests {
98369860
use crate::tokenizer::Location;

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1624,6 +1624,7 @@ impl Spanned for Expr {
16241624
Expr::OuterJoin(expr) => expr.span(),
16251625
Expr::Prior(expr) => expr.span(),
16261626
Expr::Lambda(_) => Span::empty(),
1627+
Expr::MemberOf(member_of) => member_of.value.span().union(&member_of.array.span()),
16271628
}
16281629
}
16291630
}

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)