mecomp_storage/db/schemas/dynamic/
query.rs

1//! Module for the query that generates the list of songs for a dynamic playlist.
2//!
3//! # BNF Grammar
4//!
5//! ```bnf
6//! <query> ::= <clause>
7//!
8//! <clause> ::= <compound> | <leaf>
9//!
10//! <compound> ::= (<clause> (" OR " | " AND ") <clause>)
11//!
12//! <leaf> ::= <value> <operator> <value>
13//!
14//! <value> ::= <string> | <int> | <set> | <field>
15//!
16//! <field> ::= "title" | "artist" | "album" | "album_artist" | "genre" | "release_year"
17//!
18//! <operator> ::= "=" | "!=" | "?=" | "*=" | ">" | ">=" | "<" | "<=" | "~" | "!~" | "?~" | "*~" | "IN" | "NOT IN" | "CONTAINS" | "CONTAINSNOT" | "CONTAINSALL" | "CONTAINSANY" | "CONTAINSNONE"
19//!
20//! <string> ::= <quote> { <char> } <quote>
21//!
22//! <set> ::= '[' <value> { ", " <value> } ']' | '[' ']'
23//!
24//! <quote> ::= '"' | "'"
25//!
26//! <int> ::= <digit> { <digit> }
27//! ```
28//!
29//! We will use this grammar as a reference to implement the parser, which we will do using the `pom` crate.
30
31use std::str::FromStr;
32
33use serde::{Deserialize, Serialize};
34
35/// Contexts where the query can be used.
36///
37/// Used to enable queries to compile differently based on the context, for example when we compile it to store in the database we want to be able to parse it back into a Query,
38/// but when we compile it to run in the database we may want certain things to be compiled differently.
39/// Specifically, we want `OneOrMany` fields like `artist` to compile to `array::flatten([artist][? $this])` in the database query but just `artist` for storage.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
41pub enum Context {
42    /// We are compiling the query to for storage or transport, this is the default context.
43    /// Queries compiled in this context can be parsed back into a `Query`.
44    #[default]
45    Storage,
46    /// We are compiling the query to run in the database.
47    /// Queries compiled in this context are not expected to be parsed back into a `Query`.
48    Execution,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52/// The query that generates the list of songs for a dynamic playlist.
53pub struct Query {
54    pub root: Clause,
55}
56
57impl Serialize for Query {
58    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
59    where
60        S: serde::Serializer,
61    {
62        serializer.serialize_str(&self.compile_for_storage())
63    }
64}
65
66impl<'de> Deserialize<'de> for Query {
67    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
68    where
69        D: serde::Deserializer<'de>,
70    {
71        let query = String::deserialize(deserializer)?;
72        Self::from_str(&query).map_err(serde::de::Error::custom)
73    }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Hash)]
77/// A clause in a query.
78/// A query is a tree of clauses.
79pub enum Clause {
80    Compound(CompoundClause),
81    Leaf(LeafClause),
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Hash)]
85/// A compound clause that is either an OR or an AND.
86/// An OR clause is a disjunction of clauses.
87/// An AND clause is a conjunction of clauses.
88pub struct CompoundClause {
89    pub clauses: Vec<Clause>,
90    pub kind: CompoundKind,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Hash)]
94/// The kind of a compound clause.
95pub enum CompoundKind {
96    Or,
97    And,
98}
99
100impl CompoundKind {
101    #[must_use]
102    pub const fn operator(&self) -> &'static str {
103        match self {
104            Self::Or => " OR ",
105            Self::And => " AND ",
106        }
107    }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Hash)]
111/// A single clause in a query.
112pub struct LeafClause {
113    pub left: Value,
114    pub operator: Operator,
115    pub right: Value,
116}
117
118impl LeafClause {
119    #[must_use]
120    pub const fn has_valid_operator(&self) -> bool {
121        match (&self.left, &self.right) {
122            // Value to Value comparison
123            // special case: strings
124            (
125                Value::String(_) | Value::Field(Field::Album | Field::Title),
126                Value::String(_) | Value::Field(Field::Album | Field::Title),
127            ) if matches!(
128                self.operator,
129                Operator::Contains
130                    | Operator::ContainsNot
131                    | Operator::Inside
132                    | Operator::NotInside
133                    | Operator::In
134                    | Operator::NotIn
135            ) =>
136            {
137                true
138            }
139            (
140                Value::String(_)
141                | Value::Int(_)
142                | Value::Field(Field::Album | Field::ReleaseYear | Field::Title),
143                Value::String(_)
144                | Value::Int(_)
145                | Value::Field(Field::Album | Field::ReleaseYear | Field::Title),
146            ) => matches!(
147                self.operator,
148                Operator::Equal
149                    | Operator::NotEqual
150                    | Operator::Like
151                    | Operator::NotLike
152                    | Operator::LessThan
153                    | Operator::LessThanOrEqual
154                    | Operator::GreaterThan
155                    | Operator::GreaterThanOrEqual,
156            ),
157            // Value to Set comparison
158            (
159                Value::String(_)
160                | Value::Int(_)
161                | Value::Field(Field::Album | Field::ReleaseYear | Field::Title),
162                Value::Set(_) | Value::Field(Field::AlbumArtists | Field::Artists | Field::Genre),
163            ) => matches!(
164                self.operator,
165                Operator::Inside | Operator::NotInside | Operator::In | Operator::NotIn
166            ),
167            // Set to Value comparison
168            (
169                Value::Set(_) | Value::Field(Field::AlbumArtists | Field::Artists | Field::Genre),
170                Value::String(_)
171                | Value::Int(_)
172                | Value::Field(Field::Album | Field::ReleaseYear | Field::Title),
173            ) => matches!(
174                self.operator,
175                Operator::Contains
176                    | Operator::ContainsNot
177                    | Operator::AllEqual
178                    | Operator::AnyEqual
179                    | Operator::AllLike
180                    | Operator::AnyLike
181            ),
182            // Set to Set comparison
183            (
184                Value::Set(_) | Value::Field(Field::AlbumArtists | Field::Artists | Field::Genre),
185                Value::Set(_) | Value::Field(Field::AlbumArtists | Field::Artists | Field::Genre),
186            ) => matches!(
187                self.operator,
188                Operator::Contains
189                    | Operator::ContainsAll
190                    | Operator::ContainsAny
191                    | Operator::ContainsNone
192                    | Operator::AllInside
193                    | Operator::AnyInside
194                    | Operator::NoneInside
195            ),
196        }
197    }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Hash)]
201/// The types of values that can be used in a clause.
202pub enum Value {
203    String(String),
204    Int(i64),
205    Set(Vec<Value>),
206    Field(Field),
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
210///  The fields of a song that are available for filtering.
211pub enum Field {
212    // Song
213    Title,
214    Artists,
215    Album,
216    AlbumArtists,
217    Genre,
218    ReleaseYear,
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
222/// The operators that can be used in a clause.
223pub enum Operator {
224    // Comparison
225    Equal,
226    NotEqual,
227    AnyEqual,
228    AllEqual,
229    GreaterThan,
230    GreaterThanOrEqual,
231    LessThan,
232    LessThanOrEqual,
233    // Fuzzy string comparison
234    Like,
235    NotLike,
236    AnyLike,
237    AllLike,
238    // Set comparison
239    In,
240    NotIn,
241    Contains,
242    ContainsNot,
243    ContainsAll,
244    ContainsAny,
245    ContainsNone,
246    Inside,
247    NotInside,
248    AllInside,
249    AnyInside,
250    NoneInside,
251}
252
253pub trait Compile {
254    fn compile(&self, context: Context) -> String;
255
256    fn compile_for_storage(&self) -> String {
257        self.compile(Context::Storage)
258    }
259
260    fn compile_for_execution(&self) -> String {
261        self.compile(Context::Execution)
262    }
263}
264
265macro_rules! impl_display {
266    ($($t:ty),*) => {
267        $(
268            impl std::fmt::Display for $t {
269                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270                    write!(f, "{}", self.compile_for_storage())
271                }
272            }
273        )*
274    };
275}
276impl_display!(
277    Query,
278    Clause,
279    CompoundClause,
280    LeafClause,
281    Value,
282    Field,
283    Operator
284);
285
286impl Compile for Query {
287    fn compile(&self, context: Context) -> String {
288        self.root.compile(context)
289    }
290}
291
292impl Compile for Clause {
293    fn compile(&self, context: Context) -> String {
294        match self {
295            Self::Compound(compound) => compound.compile(context),
296            Self::Leaf(leaf) => leaf.compile(context),
297        }
298    }
299}
300
301impl Compile for CompoundClause {
302    fn compile(&self, context: Context) -> String {
303        debug_assert!(!self.clauses.is_empty());
304        debug_assert_eq!(self.clauses.len(), 2);
305
306        let operator = self.kind.operator();
307        let mut clauses = self
308            .clauses
309            .iter()
310            .map(|c| c.compile(context))
311            .collect::<Vec<_>>()
312            .join(operator);
313        if self.clauses.len() > 1 {
314            clauses = format!("({clauses})");
315        }
316        clauses
317    }
318}
319
320impl Compile for LeafClause {
321    fn compile(&self, context: Context) -> String {
322        format!(
323            "{} {} {}",
324            self.left.compile(context),
325            self.operator.compile(context),
326            self.right.compile(context)
327        )
328    }
329}
330
331impl Compile for Value {
332    fn compile(&self, context: Context) -> String {
333        match self {
334            Self::String(s) => format!("\"{s}\""),
335            Self::Int(i) => i.to_string(),
336            Self::Set(set) => {
337                let set = set
338                    .iter()
339                    .map(|v| v.compile(context))
340                    .collect::<Vec<_>>()
341                    .join(", ");
342                format!("[{set}]")
343            }
344            Self::Field(field) => field.compile(context),
345        }
346    }
347}
348
349impl Compile for Field {
350    fn compile(&self, context: Context) -> String {
351        match (self, context) {
352            (Self::Title, _) => "title".to_string(),
353            (Self::Album, _) => "album".to_string(),
354            (Self::Artists, Context::Storage) => "artist".to_string(),
355            (Self::Artists, Context::Execution) => "array::flatten([artist][? $this])".to_string(),
356            (Self::AlbumArtists, Context::Storage) => "album_artist".to_string(),
357            (Self::AlbumArtists, Context::Execution) => {
358                "array::flatten([album_artist][? $this])".to_string()
359            }
360            (Self::Genre, Context::Storage) => "genre".to_string(),
361            (Self::Genre, Context::Execution) => "array::flatten([genre][? $this])".to_string(),
362            (Self::ReleaseYear, _) => "release_year".to_string(),
363        }
364    }
365}
366
367impl Compile for Operator {
368    fn compile(&self, _: Context) -> String {
369        match self {
370            Self::Equal => "=".to_string(),
371            Self::NotEqual => "!=".to_string(),
372            Self::AnyEqual => "?=".to_string(),
373            Self::AllEqual => "*=".to_string(),
374            Self::GreaterThan => ">".to_string(),
375            Self::GreaterThanOrEqual => ">=".to_string(),
376            Self::LessThan => "<".to_string(),
377            Self::LessThanOrEqual => "<=".to_string(),
378            Self::Like => "~".to_string(),
379            Self::NotLike => "!~".to_string(),
380            Self::AnyLike => "?~".to_string(),
381            Self::AllLike => "*~".to_string(),
382            Self::In => "IN".to_string(),
383            Self::NotIn => "NOT IN".to_string(),
384            Self::Contains => "CONTAINS".to_string(),
385            Self::ContainsNot => "CONTAINSNOT".to_string(),
386            Self::ContainsAll => "CONTAINSALL".to_string(),
387            Self::ContainsAny => "CONTAINSANY".to_string(),
388            Self::ContainsNone => "CONTAINSNONE".to_string(),
389            Self::Inside => "INSIDE".to_string(),
390            Self::NotInside => "NOTINSIDE".to_string(),
391            Self::AllInside => "ALLINSIDE".to_string(),
392            Self::AnyInside => "ANYINSIDE".to_string(),
393            Self::NoneInside => "NONEINSIDE".to_string(),
394        }
395    }
396}
397
398#[cfg(test)]
399mod tests {
400    use std::marker::PhantomData;
401
402    use super::*;
403    use pretty_assertions::assert_eq;
404    use rstest::rstest;
405    use rstest_reuse::{apply, template};
406
407    #[template]
408    #[rstest]
409    #[case::operator(Operator::Equal, "=")]
410    #[case::operator(Operator::NotEqual, "!=")]
411    #[case::operator(Operator::AnyEqual, "?=")]
412    #[case::operator(Operator::AllEqual, "*=")]
413    #[case::operator(Operator::GreaterThan, ">")]
414    #[case::operator(Operator::GreaterThanOrEqual, ">=")]
415    #[case::operator(Operator::LessThan, "<")]
416    #[case::operator(Operator::LessThanOrEqual, "<=")]
417    #[case::operator(Operator::Like, "~")]
418    #[case::operator(Operator::NotLike, "!~")]
419    #[case::operator(Operator::AnyLike, "?~")]
420    #[case::operator(Operator::AllLike, "*~")]
421    #[case::operator(Operator::In, "IN")]
422    #[case::operator(Operator::NotIn, "NOT IN")]
423    #[case::operator(Operator::Contains, "CONTAINS")]
424    #[case::operator(Operator::ContainsNot, "CONTAINSNOT")]
425    #[case::operator(Operator::ContainsAll, "CONTAINSALL")]
426    #[case::operator(Operator::ContainsAny, "CONTAINSANY")]
427    #[case::operator(Operator::ContainsNone, "CONTAINSNONE")]
428    #[case::operator(Operator::Inside, "INSIDE")]
429    #[case::operator(Operator::NotInside, "NOTINSIDE")]
430    #[case::operator(Operator::AllInside, "ALLINSIDE")]
431    #[case::operator(Operator::AnyInside, "ANYINSIDE")]
432    #[case::operator(Operator::NoneInside, "NONEINSIDE")]
433    #[case::field(Field::Title, "title")]
434    #[case::field(Field::Artists, "artist")]
435    #[case::field(Field::Album, "album")]
436    #[case::field(Field::AlbumArtists, "album_artist")]
437    #[case::field(Field::Genre, "genre")]
438    #[case::field(Field::ReleaseYear, "release_year")]
439    #[case::value(Value::String("foo".to_string()), "\"foo\"")]
440    #[case::value(Value::Int(42), "42")]
441    #[case::value(Value::Set(vec![Value::String("foo".to_string()), Value::Int(42)]), "[\"foo\", 42]")]
442    #[case::value(Value::Field(Field::Title), "title")]
443    #[case::leaf_clause(
444        LeafClause {
445            left: Value::Field(Field::Title),
446            operator: Operator::Equal,
447            right: Value::String("foo".to_string())
448        },
449        "title = \"foo\""
450    )]
451    #[case::leaf_clause(
452        LeafClause {
453            left: Value::Set(vec![Value::String("foo".to_string()), Value::Int(42)]),
454            operator: Operator::Contains,
455            right: Value::Int(42)
456        },
457        "[\"foo\", 42] CONTAINS 42"
458    )]
459    #[case::compound_clause(
460        CompoundClause {
461            clauses: vec![
462                Clause::Leaf(LeafClause {
463                    left: Value::Field(Field::Title),
464                    operator: Operator::Equal,
465                    right: Value::String("foo".to_string())
466                }),
467                Clause::Leaf(LeafClause {
468                    left: Value::String("bar".to_string()),
469                    operator: Operator::Inside,
470                    right: Value::Field(Field::Artists),
471                }),
472            ],
473            kind: CompoundKind::And
474        },
475        "(title = \"foo\" AND \"bar\" INSIDE artist)"
476    )]
477    #[case::compound_clause(
478        CompoundClause {
479            clauses: vec![
480                Clause::Leaf(LeafClause {
481                    left: Value::Field(Field::Title),
482                    operator: Operator::Equal,
483                    right: Value::String("foo".to_string())
484                }),
485                Clause::Leaf(LeafClause {
486                    left: Value::Field(Field::Artists),
487                    operator: Operator::Contains,
488                    right: Value::String("bar".to_string())
489                }),
490            ],
491            kind: CompoundKind::Or
492        },
493        "(title = \"foo\" OR artist CONTAINS \"bar\")"
494    )]
495    #[case::query(
496        Query {
497            root: Clause::Compound(CompoundClause {
498                clauses: vec![
499                    Clause::Compound(
500                        CompoundClause {
501                            clauses: vec![
502                                Clause::Leaf(LeafClause {
503                                    left: Value::Field(Field::Title),
504                                    operator: Operator::Equal,
505                                    right: Value::String("foo".to_string())
506                                }),
507                                Clause::Compound(CompoundClause {
508                                    clauses: vec![
509                                        Clause::Leaf(LeafClause {
510                                            left: Value::Field(Field::Artists),
511                                            operator: Operator::ContainsNot,
512                                            right: Value::String("bar".to_string())
513                                        }),
514                                        Clause::Leaf(LeafClause {
515                                            left: Value::Field(Field::Album),
516                                            operator: Operator::Equal,
517                                            right: Value::String("baz".to_string())
518                                        }),
519                                    ],
520                                    kind: CompoundKind::Or
521                                }),
522                            ],
523                            kind: CompoundKind::And
524                        }
525                    ),
526                    Clause::Leaf(LeafClause {
527                        left: Value::Field(Field::ReleaseYear),
528                        operator: Operator::GreaterThan,
529                        right: Value::Int(2020)
530                    }),
531                ],
532                kind: CompoundKind::And
533            })
534        },
535        "((title = \"foo\" AND (artist CONTAINSNOT \"bar\" OR album = \"baz\")) AND release_year > 2020)"
536    )]
537    fn compilables<T: Compile>(#[case] input: T, #[case] expected: &str) {}
538
539    #[apply(compilables)]
540    fn test_compile<T: Compile>(#[case] input: T, #[case] expected: &str) {
541        let compiled = input.compile(Context::Storage);
542        assert_eq!(compiled, expected);
543    }
544
545    #[rstest]
546    #[case::field(PhantomData::<Field>, "title", "title")]
547    #[case::field(PhantomData::<Field>, "artist", "array::flatten([artist][? $this])")]
548    #[case::field(PhantomData::<Field>, "album", "album")]
549    #[case::field(PhantomData::<Field>, "album_artist", "array::flatten([album_artist][? $this])")]
550    #[case::field(PhantomData::<Field>, "genre", "array::flatten([genre][? $this])")]
551    #[case::field(PhantomData::<Field>, "release_year", "release_year")]
552    #[case::compound_query(PhantomData::<CompoundClause>, "(title = \"foo\" AND \"bar\" INSIDE artist)", "(title = \"foo\" AND \"bar\" INSIDE array::flatten([artist][? $this]))")]
553    #[case::complex_query(PhantomData::<Query>, "((title = \"foo\" AND (artist CONTAINSNOT \"bar\" OR album = \"baz\")) AND release_year > 2020)", "((title = \"foo\" AND (array::flatten([artist][? $this]) CONTAINSNOT \"bar\" OR album = \"baz\")) AND release_year > 2020)")]
554    fn test_compile_for_execution<T>(
555        #[case] _phantom: PhantomData<T>,
556        #[case] storage: &str,
557        #[case] expected: &str,
558    ) where
559        T: Compile + FromStr,
560        <T as std::str::FromStr>::Err: std::fmt::Debug,
561    {
562        let parsed = T::from_str(storage).unwrap();
563        let compiled = parsed.compile(Context::Execution);
564        assert_eq!(compiled, expected);
565    }
566
567    #[apply(compilables)]
568    fn test_display<T: Compile + std::fmt::Display>(#[case] input: T, #[case] expected: &str) {
569        let displayed = format!("{}", input);
570        assert_eq!(displayed, expected);
571    }
572
573    #[apply(compilables)]
574    fn test_from_str<T: Compile + std::str::FromStr + std::cmp::PartialEq + std::fmt::Debug>(
575        #[case] expected: T,
576        #[case] input: &str,
577    ) where
578        <T as std::str::FromStr>::Err: std::fmt::Debug,
579        <T as std::str::FromStr>::Err: PartialEq,
580    {
581        let parsed = T::from_str(input);
582        assert_eq!(parsed, Ok(expected));
583    }
584}
585
586macro_rules! impl_from_str {
587    ($(($t:ty, $p:expr)),*) => {
588        $(
589            impl std::str::FromStr for $t {
590                type Err = pom::Error;
591
592                fn from_str(s: &str) -> Result<Self, Self::Err> {
593                    $p.parse(s.as_bytes())
594                }
595            }
596        )*
597    };
598}
599
600impl_from_str!(
601    (Operator, parser::operator()),
602    (Field, parser::field()),
603    (Value, parser::value()),
604    (LeafClause, parser::leaf()),
605    (CompoundClause, parser::compound()),
606    (Clause, parser::clause()),
607    (Query, parser::query())
608);
609
610mod parser {
611    use std::str::FromStr;
612
613    use pom::parser::{call, end, list, none_of, one_of, seq, sym, Parser};
614
615    use super::{Clause, CompoundClause, CompoundKind, Field, LeafClause, Operator, Query, Value};
616
617    pub fn query<'a>() -> Parser<'a, u8, Query> {
618        clause().map(|root| Query { root }).name("query") - end()
619    }
620
621    pub fn clause<'a>() -> Parser<'a, u8, Clause> {
622        compound().map(Clause::Compound) | leaf().map(Clause::Leaf).name("clause")
623    }
624
625    pub fn compound<'a>() -> Parser<'a, u8, CompoundClause> {
626        (sym(b'(')
627            * space()
628            * (call(clause) - space() + (seq(b"AND") | seq(b"OR")) - space() + call(clause)).map(
629                |((left, sep), right)| CompoundClause {
630                    clauses: vec![left, right],
631                    kind: match sep {
632                        b"AND" => CompoundKind::And,
633                        b"OR" => CompoundKind::Or,
634                        _ => unreachable!(),
635                    },
636                },
637            )
638            - space()
639            - sym(b')'))
640        .name("compound clause")
641    }
642
643    pub fn leaf<'a>() -> Parser<'a, u8, LeafClause> {
644        (value() - space() + operator() - space() + value())
645            .convert(|((left, operator), right)| {
646                let parsed = LeafClause {
647                    left,
648                    operator,
649                    right,
650                };
651                if parsed.has_valid_operator() {
652                    Ok(parsed)
653                } else {
654                    Err(pom::Error::Conversion {
655                        position: 0,
656                        message: format!(
657                            "Invalid operator ({op}) for values: {left:?}, {right:?}",
658                            left = parsed.left,
659                            op = parsed.operator,
660                            right = parsed.right
661                        ),
662                    })
663                }
664            })
665            .name("leaf clause")
666    }
667
668    pub fn value<'a>() -> Parser<'a, u8, Value> {
669        (string().map(Value::String)
670            | int().map(Value::Int)
671            | set().map(Value::Set)
672            | field().map(Value::Field))
673        .name("value")
674    }
675
676    pub fn field<'a>() -> Parser<'a, u8, Field> {
677        (seq(b"title").map(|_| Field::Title)
678            | seq(b"artist").map(|_| Field::Artists)
679            | seq(b"album_artist").map(|_| Field::AlbumArtists)
680            | seq(b"album").map(|_| Field::Album)
681            | seq(b"genre").map(|_| Field::Genre)
682            | seq(b"release_year").map(|_| Field::ReleaseYear))
683        .name("field")
684    }
685
686    pub fn operator<'a>() -> Parser<'a, u8, Operator> {
687        (seq(b"!=").map(|_| Operator::NotEqual)
688            | seq(b"?=").map(|_| Operator::AnyEqual)
689            | seq(b"*=").map(|_| Operator::AllEqual)
690            | seq(b"=").map(|_| Operator::Equal)
691            | seq(b">=").map(|_| Operator::GreaterThanOrEqual)
692            | seq(b">").map(|_| Operator::GreaterThan)
693            | seq(b"<=").map(|_| Operator::LessThanOrEqual)
694            | seq(b"<").map(|_| Operator::LessThan)
695            | seq(b"!~").map(|_| Operator::NotLike)
696            | seq(b"?~").map(|_| Operator::AnyLike)
697            | seq(b"*~").map(|_| Operator::AllLike)
698            | seq(b"~").map(|_| Operator::Like)
699            | seq(b"NOTINSIDE").map(|_| Operator::NotInside)
700            | seq(b"ALLINSIDE").map(|_| Operator::AllInside)
701            | seq(b"ANYINSIDE").map(|_| Operator::AnyInside)
702            | seq(b"NONEINSIDE").map(|_| Operator::NoneInside)
703            | seq(b"INSIDE").map(|_| Operator::Inside)
704            | seq(b"NOT IN").map(|_| Operator::NotIn)
705            | seq(b"IN").map(|_| Operator::In)
706            | seq(b"CONTAINSNOT").map(|_| Operator::ContainsNot)
707            | seq(b"CONTAINSALL").map(|_| Operator::ContainsAll)
708            | seq(b"CONTAINSANY").map(|_| Operator::ContainsAny)
709            | seq(b"CONTAINSNONE").map(|_| Operator::ContainsNone)
710            | seq(b"CONTAINS").map(|_| Operator::Contains))
711        .name("operator")
712    }
713
714    pub fn string<'a>() -> Parser<'a, u8, String> {
715        let string_pf = |quote_sym, escaped_quote| {
716            let special_char = sym(b'\\')
717                | sym(b'/')
718                | sym(quote_sym)
719                | sym(b'b').map(|_| b'\x08')
720                | sym(b'f').map(|_| b'\x0C')
721                | sym(b'n').map(|_| b'\n')
722                | sym(b'r').map(|_| b'\r')
723                | sym(b't').map(|_| b'\t');
724            let escape_sequence = sym(b'\\') * special_char;
725            let char_string = (none_of(escaped_quote) | escape_sequence)
726                .repeat(1..)
727                .convert(String::from_utf8);
728
729            sym(quote_sym) * char_string.repeat(0..) - sym(quote_sym)
730        };
731        let string = string_pf(b'"', b"\\\"") | string_pf(b'\'', b"\\'");
732        string.map(|strings| strings.concat()).name("string")
733    }
734
735    pub fn int<'a>() -> Parser<'a, u8, i64> {
736        let number = sym(b'-').opt() + one_of(b"0123456789").repeat(1..);
737        number
738            .collect()
739            .convert(std::str::from_utf8)
740            .convert(i64::from_str)
741            .name("int")
742    }
743
744    pub fn set<'a>() -> Parser<'a, u8, Vec<Value>> {
745        let elems = list(call(value), sym(b',') * space());
746        (sym(b'[') * space() * elems - sym(b']')).name("set")
747    }
748
749    pub fn space<'a>() -> Parser<'a, u8, ()> {
750        one_of(b" \t\r\n").repeat(0..).discard().name("space")
751    }
752
753    #[cfg(test)]
754    mod tests {
755        use crate::db::schemas::dynamic::query::Context;
756
757        use super::super::Compile;
758        use super::*;
759        use pretty_assertions::assert_eq;
760        use rstest::rstest;
761
762        #[rstest]
763        #[case(Ok(Operator::Equal), "=")]
764        #[case(Ok(Operator::NotEqual), "!=")]
765        #[case(Ok(Operator::AnyEqual), "?=")]
766        #[case(Ok(Operator::AllEqual), "*=")]
767        #[case(Ok(Operator::GreaterThan), ">")]
768        #[case(Ok(Operator::GreaterThanOrEqual), ">=")]
769        #[case(Ok(Operator::LessThan), "<")]
770        #[case(Ok(Operator::LessThanOrEqual), "<=")]
771        #[case(Ok(Operator::Like), "~")]
772        #[case(Ok(Operator::NotLike), "!~")]
773        #[case(Ok(Operator::AnyLike), "?~")]
774        #[case(Ok(Operator::AllLike), "*~")]
775        #[case(Ok(Operator::Inside), "INSIDE")]
776        #[case(Ok(Operator::NotInside), "NOTINSIDE")]
777        #[case(Ok(Operator::AllInside), "ALLINSIDE")]
778        #[case(Ok(Operator::AnyInside), "ANYINSIDE")]
779        #[case(Ok(Operator::NoneInside), "NONEINSIDE")]
780        #[case(Ok(Operator::In), "IN")]
781        #[case(Ok(Operator::NotIn), "NOT IN")]
782        #[case(Ok(Operator::Contains), "CONTAINS")]
783        #[case(Ok(Operator::ContainsNot), "CONTAINSNOT")]
784        #[case(Ok(Operator::ContainsAll), "CONTAINSALL")]
785        #[case(Ok(Operator::ContainsAny), "CONTAINSANY")]
786        #[case(Ok(Operator::ContainsNone), "CONTAINSNONE")]
787        #[case(
788            Err(pom::Error::Custom { message: "failed to parse operator".to_string(), position:0, inner: Some(Box::new(pom::Error::Mismatch { message: "seq [67, 79, 78, 84, 65, 73, 78, 83] expect: 67, found: 105".to_string(), position: 0 }))}),
789            "invalid"
790        )]
791        fn test_operator_parse_compile(
792            #[case] expected: Result<Operator, pom::Error>,
793            #[case] s: &str,
794        ) {
795            let parsed = operator().parse(s.as_bytes());
796            assert_eq!(parsed, expected);
797            if let Ok(operator) = parsed {
798                let compiled = operator.compile(Context::Storage);
799                assert_eq!(compiled, s);
800            }
801        }
802
803        #[rstest]
804        #[case(Ok(Field::Title), "title")]
805        #[case(Ok(Field::Artists), "artist")]
806        #[case(Ok(Field::Album), "album")]
807        #[case(Ok(Field::AlbumArtists), "album_artist")]
808        #[case(Ok(Field::Genre), "genre")]
809        #[case(Ok(Field::ReleaseYear), "release_year")]
810        #[case(Err(pom::Error::Custom{ message: "failed to parse field".to_string(), position:0, inner: Some(Box::new(pom::Error::Mismatch { message: "seq [114, 101, 108, 101, 97, 115, 101, 95, 121, 101, 97, 114] expect: 114, found: 105".to_string(), position: 0 }))}), "invalid")]
811        fn test_field_parse_compile(#[case] expected: Result<Field, pom::Error>, #[case] s: &str) {
812            let parsed = field().parse(s.as_bytes());
813            assert_eq!(parsed, expected);
814            if let Ok(field) = parsed {
815                let compiled = field.compile(Context::Storage);
816                assert_eq!(compiled, s);
817            }
818        }
819
820        #[rstest]
821        #[case(Ok(Value::String("foo".to_string())), "\"foo\"")]
822        #[case(Ok(Value::Int(42)), "42")]
823        #[case(Ok(Value::Set(vec![Value::String("foo".to_string()), Value::Int(42)])), "[\"foo\", 42]")]
824        #[case::nested(
825            Ok(Value::Set(vec![
826                Value::String("foo".to_string()),
827                Value::Set(vec![Value::String("bar".to_string()), Value::Int(42)])
828                ])),
829                "[\"foo\", [\"bar\", 42]]"
830            )]
831        #[case(Ok(Value::Field(Field::Title)), "title")]
832        #[case(Ok(Value::Field(Field::Artists)), "artist")]
833        #[case(Ok(Value::Field(Field::Album)), "album")]
834        #[case(Ok(Value::Field(Field::AlbumArtists)), "album_artist")]
835        #[case(Ok(Value::Field(Field::Genre)), "genre")]
836        #[case(Ok(Value::Field(Field::ReleaseYear)), "release_year")]
837        #[case(Err(pom::Error::Custom {message: "failed to parse field".to_string(), position: 0, inner: Some(Box::new(pom::Error::Mismatch { message: "seq [114, 101, 108, 101, 97, 115, 101, 95, 121, 101, 97, 114] expect: 114, found: 34".to_string(), position: 0 }))}), "\"foo")]
838        #[case(Err(pom::Error::Custom {message: "failed to parse field".to_string(), position: 0, inner: Some(Box::new(pom::Error::Mismatch { message: "seq [114, 101, 108, 101, 97, 115, 101, 95, 121, 101, 97, 114] expect: 114, found: 91".to_string(), position: 0 }))}), "[foo, 42")]
839        #[case(Err(pom::Error::Custom {message: "failed to parse field".to_string(), position: 0, inner: Some(Box::new(pom::Error::Mismatch { message: "seq [114, 101, 108, 101, 97, 115, 101, 95, 121, 101, 97, 114] expect: 114, found: 105".to_string(), position: 0 }))}), "invalid")]
840        fn test_value_parse_compile(#[case] expected: Result<Value, pom::Error>, #[case] s: &str) {
841            let parsed = value().parse(s.as_bytes());
842            assert_eq!(parsed, expected);
843            if let Ok(value) = parsed {
844                let compiled = value.compile(Context::Storage);
845                assert_eq!(compiled, s);
846            }
847        }
848
849        #[rstest]
850        #[case(Ok(Value::String("foo bar".to_string())), "\"foo bar\"")]
851        #[case(Ok(Value::String("foo bar".to_string())), "'foo bar'")]
852        #[case(Err(pom::Error::Custom {message: "failed to parse field".to_string(), position: 0, inner: Some(Box::new(pom::Error::Mismatch { message: "seq [114, 101, 108, 101, 97, 115, 101, 95, 121, 101, 97, 114] expect: 114, found: 34".to_string(), position: 0 }))}), "\"foo")]
853        #[case(Err(pom::Error::Custom {message: "failed to parse field".to_string(), position: 0, inner: Some(Box::new(pom::Error::Mismatch { message: "seq [114, 101, 108, 101, 97, 115, 101, 95, 121, 101, 97, 114] expect: 114, found: 39".to_string(), position: 0 }))}), "'foo")]
854        fn test_value_parse_string(#[case] expected: Result<Value, pom::Error>, #[case] s: &str) {
855            let parsed = value().parse(s.as_bytes());
856            assert_eq!(parsed, expected);
857        }
858
859        #[rstest]
860        // we know that each part of a clause is parsed and compiled correctly, so we only need to test the combination
861        #[case(Ok(LeafClause {
862            left: Value::Field(Field::Title),
863            operator: Operator::Equal,
864            right: Value::String("foo".to_string())
865        }), "title = \"foo\"")]
866        #[case(Ok(LeafClause {
867            left: Value::Field(Field::Title),
868            operator: Operator::Equal,
869            right: Value::Int(42)
870        }), "title = 42")]
871        #[case(Ok(LeafClause {
872            left: Value::Field(Field::Title),
873            operator: Operator::Inside,
874            right: Value::Set(vec![Value::String("foo".to_string()), Value::Int(42)])
875        }), "title INSIDE [\"foo\", 42]")]
876        #[case(Err(
877            pom::Error::Custom {
878                message: "failed to parse leaf clause".to_string(),
879                position: 0,
880                inner: Some(Box::new(pom::Error::
881                    Conversion {
882                        message: "Conversion error: Conversion { message: \"Invalid operator (=) for values: Field(Title), Field(Artists)\", position: 0 }".to_string(),
883                        position: 0,
884                    }
885                )),
886            }
887        ), "title = artist")]
888        #[case(Err(pom::Error::Custom{message:"failed to parse operator".to_string(),position:5, inner:Some(Box::new(pom::Error::Incomplete))}), "title")]
889        #[case(Err(pom::Error::Custom{message: "failed to parse field".to_string(),position: 0, inner: Some(Box::new(pom::Error:: Mismatch { message: "seq [114, 101, 108, 101, 97, 115, 101, 95, 121, 101, 97, 114] expect: 114, found: 32".to_string(), position: 0 }))}), " = \"foo\"")]
890        #[case(Err(pom::Error::Custom{message:"failed to parse field".to_string(),position:8, inner:Some(Box::new(pom::Error::Incomplete))}), "title = ")]
891        #[case(Err(pom::Error::Custom{message: "failed to parse operator".to_string(),position: 6, inner: Some(Box::new(pom::Error:: Mismatch { message: "seq [67, 79, 78, 84, 65, 73, 78, 83] expect: 67, found: 105".to_string(), position: 6 }))}), "title invalid \"foo\"")]
892        // special cases
893        #[case::left_has_spaces(Ok(LeafClause {
894                left: Value::String("foo bar".to_string()),
895                operator: Operator::Equal,
896                right: Value::Int(42)
897            }), "\"foo bar\" = 42")]
898        #[case::operator_has_spaces(Ok(LeafClause {
899            left: Value::Field(Field::Title),
900            operator: Operator::NotIn,
901            right: Value::String("foo bar".to_string())
902        }), "title NOT IN \"foo bar\"")]
903        fn test_leaf_clause_parse(
904            #[case] expected: Result<LeafClause, pom::Error>,
905            #[case] s: &str,
906        ) {
907            let parsed = leaf().parse(s.as_bytes());
908            assert_eq!(parsed, expected);
909            if let Ok(clause) = parsed {
910                let compiled = clause.compile(Context::Storage);
911                assert_eq!(compiled, s);
912            }
913        }
914
915        #[rstest]
916        #[case::value_to_value("artist = \"foo\"", Err(pom::Error::Custom {message:"failed to parse leaf clause".to_string(), position: 0, inner: Some(Box::new(pom::Error::Conversion { position: 0, message: "Conversion error: Conversion { message: \"Invalid operator (=) for values: Field(Artists), String(\\\"foo\\\")\", position: 0 }".to_string() }))}))]
917        #[case::value_to_value("title = \"foo\"", Ok(LeafClause {
918            left: Value::Field(Field::Title),
919            operator: Operator::Equal,
920            right: Value::String("foo".to_string())
921        }))]
922        #[case::value_to_set("42 IN [\"foo\", 42]", Ok(LeafClause {
923            left: Value::Int(42),
924            operator: Operator::In,
925            right: Value::Set(vec![Value::String("foo".to_string()), Value::Int(42)])
926        }))]
927        #[case::set_to_value("[\"foo\", 42] CONTAINS 42", Ok(LeafClause {
928            left: Value::Set(vec![Value::String("foo".to_string()), Value::Int(42)]),
929            operator: Operator::Contains,
930            right: Value::Int(42)
931        }))]
932        #[case::set_to_set("[\"foo\", 42] CONTAINSALL [\"foo\", 42]", Ok(LeafClause {
933            left: Value::Set(vec![Value::String("foo".to_string()), Value::Int(42)]),
934            operator: Operator::ContainsAll,
935            right: Value::Set(vec![Value::String("foo".to_string()), Value::Int(42)])
936        }))]
937        #[case::string_to_string("\"foo\" IN \"foo\"", Ok(LeafClause {
938            left: Value::String("foo".to_string()),
939            operator: Operator::In,
940            right: Value::String("foo".to_string())
941        }))]
942        fn test_operator_checking(
943            #[case] input: &str,
944            #[case] expected: Result<LeafClause, pom::Error>,
945        ) {
946            let parsed = leaf().parse(input.as_bytes());
947            assert_eq!(parsed, expected);
948        }
949
950        #[rstest]
951        // we know that each part of a clause is parsed and compiled correctly, so we only need to test the combination
952        #[case(Ok(CompoundClause {
953            clauses: vec![
954                Clause::Leaf(LeafClause {
955                    left: Value::Field(Field::Title),
956                    operator: Operator::Equal,
957                    right: Value::String("foo".to_string())
958                }),
959                Clause::Leaf(LeafClause {
960                    left: Value::Field(Field::Artists),
961                    operator: Operator::AllLike,
962                    right: Value::String("bar".to_string())
963                }),
964            ],
965            kind: CompoundKind::And
966        }), "(title = \"foo\" AND artist *~ \"bar\")")]
967        #[case(Ok(CompoundClause {
968            clauses: vec![
969                Clause::Leaf(LeafClause {
970                    left: Value::Field(Field::Title),
971                    operator: Operator::Equal,
972                    right: Value::String("foo".to_string())
973                }),
974                Clause::Leaf(LeafClause {
975                    left: Value::Field(Field::Artists),
976                    operator: Operator::AnyLike,
977                    right: Value::String("bar".to_string())
978                }),
979            ],
980            kind: CompoundKind::Or
981        }), "(title = \"foo\" OR artist ?~ \"bar\")")]
982        #[case(Err(pom::Error::Custom { message: "failed to parse compound clause".to_string(), position: 0, inner: Some(Box::new(pom::Error::Incomplete))}), "(title = \"foo\"")]
983        fn test_compound_clause_parse(
984            #[case] expected: Result<CompoundClause, pom::Error>,
985            #[case] s: &str,
986        ) {
987            let parsed = compound().parse(s.as_bytes());
988            assert_eq!(parsed, expected);
989            if let Ok(clause) = parsed {
990                let compiled = clause.compile(Context::Storage);
991                assert_eq!(compiled, s);
992            }
993        }
994
995        #[rstest]
996        // we know that each part of a clause is parsed and compiled correctly, so we only need to test the combination
997        #[case(Ok(Query {
998            root: Clause::Compound(CompoundClause {
999                clauses: vec![
1000                    Clause::Compound(
1001                        CompoundClause {
1002                            clauses: vec![
1003                                Clause::Leaf(LeafClause {
1004                                    left: Value::Field(Field::Title),
1005                                    operator: Operator::Equal,
1006                                    right: Value::String("foo".to_string())
1007                                }),
1008                                Clause::Compound(CompoundClause {
1009                                    clauses: vec![
1010                                        Clause::Leaf(LeafClause {
1011                                            left: Value::Field(Field::Artists),
1012                                            operator: Operator::Contains,
1013                                            right: Value::String("bar".to_string())
1014                                        }),
1015                                        Clause::Leaf(LeafClause {
1016                                            left: Value::Field(Field::Album),
1017                                            operator: Operator::Equal,
1018                                            right: Value::String("baz".to_string())
1019                                        }),
1020                                    ],
1021                                    kind: CompoundKind::Or
1022                                }),
1023                            ],
1024                            kind: CompoundKind::And
1025                        }
1026                    ),
1027                    Clause::Leaf(LeafClause {
1028                        left: Value::Field(Field::ReleaseYear),
1029                        operator: Operator::GreaterThan,
1030                        right: Value::Int(2020)
1031                    }),
1032                ],
1033                kind: CompoundKind::And
1034            })
1035        },), "((title = \"foo\" AND (artist CONTAINS \"bar\" OR album = \"baz\")) AND release_year > 2020)")]
1036        fn test_query_parse(#[case] expected: Result<Query, pom::Error>, #[case] s: &str) {
1037            let parsed = query().parse(s.as_bytes());
1038            assert_eq!(parsed, expected);
1039            if let Ok(query) = parsed {
1040                let compiled = query.compile(Context::Storage);
1041                assert_eq!(compiled, s);
1042            }
1043        }
1044    }
1045}