rusty_promql_parser/parser/
subquery.rs

1//! Subquery expression parsing for PromQL.
2//!
3//! Subqueries allow evaluating an instant vector expression over a time range,
4//! producing a range vector. This is useful for applying range vector functions
5//! (like `avg_over_time`) to expressions that would otherwise produce instant vectors.
6//!
7//! # Syntax
8//!
9//! ```text
10//! expr[range:step]  # With explicit step
11//! expr[range:]      # With default step
12//! ```
13//!
14//! - **range**: The lookback window (required)
15//! - **step**: The evaluation interval (optional; uses default if omitted)
16//!
17//! # Examples
18//!
19//! ```text
20//! some_metric[5m:1m]              # Evaluate every minute over 5 minutes
21//! rate(http_requests[5m])[30m:1m] # Rate over 5m, sampled every minute for 30m
22//! avg_over_time(metric[5m:])      # Average using default step
23//! ```
24//!
25//! # Code Example
26//!
27//! ```rust
28//! use rusty_promql_parser::parser::subquery::subquery_range;
29//!
30//! let (rest, (range, step)) = subquery_range("[5m:1m]").unwrap();
31//! assert!(rest.is_empty());
32//! assert_eq!(range.as_millis(), 5 * 60 * 1000);
33//! assert_eq!(step.unwrap().as_millis(), 60 * 1000);
34//! ```
35
36use nom::{
37    IResult, Parser,
38    character::complete::char,
39    combinator::{map, opt, peek, recognize},
40    sequence::delimited,
41};
42
43use crate::ast::{Expr, SubqueryExpr};
44use crate::lexer::duration::{Duration, duration};
45use crate::parser::selector::parse_modifiers;
46
47/// Parse a subquery range: `[range:step]` or `[range:]`
48///
49/// Returns (range, optional_step)
50///
51/// # Examples
52///
53/// ```
54/// use rusty_promql_parser::parser::subquery::subquery_range;
55///
56/// let (rest, (range, step)) = subquery_range("[5m:1m]").unwrap();
57/// assert!(rest.is_empty());
58/// assert_eq!(range.as_millis(), 5 * 60 * 1000);
59/// assert_eq!(step.unwrap().as_millis(), 60 * 1000);
60///
61/// let (rest, (range, step)) = subquery_range("[30m:]").unwrap();
62/// assert!(rest.is_empty());
63/// assert_eq!(range.as_millis(), 30 * 60 * 1000);
64/// assert!(step.is_none());
65/// ```
66pub fn subquery_range(input: &str) -> IResult<&str, (Duration, Option<Duration>)> {
67    delimited(
68        char('['),
69        map((duration, char(':'), opt(duration)), |(range, _, step)| {
70            (range, step)
71        }),
72        char(']'),
73    )
74    .parse(input)
75}
76
77/// Try to parse a subquery suffix on an expression.
78///
79/// This function attempts to parse `[range:step]` followed by optional modifiers.
80/// Returns None if the input doesn't start with a subquery bracket.
81///
82/// Note: This only parses the subquery part, not the inner expression.
83/// The caller is responsible for providing the already-parsed inner expression.
84#[allow(dead_code)]
85pub(crate) fn try_parse_subquery(input: &str, expr: Expr) -> IResult<&str, SubqueryExpr> {
86    map(
87        (subquery_range, parse_modifiers),
88        move |((range, step), (at, offset))| SubqueryExpr {
89            expr: expr.clone(),
90            range,
91            step,
92            offset,
93            at,
94        },
95    )
96    .parse(input)
97}
98
99/// Check if the input looks like a subquery bracket `[duration:`.
100///
101/// This distinguishes between matrix selectors (`metric[5m]`) and
102/// subqueries (`metric[5m:]` or `metric[5m:1m]`). The key difference
103/// is the presence of `:` after the first duration.
104pub(crate) fn looks_like_subquery(input: &str) -> bool {
105    // Pattern: '[' duration ':'
106    peek(recognize((char('['), duration, char(':'))))
107        .parse(input)
108        .is_ok()
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use crate::parser::selector::VectorSelector;
115
116    #[test]
117    fn test_subquery_range_with_step() {
118        let (rest, (range, step)) = subquery_range("[5m:1m]").unwrap();
119        assert!(rest.is_empty());
120        assert_eq!(range.as_millis(), 5 * 60 * 1000);
121        assert_eq!(step.unwrap().as_millis(), 60 * 1000);
122    }
123
124    #[test]
125    fn test_subquery_range_without_step() {
126        let (rest, (range, step)) = subquery_range("[30m:]").unwrap();
127        assert!(rest.is_empty());
128        assert_eq!(range.as_millis(), 30 * 60 * 1000);
129        assert!(step.is_none());
130    }
131
132    #[test]
133    fn test_subquery_range_various_durations() {
134        let (_, (range, step)) = subquery_range("[1h:5m]").unwrap();
135        assert_eq!(range.as_millis(), 60 * 60 * 1000);
136        assert_eq!(step.unwrap().as_millis(), 5 * 60 * 1000);
137
138        let (_, (range, step)) = subquery_range("[1d:1h]").unwrap();
139        assert_eq!(range.as_millis(), 24 * 60 * 60 * 1000);
140        assert_eq!(step.unwrap().as_millis(), 60 * 60 * 1000);
141
142        let (_, (range, step)) = subquery_range("[1w:]").unwrap();
143        assert_eq!(range.as_millis(), 7 * 24 * 60 * 60 * 1000);
144        assert!(step.is_none());
145    }
146
147    #[test]
148    fn test_subquery_range_compound_duration() {
149        let (_, (range, step)) = subquery_range("[1h30m:5m30s]").unwrap();
150        assert_eq!(range.as_millis(), (60 + 30) * 60 * 1000);
151        assert_eq!(step.unwrap().as_millis(), (5 * 60 + 30) * 1000);
152    }
153
154    #[test]
155    fn test_subquery_range_invalid_no_colon() {
156        // This is a matrix selector syntax, not subquery
157        assert!(subquery_range("[5m]").is_err());
158    }
159
160    #[test]
161    fn test_subquery_range_invalid_empty() {
162        assert!(subquery_range("[]").is_err());
163        assert!(subquery_range("[:]").is_err());
164    }
165
166    #[test]
167    fn test_try_parse_subquery() {
168        let expr = Expr::VectorSelector(VectorSelector::new("metric"));
169        let (rest, sq) = try_parse_subquery("[5m:1m]", expr).unwrap();
170        assert!(rest.is_empty());
171        assert_eq!(sq.range.as_millis(), 5 * 60 * 1000);
172        assert_eq!(sq.step.unwrap().as_millis(), 60 * 1000);
173    }
174
175    #[test]
176    fn test_try_parse_subquery_with_offset() {
177        let expr = Expr::VectorSelector(VectorSelector::new("metric"));
178        let (rest, sq) = try_parse_subquery("[5m:1m] offset 10m", expr).unwrap();
179        assert!(rest.is_empty());
180        assert_eq!(sq.offset.unwrap().as_millis(), 10 * 60 * 1000);
181    }
182
183    #[test]
184    fn test_try_parse_subquery_with_at() {
185        let expr = Expr::VectorSelector(VectorSelector::new("metric"));
186        let (rest, sq) = try_parse_subquery("[5m:1m] @ 1609459200", expr).unwrap();
187        assert!(rest.is_empty());
188        assert!(sq.at.is_some());
189    }
190
191    #[test]
192    fn test_try_parse_subquery_with_both_modifiers() {
193        let expr = Expr::VectorSelector(VectorSelector::new("metric"));
194        let (rest, sq) = try_parse_subquery("[5m:1m] @ 1609459200 offset 10m", expr).unwrap();
195        assert!(rest.is_empty());
196        assert!(sq.at.is_some());
197        assert!(sq.offset.is_some());
198
199        // Also test the other order
200        let expr = Expr::VectorSelector(VectorSelector::new("metric"));
201        let (rest, sq) = try_parse_subquery("[5m:1m] offset 10m @ 1609459200", expr).unwrap();
202        assert!(rest.is_empty());
203        assert!(sq.at.is_some());
204        assert!(sq.offset.is_some());
205    }
206
207    #[test]
208    fn test_looks_like_subquery() {
209        // Subquery syntax
210        assert!(looks_like_subquery("[5m:]"));
211        assert!(looks_like_subquery("[5m:1m]"));
212        assert!(looks_like_subquery("[1h30m:5m]"));
213
214        // Matrix selector syntax (not subquery)
215        assert!(!looks_like_subquery("[5m]"));
216        assert!(!looks_like_subquery("[1h]"));
217
218        // Not brackets at all
219        assert!(!looks_like_subquery("foo"));
220        assert!(!looks_like_subquery(""));
221        assert!(!looks_like_subquery("(5m)"));
222    }
223
224    #[test]
225    fn test_subquery_expr_display() {
226        let sq = SubqueryExpr {
227            expr: Expr::VectorSelector(VectorSelector::new("metric")),
228            range: Duration::from_secs(300),
229            step: Some(Duration::from_secs(60)),
230            offset: None,
231            at: None,
232        };
233        assert_eq!(sq.to_string(), "metric[5m:1m]");
234
235        let sq = SubqueryExpr {
236            expr: Expr::VectorSelector(VectorSelector::new("metric")),
237            range: Duration::from_secs(300),
238            step: None,
239            offset: None,
240            at: None,
241        };
242        assert_eq!(sq.to_string(), "metric[5m:]");
243
244        let sq = SubqueryExpr {
245            expr: Expr::VectorSelector(VectorSelector::new("metric")),
246            range: Duration::from_secs(300),
247            step: Some(Duration::from_secs(60)),
248            offset: Some(Duration::from_secs(600)),
249            at: None,
250        };
251        assert_eq!(sq.to_string(), "metric[5m:1m] offset 10m");
252    }
253}