1use std::str::FromStr;
32
33use serde::{Deserialize, Serialize};
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
41pub enum Context {
42 #[default]
45 Storage,
46 Execution,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52pub 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)]
79pub enum Clause {
82 Compound(CompoundClause),
83 Leaf(LeafClause),
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Hash)]
87pub struct CompoundClause {
91 pub clauses: Vec<Clause>,
92 pub kind: CompoundKind,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Hash)]
96pub 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)]
114pub 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 (
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 (
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 (
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 (
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)]
205pub 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)]
214pub enum Field {
216 Title,
218 Artists,
219 Album,
220 AlbumArtists,
221 Genre,
222 ReleaseYear,
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
226pub enum Operator {
228 Equal,
230 NotEqual,
231 AnyEqual,
232 AllEqual,
233 GreaterThan,
234 GreaterThanOrEqual,
235 LessThan,
236 LessThanOrEqual,
237 Like,
239 NotLike,
240 AnyLike,
241 AllLike,
242 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 #[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 #[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 #[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 #[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}