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,
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 #[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 #[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 #[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 #[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}