Skip to main content

rusty_promql_parser/parser/
selector.rs

1//! Vector and matrix selector parsing for PromQL.
2//!
3//! Selectors are the fundamental way to query time series data in PromQL.
4//!
5//! # Vector Selectors (Instant Vectors)
6//!
7//! A vector selector selects a set of time series with a single sample value
8//! for each at the current timestamp.
9//!
10//! ```text
11//! metric_name
12//! metric_name{label_matchers}
13//! {label_matchers}
14//! ```
15//!
16//! # Matrix Selectors (Range Vectors)
17//!
18//! A matrix selector extends a vector selector with a time range, selecting
19//! multiple samples per time series.
20//!
21//! ```text
22//! metric_name[5m]
23//! metric_name{label="value"}[1h]
24//! ```
25//!
26//! # Label Matchers
27//!
28//! | Operator | Description       | Example               |
29//! |----------|-------------------|-----------------------|
30//! | `=`      | Exact equality    | `job="prometheus"`    |
31//! | `!=`     | Not equal         | `env!="prod"`         |
32//! | `=~`     | Regex match       | `path=~"/api/.*"`     |
33//! | `!~`     | Regex not match   | `status!~"5.."`       |
34//!
35//! # Modifiers
36//!
37//! Selectors can have optional modifiers:
38//!
39//! - **offset**: Shift the time range back: `metric offset 5m`
40//! - **@**: Pin to a specific timestamp: `metric @ 1609459200`
41//!
42//! # Examples
43//!
44//! ```rust
45//! use rusty_promql_parser::parser::selector::{vector_selector, matrix_selector};
46//!
47//! // Simple vector selector
48//! let (_, sel) = vector_selector("http_requests_total").unwrap();
49//! assert_eq!(sel.name, Some("http_requests_total".to_string()));
50//!
51//! // With label matchers
52//! let (_, sel) = vector_selector(r#"http_requests{job="api"}"#).unwrap();
53//! assert_eq!(sel.matchers.len(), 1);
54//!
55//! // Matrix selector with range
56//! let (_, sel) = matrix_selector("http_requests[5m]").unwrap();
57//! assert_eq!(sel.range_millis(), 5 * 60 * 1000);
58//! ```
59
60use nom::{
61    IResult, Parser,
62    branch::alt,
63    bytes::complete::tag,
64    character::complete::char,
65    combinator::{map, opt, success},
66    multi::separated_list1,
67    sequence::{delimited, terminated},
68};
69
70use crate::lexer::{
71    duration::{Duration, duration, signed_duration},
72    identifier::{label_name, metric_name},
73    number::number,
74    string::string_literal,
75    whitespace::ws_opt,
76};
77
78/// The `@` modifier for timestamp pinning.
79///
80/// The `@` modifier allows pinning a query to a specific timestamp,
81/// or to the start/end of the evaluation range.
82///
83/// # Examples
84///
85/// - `metric @ 1609459200` - Pin to Unix timestamp
86/// - `metric @ start()` - Pin to evaluation start
87/// - `metric @ end()` - Pin to evaluation end
88#[derive(Debug, Clone, PartialEq)]
89pub enum AtModifier {
90    /// Pin to a specific Unix timestamp (in milliseconds).
91    Timestamp(i64),
92    /// Pin to the start of the evaluation range: `@ start()`
93    Start,
94    /// Pin to the end of the evaluation range: `@ end()`
95    End,
96}
97
98impl std::fmt::Display for AtModifier {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        match self {
101            AtModifier::Timestamp(ts) => {
102                // Convert milliseconds to seconds with 3 decimal places
103                let secs = *ts as f64 / 1000.0;
104                write!(f, "@ {:.3}", secs)
105            }
106            AtModifier::Start => write!(f, "@ start()"),
107            AtModifier::End => write!(f, "@ end()"),
108        }
109    }
110}
111
112/// Label matching operator.
113///
114/// Used in label matchers to specify how to compare label values.
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum LabelMatchOp {
117    /// `=` - Exact string equality.
118    Equal,
119    /// `!=` - String inequality.
120    NotEqual,
121    /// `=~` - Regex match.
122    RegexMatch,
123    /// `!~` - Regex not match.
124    RegexNotMatch,
125}
126
127impl LabelMatchOp {
128    /// Get the operator as a string
129    pub fn as_str(&self) -> &'static str {
130        match self {
131            LabelMatchOp::Equal => "=",
132            LabelMatchOp::NotEqual => "!=",
133            LabelMatchOp::RegexMatch => "=~",
134            LabelMatchOp::RegexNotMatch => "!~",
135        }
136    }
137
138    /// Check if this is a negative matcher (!=, !~)
139    pub fn is_negative(&self) -> bool {
140        matches!(self, LabelMatchOp::NotEqual | LabelMatchOp::RegexNotMatch)
141    }
142
143    /// Check if this is a regex matcher (=~, !~)
144    pub fn is_regex(&self) -> bool {
145        matches!(self, LabelMatchOp::RegexMatch | LabelMatchOp::RegexNotMatch)
146    }
147}
148
149impl std::fmt::Display for LabelMatchOp {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        write!(f, "{}", self.as_str())
152    }
153}
154
155/// A single label matcher.
156///
157/// Label matchers filter time series based on their label values.
158///
159/// # Example
160///
161/// ```rust
162/// use rusty_promql_parser::parser::selector::{LabelMatcher, LabelMatchOp};
163///
164/// let matcher = LabelMatcher::new("job", LabelMatchOp::Equal, "prometheus");
165/// assert_eq!(matcher.to_string(), r#"job="prometheus""#);
166/// ```
167#[derive(Debug, Clone, PartialEq, Eq)]
168pub struct LabelMatcher {
169    /// Label name (e.g., "job", "__name__").
170    pub name: String,
171    /// Matching operator.
172    pub op: LabelMatchOp,
173    /// Value to match against.
174    pub value: String,
175}
176
177impl LabelMatcher {
178    /// Create a new label matcher
179    pub fn new(name: impl Into<String>, op: LabelMatchOp, value: impl Into<String>) -> Self {
180        Self {
181            name: name.into(),
182            op,
183            value: value.into(),
184        }
185    }
186
187    /// Check if this matcher matches the empty string
188    pub fn matches_empty(&self) -> bool {
189        match self.op {
190            LabelMatchOp::Equal => self.value.is_empty(),
191            LabelMatchOp::NotEqual => !self.value.is_empty(),
192            LabelMatchOp::RegexMatch => {
193                // A regex matches empty if it can match ""
194                // Common patterns: "", ".*", "^$", etc.
195                self.value.is_empty()
196                    || self.value == ".*"
197                    || self.value == "^$"
198                    || self.value == "^.*$"
199            }
200            LabelMatchOp::RegexNotMatch => {
201                // !~ matches empty if the regex doesn't match ""
202                // ".+" requires at least one character, so it doesn't match ""
203                self.value == ".+"
204            }
205        }
206    }
207}
208
209impl std::fmt::Display for LabelMatcher {
210    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211        write!(
212            f,
213            "{}{}\"{}\"",
214            self.name,
215            self.op,
216            self.value.escape_default()
217        )
218    }
219}
220
221/// A vector selector expression (instant vector).
222///
223/// Selects a set of time series with a single sample value for each
224/// at the query evaluation time.
225///
226/// # Example
227///
228/// ```rust
229/// use rusty_promql_parser::parser::selector::{VectorSelector, LabelMatcher, LabelMatchOp};
230///
231/// let mut sel = VectorSelector::new("http_requests_total");
232/// sel.add_matcher(LabelMatcher::new("job", LabelMatchOp::Equal, "api"));
233/// assert_eq!(sel.to_string(), r#"http_requests_total{job="api"}"#);
234/// ```
235#[derive(Debug, Clone, PartialEq)]
236pub struct VectorSelector {
237    /// Metric name (optional if label matchers include `__name__`).
238    pub name: Option<String>,
239    /// Label matchers.
240    pub matchers: Vec<LabelMatcher>,
241    /// Offset modifier (e.g., `offset 5m`, `offset -1h`).
242    pub offset: Option<Duration>,
243    /// `@` modifier for timestamp pinning.
244    pub at: Option<AtModifier>,
245}
246
247impl VectorSelector {
248    /// Create a new vector selector with just a metric name
249    pub fn new(name: impl Into<String>) -> Self {
250        Self {
251            name: Some(name.into()),
252            matchers: Vec::new(),
253            offset: None,
254            at: None,
255        }
256    }
257
258    /// Create a new vector selector with only label matchers
259    pub fn with_matchers(matchers: Vec<LabelMatcher>) -> Self {
260        Self {
261            name: None,
262            matchers,
263            offset: None,
264            at: None,
265        }
266    }
267
268    /// Add a label matcher
269    pub fn add_matcher(&mut self, matcher: LabelMatcher) {
270        self.matchers.push(matcher);
271    }
272
273    /// Get all matchers including the implicit __name__ matcher
274    pub fn all_matchers(&self) -> Vec<LabelMatcher> {
275        let mut result = self.matchers.clone();
276        if let Some(ref name) = self.name {
277            result.push(LabelMatcher::new(
278                "__name__",
279                LabelMatchOp::Equal,
280                name.clone(),
281            ));
282        }
283        result
284    }
285
286    /// Check if this selector has at least one non-empty matcher
287    /// (Required for valid selectors to avoid selecting all series)
288    pub fn has_non_empty_matcher(&self) -> bool {
289        // If we have an explicit metric name, that's a non-empty matcher
290        if self.name.is_some() {
291            return true;
292        }
293
294        // Check if any label matcher doesn't match empty
295        self.matchers.iter().any(|m| !m.matches_empty())
296    }
297}
298
299impl std::fmt::Display for VectorSelector {
300    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
301        if let Some(ref name) = self.name {
302            write!(f, "{}", name)?;
303        }
304        if !self.matchers.is_empty() {
305            write!(f, "{{")?;
306            for (i, m) in self.matchers.iter().enumerate() {
307                if i > 0 {
308                    write!(f, ", ")?;
309                }
310                write!(f, "{}", m)?;
311            }
312            write!(f, "}}")?;
313        }
314        // @ modifier comes before offset in PromQL
315        if let Some(ref at) = self.at {
316            write!(f, " {}", at)?;
317        }
318        if let Some(ref offset) = self.offset {
319            write!(f, " offset {}", offset)?;
320        }
321        Ok(())
322    }
323}
324
325/// A matrix selector expression (range vector).
326///
327/// Selects a range of samples over time for each matching time series.
328/// Extends a vector selector with a duration in square brackets.
329///
330/// # Example
331///
332/// ```rust
333/// use rusty_promql_parser::parser::selector::{MatrixSelector, VectorSelector};
334/// use rusty_promql_parser::lexer::duration::Duration;
335///
336/// let sel = MatrixSelector::with_name("http_requests", Duration::from_secs(300));
337/// assert_eq!(sel.to_string(), "http_requests[5m]");
338/// ```
339#[derive(Debug, Clone, PartialEq)]
340pub struct MatrixSelector {
341    /// The underlying vector selector.
342    pub selector: VectorSelector,
343    /// The range duration (e.g., 5m, 1h, 30s).
344    pub range: Duration,
345}
346
347impl MatrixSelector {
348    /// Create a new matrix selector from a vector selector and range
349    pub fn new(selector: VectorSelector, range: Duration) -> Self {
350        Self { selector, range }
351    }
352
353    /// Create a matrix selector with just a metric name and range
354    pub fn with_name(name: impl Into<String>, range: Duration) -> Self {
355        Self {
356            selector: VectorSelector::new(name),
357            range,
358        }
359    }
360
361    /// Get the metric name (if any)
362    pub fn name(&self) -> Option<&str> {
363        self.selector.name.as_deref()
364    }
365
366    /// Get the label matchers
367    pub fn matchers(&self) -> &[LabelMatcher] {
368        &self.selector.matchers
369    }
370
371    /// Get the range duration in milliseconds
372    pub fn range_millis(&self) -> i64 {
373        self.range.as_millis()
374    }
375
376    /// Get the offset duration (if any)
377    pub fn offset(&self) -> Option<&Duration> {
378        self.selector.offset.as_ref()
379    }
380
381    /// Get the offset duration in milliseconds (if any)
382    pub fn offset_millis(&self) -> Option<i64> {
383        self.selector.offset.map(|d| d.as_millis())
384    }
385
386    /// Get the @ modifier (if any)
387    pub fn at(&self) -> Option<&AtModifier> {
388        self.selector.at.as_ref()
389    }
390}
391
392impl std::fmt::Display for MatrixSelector {
393    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394        // Write name and matchers without offset/at
395        if let Some(ref name) = self.selector.name {
396            write!(f, "{}", name)?;
397        }
398        if !self.selector.matchers.is_empty() {
399            write!(f, "{{")?;
400            for (i, m) in self.selector.matchers.iter().enumerate() {
401                if i > 0 {
402                    write!(f, ", ")?;
403                }
404                write!(f, "{}", m)?;
405            }
406            write!(f, "}}")?;
407        }
408        // Write range
409        write!(f, "[{}]", self.range)?;
410        // Write @ modifier (if any) - comes before offset
411        if let Some(ref at) = self.selector.at {
412            write!(f, " {}", at)?;
413        }
414        // Write offset (if any)
415        if let Some(ref offset) = self.selector.offset {
416            write!(f, " offset {}", offset)?;
417        }
418        Ok(())
419    }
420}
421
422/// Parse a range duration in square brackets: `[5m]`, `[1h30m]`
423fn range_duration(input: &str) -> IResult<&str, Duration> {
424    delimited(char('['), duration, char(']')).parse(input)
425}
426
427/// Parse the offset modifier keyword (case-insensitive)
428fn offset_keyword(input: &str) -> IResult<&str, &str> {
429    alt((tag("offset"), tag("OFFSET"), tag("Offset"))).parse(input)
430}
431
432/// Parse an offset modifier: `offset 5m`, `offset -1h`
433///
434/// The offset modifier shifts the time range of a vector selector back in time.
435/// Negative offsets look forward in time (relative to query evaluation time).
436///
437/// # Examples
438///
439/// ```
440/// use rusty_promql_parser::parser::selector::offset_modifier;
441///
442/// let (rest, dur) = offset_modifier(" offset 5m").unwrap();
443/// assert!(rest.is_empty());
444/// assert_eq!(dur.as_millis(), 300_000);
445///
446/// let (rest, dur) = offset_modifier(" offset -7m").unwrap();
447/// assert!(rest.is_empty());
448/// assert_eq!(dur.as_millis(), -420_000);
449///
450/// let (rest, dur) = offset_modifier(" OFFSET 1h30m").unwrap();
451/// assert!(rest.is_empty());
452/// assert_eq!(dur.as_millis(), 5_400_000);
453/// ```
454pub fn offset_modifier(input: &str) -> IResult<&str, Duration> {
455    let (rest, _) = ws_opt(input)?;
456    let (rest, _) = offset_keyword(rest)?;
457    let (rest, _) = ws_opt(rest)?;
458    signed_duration(rest)
459}
460
461/// Parse the @ modifier: `@ <timestamp>`, `@ start()`, `@ end()`
462///
463/// The @ modifier allows pinning a query to a specific timestamp,
464/// or to the start/end of the evaluation range.
465///
466/// # Examples
467///
468/// ```
469/// use rusty_promql_parser::parser::selector::at_modifier;
470///
471/// // Timestamp in seconds
472/// let (rest, at) = at_modifier(" @ 1603774568").unwrap();
473/// assert!(rest.is_empty());
474///
475/// // start() preprocessor
476/// let (rest, at) = at_modifier(" @ start()").unwrap();
477/// assert!(rest.is_empty());
478///
479/// // end() preprocessor
480/// let (rest, at) = at_modifier(" @ end()").unwrap();
481/// assert!(rest.is_empty());
482/// ```
483pub fn at_modifier(input: &str) -> IResult<&str, AtModifier> {
484    let (rest, _) = ws_opt(input)?;
485    let (rest, _) = char('@')(rest)?;
486    let (rest, _) = ws_opt(rest)?;
487
488    // Try start() or end() first
489    if let Ok((rest, _)) = tag::<&str, &str, nom::error::Error<&str>>("start()")(rest) {
490        return Ok((rest, AtModifier::Start));
491    }
492    if let Ok((rest, _)) = tag::<&str, &str, nom::error::Error<&str>>("end()")(rest) {
493        return Ok((rest, AtModifier::End));
494    }
495
496    // Otherwise parse a number (timestamp in seconds)
497    let (rest, ts) = number(rest)?;
498
499    // Check for invalid timestamps (Inf, NaN)
500    if ts.is_infinite() || ts.is_nan() {
501        return Err(nom::Err::Error(nom::error::Error::new(
502            input,
503            nom::error::ErrorKind::Verify,
504        )));
505    }
506
507    // Convert seconds to milliseconds, rounding to nearest
508    let ts_ms = (ts * 1000.0).round() as i64;
509    Ok((rest, AtModifier::Timestamp(ts_ms)))
510}
511
512/// Parse a matrix selector (range vector)
513///
514/// A matrix selector consists of a vector selector followed by a range duration
515/// in square brackets.
516///
517/// # Examples
518///
519/// ```
520/// use rusty_promql_parser::parser::selector::matrix_selector;
521///
522/// // Simple metric with range
523/// let (rest, sel) = matrix_selector("http_requests_total[5m]").unwrap();
524/// assert!(rest.is_empty());
525/// assert_eq!(sel.name(), Some("http_requests_total"));
526/// assert_eq!(sel.range_millis(), 5 * 60 * 1000);
527///
528/// // With label matchers
529/// let (rest, sel) = matrix_selector(r#"http_requests_total{job="api"}[1h]"#).unwrap();
530/// assert!(rest.is_empty());
531/// assert_eq!(sel.matchers().len(), 1);
532/// ```
533pub fn matrix_selector(input: &str) -> IResult<&str, MatrixSelector> {
534    map(
535        (base_vector_selector, range_duration, parse_modifiers),
536        |(mut selector, range, (at, offset))| {
537            selector.at = at;
538            selector.offset = offset;
539            MatrixSelector::new(selector, range)
540        },
541    )
542    .parse(input)
543}
544
545/// Parse @ and offset modifiers in any order.
546/// Returns (at_modifier, offset_modifier)
547pub(crate) fn parse_modifiers(
548    input: &str,
549) -> IResult<&str, (Option<AtModifier>, Option<Duration>)> {
550    let mut rest = input;
551    let mut at = None;
552    let mut offset = None;
553
554    loop {
555        if let Ok((next, parsed_at)) = at_modifier(rest) {
556            if at.is_some() {
557                return Err(nom::Err::Error(nom::error::Error::new(
558                    rest,
559                    nom::error::ErrorKind::Verify,
560                )));
561            }
562            at = Some(parsed_at);
563            rest = next;
564            continue;
565        }
566
567        if let Ok((next, parsed_offset)) = offset_modifier(rest) {
568            if offset.is_some() {
569                return Err(nom::Err::Error(nom::error::Error::new(
570                    rest,
571                    nom::error::ErrorKind::Verify,
572                )));
573            }
574            offset = Some(parsed_offset);
575            rest = next;
576            continue;
577        }
578
579        break;
580    }
581
582    Ok((rest, (at, offset)))
583}
584
585/// Parse a label match operator
586fn label_match_op(input: &str) -> IResult<&str, LabelMatchOp> {
587    alt((
588        map(tag("!="), |_| LabelMatchOp::NotEqual),
589        map(tag("!~"), |_| LabelMatchOp::RegexNotMatch),
590        map(tag("=~"), |_| LabelMatchOp::RegexMatch),
591        map(tag("="), |_| LabelMatchOp::Equal),
592    ))
593    .parse(input)
594}
595
596/// Parse a single label matcher: `label_name op "value"`
597fn label_matcher(input: &str) -> IResult<&str, LabelMatcher> {
598    map(
599        (
600            ws_opt,
601            label_name,
602            ws_opt,
603            label_match_op,
604            ws_opt,
605            string_literal,
606        ),
607        |(_, name, _, op, _, value)| LabelMatcher::new(name.to_string(), op, value),
608    )
609    .parse(input)
610}
611
612/// Parse a quoted metric name as a matcher: `"metric_name"` inside braces
613fn quoted_metric_matcher(input: &str) -> IResult<&str, LabelMatcher> {
614    map((ws_opt, string_literal), |(_, name)| {
615        LabelMatcher::new("__name__", LabelMatchOp::Equal, name)
616    })
617    .parse(input)
618}
619
620/// Parse a matcher item (either a label matcher or quoted metric name)
621fn matcher_item(input: &str) -> IResult<&str, LabelMatcher> {
622    alt((label_matcher, quoted_metric_matcher)).parse(input)
623}
624
625/// Parse label matchers inside braces: `{label="value", ...}`
626pub fn label_matchers(input: &str) -> IResult<&str, Vec<LabelMatcher>> {
627    delimited(
628        (char('{'), ws_opt),
629        alt((
630            terminated(
631                separated_list1(delimited(ws_opt, char(','), ws_opt), matcher_item),
632                opt((ws_opt, char(','))),
633            ),
634            success(Vec::new()),
635        )),
636        (ws_opt, char('}')),
637    )
638    .parse(input)
639}
640
641/// Parse a vector selector
642///
643/// Supports:
644/// - `metric_name` - Simple metric name
645/// - `metric_name{label="value"}` - Metric with label matchers
646/// - `{label="value"}` - Label matchers only
647/// - `{"metric_name"}` - Quoted metric name in braces
648///
649/// # Examples
650///
651/// ```
652/// use rusty_promql_parser::parser::selector::vector_selector;
653///
654/// let (_, sel) = vector_selector("http_requests_total").unwrap();
655/// assert_eq!(sel.name, Some("http_requests_total".to_string()));
656///
657/// let (_, sel) = vector_selector(r#"foo{bar="baz"}"#).unwrap();
658/// assert_eq!(sel.name, Some("foo".to_string()));
659/// assert_eq!(sel.matchers.len(), 1);
660/// ```
661pub fn vector_selector(input: &str) -> IResult<&str, VectorSelector> {
662    map(
663        (base_vector_selector, parse_modifiers),
664        |(mut selector, (at, offset))| {
665            selector.at = at;
666            selector.offset = offset;
667            selector
668        },
669    )
670    .parse(input)
671}
672
673/// Parse a vector selector without offset modifier.
674/// This is used internally by matrix_selector which handles offset after the range.
675pub fn base_vector_selector(input: &str) -> IResult<&str, VectorSelector> {
676    // Try to parse metric name first
677    let name_result = metric_name(input);
678
679    match name_result {
680        Ok((rest, name)) => {
681            // Check for label matchers
682            let (rest, matchers) = opt(label_matchers).parse(rest)?;
683            Ok((
684                rest,
685                VectorSelector {
686                    name: Some(name.to_string()),
687                    matchers: matchers.unwrap_or_default(),
688                    offset: None,
689                    at: None,
690                },
691            ))
692        }
693        Err(_) => {
694            // No metric name, try label matchers only
695            let (rest, matchers) = label_matchers(input)?;
696
697            // Check if any matcher is a __name__ matcher (quoted metric name)
698            let name = matchers
699                .iter()
700                .find(|m| m.name == "__name__" && m.op == LabelMatchOp::Equal)
701                .map(|m| m.value.clone());
702
703            // Filter out the __name__= matcher that we're using as the name
704            let other_matchers: Vec<_> = if name.is_some() {
705                matchers
706                    .into_iter()
707                    .filter(|m| !(m.name == "__name__" && m.op == LabelMatchOp::Equal))
708                    .collect()
709            } else {
710                matchers
711            };
712
713            Ok((
714                rest,
715                VectorSelector {
716                    name,
717                    matchers: other_matchers,
718                    offset: None,
719                    at: None,
720                },
721            ))
722        }
723    }
724}
725
726#[cfg(test)]
727mod tests {
728    use super::*;
729
730    // LabelMatchOp tests
731    #[test]
732    fn test_label_match_op_parse() {
733        assert_eq!(label_match_op("=").unwrap().1, LabelMatchOp::Equal);
734        assert_eq!(label_match_op("!=").unwrap().1, LabelMatchOp::NotEqual);
735        assert_eq!(label_match_op("=~").unwrap().1, LabelMatchOp::RegexMatch);
736        assert_eq!(label_match_op("!~").unwrap().1, LabelMatchOp::RegexNotMatch);
737    }
738
739    #[test]
740    fn test_label_match_op_display() {
741        assert_eq!(LabelMatchOp::Equal.to_string(), "=");
742        assert_eq!(LabelMatchOp::NotEqual.to_string(), "!=");
743        assert_eq!(LabelMatchOp::RegexMatch.to_string(), "=~");
744        assert_eq!(LabelMatchOp::RegexNotMatch.to_string(), "!~");
745    }
746
747    #[test]
748    fn test_label_match_op_properties() {
749        assert!(!LabelMatchOp::Equal.is_negative());
750        assert!(LabelMatchOp::NotEqual.is_negative());
751        assert!(!LabelMatchOp::RegexMatch.is_negative());
752        assert!(LabelMatchOp::RegexNotMatch.is_negative());
753
754        assert!(!LabelMatchOp::Equal.is_regex());
755        assert!(!LabelMatchOp::NotEqual.is_regex());
756        assert!(LabelMatchOp::RegexMatch.is_regex());
757        assert!(LabelMatchOp::RegexNotMatch.is_regex());
758    }
759
760    // LabelMatcher tests
761    #[test]
762    fn test_label_matcher_parse() {
763        let (rest, m) = label_matcher(r#"job="prometheus""#).unwrap();
764        assert!(rest.is_empty());
765        assert_eq!(m.name, "job");
766        assert_eq!(m.op, LabelMatchOp::Equal);
767        assert_eq!(m.value, "prometheus");
768    }
769
770    #[test]
771    fn test_label_matcher_parse_with_spaces() {
772        let (rest, m) = label_matcher(r#"  job  =  "prometheus"  "#).unwrap();
773        assert_eq!(rest, "  "); // Trailing space not consumed
774        assert_eq!(m.name, "job");
775        assert_eq!(m.value, "prometheus");
776    }
777
778    #[test]
779    fn test_label_matcher_not_equal() {
780        let (_, m) = label_matcher(r#"env!="prod""#).unwrap();
781        assert_eq!(m.op, LabelMatchOp::NotEqual);
782    }
783
784    #[test]
785    fn test_label_matcher_regex() {
786        let (_, m) = label_matcher(r#"path=~"/api/.*""#).unwrap();
787        assert_eq!(m.op, LabelMatchOp::RegexMatch);
788        assert_eq!(m.value, "/api/.*");
789    }
790
791    #[test]
792    fn test_label_matcher_regex_not() {
793        let (_, m) = label_matcher(r#"status!~"5..""#).unwrap();
794        assert_eq!(m.op, LabelMatchOp::RegexNotMatch);
795    }
796
797    #[test]
798    fn test_label_matcher_matches_empty() {
799        // Equal empty matches empty
800        assert!(LabelMatcher::new("a", LabelMatchOp::Equal, "").matches_empty());
801        // Equal non-empty doesn't match empty
802        assert!(!LabelMatcher::new("a", LabelMatchOp::Equal, "foo").matches_empty());
803        // NotEqual empty doesn't match empty
804        assert!(!LabelMatcher::new("a", LabelMatchOp::NotEqual, "").matches_empty());
805        // NotEqual non-empty matches empty
806        assert!(LabelMatcher::new("a", LabelMatchOp::NotEqual, "foo").matches_empty());
807        // Regex .* matches empty
808        assert!(LabelMatcher::new("a", LabelMatchOp::RegexMatch, ".*").matches_empty());
809        // Regex .+ doesn't match empty
810        assert!(!LabelMatcher::new("a", LabelMatchOp::RegexMatch, ".+").matches_empty());
811        // Not regex .+ matches empty
812        assert!(LabelMatcher::new("a", LabelMatchOp::RegexNotMatch, ".+").matches_empty());
813    }
814
815    // VectorSelector tests
816    #[test]
817    fn test_vector_selector_simple_name() {
818        let (rest, sel) = vector_selector("foo").unwrap();
819        assert!(rest.is_empty());
820        assert_eq!(sel.name, Some("foo".to_string()));
821        assert!(sel.matchers.is_empty());
822    }
823
824    #[test]
825    fn test_vector_selector_with_underscore() {
826        let (rest, sel) = vector_selector("http_requests_total").unwrap();
827        assert!(rest.is_empty());
828        assert_eq!(sel.name, Some("http_requests_total".to_string()));
829    }
830
831    #[test]
832    fn test_vector_selector_with_colon() {
833        let (rest, sel) = vector_selector("foo:bar:baz").unwrap();
834        assert!(rest.is_empty());
835        assert_eq!(sel.name, Some("foo:bar:baz".to_string()));
836    }
837
838    #[test]
839    fn test_vector_selector_with_label() {
840        let (rest, sel) = vector_selector(r#"foo{bar="baz"}"#).unwrap();
841        assert!(rest.is_empty());
842        assert_eq!(sel.name, Some("foo".to_string()));
843        assert_eq!(sel.matchers.len(), 1);
844        assert_eq!(sel.matchers[0].name, "bar");
845        assert_eq!(sel.matchers[0].value, "baz");
846    }
847
848    #[test]
849    fn test_vector_selector_multiple_labels() {
850        let (rest, sel) = vector_selector(r#"foo{a="b", c="d"}"#).unwrap();
851        assert!(rest.is_empty());
852        assert_eq!(sel.name, Some("foo".to_string()));
853        assert_eq!(sel.matchers.len(), 2);
854        assert_eq!(sel.matchers[0].name, "a");
855        assert_eq!(sel.matchers[1].name, "c");
856    }
857
858    #[test]
859    fn test_vector_selector_trailing_comma() {
860        let (rest, sel) = vector_selector(r#"foo{a="b",}"#).unwrap();
861        assert!(rest.is_empty());
862        assert_eq!(sel.matchers.len(), 1);
863    }
864
865    #[test]
866    fn test_vector_selector_labels_only() {
867        let (rest, sel) = vector_selector(r#"{job="prometheus"}"#).unwrap();
868        assert!(rest.is_empty());
869        assert!(sel.name.is_none());
870        assert_eq!(sel.matchers.len(), 1);
871        assert_eq!(sel.matchers[0].name, "job");
872    }
873
874    #[test]
875    fn test_vector_selector_quoted_metric_name() {
876        let (rest, sel) = vector_selector(r#"{"foo"}"#).unwrap();
877        assert!(rest.is_empty());
878        assert_eq!(sel.name, Some("foo".to_string()));
879        assert!(sel.matchers.is_empty());
880    }
881
882    #[test]
883    fn test_vector_selector_quoted_metric_with_labels() {
884        let (rest, sel) = vector_selector(r#"{"foo", bar="baz"}"#).unwrap();
885        assert!(rest.is_empty());
886        assert_eq!(sel.name, Some("foo".to_string()));
887        assert_eq!(sel.matchers.len(), 1);
888    }
889
890    #[test]
891    fn test_vector_selector_all_operators() {
892        let (rest, sel) = vector_selector(r#"foo{a="b", c!="d", e=~"f", g!~"h"}"#).unwrap();
893        assert!(rest.is_empty());
894        assert_eq!(sel.matchers.len(), 4);
895        assert_eq!(sel.matchers[0].op, LabelMatchOp::Equal);
896        assert_eq!(sel.matchers[1].op, LabelMatchOp::NotEqual);
897        assert_eq!(sel.matchers[2].op, LabelMatchOp::RegexMatch);
898        assert_eq!(sel.matchers[3].op, LabelMatchOp::RegexNotMatch);
899    }
900
901    #[test]
902    fn test_vector_selector_has_non_empty_matcher() {
903        // With metric name - always has non-empty
904        let sel = VectorSelector::new("foo");
905        assert!(sel.has_non_empty_matcher());
906
907        // With non-empty label value
908        let mut sel = VectorSelector::with_matchers(vec![]);
909        sel.add_matcher(LabelMatcher::new("job", LabelMatchOp::Equal, "test"));
910        assert!(sel.has_non_empty_matcher());
911
912        // With only empty matcher
913        let sel =
914            VectorSelector::with_matchers(vec![LabelMatcher::new("x", LabelMatchOp::Equal, "")]);
915        assert!(!sel.has_non_empty_matcher());
916    }
917
918    #[test]
919    fn test_vector_selector_display() {
920        let mut sel = VectorSelector::new("foo");
921        assert_eq!(sel.to_string(), "foo");
922
923        sel.add_matcher(LabelMatcher::new("bar", LabelMatchOp::Equal, "baz"));
924        assert_eq!(sel.to_string(), r#"foo{bar="baz"}"#);
925    }
926
927    #[test]
928    fn test_vector_selector_single_quoted() {
929        let (rest, sel) = vector_selector(r#"foo{bar='baz'}"#).unwrap();
930        assert!(rest.is_empty());
931        assert_eq!(sel.matchers[0].value, "baz");
932    }
933
934    #[test]
935    fn test_vector_selector_backtick() {
936        let (rest, sel) = vector_selector(r#"foo{bar=`baz`}"#).unwrap();
937        assert!(rest.is_empty());
938        assert_eq!(sel.matchers[0].value, "baz");
939    }
940
941    #[test]
942    fn test_vector_selector_keyword_as_metric() {
943        // Keywords can be used as metric names
944        for keyword in [
945            "sum", "min", "max", "avg", "count", "offset", "by", "without",
946        ] {
947            let result = vector_selector(keyword);
948            assert!(
949                result.is_ok(),
950                "Failed to parse keyword as metric: {}",
951                keyword
952            );
953            let (_, sel) = result.unwrap();
954            assert_eq!(sel.name, Some(keyword.to_string()));
955        }
956    }
957
958    #[test]
959    fn test_vector_selector_empty_braces() {
960        // Empty braces should parse but result in no matchers
961        let (rest, sel) = vector_selector("{}").unwrap();
962        assert!(rest.is_empty());
963        assert!(sel.name.is_none());
964        assert!(sel.matchers.is_empty());
965        // Note: validation that this is invalid should happen at a higher level
966    }
967
968    // MatrixSelector tests
969    #[test]
970    fn test_matrix_selector_simple() {
971        let (rest, sel) = matrix_selector("foo[5m]").unwrap();
972        assert!(rest.is_empty());
973        assert_eq!(sel.name(), Some("foo"));
974        assert_eq!(sel.range_millis(), 5 * 60 * 1000);
975    }
976
977    #[test]
978    fn test_matrix_selector_with_labels() {
979        let (rest, sel) = matrix_selector(r#"foo{bar="baz"}[5m]"#).unwrap();
980        assert!(rest.is_empty());
981        assert_eq!(sel.name(), Some("foo"));
982        assert_eq!(sel.matchers().len(), 1);
983        assert_eq!(sel.range_millis(), 5 * 60 * 1000);
984    }
985
986    #[test]
987    fn test_matrix_selector_various_durations() {
988        // Seconds
989        let (_, sel) = matrix_selector("foo[30s]").unwrap();
990        assert_eq!(sel.range_millis(), 30 * 1000);
991
992        // Minutes
993        let (_, sel) = matrix_selector("foo[5m]").unwrap();
994        assert_eq!(sel.range_millis(), 5 * 60 * 1000);
995
996        // Hours
997        let (_, sel) = matrix_selector("foo[1h]").unwrap();
998        assert_eq!(sel.range_millis(), 60 * 60 * 1000);
999
1000        // Days
1001        let (_, sel) = matrix_selector("foo[1d]").unwrap();
1002        assert_eq!(sel.range_millis(), 24 * 60 * 60 * 1000);
1003
1004        // Weeks
1005        let (_, sel) = matrix_selector("foo[1w]").unwrap();
1006        assert_eq!(sel.range_millis(), 7 * 24 * 60 * 60 * 1000);
1007
1008        // Milliseconds
1009        let (_, sel) = matrix_selector("foo[100ms]").unwrap();
1010        assert_eq!(sel.range_millis(), 100);
1011    }
1012
1013    #[test]
1014    fn test_matrix_selector_compound_duration() {
1015        // 1h30m = 90 minutes
1016        let (rest, sel) = matrix_selector("foo[1h30m]").unwrap();
1017        assert!(rest.is_empty());
1018        assert_eq!(sel.range_millis(), (60 + 30) * 60 * 1000);
1019    }
1020
1021    #[test]
1022    fn test_matrix_selector_labels_only() {
1023        let (rest, sel) = matrix_selector(r#"{job="prometheus"}[5m]"#).unwrap();
1024        assert!(rest.is_empty());
1025        assert!(sel.name().is_none());
1026        assert_eq!(sel.matchers().len(), 1);
1027    }
1028
1029    #[test]
1030    fn test_matrix_selector_display() {
1031        let sel = MatrixSelector::with_name("foo", Duration::from_secs(300));
1032        assert_eq!(sel.to_string(), "foo[5m]");
1033    }
1034
1035    #[test]
1036    fn test_matrix_selector_display_with_labels() {
1037        let mut vs = VectorSelector::new("foo");
1038        vs.add_matcher(LabelMatcher::new("bar", LabelMatchOp::Equal, "baz"));
1039        let sel = MatrixSelector::new(vs, Duration::from_secs(300));
1040        assert_eq!(sel.to_string(), r#"foo{bar="baz"}[5m]"#);
1041    }
1042
1043    #[test]
1044    fn test_matrix_selector_no_range_fails() {
1045        // Vector selector without range should fail for matrix_selector
1046        let result = matrix_selector("foo");
1047        assert!(result.is_err());
1048    }
1049
1050    #[test]
1051    fn test_matrix_selector_empty_range_fails() {
1052        let result = matrix_selector("foo[]");
1053        assert!(result.is_err());
1054    }
1055
1056    // Offset modifier tests
1057    #[test]
1058    fn test_offset_modifier_basic() {
1059        let (rest, dur) = offset_modifier(" offset 5m").unwrap();
1060        assert!(rest.is_empty());
1061        assert_eq!(dur.as_millis(), 5 * 60 * 1000);
1062    }
1063
1064    #[test]
1065    fn test_offset_modifier_negative() {
1066        let (rest, dur) = offset_modifier(" offset -7m").unwrap();
1067        assert!(rest.is_empty());
1068        assert_eq!(dur.as_millis(), -7 * 60 * 1000);
1069    }
1070
1071    #[test]
1072    fn test_offset_modifier_uppercase() {
1073        let (rest, dur) = offset_modifier(" OFFSET 1h30m").unwrap();
1074        assert!(rest.is_empty());
1075        assert_eq!(dur.as_millis(), 90 * 60 * 1000);
1076    }
1077
1078    #[test]
1079    fn test_offset_modifier_complex_duration() {
1080        let (rest, dur) = offset_modifier(" OFFSET 1m30ms").unwrap();
1081        assert!(rest.is_empty());
1082        assert_eq!(dur.as_millis(), 60 * 1000 + 30);
1083    }
1084
1085    #[test]
1086    fn test_vector_selector_with_offset() {
1087        let (rest, sel) = vector_selector("foo offset 5m").unwrap();
1088        assert!(rest.is_empty());
1089        assert_eq!(sel.name, Some("foo".to_string()));
1090        assert_eq!(sel.offset.unwrap().as_millis(), 5 * 60 * 1000);
1091    }
1092
1093    #[test]
1094    fn test_vector_selector_with_negative_offset() {
1095        let (rest, sel) = vector_selector("foo offset -7m").unwrap();
1096        assert!(rest.is_empty());
1097        assert_eq!(sel.name, Some("foo".to_string()));
1098        assert_eq!(sel.offset.unwrap().as_millis(), -7 * 60 * 1000);
1099    }
1100
1101    #[test]
1102    fn test_vector_selector_with_labels_and_offset() {
1103        let (rest, sel) = vector_selector(r#"foo{bar="baz"} offset 1h"#).unwrap();
1104        assert!(rest.is_empty());
1105        assert_eq!(sel.name, Some("foo".to_string()));
1106        assert_eq!(sel.matchers.len(), 1);
1107        assert_eq!(sel.offset.unwrap().as_millis(), 60 * 60 * 1000);
1108    }
1109
1110    #[test]
1111    fn test_vector_selector_display_with_offset() {
1112        let mut sel = VectorSelector::new("foo");
1113        sel.offset = Some(Duration::from_secs(300));
1114        assert_eq!(sel.to_string(), "foo offset 5m");
1115    }
1116
1117    #[test]
1118    fn test_matrix_selector_with_offset() {
1119        let (rest, sel) = matrix_selector("foo[5m] offset 1h").unwrap();
1120        assert!(rest.is_empty());
1121        assert_eq!(sel.name(), Some("foo"));
1122        assert_eq!(sel.range_millis(), 5 * 60 * 1000);
1123        assert_eq!(sel.offset_millis(), Some(60 * 60 * 1000));
1124    }
1125
1126    #[test]
1127    fn test_matrix_selector_with_labels_and_offset() {
1128        let (rest, sel) = matrix_selector(r#"foo{bar="baz"}[5m] offset 30m"#).unwrap();
1129        assert!(rest.is_empty());
1130        assert_eq!(sel.name(), Some("foo"));
1131        assert_eq!(sel.matchers().len(), 1);
1132        assert_eq!(sel.range_millis(), 5 * 60 * 1000);
1133        assert_eq!(sel.offset_millis(), Some(30 * 60 * 1000));
1134    }
1135
1136    #[test]
1137    fn test_matrix_selector_with_negative_offset() {
1138        let (rest, sel) = matrix_selector("foo[5m] offset -1h").unwrap();
1139        assert!(rest.is_empty());
1140        assert_eq!(sel.offset_millis(), Some(-60 * 60 * 1000));
1141    }
1142
1143    #[test]
1144    fn test_matrix_selector_display_with_offset() {
1145        let mut vs = VectorSelector::new("foo");
1146        vs.offset = Some(Duration::from_secs(3600));
1147        let sel = MatrixSelector::new(vs, Duration::from_secs(300));
1148        assert_eq!(sel.to_string(), "foo[5m] offset 1h");
1149    }
1150
1151    // @ modifier tests
1152    #[test]
1153    fn test_at_modifier_timestamp() {
1154        let (rest, at) = at_modifier(" @ 1603774568").unwrap();
1155        assert!(rest.is_empty());
1156        assert_eq!(at, AtModifier::Timestamp(1_603_774_568_000));
1157    }
1158
1159    #[test]
1160    fn test_at_modifier_negative_timestamp() {
1161        let (rest, at) = at_modifier(" @ -100").unwrap();
1162        assert!(rest.is_empty());
1163        assert_eq!(at, AtModifier::Timestamp(-100_000));
1164    }
1165
1166    #[test]
1167    fn test_at_modifier_float_timestamp() {
1168        let (rest, at) = at_modifier(" @ 3.33").unwrap();
1169        assert!(rest.is_empty());
1170        assert_eq!(at, AtModifier::Timestamp(3_330));
1171    }
1172
1173    #[test]
1174    fn test_at_modifier_start() {
1175        let (rest, at) = at_modifier(" @ start()").unwrap();
1176        assert!(rest.is_empty());
1177        assert_eq!(at, AtModifier::Start);
1178    }
1179
1180    #[test]
1181    fn test_at_modifier_end() {
1182        let (rest, at) = at_modifier(" @ end()").unwrap();
1183        assert!(rest.is_empty());
1184        assert_eq!(at, AtModifier::End);
1185    }
1186
1187    #[test]
1188    fn test_at_modifier_display_timestamp() {
1189        let at = AtModifier::Timestamp(1_603_774_568_000);
1190        assert_eq!(at.to_string(), "@ 1603774568.000");
1191    }
1192
1193    #[test]
1194    fn test_at_modifier_display_start() {
1195        assert_eq!(AtModifier::Start.to_string(), "@ start()");
1196    }
1197
1198    #[test]
1199    fn test_at_modifier_display_end() {
1200        assert_eq!(AtModifier::End.to_string(), "@ end()");
1201    }
1202
1203    #[test]
1204    fn test_vector_selector_with_at() {
1205        let (rest, sel) = vector_selector("foo @ 1603774568").unwrap();
1206        assert!(rest.is_empty());
1207        assert_eq!(sel.name, Some("foo".to_string()));
1208        assert_eq!(sel.at, Some(AtModifier::Timestamp(1_603_774_568_000)));
1209    }
1210
1211    #[test]
1212    fn test_vector_selector_with_at_start() {
1213        let (rest, sel) = vector_selector("foo @ start()").unwrap();
1214        assert!(rest.is_empty());
1215        assert_eq!(sel.at, Some(AtModifier::Start));
1216    }
1217
1218    #[test]
1219    fn test_vector_selector_with_at_and_offset() {
1220        // @ before offset
1221        let (rest, sel) = vector_selector("foo @ 123 offset 5m").unwrap();
1222        assert!(rest.is_empty());
1223        assert_eq!(sel.at, Some(AtModifier::Timestamp(123_000)));
1224        assert_eq!(sel.offset.unwrap().as_millis(), 5 * 60 * 1000);
1225    }
1226
1227    #[test]
1228    fn test_vector_selector_with_offset_and_at() {
1229        // offset before @
1230        let (rest, sel) = vector_selector("foo offset 5m @ 123").unwrap();
1231        assert!(rest.is_empty());
1232        assert_eq!(sel.at, Some(AtModifier::Timestamp(123_000)));
1233        assert_eq!(sel.offset.unwrap().as_millis(), 5 * 60 * 1000);
1234    }
1235
1236    #[test]
1237    fn test_vector_selector_display_with_at() {
1238        let mut sel = VectorSelector::new("foo");
1239        sel.at = Some(AtModifier::Timestamp(123_000));
1240        assert_eq!(sel.to_string(), "foo @ 123.000");
1241    }
1242
1243    #[test]
1244    fn test_vector_selector_display_with_at_and_offset() {
1245        let mut sel = VectorSelector::new("foo");
1246        sel.at = Some(AtModifier::Start);
1247        sel.offset = Some(Duration::from_secs(300));
1248        assert_eq!(sel.to_string(), "foo @ start() offset 5m");
1249    }
1250
1251    #[test]
1252    fn test_matrix_selector_with_at() {
1253        let (rest, sel) = matrix_selector("foo[5m] @ 123").unwrap();
1254        assert!(rest.is_empty());
1255        assert_eq!(sel.at(), Some(&AtModifier::Timestamp(123_000)));
1256    }
1257
1258    #[test]
1259    fn test_matrix_selector_with_at_and_offset() {
1260        let (rest, sel) = matrix_selector("foo[5m] @ 123 offset 1h").unwrap();
1261        assert!(rest.is_empty());
1262        assert_eq!(sel.at(), Some(&AtModifier::Timestamp(123_000)));
1263        assert_eq!(sel.offset_millis(), Some(60 * 60 * 1000));
1264    }
1265
1266    #[test]
1267    fn test_matrix_selector_with_offset_and_at() {
1268        let (rest, sel) = matrix_selector("foo[5m] offset 1h @ 123").unwrap();
1269        assert!(rest.is_empty());
1270        assert_eq!(sel.at(), Some(&AtModifier::Timestamp(123_000)));
1271        assert_eq!(sel.offset_millis(), Some(60 * 60 * 1000));
1272    }
1273
1274    #[test]
1275    fn test_matrix_selector_display_with_at() {
1276        let mut vs = VectorSelector::new("foo");
1277        vs.at = Some(AtModifier::Start);
1278        let sel = MatrixSelector::new(vs, Duration::from_secs(300));
1279        assert_eq!(sel.to_string(), "foo[5m] @ start()");
1280    }
1281
1282    #[test]
1283    fn test_matrix_selector_display_with_at_and_offset() {
1284        let mut vs = VectorSelector::new("foo");
1285        vs.at = Some(AtModifier::Start);
1286        vs.offset = Some(Duration::from_secs(60));
1287        let sel = MatrixSelector::new(vs, Duration::from_secs(300));
1288        assert_eq!(sel.to_string(), "foo[5m] @ start() offset 1m");
1289    }
1290}