Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add or operator for metric expression #84

Merged
merged 9 commits into from
May 20, 2024
Merged
88 changes: 72 additions & 16 deletions src/label/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,19 @@ impl Matcher {
}

pub fn new_matcher(id: TokenId, name: String, value: String) -> Result<Matcher, String> {
let op = Self::find_matcher_op(id, &value)?;
op.map(|op| Matcher::new(op, name.as_str(), value.as_str()))
}

fn find_matcher_op(id: TokenId, value: &str) -> Result<Result<MatchOp, String>, String> {
let op = match id {
T_EQL => Ok(MatchOp::Equal),
T_NEQ => Ok(MatchOp::NotEqual),
T_EQL_REGEX => Ok(MatchOp::Re(Matcher::try_parse_re(&value)?)),
T_NEQ_REGEX => Ok(MatchOp::NotRe(Matcher::try_parse_re(&value)?)),
T_EQL_REGEX => Ok(MatchOp::Re(Matcher::try_parse_re(value)?)),
T_NEQ_REGEX => Ok(MatchOp::NotRe(Matcher::try_parse_re(value)?)),
_ => Err(format!("invalid match op {}", token_display(id))),
};

op.map(|op| Matcher { op, name, value })
Ok(op)
}
}

Expand Down Expand Up @@ -181,24 +185,58 @@ fn try_escape_for_repeat_re(re: &str) -> String {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Matchers {
pub matchers: Vec<Matcher>,
pub or_matchers: Vec<Vec<Matcher>>,
evenyag marked this conversation as resolved.
Show resolved Hide resolved
}

impl Matchers {
pub fn empty() -> Self {
Self { matchers: vec![] }
Self {
matchers: vec![],
or_matchers: vec![],
}
}

pub fn one(matcher: Matcher) -> Self {
let matchers = vec![matcher];
Self { matchers }
Self {
matchers,
or_matchers: vec![],
}
}

pub fn new(matchers: Vec<Matcher>) -> Self {
Self { matchers }
Self {
matchers,
or_matchers: vec![],
}
}

pub fn with_or_matchers(mut self, or_matchers: Vec<Vec<Matcher>>) -> Self {
self.or_matchers = or_matchers;
self
}

pub fn append(mut self, matcher: Matcher) -> Self {
self.matchers.push(matcher);
// Check the latest or_matcher group. If it is not empty,
// we need to add the current matcher to this group.
let last_or_matcher = self.or_matchers.last_mut();
if let Some(last_or_matcher) = last_or_matcher {
last_or_matcher.push(matcher);
} else {
self.matchers.push(matcher);
}
self
}

pub fn append_or(mut self, matcher: Matcher) -> Self {
if !self.matchers.is_empty() {
// Be careful not to move ownership here, because it
// will be used by the subsequent append method.
let last_matchers = std::mem::take(&mut self.matchers);
self.or_matchers.push(last_matchers);
}
let new_or_matchers = vec![matcher];
self.or_matchers.push(new_or_matchers);
self
}

Expand All @@ -208,24 +246,29 @@ impl Matchers {
/// The following expression is illegal:
/// {job=~".*"} # Bad!
pub fn is_empty_matchers(&self) -> bool {
self.matchers.is_empty() || self.matchers.iter().all(|m| m.is_match(""))
(self.matchers.is_empty() && self.or_matchers.is_empty())
|| self
.matchers
.iter()
.chain(self.or_matchers.iter().flatten())
.all(|m| m.is_match(""))
}

/// find the matcher's value whose name equals the specified name. This function
/// is designed to prepare error message of invalid promql expression.
pub(crate) fn find_matcher_value(&self, name: &str) -> Option<String> {
for m in &self.matchers {
if m.name.eq(name) {
return Some(m.value.clone());
}
}
None
self.matchers
.iter()
.chain(self.or_matchers.iter().flatten())
.find(|m| m.name.eq(name))
.map(|m| m.value.clone())
}

/// find matchers whose name equals the specified name
pub fn find_matchers(&self, name: &str) -> Vec<Matcher> {
self.matchers
.iter()
.chain(self.or_matchers.iter().flatten())
.filter(|m| m.name.eq(name))
.cloned()
.collect()
Expand All @@ -234,7 +277,20 @@ impl Matchers {

impl fmt::Display for Matchers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", join_vector(&self.matchers, ",", true))
let simple_matchers = &self.matchers;
let or_matchers = &self.or_matchers;
if or_matchers.is_empty() {
write!(f, "{}", join_vector(simple_matchers, ",", true))
} else {
let or_matchers_string =
self.or_matchers
.iter()
.fold(String::new(), |or_matchers_str, pair| {
format!("{} or {}", or_matchers_str, join_vector(pair, ", ", false))
});
let or_matchers_string = or_matchers_string.trim_start_matches(" or").trim();
write!(f, "{}", or_matchers_string)
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions src/parser/lex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,18 @@ impl Lexer {
match self.pop() {
Some('#') => State::LineComment,
Some(',') => State::Lexeme(T_COMMA),
Some('o') | Some('O') => {
if let Some('r') | Some('R') = self.peek() {
self.pop();
if let Some(' ') = self.peek() {
State::Lexeme(T_LOR)
} else {
State::Identifier
}
} else {
State::Identifier
}
}
Some(ch) if ch.is_ascii_whitespace() => State::Space,
Some(ch) if is_alpha(ch) => State::Identifier,
Some(ch) if STRING_SYMBOLS.contains(ch) => State::String(ch),
Expand Down
120 changes: 120 additions & 0 deletions src/parser/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ mod tests {
use regex::Regex;

use crate::label::{Labels, MatchOp, Matcher, Matchers, METRIC_NAME};
use crate::parser;
use crate::parser::function::get_function;
use crate::parser::{
token, AtModifier as At, BinModifier, Expr, FunctionArgs, LabelModifier, Offset,
VectorMatchCardinality, VectorSelector, INVALID_QUERY_INFO,
};
use crate::util::duration;
use std::time::Duration;
use std::vec;

struct Case {
input: String,
Expand Down Expand Up @@ -2115,4 +2117,122 @@ mod tests {
];
assert_cases(fail_cases);
}

#[test]
fn test_or_filters() {
let cases = vec![
(r#"foo{label1="1" or label1="2"}"#, {
let matchers = Matchers::new(vec![]).with_or_matchers(vec![
vec![Matcher::new(MatchOp::Equal, "label1", "1")],
vec![Matcher::new(MatchOp::Equal, "label1", "2")],
]);
Expr::new_vector_selector(Some(String::from("foo")), matchers)
}),
(r#"foo{label1="1" OR label1="2"}"#, {
let matchers = Matchers::new(vec![]).with_or_matchers(vec![
vec![Matcher::new(MatchOp::Equal, "label1", "1")],
vec![Matcher::new(MatchOp::Equal, "label1", "2")],
]);
Expr::new_vector_selector(Some(String::from("foo")), matchers)
}),
(r#"foo{label1="1" Or label1="2"}"#, {
let matchers = Matchers::new(vec![]).with_or_matchers(vec![
vec![Matcher::new(MatchOp::Equal, "label1", "1")],
vec![Matcher::new(MatchOp::Equal, "label1", "2")],
]);
Expr::new_vector_selector(Some(String::from("foo")), matchers)
}),
(r#"foo{label1="1" oR label1="2"}"#, {
let matchers = Matchers::new(vec![]).with_or_matchers(vec![
vec![Matcher::new(MatchOp::Equal, "label1", "1")],
vec![Matcher::new(MatchOp::Equal, "label1", "2")],
]);
Expr::new_vector_selector(Some(String::from("foo")), matchers)
}),
(r#"foo{label1="1" or or="or"}"#, {
let matchers = Matchers::new(vec![]).with_or_matchers(vec![
vec![Matcher::new(MatchOp::Equal, "label1", "1")],
vec![Matcher::new(MatchOp::Equal, "or", "or")],
]);
Expr::new_vector_selector(Some(String::from("foo")), matchers)
}),
(
r#"foo{label1="1" or label1="2" or label1="3" or label1="4"}"#,
{
let matchers = Matchers::new(vec![]).with_or_matchers(vec![
vec![Matcher::new(MatchOp::Equal, "label1", "1")],
vec![Matcher::new(MatchOp::Equal, "label1", "2")],
vec![Matcher::new(MatchOp::Equal, "label1", "3")],
vec![Matcher::new(MatchOp::Equal, "label1", "4")],
]);
Expr::new_vector_selector(Some(String::from("foo")), matchers)
},
),
(
r#"foo{label1="1" or label1="2" or label1="3", label2="4"}"#,
{
let matchers = Matchers::new(vec![]).with_or_matchers(vec![
vec![Matcher::new(MatchOp::Equal, "label1", "1")],
vec![Matcher::new(MatchOp::Equal, "label1", "2")],
vec![
Matcher::new(MatchOp::Equal, "label1", "3"),
Matcher::new(MatchOp::Equal, "label2", "4"),
],
]);
Expr::new_vector_selector(Some(String::from("foo")), matchers)
},
),
(
r#"foo{label1="1", label2="2" or label1="3" or label1="4"}"#,
{
let matchers = Matchers::new(vec![]).with_or_matchers(vec![
vec![
Matcher::new(MatchOp::Equal, "label1", "1"),
Matcher::new(MatchOp::Equal, "label2", "2"),
],
vec![Matcher::new(MatchOp::Equal, "label1", "3")],
vec![Matcher::new(MatchOp::Equal, "label1", "4")],
]);
Expr::new_vector_selector(Some(String::from("foo")), matchers)
},
),
];
assert_cases(Case::new_result_cases(cases));

let display_cases = [
r#"a{label1="1"}"#,
r#"a{label1="1" or label2="2"}"#,
r#"a{label1="1" or label2="2" or label3="3" or label4="4"}"#,
r#"a{label1="1", label2="2" or label3="3" or label4="4"}"#,
r#"a{label1="1", label2="2" or label3="3", label4="4"}"#,
yuanbohan marked this conversation as resolved.
Show resolved Hide resolved
];
display_cases
.iter()
.for_each(|expr| assert_eq!(parser::parse(expr).unwrap().to_string(), *expr));

let or_insensitive_cases = [
r#"a{label1="1" or label2="2"}"#,
r#"a{label1="1" OR label2="2"}"#,
r#"a{label1="1" Or label2="2"}"#,
r#"a{label1="1" oR label2="2"}"#,
];

or_insensitive_cases.iter().for_each(|expr| {
assert_eq!(
parser::parse(expr).unwrap().to_string(),
r#"a{label1="1" or label2="2"}"#
)
});

let fail_cases = vec![
(
r#"foo{or}"#,
r#"invalid label matcher, expected label matching operator after 'or'"#,
),
(r#"foo{label1="1" or}"#, INVALID_QUERY_INFO),
(r#"foo{or label1="1"}"#, INVALID_QUERY_INFO),
(r#"foo{label1="1" or or label2="2"}"#, INVALID_QUERY_INFO),
];
assert_cases(Case::new_fail_cases(fail_cases));
}
}
1 change: 1 addition & 0 deletions src/parser/promql.y
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ label_matchers -> Result<Matchers, String>:

label_match_list -> Result<Matchers, String>:
label_match_list COMMA label_matcher { Ok($1?.append($3?)) }
| label_match_list LOR label_matcher { Ok($1?.append_or($3?)) }
yuanbohan marked this conversation as resolved.
Show resolved Hide resolved
| label_matcher { Ok(Matchers::empty().append($1?)) }
;

Expand Down