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,
593        <T as std::str::FromStr>::Err: PartialEq,
594    {
595        let parsed = T::from_str(input);
596        assert_eq!(parsed, Ok(expected));
597    }
598}
599
600macro_rules! impl_from_str {
601    ($(($t:ty, $p:expr)),*) => {
602        $(
603            impl std::str::FromStr for $t {
604                type Err = pom::Error;
605
606                #[inline]
607                fn from_str(s: &str) -> Result<Self, Self::Err> {
608                    $p.parse(s.as_bytes())
609                }
610            }
611        )*
612    };
613}
614
615impl_from_str!(
616    (Operator, parser::operator()),
617    (Field, parser::field()),
618    (Value, parser::value()),
619    (LeafClause, parser::leaf()),
620    (CompoundClause, parser::compound()),
621    (Clause, parser::clause()),
622    (Query, parser::query())
623);
624
625mod parser {
626    use std::str::FromStr;
627
628    use pom::parser::{call, end, list, none_of, one_of, seq, sym, Parser};
629
630    use super::{Clause, CompoundClause, CompoundKind, Field, LeafClause, Operator, Query, Value};
631
632    pub fn query<'a>() -> Parser<'a, u8, Query> {
633        clause().map(|root| Query { root }).name("query") - end()
634    }
635
636    pub fn clause<'a>() -> Parser<'a, u8, Clause> {
637        compound().map(Clause::Compound) | leaf().map(Clause::Leaf).name("clause")
638    }
639
640    pub fn compound<'a>() -> Parser<'a, u8, CompoundClause> {
641        (sym(b'(')
642            * space()
643            * (call(clause) - space() + (seq(b"AND") | seq(b"OR")) - space() + call(clause)).map(
644                |((left, sep), right)| CompoundClause {
645                    clauses: vec![left, right],
646                    kind: match sep {
647                        b"AND" => CompoundKind::And,
648                        b"OR" => CompoundKind::Or,
649                        _ => unreachable!(),
650                    },
651                },
652            )
653            - space()
654            - sym(b')'))
655        .name("compound clause")
656    }
657
658    pub fn leaf<'a>() -> Parser<'a, u8, LeafClause> {
659        (value() - space() + operator() - space() + value())
660            .convert(|((left, operator), right)| {
661                let parsed = LeafClause {
662                    left,
663                    operator,
664                    right,
665                };
666                if parsed.has_valid_operator() {
667                    Ok(parsed)
668                } else {
669                    Err(pom::Error::Conversion {
670                        position: 0,
671                        message: format!(
672                            "Invalid operator ({op}) for values: {left:?}, {right:?}",
673                            left = parsed.left,
674                            op = parsed.operator,
675                            right = parsed.right
676                        ),
677                    })
678                }
679            })
680            .name("leaf clause")
681    }
682
683    pub fn value<'a>() -> Parser<'a, u8, Value> {
684        (string().map(Value::String)
685            | int().map(Value::Int)
686            | set().map(Value::Set)
687            | field().map(Value::Field))
688        .name("value")
689    }
690
691    pub fn field<'a>() -> Parser<'a, u8, Field> {
692        (seq(b"title").map(|_| Field::Title)
693            | seq(b"artist").map(|_| Field::Artists)
694            | seq(b"album_artist").map(|_| Field::AlbumArtists)
695            | seq(b"album").map(|_| Field::Album)
696            | seq(b"genre").map(|_| Field::Genre)
697            | seq(b"release_year").map(|_| Field::ReleaseYear))
698        .name("field")
699    }
700
701    pub fn operator<'a>() -> Parser<'a, u8, Operator> {
702        (seq(b"!=").map(|_| Operator::NotEqual)
703            | seq(b"?=").map(|_| Operator::AnyEqual)
704            | seq(b"*=").map(|_| Operator::AllEqual)
705            | seq(b"=").map(|_| Operator::Equal)
706            | seq(b">=").map(|_| Operator::GreaterThanOrEqual)
707            | seq(b">").map(|_| Operator::GreaterThan)
708            | seq(b"<=").map(|_| Operator::LessThanOrEqual)
709            | seq(b"<").map(|_| Operator::LessThan)
710            | seq(b"!~").map(|_| Operator::NotLike)
711            | seq(b"?~").map(|_| Operator::AnyLike)
712            | seq(b"*~").map(|_| Operator::AllLike)
713            | seq(b"~").map(|_| Operator::Like)
714            | seq(b"NOTINSIDE").map(|_| Operator::NotInside)
715            | seq(b"ALLINSIDE").map(|_| Operator::AllInside)
716            | seq(b"ANYINSIDE").map(|_| Operator::AnyInside)
717            | seq(b"NONEINSIDE").map(|_| Operator::NoneInside)
718            | seq(b"INSIDE").map(|_| Operator::Inside)
719            | seq(b"NOT IN").map(|_| Operator::NotIn)
720            | seq(b"IN").map(|_| Operator::In)
721            | seq(b"CONTAINSNOT").map(|_| Operator::ContainsNot)
722            | seq(b"CONTAINSALL").map(|_| Operator::ContainsAll)
723            | seq(b"CONTAINSANY").map(|_| Operator::ContainsAny)
724            | seq(b"CONTAINSNONE").map(|_| Operator::ContainsNone)
725            | seq(b"CONTAINS").map(|_| Operator::Contains))
726        .name("operator")
727    }
728
729    pub fn string<'a>() -> Parser<'a, u8, String> {
730        let string_pf = |quote_sym, escaped_quote| {
731            let special_char = sym(b'\\')
732                | sym(b'/')
733                | sym(quote_sym)
734                | sym(b'b').map(|_| b'\x08')
735                | sym(b'f').map(|_| b'\x0C')
736                | sym(b'n').map(|_| b'\n')
737                | sym(b'r').map(|_| b'\r')
738                | sym(b't').map(|_| b'\t');
739            let escape_sequence = sym(b'\\') * special_char;
740            let char_string = (none_of(escaped_quote) | escape_sequence)
741                .repeat(1..)
742                .convert(String::from_utf8);
743
744            sym(quote_sym) * char_string.repeat(0..) - sym(quote_sym)
745        };
746        let string = string_pf(b'"', b"\\\"") | string_pf(b'\'', b"\\'");
747        string.map(|strings| strings.concat()).name("string")
748    }
749
750    pub fn int<'a>() -> Parser<'a, u8, i64> {
751        let number = sym(b'-').opt() + one_of(b"0123456789").repeat(1..);
752        number
753            .collect()
754            .convert(std::str::from_utf8)
755            .convert(i64::from_str)
756            .name("int")
757    }
758
759    pub fn set<'a>() -> Parser<'a, u8, Vec<Value>> {
760        let elems = list(call(value), sym(b',') * space());
761        (sym(b'[') * space() * elems - sym(b']')).name("set")
762    }
763
764    pub fn space<'a>() -> Parser<'a, u8, ()> {
765        one_of(b" \t\r\n").repeat(0..).discard().name("space")
766    }
767
768    #[cfg(test)]
769    mod tests {
770        use crate::db::schemas::dynamic::query::Context;
771
772        use super::super::Compile;
773        use super::*;
774        use pretty_assertions::assert_eq;
775        use rstest::rstest;
776
777        #[rstest]
778        #[case(Ok(Operator::Equal), "=")]
779        #[case(Ok(Operator::NotEqual), "!=")]
780        #[case(Ok(Operator::AnyEqual), "?=")]
781        #[case(Ok(Operator::AllEqual), "*=")]
782        #[case(Ok(Operator::GreaterThan), ">")]
783        #[case(Ok(Operator::GreaterThanOrEqual), ">=")]
784        #[case(Ok(Operator::LessThan), "<")]
785        #[case(Ok(Operator::LessThanOrEqual), "<=")]
786        #[case(Ok(Operator::Like), "~")]
787        #[case(Ok(Operator::NotLike), "!~")]
788        #[case(Ok(Operator::AnyLike), "?~")]
789        #[case(Ok(Operator::AllLike), "*~")]
790        #[case(Ok(Operator::Inside), "INSIDE")]
791        #[case(Ok(Operator::NotInside), "NOTINSIDE")]
792        #[case(Ok(Operator::AllInside), "ALLINSIDE")]
793        #[case(Ok(Operator::AnyInside), "ANYINSIDE")]
794        #[case(Ok(Operator::NoneInside), "NONEINSIDE")]
795        #[case(Ok(Operator::In), "IN")]
796        #[case(Ok(Operator::NotIn), "NOT IN")]
797        #[case(Ok(Operator::Contains), "CONTAINS")]
798        #[case(Ok(Operator::ContainsNot), "CONTAINSNOT")]
799        #[case(Ok(Operator::ContainsAll), "CONTAINSALL")]
800        #[case(Ok(Operator::ContainsAny), "CONTAINSANY")]
801        #[case(Ok(Operator::ContainsNone), "CONTAINSNONE")]
802        #[case(
803            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 }))}),
804            "invalid"
805        )]
806        fn test_operator_parse_compile(
807            #[case] expected: Result<Operator, pom::Error>,
808            #[case] s: &str,
809        ) {
810            let parsed = operator().parse(s.as_bytes());
811            assert_eq!(parsed, expected);
812            if let Ok(operator) = parsed {
813                let compiled = operator.compile(Context::Storage);
814                assert_eq!(compiled, s);
815            }
816        }
817
818        #[rstest]
819        #[case(Ok(Field::Title), "title")]
820        #[case(Ok(Field::Artists), "artist")]
821        #[case(Ok(Field::Album), "album")]
822        #[case(Ok(Field::AlbumArtists), "album_artist")]
823        #[case(Ok(Field::Genre), "genre")]
824        #[case(Ok(Field::ReleaseYear), "release_year")]
825        #[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")]
826        fn test_field_parse_compile(#[case] expected: Result<Field, pom::Error>, #[case] s: &str) {
827            let parsed = field().parse(s.as_bytes());
828            assert_eq!(parsed, expected);
829            if let Ok(field) = parsed {
830                let compiled = field.compile(Context::Storage);
831                assert_eq!(compiled, s);
832            }
833        }
834
835        #[rstest]
836        #[case(Ok(Value::String("foo".to_string())), "\"foo\"")]
837        #[case(Ok(Value::Int(42)), "42")]
838        #[case(Ok(Value::Set(vec![Value::String("foo".to_string()), Value::Int(42)])), "[\"foo\", 42]")]
839        #[case::nested(
840            Ok(Value::Set(vec![
841                Value::String("foo".to_string()),
842                Value::Set(vec![Value::String("bar".to_string()), Value::Int(42)])
843                ])),
844                "[\"foo\", [\"bar\", 42]]"
845            )]
846        #[case(Ok(Value::Field(Field::Title)), "title")]
847        #[case(Ok(Value::Field(Field::Artists)), "artist")]
848        #[case(Ok(Value::Field(Field::Album)), "album")]
849        #[case(Ok(Value::Field(Field::AlbumArtists)), "album_artist")]
850        #[case(Ok(Value::Field(Field::Genre)), "genre")]
851        #[case(Ok(Value::Field(Field::ReleaseYear)), "release_year")]
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: 91".to_string(), position: 0 }))}), "[foo, 42")]
854        #[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")]
855        fn test_value_parse_compile(#[case] expected: Result<Value, pom::Error>, #[case] s: &str) {
856            let parsed = value().parse(s.as_bytes());
857            assert_eq!(parsed, expected);
858            if let Ok(value) = parsed {
859                let compiled = value.compile(Context::Storage);
860                assert_eq!(compiled, s);
861            }
862        }
863
864        #[rstest]
865        #[case(Ok(Value::String("foo bar".to_string())), "\"foo bar\"")]
866        #[case(Ok(Value::String("foo bar".to_string())), "'foo bar'")]
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: 34".to_string(), position: 0 }))}), "\"foo")]
868        #[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")]
869        fn test_value_parse_string(#[case] expected: Result<Value, pom::Error>, #[case] s: &str) {
870            let parsed = value().parse(s.as_bytes());
871            assert_eq!(parsed, expected);
872        }
873
874        #[rstest]
875        // we know that each part of a clause is parsed and compiled correctly, so we only need to test the combination
876        #[case(Ok(LeafClause {
877            left: Value::Field(Field::Title),
878            operator: Operator::Equal,
879            right: Value::String("foo".to_string())
880        }), "title = \"foo\"")]
881        #[case(Ok(LeafClause {
882            left: Value::Field(Field::Title),
883            operator: Operator::Equal,
884            right: Value::Int(42)
885        }), "title = 42")]
886        #[case(Ok(LeafClause {
887            left: Value::Field(Field::Title),
888            operator: Operator::Inside,
889            right: Value::Set(vec![Value::String("foo".to_string()), Value::Int(42)])
890        }), "title INSIDE [\"foo\", 42]")]
891        #[case(Err(
892            pom::Error::Custom {
893                message: "failed to parse leaf clause".to_string(),
894                position: 0,
895                inner: Some(Box::new(pom::Error::
896                    Conversion {
897                        message: "Conversion error: Conversion { message: \"Invalid operator (=) for values: Field(Title), Field(Artists)\", position: 0 }".to_string(),
898                        position: 0,
899                    }
900                )),
901            }
902        ), "title = artist")]
903        #[case(Err(pom::Error::Custom{message:"failed to parse operator".to_string(),position:5, inner:Some(Box::new(pom::Error::Incomplete))}), "title")]
904        #[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\"")]
905        #[case(Err(pom::Error::Custom{message:"failed to parse field".to_string(),position:8, inner:Some(Box::new(pom::Error::Incomplete))}), "title = ")]
906        #[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\"")]
907        // special cases
908        #[case::left_has_spaces(Ok(LeafClause {
909                left: Value::String("foo bar".to_string()),
910                operator: Operator::Equal,
911                right: Value::Int(42)
912            }), "\"foo bar\" = 42")]
913        #[case::operator_has_spaces(Ok(LeafClause {
914            left: Value::Field(Field::Title),
915            operator: Operator::NotIn,
916            right: Value::String("foo bar".to_string())
917        }), "title NOT IN \"foo bar\"")]
918        fn test_leaf_clause_parse(
919            #[case] expected: Result<LeafClause, pom::Error>,
920            #[case] s: &str,
921        ) {
922            let parsed = leaf().parse(s.as_bytes());
923            assert_eq!(parsed, expected);
924            if let Ok(clause) = parsed {
925                let compiled = clause.compile(Context::Storage);
926                assert_eq!(compiled, s);
927            }
928        }
929
930        #[rstest]
931        #[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() }))}))]
932        #[case::value_to_value("title = \"foo\"", Ok(LeafClause {
933            left: Value::Field(Field::Title),
934            operator: Operator::Equal,
935            right: Value::String("foo".to_string())
936        }))]
937        #[case::value_to_set("42 IN [\"foo\", 42]", Ok(LeafClause {
938            left: Value::Int(42),
939            operator: Operator::In,
940            right: Value::Set(vec![Value::String("foo".to_string()), Value::Int(42)])
941        }))]
942        #[case::set_to_value("[\"foo\", 42] CONTAINS 42", Ok(LeafClause {
943            left: Value::Set(vec![Value::String("foo".to_string()), Value::Int(42)]),
944            operator: Operator::Contains,
945            right: Value::Int(42)
946        }))]
947        #[case::set_to_set("[\"foo\", 42] CONTAINSALL [\"foo\", 42]", Ok(LeafClause {
948            left: Value::Set(vec![Value::String("foo".to_string()), Value::Int(42)]),
949            operator: Operator::ContainsAll,
950            right: Value::Set(vec![Value::String("foo".to_string()), Value::Int(42)])
951        }))]
952        #[case::string_to_string("\"foo\" IN \"foo\"", Ok(LeafClause {
953            left: Value::String("foo".to_string()),
954            operator: Operator::In,
955            right: Value::String("foo".to_string())
956        }))]
957        fn test_operator_checking(
958            #[case] input: &str,
959            #[case] expected: Result<LeafClause, pom::Error>,
960        ) {
961            let parsed = leaf().parse(input.as_bytes());
962            assert_eq!(parsed, expected);
963        }
964
965        #[rstest]
966        // we know that each part of a clause is parsed and compiled correctly, so we only need to test the combination
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::AllLike,
977                    right: Value::String("bar".to_string())
978                }),
979            ],
980            kind: CompoundKind::And
981        }), "(title = \"foo\" AND artist *~ \"bar\")")]
982        #[case(Ok(CompoundClause {
983            clauses: vec![
984                Clause::Leaf(LeafClause {
985                    left: Value::Field(Field::Title),
986                    operator: Operator::Equal,
987                    right: Value::String("foo".to_string())
988                }),
989                Clause::Leaf(LeafClause {
990                    left: Value::Field(Field::Artists),
991                    operator: Operator::AnyLike,
992                    right: Value::String("bar".to_string())
993                }),
994            ],
995            kind: CompoundKind::Or
996        }), "(title = \"foo\" OR artist ?~ \"bar\")")]
997        #[case(Err(pom::Error::Custom { message: "failed to parse compound clause".to_string(), position: 0, inner: Some(Box::new(pom::Error::Incomplete))}), "(title = \"foo\"")]
998        fn test_compound_clause_parse(
999            #[case] expected: Result<CompoundClause, pom::Error>,
1000            #[case] s: &str,
1001        ) {
1002            let parsed = compound().parse(s.as_bytes());
1003            assert_eq!(parsed, expected);
1004            if let Ok(clause) = parsed {
1005                let compiled = clause.compile(Context::Storage);
1006                assert_eq!(compiled, s);
1007            }
1008        }
1009
1010        #[rstest]
1011        // we know that each part of a clause is parsed and compiled correctly, so we only need to test the combination
1012        #[case(Ok(Query {
1013            root: Clause::Compound(CompoundClause {
1014                clauses: vec![
1015                    Clause::Compound(
1016                        CompoundClause {
1017                            clauses: vec![
1018                                Clause::Leaf(LeafClause {
1019                                    left: Value::Field(Field::Title),
1020                                    operator: Operator::Equal,
1021                                    right: Value::String("foo".to_string())
1022                                }),
1023                                Clause::Compound(CompoundClause {
1024                                    clauses: vec![
1025                                        Clause::Leaf(LeafClause {
1026                                            left: Value::Field(Field::Artists),
1027                                            operator: Operator::Contains,
1028                                            right: Value::String("bar".to_string())
1029                                        }),
1030                                        Clause::Leaf(LeafClause {
1031                                            left: Value::Field(Field::Album),
1032                                            operator: Operator::Equal,
1033                                            right: Value::String("baz".to_string())
1034                                        }),
1035                                    ],
1036                                    kind: CompoundKind::Or
1037                                }),
1038                            ],
1039                            kind: CompoundKind::And
1040                        }
1041                    ),
1042                    Clause::Leaf(LeafClause {
1043                        left: Value::Field(Field::ReleaseYear),
1044                        operator: Operator::GreaterThan,
1045                        right: Value::Int(2020)
1046                    }),
1047                ],
1048                kind: CompoundKind::And
1049            })
1050        },), "((title = \"foo\" AND (artist CONTAINS \"bar\" OR album = \"baz\")) AND release_year > 2020)")]
1051        fn test_query_parse(#[case] expected: Result<Query, pom::Error>, #[case] s: &str) {
1052            let parsed = query().parse(s.as_bytes());
1053            assert_eq!(parsed, expected);
1054            if let Ok(query) = parsed {
1055                let compiled = query.compile(Context::Storage);
1056                assert_eq!(compiled, s);
1057            }
1058        }
1059    }
1060}