Skip to content
Snippets Groups Projects
Commit e06188d0 authored by Maaz Ahmed's avatar Maaz Ahmed
Browse files

Merge branch '43-subquery-support' into 'main'

Resolve "Subquery support"

Closes #43

See merge request !24
parents cd1d420d 00c510d8
No related branches found
No related tags found
1 merge request!24Resolve "Subquery support"
Pipeline #14880 passed with stages
in 1 minute and 38 seconds
......@@ -244,7 +244,7 @@ impl From<(u64, Unit)> for Duration {
/// Specify time offset for the queries
#[derive(Debug, Clone, Copy)]
pub struct Offset(i64, Unit);
pub struct Offset(pub i64, pub Unit);
impl Display for Offset {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
......@@ -269,6 +269,71 @@ impl Display for Scalar {
impl Sealed for Scalar {}
impl ops::Operable for Scalar {}
/// A subquery that lets you make a ranged query on an instant query with an optional resolution/set and offset
///
/// This type can also be constructed using the [`subquery`] method provided by the [`QueryExt`] trait.
#[derive(Clone, Debug)]
pub struct SubQry<E: Operable> {
expr: E,
dur: (u64, Unit),
res: Option<(u64, Unit)>,
off: Option<Offset>,
}
impl<E: Operable> SubQry<E> {
/// Create a subquery using an instant query
pub fn new(expr: E, duration: Duration) -> Self {
Self {
expr,
dur: (duration.0, duration.1),
res: None,
off: None,
}
}
/// Set the resolution/step for the subquery
pub fn resolution(mut self, res: Duration) -> Self {
self.res.replace((res.0, res.1));
self
}
/// Set the subquery's offset duration
pub fn offset(mut self, off: Offset) -> Self {
self.off.replace(off);
self
}
}
impl<E: Operable> Display for SubQry<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"({expr})[{v}{unit}",
expr = self.expr,
v = self.dur.0,
unit = self.dur.1
)?;
if let Some(res) = self.res.as_ref() {
write!(f, ":{v}{unit}", v = res.0, unit = res.1)?;
}
f.write_str("]")?;
if let Some(off) = self.off.as_ref() {
write!(f, " {off}")?;
}
Ok(())
}
}
impl<E: Operable> Sealed for SubQry<E> {}
impl<E: Operable> ops::Operable for SubQry<E> {}
impl<E: Operable> IntoQuery for SubQry<E> {
type Target = String;
fn into_query(self) -> Self::Target {
self.to_string()
}
}
/// Raw Expression type that allows raw strings to be used in the builder API where necessary
///
/// This also allows combining complex queries, especially when a function is generic over a single type
......@@ -339,7 +404,7 @@ impl Display for RawExpr {
/// Additional/extended query builder API functionality
///
/// This trait has a blanket implementation for all types that implement [`Operable`]
pub trait QueryExt {
pub trait QueryExt: Operable {
/// Convert a query builder API type into a [`RawExpr`] type
///
/// This is useful when a function takes a sequence (array/vector/iterator) of a generic type `T: Operable`
......@@ -348,7 +413,9 @@ pub trait QueryExt {
/// While the `String` type could have been allowed to be part of the builder API directly,
/// it is by design that a user must explicitly turn a string into a [`RawExpr`] saying that
/// they're opting in to use a raw string in the query builder API.
fn to_expr(&self) -> RawExpr;
fn to_expr(&self) -> RawExpr {
RawExpr(self.to_string())
}
/// Call a function or closure on `Self` to transform it into another type
///
/// This is similar to the `Iterator's` `map` method, except this is eagerly evaluated and operates on a single item.
......@@ -368,22 +435,25 @@ pub trait QueryExt {
/// > idea of what's happening when writing/reading the complex queries.
fn then<R>(self, func: impl FnOnce(Self) -> R) -> R
where
Self: Sized;
}
impl<T: Operable> QueryExt for T {
fn to_expr(&self) -> RawExpr {
RawExpr(self.to_string())
Self: Sized,
{
func(self)
}
fn then<R>(self, func: impl FnOnce(Self) -> R) -> R
/// Turn the current query expression into a subquery using the provided range
///
/// Optional resolution and offset can be set for the subquery using the provided methods of the
/// returned type.
fn subquery(self, dur: Duration) -> SubQry<Self>
where
Self: Sized,
{
func(self)
SubQry::new(self, dur)
}
}
impl<T: Operable> QueryExt for T {}
#[cfg(test)]
mod tests {
use super::*;
......@@ -407,4 +477,16 @@ mod tests {
metric
)
}
#[test]
fn subquery() {
let qry = Metric::new("metric").subquery(Duration(30, Unit::Day));
assert_eq!(qry.to_string(), "(metric)[30d]");
let qry = qry.resolution(Duration(5, Unit::Min));
assert_eq!(qry.to_string(), "(metric)[30d:5m]");
let qry = qry.offset(Offset(1, Unit::Day));
assert_eq!(qry.to_string(), "(metric)[30d:5m] offset 1d");
}
}
use mquery::query::{
ops::{Arithmetic, Comparison, Logical},
Label, Metric, Scalar,
Duration, Label, Metric, Offset, QueryExt, Scalar, Unit,
};
mod utils;
......@@ -18,6 +18,20 @@ async fn basic_query_with_labels() {
utils::send_query(query).await.unwrap();
}
#[tokio::test]
async fn basic_subquery() {
let query = Metric::new("test_metric")
.labels([
Label::Eq("eq", "eqvalue"),
Label::RxEq("rxeq", ".*"),
Label::RxNe("rxne", ".*"),
])
.subquery(Duration(30, Unit::Day))
.resolution(Duration(10, Unit::Min))
.offset(Offset(1, Unit::Sec));
utils::send_query(query).await.unwrap();
}
#[tokio::test]
async fn arithmetic_ops() {
let query = Metric::new("test_metric")
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment