1use std::str::FromStr;
2
3use pest::Parser;
4
5use super::{
6 grammar::{DEFAULT_FIELD, EventPlatformQuery, QueryVisitor},
7 node::QueryNode,
8};
9
10pub type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
11
12impl FromStr for QueryNode {
14 type Err = Error;
15
16 fn from_str(query: &str) -> Result<Self, Self::Err> {
17 let clean_query = query.trim();
19 Ok(if clean_query.is_empty() {
21 Self::MatchAllDocs
22 } else {
23 let mut ast = EventPlatformQuery::parse(super::grammar::Rule::queryroot, query)?;
25 let rootquery = ast.next().ok_or("Unable to find root query")?;
26 QueryVisitor::visit_queryroot(rootquery, DEFAULT_FIELD)
27 })
28 }
29}
30
31#[cfg(test)]
32mod tests {
33 use super::super::node::{BooleanType, Comparison, ComparisonValue, QueryNode};
34 use super::*;
35
36 fn parse(s: &str) -> QueryNode {
37 s.parse()
38 .unwrap_or_else(|error| panic!("Unable to parse {s:?}: {error}."))
39 }
40
41 #[test]
42 fn parses_basic_string() {
43 parse("foo:bar");
44 }
45
46 #[test]
47 fn parses_whitespace() {
48 let cases = [" ", " ", "\t"];
49 for query in cases.iter() {
50 let res = parse(query);
51 assert!(
52 matches!(res, QueryNode::MatchAllDocs),
53 "Failed to parse MatchAllDocs query out of empty input"
54 );
55 }
56 }
57
58 #[test]
59 fn parses_unquoted_default_field_query() {
60 let cases = ["foo"];
61 for query in cases.iter() {
62 let res = parse(query);
63 assert!(
64 matches!(res,
65 QueryNode::AttributeTerm { ref attr, ref value }
66 if attr == DEFAULT_FIELD && value == "foo"),
67 "Unable to properly parse '{query:?}' - got {res:?}"
68 );
69 }
70 }
71
72 #[test]
73 fn parses_quoted_default_field_query() {
74 let cases = ["\"foo bar\""];
75 for query in cases.iter() {
76 let res = parse(query);
77 assert!(
78 matches!(res,
79 QueryNode::QuotedAttribute { ref attr, ref phrase }
80 if attr == DEFAULT_FIELD && phrase == "foo bar"),
81 "Unable to properly parse '{query:?}' - got {res:?}"
82 );
83 }
84 }
85
86 #[test]
87 fn parses_attribute_term_query() {
88 let cases = ["foo:bar", "foo:(bar)", "foo:b\\ar", "foo:(b\\ar)"];
89 for query in cases.iter() {
90 let res = parse(query);
91 assert!(
92 matches!(res,
93 QueryNode::AttributeTerm { ref attr, ref value }
94 if attr == "foo" && value == "bar"),
95 "Unable to properly parse '{query:?}' - got {res:?}"
96 );
97 }
98 }
99
100 #[test]
101 fn parses_numeric_attribute_term_query() {
102 let cases = ["foo:10"];
103 for query in cases.iter() {
104 let res = parse(query);
105 assert!(
106 matches!(res,
107 QueryNode::AttributeTerm { ref attr, ref value }
108 if attr == "foo" && value == "10"),
109 "Unable to properly parse '{query:?}' - got {res:?}"
110 );
111 }
112 }
113
114 #[test]
115 fn parses_attribute_term_query_with_escapes() {
116 let cases = ["foo:bar\\:baz", "fo\\o:bar\\:baz"];
117 for query in cases.iter() {
118 let res = parse(query);
119 assert!(
120 matches!(res,
121 QueryNode::AttributeTerm { ref attr, ref value }
122 if attr == "foo" && value == "bar:baz"),
123 "Unable to properly parse '{query:?}' - got {res:?}"
124 );
125 }
126 }
127
128 #[test]
129 fn parses_attribute_comparison_query_with_escapes() {
130 let cases = ["foo:<4.12345E-4", "foo:<4.12345E\\-4"];
131 for query in cases.iter() {
132 let res = parse(query);
133 assert!(
134 matches!(res,
135 QueryNode::AttributeComparison { ref attr, value: ComparisonValue::Float(ref compvalue), comparator: Comparison::Lt }
136 if attr == "foo" && (*compvalue - 4.12345E-4).abs() < 0.001),
137 "Unable to properly parse '{query:?}' - got {res:?}"
138 );
139 }
140 }
141
142 #[test]
143 fn parses_and_normalizes_multiterm_query() {
144 let cases = ["foo bar", "foo bar"];
145 for query in cases.iter() {
146 let res = parse(query);
147 assert!(
148 matches!(res,
149 QueryNode::AttributeTerm { ref attr, ref value }
150 if attr == DEFAULT_FIELD && value == "foo bar"),
151 "Unable to properly parse '{query:?}' - got {res:?}"
152 );
153 }
154 }
155
156 #[test]
157 fn parses_multiple_multiterm_query() {
158 let cases = ["foo bar baz AND qux quux quuz"];
159 for query in cases.iter() {
160 let res = parse(query);
161 assert!(
162 matches!(res, QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
163 matches!(nodes[0], QueryNode::AttributeTerm { ref attr, ref value } if attr == "_default_" && value == "foo bar")
164 && matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == "_default_" && value == "baz")
165 && matches!(nodes[2], QueryNode::AttributeTerm { ref attr, ref value } if attr == "_default_" && value == "qux")
166 && matches!(nodes[3], QueryNode::AttributeTerm { ref attr, ref value } if attr == "_default_" && value == "quux quuz")
167 ),
168 "Unable to properly parse '{query:?}' - got {res:?}"
169 );
170 }
171 }
172
173 #[test]
174 fn parses_negated_attribute_term_query() {
175 let cases = ["-foo:bar", "- foo:bar", "NOT foo:bar"];
176 for query in cases.iter() {
177 let res = parse(query);
178 if let QueryNode::NegatedNode { ref node } = res
179 && let QueryNode::AttributeTerm {
180 ref attr,
181 ref value,
182 } = **node
183 && attr == "foo"
184 && value == "bar"
185 {
186 continue;
187 }
188 panic!("Unable to properly parse '{query:?}' - got {res:?}")
189 }
190 }
191
192 #[test]
193 fn parses_quoted_attribute_term_query() {
194 let cases = ["foo:\"bar baz\""];
195 for query in cases.iter() {
196 let res = parse(query);
197 assert!(
198 matches!(res,
199 QueryNode::QuotedAttribute { ref attr, ref phrase }
200 if attr == "foo" && phrase == "bar baz"),
201 "Unable to properly parse '{query:?}' - got {res:?}"
202 );
203 }
204 }
205
206 #[test]
207 fn parses_attribute_prefix_query() {
208 let cases = ["foo:ba*"];
209 for query in cases.iter() {
210 let res = parse(query);
211 assert!(
212 matches!(res,
213 QueryNode::AttributePrefix { ref attr, ref prefix }
214 if attr == "foo" && prefix == "ba"), "Unable to properly parse '{query:?}' - got {res:?}"
216 );
217 }
218 }
219
220 #[test]
221 fn parses_attribute_wildcard_query() {
222 let cases = ["foo:b*r"];
223 for query in cases.iter() {
224 let res = parse(query);
225 assert!(
226 matches!(res,
227 QueryNode::AttributeWildcard { ref attr, ref wildcard }
228 if attr == "foo" && wildcard == "b*r"),
229 "Unable to properly parse '{query:?}' - got {res:?}"
230 );
231 }
232 }
233
234 #[test]
235 fn parses_attribute_wildcard_query_with_trailing_question_mark() {
236 let cases = ["foo:ba?"];
237 for query in cases.iter() {
238 let res = parse(query);
239 assert!(
240 matches!(res,
241 QueryNode::AttributeWildcard { ref attr, ref wildcard }
242 if attr == "foo" && wildcard == "ba?"),
243 "Unable to properly parse '{query:?}' - got {res:?}"
244 );
245 }
246 }
247
248 #[test]
249 fn parses_attribute_wildcard_query_with_leading_wildcard() {
250 let cases = ["foo:*ar"];
251 for query in cases.iter() {
252 let res = parse(query);
253 assert!(
254 matches!(res,
255 QueryNode::AttributeWildcard { ref attr, ref wildcard }
256 if attr == "foo" && wildcard == "*ar"),
257 "Unable to properly parse '{query:?}' - got {res:?}"
258 );
259 }
260 }
261
262 #[test]
263 fn parses_non_numeric_attribute_comparison_query() {
264 let cases = ["foo:>=bar"];
265 for query in cases.iter() {
266 let res = parse(query);
267 assert!(
268 matches!(res,
269 QueryNode::AttributeComparison {
270 ref attr,
271 value: ComparisonValue::String(ref cval),
272 comparator: Comparison::Gte
273 } if attr == "foo" && cval == "bar"),
274 "Unable to properly parse '{query:?}' - got {res:?}'"
275 );
276 }
277 }
278
279 #[test]
280 fn parses_numeric_attribute_range_query() {
281 let cases = ["foo:[10 TO 20]"];
282 for query in cases.iter() {
283 let res = parse(query);
284 assert!(
285 matches!(res,
286 QueryNode::AttributeRange {
287 ref attr,
288 lower: ComparisonValue::Integer(ref lstr),
289 lower_inclusive: true,
290 upper: ComparisonValue::Integer(ref ustr),
291 upper_inclusive: true
292 } if attr == "foo" && *lstr == 10 && *ustr == 20),
293 "Unable to properly parse '{query:?}' - got {res:?}'"
294 );
295 }
296 }
297
298 #[test]
299 fn parses_non_numeric_attribute_range_query() {
300 let cases = ["foo:{bar TO baz}"];
301 for query in cases.iter() {
302 let res = parse(query);
303 assert!(
304 matches!(res,
305 QueryNode::AttributeRange {
306 ref attr,
307 lower: ComparisonValue::String(ref lstr),
308 lower_inclusive: false,
309 upper: ComparisonValue::String(ref ustr),
310 upper_inclusive: false
311 } if attr == "foo" && lstr == "bar" && ustr == "baz"),
312 "Unable to properly parse '{query:?}' - got {res:?}'"
313 );
314 }
315 }
316
317 #[test]
318 fn parses_attribute_range_query_with_open_endpoints() {
319 let cases = ["foo:[* TO *]"];
320 for query in cases.iter() {
321 let res = parse(query);
322 assert!(
323 matches!(res,
324 QueryNode::AttributeRange {
325 ref attr,
326 lower: ComparisonValue::Unbounded,
327 lower_inclusive: true,
328 upper: ComparisonValue::Unbounded,
329 upper_inclusive: true
330 } if attr == "foo"),
331 "Unable to properly parse '{query:?}' - got {res:?}'"
332 );
333 }
334 }
335
336 #[test]
337 fn parses_attribute_range_query_with_fake_wildcards() {
338 let cases = ["foo:[ba* TO b*z]"];
339 for query in cases.iter() {
340 let res = parse(query);
341 assert!(
342 matches!(res,
343 QueryNode::AttributeRange {
344 ref attr,
345 lower: ComparisonValue::String(ref lstr),
346 lower_inclusive: true,
347 upper: ComparisonValue::String(ref ustr),
348 upper_inclusive: true
349 } if attr == "foo" && lstr == "ba*" && ustr == "b*z"),
350 "Unable to properly parse '{query:?}' - got {res:?}'"
351 );
352 }
353 }
354
355 #[test]
356 fn parses_attribute_exists_query() {
357 let cases = ["_exists_:foo", "_exists_:\"foo\""];
358 for query in cases.iter() {
359 let res = parse(query);
360 assert!(
361 matches!(res,
362 QueryNode::AttributeExists { ref attr }
363 if attr == "foo"),
364 "Unable to properly parse '{query:?}' - got {res:?}"
365 );
366 }
367 }
368
369 #[test]
370 fn parses_attribute_exists_query_with_escapes() {
371 let cases = ["_exists_:foo\\ bar"];
372 for query in cases.iter() {
373 let res = parse(query);
374 assert!(
375 matches!(res,
376 QueryNode::AttributeExists { ref attr }
377 if attr == "foo bar"),
378 "Unable to properly parse '{query:?}' - got {res:?}"
379 );
380 }
381 }
382
383 #[test]
384 fn parses_star_as_wildcard_not_exists() {
385 let cases = ["foo:*"];
386 for query in cases.iter() {
387 let res = parse(query);
388 assert!(
389 matches!(res,
390 QueryNode::AttributeWildcard { ref attr, ref wildcard }
391 if attr == "foo" && wildcard == "*"),
392 "Unable to properly parse '{query:?}' - got {res:?}"
393 );
394 }
395 }
396
397 #[test]
398 fn parses_attribute_missing_query() {
399 let cases = ["_missing_:foo", "_missing_:\"foo\""];
400 for query in cases.iter() {
401 let res = parse(query);
402 assert!(
403 matches!(res,
404 QueryNode::AttributeMissing { ref attr }
405 if attr == "foo"),
406 "Unable to properly parse '{query:?}' - got {res:?}"
407 );
408 }
409 }
410
411 #[test]
412 fn parses_attribute_missing_query_with_escapes() {
413 let cases = ["_missing_:foo\\ bar"];
414 for query in cases.iter() {
415 let res = parse(query);
416 assert!(
417 matches!(res,
418 QueryNode::AttributeMissing { ref attr }
419 if attr == "foo bar"),
420 "Unable to properly parse '{query:?}' - got {res:?}"
421 );
422 }
423 }
424
425 #[test]
426 fn parses_match_all_docs_query() {
427 let cases = ["*:*", "*", "_default_:*", "foo:(*:*)"];
428 for query in cases.iter() {
429 let res = parse(query);
430 assert!(
431 matches!(res, QueryNode::MatchAllDocs),
432 "Failed to parse '{query:?}' as MatchAllDocs, got {res:?}"
433 );
434 }
435 }
436
437 #[test]
438 fn parses_all_as_wildcard() {
439 let cases = ["_all:*"];
440 for query in cases.iter() {
441 let res = parse(query);
442 assert!(
443 matches!(res,
444 QueryNode::AttributeWildcard { ref attr, ref wildcard }
445 if attr == "_all" && wildcard == "*"),
446 "Unable to properly parse '{query:?}' - got {res:?}"
447 );
448 }
449 }
450
451 #[test]
452 fn parses_match_no_docs_query() {
453 let cases = [
454 "NOT *:*",
455 "NOT *",
456 "NOT _default_:*",
457 "NOT foo:(*:*)",
458 "foo:(NOT *:*)",
459 ];
460 for query in cases.iter() {
461 let res = parse(query);
462 assert!(
463 matches!(res, QueryNode::MatchNoDocs),
464 "Failed to parse '{query:?}' as MatchNoDocs, got {res:?}"
465 );
466 }
467 }
468
469 #[test]
470 fn parses_boolean_nodes_with_implicit_operators() {
471 let cases = ["foo:bar baz:qux quux:quuz"];
472 for query in cases.iter() {
473 let res = parse(query);
474 assert!(
475 matches!(res, QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
476 matches!(nodes[0], QueryNode::AttributeTerm { ref attr, ref value } if attr == "foo" && value == "bar")
477 && matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == "baz" && value == "qux")
478 && matches!(nodes[2], QueryNode::AttributeTerm { ref attr, ref value } if attr == "quux" && value == "quuz")
479 ),
480 "Unable to properly parse '{query:?}' - got {res:?}"
481 );
482 }
483 }
484
485 #[test]
486 fn parses_boolean_nodes_with_implicit_operators_and_negated_clauses() {
487 let cases = [
488 "NOT foo:bar baz:qux NOT quux:quuz",
489 "NOT foo:bar baz:qux -quux:quuz",
490 "-foo:bar baz:qux NOT quux:quuz",
491 "-foo:bar baz:qux -quux:quuz",
492 ];
493 for query in cases.iter() {
494 let res = parse(query);
495 assert!(
496 matches!(res, QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
497 matches!(nodes[0], QueryNode::NegatedNode { ref node } if matches!(**node, QueryNode::AttributeTerm {ref attr, ref value } if attr == "foo" && value == "bar"))
498 && matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == "baz" && value == "qux")
499 && matches!(nodes[2], QueryNode::NegatedNode { ref node } if matches!(**node, QueryNode::AttributeTerm {ref attr, ref value } if attr == "quux" && value == "quuz"))
500 ),
501 "Unable to properly parse '{query:?}' - got {res:?}"
502 );
503 }
504 }
505
506 #[test]
507 fn parses_boolean_nodes_with_or_not() {
508 let cases = [
509 "foo:bar OR -baz:qux quux:quuz",
510 "foo:bar OR NOT baz:qux quux:quuz",
511 "foo:bar OR -baz:qux AND quux:quuz",
512 "foo:bar OR NOT baz:qux AND quux:quuz",
513 ];
514 for query in cases.iter() {
515 let res = parse(query);
516 assert!(
517 matches!(res, QueryNode::Boolean { oper: BooleanType::Or, ref nodes } if
518 matches!(nodes[0], QueryNode::AttributeTerm {ref attr, ref value } if attr == "foo" && value == "bar")
519 && matches!(nodes[1], QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
520 matches!(nodes[0], QueryNode::NegatedNode { ref node } if matches!(**node, QueryNode::AttributeTerm {ref attr, ref value } if attr == "baz" && value == "qux") &&
521 matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == "quux" && value == "quuz"))
522 )
523 ),
524 "Unable to properly parse '{query:?}' - got {res:?}"
525 );
526 }
527 }
528
529 #[test]
530 fn parses_boolean_nodes_with_implicit_or_explicit_operators() {
531 let cases = [
532 "foo:bar OR baz:qux quux:quuz",
533 "foo:bar || baz:qux quux:quuz",
534 "foo:bar OR baz:qux AND quux:quuz",
535 "foo:bar || baz:qux && quux:quuz",
536 ];
537 for query in cases.iter() {
538 let res = parse(query);
539 assert!(
540 matches!(res, QueryNode::Boolean { oper: BooleanType::Or, ref nodes } if
541 matches!(nodes[0], QueryNode::AttributeTerm {ref attr, ref value } if attr == "foo" && value == "bar")
542 && matches!(nodes[1], QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
543 matches!(nodes[0], QueryNode::AttributeTerm { ref attr, ref value } if attr == "baz" && value == "qux") &&
544 matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == "quux" && value == "quuz")
545 )
546 ),
547 "Unable to properly parse '{query:?}' - got {res:?}"
548 );
549 }
550 }
551
552 #[test]
553 fn parses_nested_boolean_query_node() {
554 let cases = ["foo:bar (baz:qux quux:quuz)"];
555 for query in cases.iter() {
556 let res = parse(query);
557 assert!(
558 matches!(res, QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
559 matches!(nodes[0], QueryNode::AttributeTerm {ref attr, ref value } if attr == "foo" && value == "bar")
560 && matches!(nodes[1], QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
561 matches!(nodes[0], QueryNode::AttributeTerm { ref attr, ref value } if attr == "baz" && value == "qux") &&
562 matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == "quux" && value == "quuz")
563 )
564 ),
565 "Unable to properly parse '{query:?}' - got {res:?}"
566 );
567 }
568 }
569
570 #[test]
571 fn parses_nested_boolean_query_node_with_or() {
572 let cases = ["(foo:bar OR baz:qux) quux:quuz"];
573 for query in cases.iter() {
574 let res = parse(query);
575
576 assert!(
577 matches!(res, QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
578 matches!(nodes[0], QueryNode::Boolean { oper: BooleanType::Or, ref nodes } if
579 matches!(nodes[0], QueryNode::AttributeTerm { ref attr, ref value } if attr == "foo" && value == "bar") &&
580 matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == "baz" && value == "qux")
581 ) && matches!(nodes[1], QueryNode::AttributeTerm {ref attr, ref value } if attr == "quux" && value == "quuz")),
582 "Unable to properly parse '{query:?}' - got {res:?}"
583 );
584 }
585 }
586
587 #[test]
588 fn parses_negated_parenthesized_default_multiterm_query() {
589 let cases = ["NOT (foo bar)", "-(foo bar)"];
590 for query in cases.iter() {
591 let res = parse(query);
592 if let QueryNode::NegatedNode { ref node } = res
593 && let QueryNode::AttributeTerm {
594 ref attr,
595 ref value,
596 } = **node
597 && attr == DEFAULT_FIELD
598 && value == "foo bar"
599 {
600 continue;
601 }
602 panic!("Unable to properly parse '{query:?}' - got {res:?}")
603 }
604 }
605
606 #[test]
607 fn parses_multiterm_with_leading_not_without_parens() {
608 let cases = ["NOT foo bar", "- foo bar"]; for query in cases.iter() {
610 let res = parse(query);
611
612 assert!(
613 matches!(res, QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
614 matches!(nodes[0], QueryNode::NegatedNode { ref node } if matches!(**node, QueryNode::AttributeTerm { ref attr, ref value } if attr == DEFAULT_FIELD && value == "foo"))
615 && matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == DEFAULT_FIELD && value == "bar")),
616 "Unable to properly parse '{query:?}' - got {res:?}"
617 );
618 }
619 }
620
621 #[test]
622 fn parses_negated_parenthesized_fielded_multiterm_query() {
623 let cases = ["NOT foo:(bar baz)", "-foo:(bar baz)"];
624 for query in cases.iter() {
625 let res = parse(query);
626 if let QueryNode::NegatedNode { ref node } = res
627 && let QueryNode::AttributeTerm {
628 ref attr,
629 ref value,
630 } = **node
631 && attr == "foo"
632 && value == "bar baz"
633 {
634 continue;
635 }
636 panic!("Unable to properly parse '{query:?}' - got {res:?}")
637 }
638 }
639}