Skip to content

Commit

Permalink
Extended Machers structure to support or filters
Browse files Browse the repository at this point in the history
  • Loading branch information
groobyming committed May 16, 2024
1 parent ada6424 commit 1110f9b
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 104 deletions.
114 changes: 73 additions & 41 deletions src/label/matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ pub struct Matcher {
pub op: MatchOp,
pub name: String,
pub value: String,
pub is_or: bool,
}

impl Matcher {
Expand All @@ -79,16 +78,6 @@ impl Matcher {
op,
name: name.into(),
value: value.into(),
is_or: false,
}
}

pub fn new_or(op: MatchOp, name: &str, value: &str) -> Self {
Self {
op,
name: name.into(),
value: value.into(),
is_or: true,
}
}

Expand Down Expand Up @@ -117,11 +106,6 @@ impl Matcher {
op.map(|op| Matcher::new(op, name.as_str(), value.as_str()))
}

pub fn new_matcher_or(id: TokenId, name: String, value: String) -> Result<Matcher, String> {
let op = Self::find_matcher_op(id, &value)?;
op.map(|op| Matcher::new_or(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),
Expand Down Expand Up @@ -201,51 +185,95 @@ 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>>,
}

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 new_or(or_matchers: Vec<Vec<Matcher>>) -> Self {
Self {
matchers: vec![],
or_matchers,
}
}

pub fn new_mixed(matchers: Vec<Matcher>, or_matchers: Vec<Vec<Matcher>>) -> Self {
Self {
matchers,
or_matchers,
}
}

pub fn append(mut self, matcher: Matcher) -> Self {
self.matchers.push(matcher);
self
}

pub fn append_or(mut self, matcher: Matcher) -> Self {
let latest_or_matchers = self.or_matchers.pop();
if let Some(mut latest_or_matchers) = latest_or_matchers {
latest_or_matchers.push(matcher);
self.or_matchers.push(latest_or_matchers);
} else {
let latest_matcher = self.matchers.pop();
if let Some(latest_matcher) = latest_matcher {
let or_matcher_pair = vec![latest_matcher, matcher];
self.or_matchers.push(or_matcher_pair);
}
}
self
}

/// Vector selectors must either specify a name or at least one label
/// matcher that does not match the empty string.
///
/// 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 @@ -254,22 +282,26 @@ impl Matchers {

impl fmt::Display for Matchers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let not_contains_or = &self.matchers.iter().all(|matcher| !matcher.is_or);
if *not_contains_or {
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 matchers_str = self
.matchers
.iter()
.map(|matcher| {
if matcher.is_or {
format!(" or {}", matcher)
} else {
format!(",{}", matcher)
}
})
.collect::<String>();
write!(f, "{}", matchers_str.trim_start_matches(','))
let or_matchers_str = self.or_matchers.iter().fold(String::new(), |_, pair| {
format!("{},", join_vector(pair, " or ", true))
});
let or_matchers_str = or_matchers_str
.trim_start_matches(',')
.trim_end_matches(',');
let simple_matchers_str = join_vector(simple_matchers, ",", true).to_string();
let simple_matchers_str = simple_matchers_str
.trim_start_matches(',')
.trim_end_matches(',');
let last_matchers_str = format!("{},{}", simple_matchers_str, or_matchers_str);
let last_matchers_str = last_matchers_str
.trim_start_matches(',')
.trim_end_matches(',');
write!(f, "{}", last_matchers_str)
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/parser/lex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,18 +564,18 @@ impl Lexer {
match self.pop() {
Some('#') => State::LineComment,
Some(',') => State::Lexeme(T_COMMA),
Some('o') => {
if let Some('r') = self.peek() {
Some('o') => {
if let Some('r') = self.peek() {
self.pop();
if let Some(' ') = self.peek() {
State::Lexeme(T_LOR)
} else {
} 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
76 changes: 55 additions & 21 deletions src/parser/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ 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,
Expand Down Expand Up @@ -2119,48 +2120,81 @@ mod tests {
#[test]
fn test_or_filters() {
let cases = vec![
(r#"foo{label1="1"}"#, {
let matchers = Matchers::new(vec![Matcher::new(MatchOp::Equal, "label1", "1")]);
Expr::new_vector_selector(Some(String::from("foo")), matchers)
}),
(r#"foo{label1="1" or label2="2"}"#, {
let matchers = Matchers::new(vec![
let matchers = Matchers::new_or(vec![vec![
Matcher::new(MatchOp::Equal, "label1", "1"),
Matcher::new_or(MatchOp::Equal, "label2", "2"),
]);
Matcher::new(MatchOp::Equal, "label2", "2"),
]]);
Expr::new_vector_selector(Some(String::from("foo")), matchers)
}),
(r#"foo{label1="1" or or="or"}"#, {
let matchers = Matchers::new(vec![
let matchers = Matchers::new_or(vec![vec![
Matcher::new(MatchOp::Equal, "label1", "1"),
Matcher::new_or(MatchOp::Equal, "or", "or"),
]);
Matcher::new(MatchOp::Equal, "or", "or"),
]]);
Expr::new_vector_selector(Some(String::from("foo")), matchers)
}),
(r#"foo{label1="1" or label2="2" or label3="3"}"#, {
let matchers = Matchers::new(vec![
let matchers = Matchers::new_or(vec![vec![
Matcher::new(MatchOp::Equal, "label1", "1"),
Matcher::new_or(MatchOp::Equal, "label2", "2"),
Matcher::new_or(MatchOp::Equal, "label3", "3"),
]);
Expr::new_vector_selector(Some(String::from("foo")), matchers)
}),
(r#"foo{label1="1" or label2="2" or label3="3" or label4="4"}"#, {
let matchers = Matchers::new(vec![
Matcher::new(MatchOp::Equal, "label1", "1"),
Matcher::new_or(MatchOp::Equal, "label2", "2"),
Matcher::new_or(MatchOp::Equal, "label3", "3"),
Matcher::new_or(MatchOp::Equal, "label4", "4"),
]);
Matcher::new(MatchOp::Equal, "label2", "2"),
Matcher::new(MatchOp::Equal, "label3", "3"),
]]);
Expr::new_vector_selector(Some(String::from("foo")), matchers)
}),
(
r#"foo{label1="1" or label2="2" or label3="3" or label4="4"}"#,
{
let matchers = Matchers::new_or(vec![vec![
Matcher::new(MatchOp::Equal, "label1", "1"),
Matcher::new(MatchOp::Equal, "label2", "2"),
Matcher::new(MatchOp::Equal, "label3", "3"),
Matcher::new(MatchOp::Equal, "label4", "4"),
]]);
Expr::new_vector_selector(Some(String::from("foo")), matchers)
},
),
];
assert_cases(Case::new_result_cases(cases));

let promql = r#"a{label1="1" or label2="2"}"#;
let expected = r#"a{label1="1" or label2="2"}"#;
let expr = parser::parse(promql).unwrap();
assert_eq!(expr.to_string(), expected);

let promql = r#"a{label1="1", label2="2"}"#;
let expected = r#"a{label1="1",label2="2"}"#;
let expr = parser::parse(promql).unwrap();
assert_eq!(expr.to_string(), expected);

let promql = r#"a{label1="1", label2="2" or label3="3", label4="4"}"#;
let expected = r#"a{label1="1",label4="4",label2="2" or label3="3"}"#;
let expr = parser::parse(promql).unwrap();
assert_eq!(expr.to_string(), expected);

let promql = r#"a{label1="1", label2="2" or label3="3" or label4="4", label5="5"}"#;
let expected = r#"a{label1="1",label5="5",label2="2" or label3="3" or label4="4"}"#;
let expr = parser::parse(promql).unwrap();
assert_eq!(expr.to_string(), expected);

let promql = r#"a{label1="1" or label2="2" or label3="3" or label4="4"}"#;
let expected = r#"a{label1="1" or label2="2" or label3="3" or label4="4"}"#;
let expr = parser::parse(promql).unwrap();
assert_eq!(expr.to_string(), expected);

let fail_cases = vec![
(r#"foo{or}"#, r#"invalid label matcher, expected label matching operator after 'or'"#),
(
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));

}
}
39 changes: 1 addition & 38 deletions src/parser/promql.y
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +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_or { Ok($1?.append($3?)) }
| label_match_list LOR label_matcher { Ok($1?.append_or($3?)) }
| label_matcher { Ok(Matchers::empty().append($1?)) }
;

Expand Down Expand Up @@ -448,43 +448,6 @@ label_matcher -> Result<Matcher, String>:
}
;

label_matcher_or -> Result<Matcher, String>:
IDENTIFIER match_op STRING
{
let name = lexeme_to_string($lexer, &$1)?;
let value = lexeme_to_string($lexer, &$3)?;
Matcher::new_matcher_or($2?.id(), name, value)
}
| IDENTIFIER match_op match_op
{
let op = $3?.val;
Err(format!("unexpected '{op}' in label matching, expected string"))

}
| IDENTIFIER match_op match_op STRING
{
let op = $3?.val;
Err(format!("unexpected '{op}' in label matching, expected string"))

}
| IDENTIFIER match_op match_op IDENTIFIER
{
let op = $3?.val;
Err(format!("unexpected '{op}' in label matching, expected string"))

}
| IDENTIFIER match_op IDENTIFIER
{
let id = lexeme_to_string($lexer, &$3)?;
Err(format!("unexpected identifier '{id}' in label matching, expected string"))
}
| IDENTIFIER
{
let id = lexeme_to_string($lexer, &$1)?;
Err(format!("invalid label matcher, expected label matching operator after '{id}'"))
}
;

/*
* Metric descriptions.
*/
Expand Down

0 comments on commit 1110f9b

Please sign in to comment.