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