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