Skip to main content

mpl_lang/
query.rs

1//! The query structures
2use std::{
3    collections::{HashMap, HashSet},
4    num::TryFromIntError,
5};
6
7#[cfg(feature = "clock")]
8use chrono::Utc;
9use chrono::{DateTime, Duration, FixedOffset};
10use miette::SourceSpan;
11use pest::Parser as _;
12use strumbra::SharedString;
13
14use crate::{
15    ParseError,
16    enc_regex::EncodableRegex,
17    linker::{AlignFunction, ComputeFunction, GroupFunction, MapFunction},
18    parser::{self, MPLParser, ParseParamError, Rule},
19    tags::TagValue,
20    time::{Resolution, ResolutionError},
21    types::{BucketSpec, BucketType, Dataset, Metric, Parameterized},
22};
23
24mod fmt;
25#[cfg(test)]
26mod tests;
27
28/// Metric identifier
29#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
30#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
31#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
32pub struct MetricId {
33    /// The dataset identifier or param
34    pub dataset: Parameterized<Dataset>,
35    /// The metric identifier
36    pub metric: Metric,
37}
38
39/// Time unit
40#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
41#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
42#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
43pub enum TimeUnit {
44    /// Millisecond
45    Millisecond,
46    /// Second
47    Second,
48    /// Minute
49    Minute,
50    /// Hour
51    Hour,
52    /// Day
53    Day,
54    /// Week
55    Week,
56    /// Month
57    Month,
58    /// Year
59    Year,
60}
61
62#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
63/// Relative time (1h)
64#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
65#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
66pub struct RelativeTime {
67    /// Value
68    pub value: u64,
69    /// Unit
70    pub unit: TimeUnit,
71}
72
73/// A point in time
74#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
75#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
76#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
77pub enum Time {
78    /// A time relative to now
79    Relative(RelativeTime),
80    /// A timestamp
81    Timestamp(i64),
82    /// A RFC3339 timestamp
83    RFC3339(#[cfg_attr(feature = "wasm", tsify(type = "string"))] DateTime<FixedOffset>),
84    /// A time modifier
85    Modifier(String),
86}
87
88/// A timerange between two times
89#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
90#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
91#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
92pub struct TimeRange {
93    /// Start time of the range
94    pub start: Time,
95    /// End time of the range or None for 'now'
96    pub end: Option<Time>,
97}
98
99/// The source for a query
100#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
101#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
102#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
103pub struct Source {
104    /// The metric
105    pub metric_id: MetricId,
106    /// The time range
107    pub time: Option<TimeRange>,
108}
109
110///An error relkated to value parsing
111#[derive(Debug, thiserror::Error)]
112pub enum ValueError {
113    /// Invalid float value
114    #[error("Invalid Float")]
115    BadFloat,
116}
117
118/// A comparison operator for filtering based on a value
119#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
120#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
121#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
122pub enum Cmp {
123    /// Equal to the given value
124    Eq(Parameterized<TagValue>),
125    /// Not equal to the given value
126    Ne(Parameterized<TagValue>),
127    /// Greater than the given value
128    Gt(Parameterized<TagValue>),
129    /// Greater than or equal to the given value
130    Ge(Parameterized<TagValue>),
131    /// Less than the given value
132    Lt(Parameterized<TagValue>),
133    /// Less than or equal to the given value
134    Le(Parameterized<TagValue>),
135    /// Matches the given regular expression
136    RegEx(Parameterized<EncodableRegex>),
137    /// Does not match the given regular expression
138    RegExNot(Parameterized<EncodableRegex>),
139    /// Is the given tag type
140    Is(TagType),
141}
142
143/// Rename the output as a new metric
144#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
145#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
146#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
147pub struct As {
148    /// The new name for the metric
149    pub name: Metric,
150}
151
152/// Filter the series
153#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
154#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
155#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
156pub enum Filter {
157    /// Logical AND of the given filters
158    And(Vec<Filter>),
159    /// Logical OR of the given filters
160    Or(Vec<Filter>),
161    /// Logical NOT of the given filters
162    Not(Box<Filter>),
163    /// Filter based on a filed
164    Cmp {
165        /// The field to filter on
166        field: String,
167        /// The compaison to perform
168        rhs: Cmp,
169    },
170}
171
172/// A Mapping function
173#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
174#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
175#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
176pub struct Mapping {
177    /// The function to apply
178    pub function: MapFunction,
179    /// The optional argument to pass to the function
180    pub arg: Option<f64>,
181}
182
183/// An Alignment function
184#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
185#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
186#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
187pub struct Align {
188    /// The function to apply
189    pub function: AlignFunction,
190    /// The time to align to
191    pub time: Parameterized<RelativeTime>,
192}
193
194/// A Grouping function
195#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
196#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
197#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
198pub struct GroupBy {
199    /// The location of the group by clause
200    #[cfg_attr(feature = "wasm", tsify(type = "{ offset: number, length: number }"))]
201    pub span: SourceSpan,
202    /// The function to apply
203    pub function: GroupFunction,
204    /// The tags to group by
205    pub tags: Vec<String>,
206}
207
208/// A Bucketing function, applying both tag and time based aggregation
209#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
210#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
211#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
212pub struct BucketBy {
213    /// The location of the group by clause
214    #[cfg_attr(feature = "wasm", tsify(type = "{ offset: number, length: number }"))]
215    pub span: SourceSpan,
216    /// The function to apply
217    pub function: BucketType,
218    /// The time to align to
219    pub time: Parameterized<RelativeTime>,
220    /// The tags to group by
221    pub tags: Vec<String>,
222    /// The buckets to produce
223    pub spec: Vec<BucketSpec>,
224}
225
226/// Possible aggregate functions
227#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
228#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
229#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
230pub enum Aggregate {
231    /// Map a function over each value
232    Map(Mapping),
233    /// Align the data to a time interval
234    Align(Align),
235    /// Group the data by tags
236    GroupBy(GroupBy),
237    /// Bucket the data by time and tags
238    Bucket(BucketBy),
239    /// Rename the metric
240    As(As),
241}
242
243/// Values for directives
244#[cfg_attr(feature = "wasm", tsify::declare)]
245#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
246#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
247pub enum DirectiveValue {
248    /// Directive with a ident value
249    Ident(String),
250    /// Directive with a literal value
251    Int(i64),
252    /// Directive with a float value
253    Float(f64),
254    /// Directive with a string value
255    String(String),
256    /// Directive with a boolean value
257    Bool(bool),
258    /// Directive with no value
259    None,
260}
261
262impl DirectiveValue {
263    /// Ident value
264    #[must_use]
265    pub fn as_ident(&self) -> Option<&str> {
266        match self {
267            DirectiveValue::Ident(ident) => Some(ident),
268            _ => None,
269        }
270    }
271    /// Int value
272    #[must_use]
273    pub fn as_int(&self) -> Option<i64> {
274        match self {
275            DirectiveValue::Int(int) => Some(*int),
276            _ => None,
277        }
278    }
279    /// Float value
280    #[must_use]
281    pub fn as_float(&self) -> Option<f64> {
282        match self {
283            DirectiveValue::Float(float) => Some(*float),
284            _ => None,
285        }
286    }
287    /// String value
288    #[must_use]
289    pub fn as_string(&self) -> Option<&str> {
290        match self {
291            DirectiveValue::String(string) => Some(string),
292            _ => None,
293        }
294    }
295    /// Bool value
296    #[must_use]
297    pub fn as_bool(&self) -> Option<bool> {
298        match self {
299            DirectiveValue::Bool(bool) => Some(*bool),
300            _ => None,
301        }
302    }
303    /// Tests if value is None
304    #[must_use]
305    pub fn is_none(&self) -> bool {
306        matches!(self, DirectiveValue::None)
307    }
308    /// Tests if value is Some
309    #[must_use]
310    pub fn is_some(&self) -> bool {
311        !self.is_none()
312    }
313}
314
315/// Types for params.
316#[cfg_attr(feature = "wasm", tsify::declare)]
317#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
318pub enum ParamType {
319    /// Duration (e.g. 25s)
320    Duration,
321    /// Dataset
322    Dataset,
323    /// Regex
324    Regex,
325    /// A tag value type
326    Tag(TagType),
327}
328impl std::fmt::Display for ParamType {
329    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
330        match self {
331            ParamType::Dataset => write!(f, "dataset"),
332            ParamType::Duration => write!(f, "duration"),
333            ParamType::Regex => write!(f, "regex"),
334            ParamType::Tag(t) => t.fmt(f),
335        }
336    }
337}
338
339/// Types for params.
340#[cfg_attr(feature = "wasm", tsify::declare)]
341#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
342#[derive(Clone, Copy, Debug, Hash, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
343pub enum TagType {
344    /// String
345    String,
346    /// Int
347    Int,
348    /// Float
349    Float,
350    /// Bool
351    Bool,
352    /// None / Null value
353    None,
354}
355
356impl std::fmt::Display for TagType {
357    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358        write!(
359            f,
360            "{}",
361            match self {
362                TagType::String => "string",
363                TagType::Int => "int",
364                TagType::Float => "float",
365                TagType::Bool => "bool",
366                TagType::None => "null",
367            }
368        )
369    }
370}
371
372/// Directives given to adjust the behavior of the runtime
373#[cfg_attr(feature = "wasm", tsify::declare)]
374pub type Directives = HashMap<String, DirectiveValue>;
375
376/// A param.
377#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
378#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
379pub struct Param {
380    /// The location of the param
381    #[cfg_attr(feature = "wasm", tsify(type = "{ offset: number, length: number }"))]
382    pub span: SourceSpan,
383    /// The name of the param
384    pub name: String,
385    /// The type of the param
386    pub typ: ParamType,
387}
388
389/// A param value.
390#[derive(Debug, Clone, PartialEq)]
391pub enum ParamValue {
392    /// Dataset
393    Dataset(Dataset),
394    /// Duration
395    Duration(RelativeTime),
396    /// String
397    String(String),
398    /// Int
399    Int(i64),
400    /// Float
401    Float(f64),
402    /// Bool
403    Bool(bool),
404    /// Regex
405    Regex(EncodableRegex),
406}
407
408impl ParamValue {
409    /// Get the type of the param value.
410    #[must_use]
411    pub fn typ(&self) -> ParamType {
412        match self {
413            ParamValue::Dataset(_) => ParamType::Dataset,
414            ParamValue::Duration(_) => ParamType::Duration,
415            ParamValue::Regex(_) => ParamType::Regex,
416            ParamValue::String(_) => ParamType::Tag(TagType::String),
417            ParamValue::Int(_) => ParamType::Tag(TagType::Int),
418            ParamValue::Float(_) => ParamType::Tag(TagType::Float),
419            ParamValue::Bool(_) => ParamType::Tag(TagType::Bool),
420        }
421    }
422}
423
424/// The param provided to the query.
425#[derive(Debug, Clone, PartialEq)]
426pub struct ProvidedParam {
427    /// The name of the param.
428    pub name: String,
429    /// The value.
430    pub value: ParamValue,
431}
432
433impl ProvidedParam {
434    /// Create a new `ProvidedParam`.
435    pub fn new(name: impl Into<String>, value: ParamValue) -> Self {
436        Self {
437            name: name.into(),
438            value,
439        }
440    }
441}
442
443/// A smol wrapper around `Vec<ProvidedParam>` for easier use.
444#[derive(Debug, Clone, Default)]
445pub struct ProvidedParams {
446    inner: Vec<ProvidedParam>,
447}
448
449/// The error returned from `ProvidedParams::resolve`.
450#[derive(Debug, thiserror::Error)]
451pub enum ResolveError {
452    /// Param not provided
453    #[error("Param ${0} was not provided to the query")]
454    ParamNotProvided(String),
455    /// Invalid type
456    #[error(
457        "Param ${name} is defined as `{defined}`, but was used in a context that expected one of: {}",
458        expected.iter().map(ToString::to_string).collect::<Vec<_>>().join(", ")
459    )]
460    InvalidType {
461        /// Name of the param
462        name: String,
463        /// Type of the param
464        defined: ParamType,
465        /// The type that is valid in the context it was used
466        expected: Vec<ParamType>,
467    },
468    /// Shared string error
469    #[error("Shared string error: {0}")]
470    SharedString(#[from] strumbra::Error),
471}
472
473/// The error returned from `ProvidedParams::parse`.
474#[derive(Debug, thiserror::Error)]
475pub enum ParseProvidedParamsError {
476    /// Parse failed
477    #[error("Failed to parse the value for ${param_name} as {expected_type}: {err}")]
478    ParseParam {
479        /// Param name
480        param_name: String,
481        /// Expected t ype
482        expected_type: ParamType,
483        /// Parse param error
484        err: ParseParamError,
485    },
486    /// Params provided more than once
487    #[error("These params were provided more than once: {}", .0.join(", "))]
488    ParamsProvidedMoreThanOnce(Vec<String>),
489    /// Params declared but not provided
490    #[error("The following params were declared but not provided: {}", .0.join(", "))]
491    ParamsDeclaredButNotProvided(Vec<String>),
492    /// Too many params provided
493    #[error("The number of params provided exceeds the upper limit of {0}")]
494    TooManyParamsProvided(usize),
495}
496
497/// Warnings we want to surface to the user instead of failing the request.
498#[derive(Debug, Default)]
499pub struct Warnings {
500    inner: Vec<String>,
501}
502
503impl Warnings {
504    /// Create a new warnings structure.
505    #[must_use]
506    pub fn new() -> Self {
507        Self::default()
508    }
509
510    /// Add a new warning.
511    pub fn push(&mut self, warning: impl Into<String>) {
512        self.inner.push(warning.into());
513    }
514
515    /// Returns true if there are no warnings.
516    #[must_use]
517    pub fn is_empty(&self) -> bool {
518        self.inner.is_empty()
519    }
520
521    /// Get the warnings as slice.
522    #[must_use]
523    pub fn as_slice(&self) -> &[String] {
524        &self.inner
525    }
526
527    /// Turn into a vector.
528    #[must_use]
529    pub fn into_vec(self) -> Vec<String> {
530        self.inner
531    }
532}
533
534impl ProvidedParams {
535    /// Create a new `ProvidedParams` struct.
536    #[must_use]
537    pub fn new(inner: Vec<ProvidedParam>) -> Self {
538        Self { inner }
539    }
540
541    /// Parse params from a hashmap of query parameters.
542    /// This will only look at params that start with `param__` and it'll use
543    /// the parser definitions to extract the values.
544    pub fn parse_and_validate(
545        mpl_params: &Params,
546        query_params: &[(String, String)],
547    ) -> Result<(Self, Warnings), ParseProvidedParamsError> {
548        const PREFIX: &str = "param__";
549        const PARAM_COUNT_LIMIT: usize = 128;
550
551        let mut warnings = Warnings::new();
552        let mut defined_more_than_once = HashSet::new();
553        let mut provided_but_not_declared = HashSet::new();
554        let mut seen = HashSet::new();
555
556        let params = query_params
557            .iter()
558            .filter_map(|(name, value)| {
559                if !name.starts_with(PREFIX) {
560                    return None;
561                }
562                let name = name.trim_start_matches(PREFIX);
563                if name.is_empty() {
564                    return None;
565                }
566
567                Some((name, value))
568            })
569            .take(PARAM_COUNT_LIMIT + 1)
570            .collect::<Vec<(&str, &String)>>();
571
572        // we don't support unlimited params
573        if params.len() > PARAM_COUNT_LIMIT {
574            return Err(ParseProvidedParamsError::TooManyParamsProvided(
575                PARAM_COUNT_LIMIT,
576            ));
577        }
578
579        let mut provided_params = Vec::new();
580        for (name, value) in params {
581            if seen.contains(name) {
582                // uh oh, we've already seen this value
583                defined_more_than_once.insert(name);
584                continue;
585            }
586            seen.insert(name);
587
588            // is the param even declared?
589            let Some(mpl_param) = mpl_params.iter().find(|p| p.name == name) else {
590                provided_but_not_declared.insert(name);
591                continue;
592            };
593
594            // parse mpl
595            let parsed = MPLParser::parse(Rule::param_value, value).map_err(|err| {
596                ParseProvidedParamsError::ParseParam {
597                    param_name: name.to_string(),
598                    expected_type: mpl_param.typ,
599                    err: ParseParamError::Parse(ParseError::from(err)),
600                }
601            })?;
602
603            // parse as correct type
604            let value = parser::parse_param_value(mpl_param, parsed).map_err(|err| {
605                ParseProvidedParamsError::ParseParam {
606                    param_name: name.to_string(),
607                    expected_type: mpl_param.typ,
608                    err,
609                }
610            })?;
611
612            provided_params.push(ProvidedParam {
613                name: name.to_string(),
614                value,
615            });
616        }
617
618        if !provided_but_not_declared.is_empty() {
619            // sort for consistency
620            let mut items = provided_but_not_declared
621                .into_iter()
622                .map(|p| format!("${p}"))
623                .collect::<Vec<String>>();
624            items.sort();
625
626            // add to warnings, no need to error
627            warnings.push(format!(
628                "These params were provided but not declared: {}",
629                items.join(", ")
630            ));
631        }
632
633        if !defined_more_than_once.is_empty() {
634            // sort for consistency
635            let mut items = defined_more_than_once
636                .into_iter()
637                .map(String::from)
638                .collect::<Vec<String>>();
639            items.sort();
640
641            return Err(ParseProvidedParamsError::ParamsProvidedMoreThanOnce(items));
642        }
643
644        let declared_param_names = mpl_params
645            .iter()
646            .map(|p| p.name.as_str())
647            .collect::<HashSet<&str>>();
648        let declared_but_not_provided = declared_param_names
649            .difference(&seen)
650            .collect::<Vec<&&str>>();
651        if !declared_but_not_provided.is_empty() {
652            // sort for consistency
653            let mut items = declared_but_not_provided
654                .into_iter()
655                .map(|s| String::from(*s))
656                .collect::<Vec<String>>();
657            items.sort();
658
659            return Err(ParseProvidedParamsError::ParamsDeclaredButNotProvided(
660                items,
661            ));
662        }
663
664        Ok((ProvidedParams::new(provided_params), warnings))
665    }
666
667    /// Return a ref to the inner value.
668    #[must_use]
669    pub fn as_slice(&self) -> &[ProvidedParam] {
670        self.inner.as_slice()
671    }
672
673    fn get_param(&self, name: &str) -> Result<&ProvidedParam, ResolveError> {
674        self.inner
675            .iter()
676            .find(|p| p.name == name)
677            .ok_or(ResolveError::ParamNotProvided(name.to_string()))
678    }
679
680    /// Resolve a `TagValue`.
681    pub fn resolve_tag_value(&self, pv: Parameterized<TagValue>) -> Result<TagValue, ResolveError> {
682        let param = match pv {
683            Parameterized::Concrete(val) => return Ok(val), // no need to resolve
684            Parameterized::Param { span: _, param } => param,
685        };
686
687        let provided_param = self.get_param(&param.name)?;
688        match &provided_param.value {
689            ParamValue::String(val) => Ok(TagValue::String(SharedString::try_from(val)?)),
690            ParamValue::Int(val) => Ok(TagValue::Int(*val)),
691            ParamValue::Float(val) => Ok(TagValue::Float(*val)),
692            ParamValue::Bool(val) => Ok(TagValue::Bool(*val)),
693            val => Err(ResolveError::InvalidType {
694                name: param.name,
695                defined: val.typ(),
696                expected: vec![
697                    ParamType::Tag(TagType::String),
698                    ParamType::Tag(TagType::Int),
699                    ParamType::Tag(TagType::Float),
700                    ParamType::Tag(TagType::Bool),
701                ],
702            }),
703        }
704    }
705
706    /// Resolve a `Dataset`.
707    pub fn resolve_dataset(&self, pv: Parameterized<Dataset>) -> Result<Dataset, ResolveError> {
708        let param = match pv {
709            Parameterized::Concrete(val) => return Ok(val), // no need to resolve
710            Parameterized::Param { span: _, param } => param,
711        };
712
713        let provided_param = self.get_param(&param.name)?;
714        match &provided_param.value {
715            ParamValue::Dataset(dataset) => Ok(dataset.clone()),
716            val => Err(ResolveError::InvalidType {
717                name: param.name,
718                defined: val.typ(),
719                expected: vec![ParamType::Dataset],
720            }),
721        }
722    }
723
724    /// Resolve a `RelativeTime`, aka duration.
725    pub fn resolve_relative_time(
726        &self,
727        pv: Parameterized<RelativeTime>,
728    ) -> Result<RelativeTime, ResolveError> {
729        let param = match pv {
730            Parameterized::Concrete(val) => return Ok(val), // no need to resolve
731            Parameterized::Param { span: _, param } => param,
732        };
733
734        let provided_param = self.get_param(&param.name)?;
735        match &provided_param.value {
736            ParamValue::Duration(relative_time) => Ok(relative_time.clone()),
737            val => Err(ResolveError::InvalidType {
738                name: param.name,
739                defined: val.typ(),
740                expected: vec![ParamType::Duration],
741            }),
742        }
743    }
744
745    /// Resolve a regex.
746    pub fn resolve_regex(
747        &self,
748        pv: Parameterized<EncodableRegex>,
749    ) -> Result<EncodableRegex, ResolveError> {
750        let param = match pv {
751            Parameterized::Concrete(val) => return Ok(val), // no need to resolve
752            Parameterized::Param { span: _, param } => param,
753        };
754
755        let provided_param = self.get_param(&param.name)?;
756        match &provided_param.value {
757            ParamValue::Regex(re) => Ok(re.clone()),
758            val => Err(ResolveError::InvalidType {
759                name: param.name,
760                defined: val.typ(),
761                expected: vec![ParamType::Regex],
762            }),
763        }
764    }
765}
766
767/// Parameters that will be set externally.
768#[cfg_attr(feature = "wasm", tsify::declare)]
769pub type Params = Vec<Param>;
770
771/// A Query AST representing a query in the `MPL` language
772#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
773#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
774#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
775pub enum Query {
776    /// A simple query that will produce a result
777    Simple {
778        /// The source of the data
779        source: Source,
780        /// The filters to apply to the data
781        filters: Vec<Filter>,
782        /// The aggregates to apply to the data
783        aggregates: Vec<Aggregate>,
784        /// The directives
785        directives: Directives,
786        /// The params
787        params: Params,
788        /// How to sample series
789        sample: Option<f64>,
790    },
791    /// A compute query taking the input of two queries and producing a by computing combined values
792    Compute {
793        /// The left hand side query to compute
794        left: Box<Query>,
795        /// The right hand side query to compute
796        right: Box<Query>,
797        /// The name of the metric to produce
798        name: Metric,
799        /// The compute operation used to combine the left and right queries
800        op: ComputeFunction,
801        /// The aggregates to apply to the combined data
802        aggregates: Vec<Aggregate>,
803        /// The directives
804        directives: Directives,
805        /// The params
806        params: Params,
807    },
808}
809
810impl Query {
811    /// Get a ref to the params of the query.
812    #[must_use]
813    pub fn params(&self) -> &Params {
814        match self {
815            Query::Simple { params, .. } | Query::Compute { params, .. } => params,
816        }
817    }
818    /// Get a ref to the directives of the query.
819    #[must_use]
820    pub fn directives(&self) -> &Directives {
821        match self {
822            Query::Simple { directives, .. } | Query::Compute { directives, .. } => directives,
823        }
824    }
825}
826
827impl RelativeTime {
828    /// Converts a relative time to a `Duration`
829    pub fn to_duration(&self) -> Result<Duration, TimeError> {
830        let v = i64::try_from(self.value).map_err(TimeError::InvalidDuration)?;
831        Ok(match self.unit {
832            TimeUnit::Millisecond => Duration::milliseconds(v),
833            TimeUnit::Second => Duration::seconds(v),
834            TimeUnit::Minute => Duration::minutes(v),
835            TimeUnit::Hour => Duration::hours(v),
836            TimeUnit::Day => Duration::days(v),
837            TimeUnit::Week => Duration::weeks(v),
838            TimeUnit::Month => Duration::days(v.saturating_mul(30)),
839            TimeUnit::Year => Duration::days(v.saturating_mul(365)),
840        })
841    }
842
843    /// Converts a relative time to a `Resolution`
844    pub fn to_resolution(&self) -> Result<Resolution, ResolutionError> {
845        match self.unit {
846            TimeUnit::Millisecond => Resolution::secs(self.value / 1000),
847            TimeUnit::Second => Resolution::secs(self.value),
848            TimeUnit::Minute => Resolution::secs(self.value.saturating_mul(60)),
849            TimeUnit::Hour => Resolution::secs(self.value.saturating_mul(60 * 60)),
850            TimeUnit::Day => Resolution::secs(self.value.saturating_mul(60 * 60 * 24)),
851            TimeUnit::Week => Resolution::secs(self.value.saturating_mul(60 * 60 * 24 * 7)),
852            TimeUnit::Month => Resolution::secs(self.value.saturating_mul(60 * 60 * 24 * 30)),
853            TimeUnit::Year => Resolution::secs(self.value.saturating_mul(60 * 60 * 24 * 365)),
854        }
855    }
856}
857
858/// An error that can occur when converting a time value.
859#[derive(Debug, thiserror::Error)]
860pub enum TimeError {
861    /// Invalid timestamp could not be converted to a UTC datetime
862    #[error("Invalid timestamp {0}, could not be converted to a UTC datetime")]
863    InvalidTimestamp(i64),
864    /// Invalid duration could not be converted to Duration as it exceeds the maximum i64
865    #[error(
866        "Invalid duration {0}, could not be converted to Duration as it exceeds the maximum i64"
867    )]
868    InvalidDuration(TryFromIntError),
869}
870#[cfg(feature = "clock")]
871impl Time {
872    fn to_datetime(&self) -> Result<DateTime<Utc>, TimeError> {
873        Ok(match self {
874            Time::Relative(t) => Utc::now() - t.to_duration()?,
875            Time::Timestamp(ts) => {
876                DateTime::<Utc>::from_timestamp(*ts, 0).ok_or(TimeError::InvalidTimestamp(*ts))?
877            }
878            Time::RFC3339(t) => t.with_timezone(&Utc),
879            Time::Modifier(_) => todo!(),
880        })
881    }
882}
883
884#[cfg(feature = "clock")]
885impl TimeRange {
886    /// Converts a time range to a start and pair
887    pub fn to_start_end(&self) -> Result<(DateTime<Utc>, DateTime<Utc>), TimeError> {
888        let start = self.start.to_datetime()?;
889        let end = self
890            .end
891            .as_ref()
892            .map_or_else(|| Ok(Utc::now()), Time::to_datetime)?;
893        Ok((start, end))
894    }
895}