-
Notifications
You must be signed in to change notification settings - Fork 21
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: vector selector yacc rule #7
Changes from all commits
3dbc21d
3fec514
128fe5b
6d8e30d
d7ef84a
a94d9a1
c65d003
e2d9913
b890260
5c66a88
330d534
13c1e37
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
use promql_parser::parser; | ||
|
||
fn main() { | ||
let promql = "node_cpu_seconds_total{cpu=0,mode=idle}"; | ||
|
||
let ast = parser::parse(promql).unwrap(); | ||
|
||
println!("AST: {:?}", ast); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
use std::collections::HashSet; | ||
|
||
// Well-known label names used by Prometheus components. | ||
pub const METRIC_NAME: &'static str = "__name__"; | ||
pub const ALERT_NAME: &'static str = "alertname"; | ||
pub const BUCKET_LABEL: &'static str = "le"; | ||
pub const INSTANCE_NAME: &'static str = "instance"; | ||
|
||
/// Label is a key/value pair of strings. | ||
#[derive(Debug, Clone, PartialEq, Eq)] | ||
pub struct Label { | ||
pub name: String, | ||
pub value: String, | ||
} | ||
|
||
impl Label { | ||
pub fn new(name: String, value: String) -> Self { | ||
Self { name, value } | ||
} | ||
} | ||
|
||
// Labels is a set of labels. | ||
pub struct Labels { | ||
pub labels: Vec<Label>, | ||
} | ||
|
||
impl Labels { | ||
pub fn empty() -> Self { | ||
Self { labels: vec![] } | ||
} | ||
|
||
pub fn new(labels: Vec<Label>) -> Self { | ||
Self { labels } | ||
} | ||
|
||
pub fn append(mut self, label: Label) -> Self { | ||
self.labels.push(label); | ||
self | ||
} | ||
|
||
/// match_labels returns a subset of Labels that matches/does not match with the provided label names based on the 'on' boolean. | ||
/// If on is set to true, it returns the subset of labels that match with the provided label names and its inverse when 'on' is set to false. | ||
pub fn match_labels(&self, on: bool, names: Vec<String>) -> Vec<Label> { | ||
let set: HashSet<String> = names.into_iter().collect(); | ||
let mut result = vec![]; | ||
for label in &self.labels { | ||
let contains = set.contains(&label.name); | ||
// if on is false, then METRIC_NAME CAN NOT be included in the result | ||
if on == contains && (on || !label.name.eq_ignore_ascii_case(METRIC_NAME)) { | ||
result.push(label.clone()); | ||
} | ||
} | ||
result | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
// TODO: more test cases needed in prometheus/model/labels/matcher_test.go | ||
#[test] | ||
fn test_match_labels() { | ||
let rust = Label { | ||
name: "Rust".into(), | ||
value: "rust".into(), | ||
}; | ||
let go = Label { | ||
name: "go".into(), | ||
value: "go".into(), | ||
}; | ||
let clojure = Label { | ||
name: "Clojure".into(), | ||
value: "Clojure".into(), | ||
}; | ||
let labels = Labels::new(vec![rust.clone(), go.clone(), clojure.clone()]); | ||
|
||
let matched_labels = labels.match_labels(true, vec!["go".into()]); | ||
assert_eq!(1, matched_labels.len()); | ||
assert_eq!(go, matched_labels[0]); | ||
|
||
let matched_labels = labels.match_labels(false, vec!["go".into()]); | ||
assert_eq!(2, matched_labels.len()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,105 @@ | ||
// Possible MatchTypes. | ||
use crate::parser::Token; | ||
use crate::parser::{T_EQL, T_EQL_REGEX, T_NEQ, T_NEQ_REGEX}; | ||
use regex::Regex; | ||
|
||
#[derive(Debug)] | ||
pub enum MatchType { | ||
MatchEqual, | ||
MatchNotEqual, | ||
MatchRegexp, | ||
MatchNotRegexp, | ||
pub enum MatchOp { | ||
Equal, | ||
NotEqual, | ||
Re(Regex), | ||
NotRe(Regex), | ||
} | ||
|
||
// Matcher models the matching of a label. | ||
#[derive(Debug)] | ||
pub struct Matcher { | ||
typ: MatchType, | ||
op: MatchOp, | ||
name: String, | ||
value: String, | ||
// FIXME: Regex Matcher | ||
// re *FastRegexMatcher | ||
} | ||
|
||
impl Matcher { | ||
pub fn new(t: MatchType, n: &str, v: &str) -> Self { | ||
Self { | ||
typ: t, | ||
name: n.into(), | ||
value: n.into(), | ||
pub fn new(op: MatchOp, name: String, value: String) -> Self { | ||
Self { op, name, value } | ||
} | ||
|
||
pub fn name(&self) -> String { | ||
self.name.clone() | ||
} | ||
|
||
// matches returns whether the matcher matches the given string value. | ||
pub fn is_match(&self, s: &str) -> bool { | ||
match &self.op { | ||
MatchOp::Equal => self.value.eq(s), | ||
MatchOp::NotEqual => self.value.ne(s), | ||
MatchOp::Re(r) => r.is_match(s), | ||
MatchOp::NotRe(r) => !r.is_match(s), | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct Matchers { | ||
matchers: Vec<Matcher>, | ||
} | ||
|
||
impl Matchers { | ||
pub fn empty() -> Self { | ||
Self { matchers: vec![] } | ||
} | ||
|
||
pub fn new(matchers: Vec<Matcher>) -> Self { | ||
Self { matchers } | ||
} | ||
|
||
pub fn append(mut self, matcher: Matcher) -> Self { | ||
self.matchers.push(matcher); | ||
self | ||
} | ||
} | ||
|
||
// Matches returns whether the matcher matches the given string value. | ||
pub fn matches(&self, s: &str) -> bool { | ||
match self.typ { | ||
MatchType::MatchEqual => self.value.eq(s), | ||
MatchType::MatchNotEqual => self.value.ne(s), | ||
MatchType::MatchRegexp => todo!(), | ||
MatchType::MatchNotRegexp => todo!(), | ||
pub fn new_matcher(token: Token, name: String, value: String) -> Result<Matcher, String> { | ||
match token.id() { | ||
T_EQL => Ok(Matcher::new(MatchOp::Equal, name, value)), | ||
T_NEQ => Ok(Matcher::new(MatchOp::NotEqual, name, value)), | ||
T_EQL_REGEX => { | ||
let re = Regex::new(&value).map_err(|_| format!("illegal regex for {}", &value))?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also display the source error in the error message? It might contains some helpful message. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error mechanism will be carefully re-designed in the future in Issue #9 |
||
Ok(Matcher::new(MatchOp::Re(re), name, value)) | ||
} | ||
T_NEQ_REGEX => { | ||
let re = Regex::new(&value).map_err(|_| format!("illegal regex for {}", &value))?; | ||
Ok(Matcher::new(MatchOp::NotRe(re), name, value)) | ||
} | ||
_ => Err(format!("invalid match op {}", token.val())), | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_eq_ne() { | ||
let op = MatchOp::Equal; | ||
let matcher = Matcher::new(op, "name".into(), "up".into()); | ||
assert!(matcher.is_match("up")); | ||
assert!(!matcher.is_match("down")); | ||
|
||
let op = MatchOp::NotEqual; | ||
let matcher = Matcher::new(op, "name".into(), "up".into()); | ||
assert!(matcher.is_match("foo")); | ||
assert!(matcher.is_match("bar")); | ||
assert!(!matcher.is_match("up")); | ||
} | ||
|
||
#[test] | ||
fn test_re() { | ||
let value = "api/v1/.*".to_string(); | ||
let re = Regex::new(&value).unwrap(); | ||
let op = MatchOp::Re(re); | ||
let matcher = Matcher::new(op, "name".into(), value); | ||
assert!(matcher.is_match("api/v1/query")); | ||
assert!(matcher.is_match("api/v1/range_query")); | ||
assert!(!matcher.is_match("api/v2")); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,5 @@ | ||
mod label; | ||
mod matcher; | ||
|
||
pub use matcher::{MatchType, Matcher}; | ||
|
||
// Well-known label names used by Prometheus components. | ||
const METRIC_NAME: &'static str = "__name__"; | ||
const ALERT_NAME: &'static str = "alertname"; | ||
const BUCKET_LABEL: &'static str = "le"; | ||
const INSTANCE_NAME: &'static str = "instance"; | ||
|
||
// Label is a key/value pair of strings. | ||
pub struct Label { | ||
name: String, | ||
value: String, | ||
} | ||
|
||
// Labels is a sorted set of labels. Order has to be guaranteed upon | ||
// instantiation. | ||
pub type Labels = Vec<Label>; | ||
pub use label::{Label, Labels, ALERT_NAME, BUCKET_LABEL, INSTANCE_NAME, METRIC_NAME}; | ||
pub use matcher::{new_matcher, MatchOp, Matcher, Matchers}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,6 @@ | ||
pub mod label; | ||
pub mod parser; | ||
pub mod util; | ||
|
||
use lrpar::lrpar_mod; | ||
lrpar_mod!("parser/promql.y"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may need to define an Error type for the parser instead of using
String
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can file a todo issue for this. I think we can leave them as are for now (i.e. keep using String). Parser error is a bit complex problem because it's part of an important user-facing UI, and we need to consider how to make it accurate and understandable.