use std::str::FromStr;
use pest::Parser;
use super::{
grammar::{DEFAULT_FIELD, EventPlatformQuery, QueryVisitor},
node::QueryNode,
};
pub type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
impl FromStr for QueryNode {
type Err = Error;
fn from_str(query: &str) -> Result<Self, Self::Err> {
let clean_query = query.trim();
Ok(if clean_query.is_empty() {
Self::MatchAllDocs
} else {
let mut ast = EventPlatformQuery::parse(super::grammar::Rule::queryroot, query)?;
let rootquery = ast.next().ok_or("Unable to find root query")?;
QueryVisitor::visit_queryroot(rootquery, DEFAULT_FIELD)
})
}
}
#[cfg(test)]
mod tests {
use super::super::node::{BooleanType, Comparison, ComparisonValue, QueryNode};
use super::*;
fn parse(s: &str) -> QueryNode {
s.parse()
.unwrap_or_else(|error| panic!("Unable to parse {s:?}: {error}."))
}
#[test]
fn parses_basic_string() {
parse("foo:bar");
}
#[test]
fn parses_whitespace() {
let cases = [" ", " ", "\t"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res, QueryNode::MatchAllDocs),
"Failed to parse MatchAllDocs query out of empty input"
);
}
}
#[test]
fn parses_unquoted_default_field_query() {
let cases = ["foo"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeTerm { ref attr, ref value }
if attr == DEFAULT_FIELD && value == "foo"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_quoted_default_field_query() {
let cases = ["\"foo bar\""];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::QuotedAttribute { ref attr, ref phrase }
if attr == DEFAULT_FIELD && phrase == "foo bar"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_attribute_term_query() {
let cases = ["foo:bar", "foo:(bar)", "foo:b\\ar", "foo:(b\\ar)"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeTerm { ref attr, ref value }
if attr == "foo" && value == "bar"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_numeric_attribute_term_query() {
let cases = ["foo:10"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeTerm { ref attr, ref value }
if attr == "foo" && value == "10"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_attribute_term_query_with_escapes() {
let cases = ["foo:bar\\:baz", "fo\\o:bar\\:baz"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeTerm { ref attr, ref value }
if attr == "foo" && value == "bar:baz"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_attribute_comparison_query_with_escapes() {
let cases = ["foo:<4.12345E-4", "foo:<4.12345E\\-4"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeComparison { ref attr, value: ComparisonValue::Float(ref compvalue), comparator: Comparison::Lt }
if attr == "foo" && (*compvalue - 4.12345E-4).abs() < 0.001),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_and_normalizes_multiterm_query() {
let cases = ["foo bar", "foo bar"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeTerm { ref attr, ref value }
if attr == DEFAULT_FIELD && value == "foo bar"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_multiple_multiterm_query() {
let cases = ["foo bar baz AND qux quux quuz"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res, QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
matches!(nodes[0], QueryNode::AttributeTerm { ref attr, ref value } if attr == "_default_" && value == "foo bar")
&& matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == "_default_" && value == "baz")
&& matches!(nodes[2], QueryNode::AttributeTerm { ref attr, ref value } if attr == "_default_" && value == "qux")
&& matches!(nodes[3], QueryNode::AttributeTerm { ref attr, ref value } if attr == "_default_" && value == "quux quuz")
),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_negated_attribute_term_query() {
let cases = ["-foo:bar", "- foo:bar", "NOT foo:bar"];
for query in cases.iter() {
let res = parse(query);
if let QueryNode::NegatedNode { ref node } = res
&& let QueryNode::AttributeTerm {
ref attr,
ref value,
} = **node
&& attr == "foo"
&& value == "bar"
{
continue;
}
panic!("Unable to properly parse '{query:?}' - got {res:?}")
}
}
#[test]
fn parses_quoted_attribute_term_query() {
let cases = ["foo:\"bar baz\""];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::QuotedAttribute { ref attr, ref phrase }
if attr == "foo" && phrase == "bar baz"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_attribute_prefix_query() {
let cases = ["foo:ba*"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributePrefix { ref attr, ref prefix }
if attr == "foo" && prefix == "ba"), "Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_attribute_wildcard_query() {
let cases = ["foo:b*r"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeWildcard { ref attr, ref wildcard }
if attr == "foo" && wildcard == "b*r"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_attribute_wildcard_query_with_trailing_question_mark() {
let cases = ["foo:ba?"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeWildcard { ref attr, ref wildcard }
if attr == "foo" && wildcard == "ba?"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_attribute_wildcard_query_with_leading_wildcard() {
let cases = ["foo:*ar"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeWildcard { ref attr, ref wildcard }
if attr == "foo" && wildcard == "*ar"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_non_numeric_attribute_comparison_query() {
let cases = ["foo:>=bar"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeComparison {
ref attr,
value: ComparisonValue::String(ref cval),
comparator: Comparison::Gte
} if attr == "foo" && cval == "bar"),
"Unable to properly parse '{query:?}' - got {res:?}'"
);
}
}
#[test]
fn parses_numeric_attribute_range_query() {
let cases = ["foo:[10 TO 20]"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeRange {
ref attr,
lower: ComparisonValue::Integer(ref lstr),
lower_inclusive: true,
upper: ComparisonValue::Integer(ref ustr),
upper_inclusive: true
} if attr == "foo" && *lstr == 10 && *ustr == 20),
"Unable to properly parse '{query:?}' - got {res:?}'"
);
}
}
#[test]
fn parses_non_numeric_attribute_range_query() {
let cases = ["foo:{bar TO baz}"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeRange {
ref attr,
lower: ComparisonValue::String(ref lstr),
lower_inclusive: false,
upper: ComparisonValue::String(ref ustr),
upper_inclusive: false
} if attr == "foo" && lstr == "bar" && ustr == "baz"),
"Unable to properly parse '{query:?}' - got {res:?}'"
);
}
}
#[test]
fn parses_attribute_range_query_with_open_endpoints() {
let cases = ["foo:[* TO *]"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeRange {
ref attr,
lower: ComparisonValue::Unbounded,
lower_inclusive: true,
upper: ComparisonValue::Unbounded,
upper_inclusive: true
} if attr == "foo"),
"Unable to properly parse '{query:?}' - got {res:?}'"
);
}
}
#[test]
fn parses_attribute_range_query_with_fake_wildcards() {
let cases = ["foo:[ba* TO b*z]"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeRange {
ref attr,
lower: ComparisonValue::String(ref lstr),
lower_inclusive: true,
upper: ComparisonValue::String(ref ustr),
upper_inclusive: true
} if attr == "foo" && lstr == "ba*" && ustr == "b*z"),
"Unable to properly parse '{query:?}' - got {res:?}'"
);
}
}
#[test]
fn parses_attribute_exists_query() {
let cases = ["_exists_:foo", "_exists_:\"foo\""];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeExists { ref attr }
if attr == "foo"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_attribute_exists_query_with_escapes() {
let cases = ["_exists_:foo\\ bar"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeExists { ref attr }
if attr == "foo bar"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_star_as_wildcard_not_exists() {
let cases = ["foo:*"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeWildcard { ref attr, ref wildcard }
if attr == "foo" && wildcard == "*"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_attribute_missing_query() {
let cases = ["_missing_:foo", "_missing_:\"foo\""];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeMissing { ref attr }
if attr == "foo"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_attribute_missing_query_with_escapes() {
let cases = ["_missing_:foo\\ bar"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeMissing { ref attr }
if attr == "foo bar"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_match_all_docs_query() {
let cases = ["*:*", "*", "_default_:*", "foo:(*:*)"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res, QueryNode::MatchAllDocs),
"Failed to parse '{query:?}' as MatchAllDocs, got {res:?}"
);
}
}
#[test]
fn parses_all_as_wildcard() {
let cases = ["_all:*"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res,
QueryNode::AttributeWildcard { ref attr, ref wildcard }
if attr == "_all" && wildcard == "*"),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_match_no_docs_query() {
let cases = [
"NOT *:*",
"NOT *",
"NOT _default_:*",
"NOT foo:(*:*)",
"foo:(NOT *:*)",
];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res, QueryNode::MatchNoDocs),
"Failed to parse '{query:?}' as MatchNoDocs, got {res:?}"
);
}
}
#[test]
fn parses_boolean_nodes_with_implicit_operators() {
let cases = ["foo:bar baz:qux quux:quuz"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res, QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
matches!(nodes[0], QueryNode::AttributeTerm { ref attr, ref value } if attr == "foo" && value == "bar")
&& matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == "baz" && value == "qux")
&& matches!(nodes[2], QueryNode::AttributeTerm { ref attr, ref value } if attr == "quux" && value == "quuz")
),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_boolean_nodes_with_implicit_operators_and_negated_clauses() {
let cases = [
"NOT foo:bar baz:qux NOT quux:quuz",
"NOT foo:bar baz:qux -quux:quuz",
"-foo:bar baz:qux NOT quux:quuz",
"-foo:bar baz:qux -quux:quuz",
];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res, QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
matches!(nodes[0], QueryNode::NegatedNode { ref node } if matches!(**node, QueryNode::AttributeTerm {ref attr, ref value } if attr == "foo" && value == "bar"))
&& matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == "baz" && value == "qux")
&& matches!(nodes[2], QueryNode::NegatedNode { ref node } if matches!(**node, QueryNode::AttributeTerm {ref attr, ref value } if attr == "quux" && value == "quuz"))
),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_boolean_nodes_with_or_not() {
let cases = [
"foo:bar OR -baz:qux quux:quuz",
"foo:bar OR NOT baz:qux quux:quuz",
"foo:bar OR -baz:qux AND quux:quuz",
"foo:bar OR NOT baz:qux AND quux:quuz",
];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res, QueryNode::Boolean { oper: BooleanType::Or, ref nodes } if
matches!(nodes[0], QueryNode::AttributeTerm {ref attr, ref value } if attr == "foo" && value == "bar")
&& matches!(nodes[1], QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
matches!(nodes[0], QueryNode::NegatedNode { ref node } if matches!(**node, QueryNode::AttributeTerm {ref attr, ref value } if attr == "baz" && value == "qux") &&
matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == "quux" && value == "quuz"))
)
),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_boolean_nodes_with_implicit_or_explicit_operators() {
let cases = [
"foo:bar OR baz:qux quux:quuz",
"foo:bar || baz:qux quux:quuz",
"foo:bar OR baz:qux AND quux:quuz",
"foo:bar || baz:qux && quux:quuz",
];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res, QueryNode::Boolean { oper: BooleanType::Or, ref nodes } if
matches!(nodes[0], QueryNode::AttributeTerm {ref attr, ref value } if attr == "foo" && value == "bar")
&& matches!(nodes[1], QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
matches!(nodes[0], QueryNode::AttributeTerm { ref attr, ref value } if attr == "baz" && value == "qux") &&
matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == "quux" && value == "quuz")
)
),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_nested_boolean_query_node() {
let cases = ["foo:bar (baz:qux quux:quuz)"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res, QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
matches!(nodes[0], QueryNode::AttributeTerm {ref attr, ref value } if attr == "foo" && value == "bar")
&& matches!(nodes[1], QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
matches!(nodes[0], QueryNode::AttributeTerm { ref attr, ref value } if attr == "baz" && value == "qux") &&
matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == "quux" && value == "quuz")
)
),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_nested_boolean_query_node_with_or() {
let cases = ["(foo:bar OR baz:qux) quux:quuz"];
for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res, QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
matches!(nodes[0], QueryNode::Boolean { oper: BooleanType::Or, ref nodes } if
matches!(nodes[0], QueryNode::AttributeTerm { ref attr, ref value } if attr == "foo" && value == "bar") &&
matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == "baz" && value == "qux")
) && matches!(nodes[1], QueryNode::AttributeTerm {ref attr, ref value } if attr == "quux" && value == "quuz")),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_negated_parenthesized_default_multiterm_query() {
let cases = ["NOT (foo bar)", "-(foo bar)"];
for query in cases.iter() {
let res = parse(query);
if let QueryNode::NegatedNode { ref node } = res
&& let QueryNode::AttributeTerm {
ref attr,
ref value,
} = **node
&& attr == DEFAULT_FIELD
&& value == "foo bar"
{
continue;
}
panic!("Unable to properly parse '{query:?}' - got {res:?}")
}
}
#[test]
fn parses_multiterm_with_leading_not_without_parens() {
let cases = ["NOT foo bar", "- foo bar"]; for query in cases.iter() {
let res = parse(query);
assert!(
matches!(res, QueryNode::Boolean { oper: BooleanType::And, ref nodes } if
matches!(nodes[0], QueryNode::NegatedNode { ref node } if matches!(**node, QueryNode::AttributeTerm { ref attr, ref value } if attr == DEFAULT_FIELD && value == "foo"))
&& matches!(nodes[1], QueryNode::AttributeTerm { ref attr, ref value } if attr == DEFAULT_FIELD && value == "bar")),
"Unable to properly parse '{query:?}' - got {res:?}"
);
}
}
#[test]
fn parses_negated_parenthesized_fielded_multiterm_query() {
let cases = ["NOT foo:(bar baz)", "-foo:(bar baz)"];
for query in cases.iter() {
let res = parse(query);
if let QueryNode::NegatedNode { ref node } = res
&& let QueryNode::AttributeTerm {
ref attr,
ref value,
} = **node
&& attr == "foo"
&& value == "bar baz"
{
continue;
}
panic!("Unable to properly parse '{query:?}' - got {res:?}")
}
}
}