diff --git a/src/query/mod.rs b/src/query/mod.rs index ee8d74e2fe3cde4b59b701347fc168952febf496..8f463aa10c4423a3603f54f7bb3745973432a241 100644 --- a/src/query/mod.rs +++ b/src/query/mod.rs @@ -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"); + } } diff --git a/tests/query.rs b/tests/query.rs index 69c2002eb929f75b92f5bab55c0bfd656cf88c70..36167bbeaa62bb118adc090491ec6156aba77029 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -1,6 +1,6 @@ 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")