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: vector selector yacc rule #7

Merged
merged 12 commits into from
Dec 13, 2022
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ edition = "2021"
authors = ["greptime"]
keywords = ["prometheus", "promql", "parser"]
license = "Apache-2.0"
license-file = "LICENSE"
categories = ["parser-implementations"]

[dependencies]
cfgrammar = "0.12"
lazy_static = "1.4.0"
lrlex = "0.12.0"
lrpar = "0.12.0"
regex = "1"

[build-dependencies]
cfgrammar = "0.12"
Expand Down
9 changes: 9 additions & 0 deletions examples/parser.rs
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);
}
85 changes: 85 additions & 0 deletions src/label/label.rs
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());
}
}
109 changes: 88 additions & 21 deletions src/label/matcher.rs
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> {
Copy link
Contributor

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.

Copy link
Member

@waynexia waynexia Dec 12, 2022

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.

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))?;
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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"));
}
}
20 changes: 3 additions & 17 deletions src/label/mod.rs
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};
4 changes: 4 additions & 0 deletions src/lib.rs
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");
Loading