use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Duration(pub f64);
impl Duration {
#[must_use]
pub fn as_secs(&self) -> f64 {
self.0
}
#[must_use]
pub fn as_nanos(&self) -> i64 {
secs_to_nanos_safe(self.0)
}
#[must_use]
pub fn as_millis(&self) -> i64 {
let ms = self.0 * 1_000.0;
float_to_i64_safe(ms)
}
}
#[inline]
fn secs_to_nanos_safe(secs: f64) -> i64 {
let ns = secs * 1_000_000_000.0;
float_to_i64_safe(ns)
}
#[inline]
fn float_to_i64_safe(v: f64) -> i64 {
#[allow(clippy::cast_possible_truncation)]
if v.is_nan() {
0
} else if v >= i64::MAX as f64 {
i64::MAX
} else if v <= i64::MIN as f64 {
i64::MIN
} else {
v as i64
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct LabelMatcher {
pub name: String,
pub op: MatchOp,
pub value: String,
}
impl LabelMatcher {
#[must_use]
pub fn matches_empty(&self) -> bool {
match self.op {
MatchOp::Equal => self.value.is_empty(),
MatchOp::NotEqual => !self.value.is_empty(),
MatchOp::RegexMatch | MatchOp::RegexNotMatch => {
let Ok(re) = regex::Regex::new(&format!("^(?s:{})$", self.value)) else {
return false;
};
re.is_match("") == (self.op == MatchOp::RegexMatch)
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MatchOp {
Equal,
NotEqual,
RegexMatch,
RegexNotMatch,
}
impl fmt::Display for MatchOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MatchOp::Equal => write!(f, "="),
MatchOp::NotEqual => write!(f, "!="),
MatchOp::RegexMatch => write!(f, "=~"),
MatchOp::RegexNotMatch => write!(f, "!~"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum AggregationModifier {
By(Vec<String>),
Without(Vec<String>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AggregationOp {
Sum,
Avg,
Min,
Max,
Count,
Stddev,
Stdvar,
Topk,
Bottomk,
Quantile,
CountValues,
Group,
}
impl AggregationOp {
#[must_use]
pub fn parse_op(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"sum" => Some(Self::Sum),
"avg" => Some(Self::Avg),
"min" => Some(Self::Min),
"max" => Some(Self::Max),
"count" => Some(Self::Count),
"stddev" => Some(Self::Stddev),
"stdvar" => Some(Self::Stdvar),
"topk" => Some(Self::Topk),
"bottomk" => Some(Self::Bottomk),
"quantile" => Some(Self::Quantile),
"count_values" => Some(Self::CountValues),
"group" => Some(Self::Group),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinaryOp {
Add,
Sub,
Mul,
Div,
Mod,
Pow,
Eql,
Neq,
Lss,
Gtr,
Lte,
Gte,
And,
Or,
Unless,
}
impl BinaryOp {
#[must_use]
pub fn precedence(self) -> u8 {
match self {
BinaryOp::Or => 1,
BinaryOp::And | BinaryOp::Unless => 2,
BinaryOp::Eql
| BinaryOp::Neq
| BinaryOp::Lss
| BinaryOp::Gtr
| BinaryOp::Lte
| BinaryOp::Gte => 3,
BinaryOp::Add | BinaryOp::Sub => 4,
BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => 5,
BinaryOp::Pow => 6,
}
}
#[must_use]
pub fn is_right_assoc(self) -> bool {
matches!(self, BinaryOp::Pow)
}
#[must_use]
pub fn is_comparison(self) -> bool {
matches!(
self,
BinaryOp::Eql
| BinaryOp::Neq
| BinaryOp::Lss
| BinaryOp::Gtr
| BinaryOp::Lte
| BinaryOp::Gte
)
}
#[must_use]
pub fn is_set_operator(self) -> bool {
matches!(self, BinaryOp::And | BinaryOp::Or | BinaryOp::Unless)
}
#[must_use]
pub fn is_arithmetic(self) -> bool {
matches!(
self,
BinaryOp::Add
| BinaryOp::Sub
| BinaryOp::Mul
| BinaryOp::Div
| BinaryOp::Mod
| BinaryOp::Pow
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct VectorMatching {
pub card: VectorMatchingCardinality,
pub labels: Vec<String>,
pub on: bool,
pub include: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VectorMatchingCardinality {
OneToOne,
ManyToOne,
OneToMany,
ManyToMany,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
NumberLiteral(f64),
StringLiteral(String),
VectorSelector {
name: Option<String>,
matchers: Vec<LabelMatcher>,
offset: Option<Duration>,
at: Option<AtModifier>,
},
MatrixSelector {
vector: Box<Expr>,
range: Duration,
},
Call {
func: String,
args: Vec<Expr>,
},
Aggregation {
op: AggregationOp,
expr: Box<Expr>,
param: Option<Box<Expr>>,
modifier: Option<AggregationModifier>,
},
BinaryExpr {
op: BinaryOp,
lhs: Box<Expr>,
rhs: Box<Expr>,
bool_mod: bool,
matching: Option<VectorMatching>,
},
UnaryExpr {
op: UnaryOp,
expr: Box<Expr>,
},
Paren(Box<Expr>),
Subquery {
expr: Box<Expr>,
range: Duration,
step: Option<Duration>,
offset: Option<Duration>,
at: Option<AtModifier>,
},
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AtModifier {
Timestamp(f64),
Start,
End,
}
impl AtModifier {
#[must_use]
pub fn resolve(self, start: i64, end: i64) -> i64 {
match self {
Self::Timestamp(secs) => secs_to_nanos_safe(secs),
Self::Start => start,
Self::End => end,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnaryOp {
Neg,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Sample {
pub timestamp: i64,
pub value: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Series {
pub labels: Vec<(String, String)>,
pub samples: Vec<Sample>,
pub histograms: Vec<HistogramSample>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct HistogramSample {
pub timestamp: i64,
pub histogram: Box<chronix_core::histogram::Histogram>,
}
impl Series {
#[must_use]
pub fn floats(labels: Vec<(String, String)>, samples: Vec<Sample>) -> Self {
Self {
labels,
samples,
histograms: Vec::new(),
}
}
#[must_use]
pub fn histograms(labels: Vec<(String, String)>, histograms: Vec<HistogramSample>) -> Self {
Self {
labels,
samples: Vec::new(),
histograms,
}
}
#[must_use]
pub fn is_histogram(&self) -> bool {
!self.histograms.is_empty()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum PromQLValue {
Vector(Vec<Series>),
Matrix(Vec<Series>),
Scalar(f64),
String(String),
}