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},
66    multi::{fold_many0, separated_list0},
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/// Modifier type for fold_many0
546enum Modifier {
547    At(AtModifier),
548    Offset(Duration),
549}
550
551/// Parse @ and offset modifiers in any order.
552/// Returns (at_modifier, offset_modifier)
553pub(crate) fn parse_modifiers(
554    input: &str,
555) -> IResult<&str, (Option<AtModifier>, Option<Duration>)> {
556    fold_many0(
557        alt((
558            at_modifier.map(Modifier::At),
559            offset_modifier.map(Modifier::Offset),
560        )),
561        || (None, None),
562        |(at, offset), modifier| match modifier {
563            Modifier::At(a) if at.is_none() => (Some(a), offset),
564            Modifier::Offset(o) if offset.is_none() => (at, Some(o)),
565            // Ignore duplicates
566            _ => (at, offset),
567        },
568    )
569    .parse(input)
570}
571
572/// Parse a label match operator
573fn label_match_op(input: &str) -> IResult<&str, LabelMatchOp> {
574    alt((
575        map(tag("!="), |_| LabelMatchOp::NotEqual),
576        map(tag("!~"), |_| LabelMatchOp::RegexNotMatch),
577        map(tag("=~"), |_| LabelMatchOp::RegexMatch),
578        map(tag("="), |_| LabelMatchOp::Equal),
579    ))
580    .parse(input)
581}
582
583/// Parse a single label matcher: `label_name op "value"`
584fn label_matcher(input: &str) -> IResult<&str, LabelMatcher> {
585    map(
586        (
587            ws_opt,
588            label_name,
589            ws_opt,
590            label_match_op,
591            ws_opt,
592            string_literal,
593        ),
594        |(_, name, _, op, _, value)| LabelMatcher::new(name.to_string(), op, value),
595    )
596    .parse(input)
597}
598
599/// Parse a quoted metric name as a matcher: `"metric_name"` inside braces
600fn quoted_metric_matcher(input: &str) -> IResult<&str, LabelMatcher> {
601    map((ws_opt, string_literal), |(_, name)| {
602        LabelMatcher::new("__name__", LabelMatchOp::Equal, name)
603    })
604    .parse(input)
605}
606
607/// Parse a matcher item (either a label matcher or quoted metric name)
608fn matcher_item(input: &str) -> IResult<&str, LabelMatcher> {
609    alt((label_matcher, quoted_metric_matcher)).parse(input)
610}
611
612/// Parse label matchers inside braces: `{label="value", ...}`
613pub fn label_matchers(input: &str) -> IResult<&str, Vec<LabelMatcher>> {
614    delimited(
615        (char('{'), ws_opt),
616        terminated(
617            separated_list0(delimited(ws_opt, char(','), ws_opt), matcher_item),
618            opt((ws_opt, char(','))), // Allow trailing comma
619        ),
620        (ws_opt, char('}')),
621    )
622    .parse(input)
623}
624
625/// Parse a vector selector
626///
627/// Supports:
628/// - `metric_name` - Simple metric name
629/// - `metric_name{label="value"}` - Metric with label matchers
630/// - `{label="value"}` - Label matchers only
631/// - `{"metric_name"}` - Quoted metric name in braces
632///
633/// # Examples
634///
635/// ```
636/// use rusty_promql_parser::parser::selector::vector_selector;
637///
638/// let (_, sel) = vector_selector("http_requests_total").unwrap();
639/// assert_eq!(sel.name, Some("http_requests_total".to_string()));
640///
641/// let (_, sel) = vector_selector(r#"foo{bar="baz"}"#).unwrap();
642/// assert_eq!(sel.name, Some("foo".to_string()));
643/// assert_eq!(sel.matchers.len(), 1);
644/// ```
645pub fn vector_selector(input: &str) -> IResult<&str, VectorSelector> {
646    map(
647        (base_vector_selector, parse_modifiers),
648        |(mut selector, (at, offset))| {
649            selector.at = at;
650            selector.offset = offset;
651            selector
652        },
653    )
654    .parse(input)
655}
656
657/// Parse a vector selector without offset modifier.
658/// This is used internally by matrix_selector which handles offset after the range.
659pub fn base_vector_selector(input: &str) -> IResult<&str, VectorSelector> {
660    // Try to parse metric name first
661    let name_result = metric_name(input);
662
663    match name_result {
664        Ok((rest, name)) => {
665            // Check for label matchers
666            let (rest, matchers) = opt(label_matchers).parse(rest)?;
667            Ok((
668                rest,
669                VectorSelector {
670                    name: Some(name.to_string()),
671                    matchers: matchers.unwrap_or_default(),
672                    offset: None,
673                    at: None,
674                },
675            ))
676        }
677        Err(_) => {
678            // No metric name, try label matchers only
679            let (rest, matchers) = label_matchers(input)?;
680
681            // Check if any matcher is a __name__ matcher (quoted metric name)
682            let name = matchers
683                .iter()
684                .find(|m| m.name == "__name__" && m.op == LabelMatchOp::Equal)
685                .map(|m| m.value.clone());
686
687            // Filter out the __name__= matcher that we're using as the name
688            let other_matchers: Vec<_> = if name.is_some() {
689                matchers
690                    .into_iter()
691                    .filter(|m| !(m.name == "__name__" && m.op == LabelMatchOp::Equal))
692                    .collect()
693            } else {
694                matchers
695            };
696
697            Ok((
698                rest,
699                VectorSelector {
700                    name,
701                    matchers: other_matchers,
702                    offset: None,
703                    at: None,
704                },
705            ))
706        }
707    }
708}
709
710#[cfg(test)]
711mod tests {
712    use super::*;
713
714    // LabelMatchOp tests
715    #[test]
716    fn test_label_match_op_parse() {
717        assert_eq!(label_match_op("=").unwrap().1, LabelMatchOp::Equal);
718        assert_eq!(label_match_op("!=").unwrap().1, LabelMatchOp::NotEqual);
719        assert_eq!(label_match_op("=~").unwrap().1, LabelMatchOp::RegexMatch);
720        assert_eq!(label_match_op("!~").unwrap().1, LabelMatchOp::RegexNotMatch);
721    }
722
723    #[test]
724    fn test_label_match_op_display() {
725        assert_eq!(LabelMatchOp::Equal.to_string(), "=");
726        assert_eq!(LabelMatchOp::NotEqual.to_string(), "!=");
727        assert_eq!(LabelMatchOp::RegexMatch.to_string(), "=~");
728        assert_eq!(LabelMatchOp::RegexNotMatch.to_string(), "!~");
729    }
730
731    #[test]
732    fn test_label_match_op_properties() {
733        assert!(!LabelMatchOp::Equal.is_negative());
734        assert!(LabelMatchOp::NotEqual.is_negative());
735        assert!(!LabelMatchOp::RegexMatch.is_negative());
736        assert!(LabelMatchOp::RegexNotMatch.is_negative());
737
738        assert!(!LabelMatchOp::Equal.is_regex());
739        assert!(!LabelMatchOp::NotEqual.is_regex());
740        assert!(LabelMatchOp::RegexMatch.is_regex());
741        assert!(LabelMatchOp::RegexNotMatch.is_regex());
742    }
743
744    // LabelMatcher tests
745    #[test]
746    fn test_label_matcher_parse() {
747        let (rest, m) = label_matcher(r#"job="prometheus""#).unwrap();
748        assert!(rest.is_empty());
749        assert_eq!(m.name, "job");
750        assert_eq!(m.op, LabelMatchOp::Equal);
751        assert_eq!(m.value, "prometheus");
752    }
753
754    #[test]
755    fn test_label_matcher_parse_with_spaces() {
756        let (rest, m) = label_matcher(r#"  job  =  "prometheus"  "#).unwrap();
757        assert_eq!(rest, "  "); // Trailing space not consumed
758        assert_eq!(m.name, "job");
759        assert_eq!(m.value, "prometheus");
760    }
761
762    #[test]
763    fn test_label_matcher_not_equal() {
764        let (_, m) = label_matcher(r#"env!="prod""#).unwrap();
765        assert_eq!(m.op, LabelMatchOp::NotEqual);
766    }
767
768    #[test]
769    fn test_label_matcher_regex() {
770        let (_, m) = label_matcher(r#"path=~"/api/.*""#).unwrap();
771        assert_eq!(m.op, LabelMatchOp::RegexMatch);
772        assert_eq!(m.value, "/api/.*");
773    }
774
775    #[test]
776    fn test_label_matcher_regex_not() {
777        let (_, m) = label_matcher(r#"status!~"5..""#).unwrap();
778        assert_eq!(m.op, LabelMatchOp::RegexNotMatch);
779    }
780
781    #[test]
782    fn test_label_matcher_matches_empty() {
783        // Equal empty matches empty
784        assert!(LabelMatcher::new("a", LabelMatchOp::Equal, "").matches_empty());
785        // Equal non-empty doesn't match empty
786        assert!(!LabelMatcher::new("a", LabelMatchOp::Equal, "foo").matches_empty());
787        // NotEqual empty doesn't match empty
788        assert!(!LabelMatcher::new("a", LabelMatchOp::NotEqual, "").matches_empty());
789        // NotEqual non-empty matches empty
790        assert!(LabelMatcher::new("a", LabelMatchOp::NotEqual, "foo").matches_empty());
791        // Regex .* matches empty
792        assert!(LabelMatcher::new("a", LabelMatchOp::RegexMatch, ".*").matches_empty());
793        // Regex .+ doesn't match empty
794        assert!(!LabelMatcher::new("a", LabelMatchOp::RegexMatch, ".+").matches_empty());
795        // Not regex .+ matches empty
796        assert!(LabelMatcher::new("a", LabelMatchOp::RegexNotMatch, ".+").matches_empty());
797    }
798
799    // VectorSelector tests
800    #[test]
801    fn test_vector_selector_simple_name() {
802        let (rest, sel) = vector_selector("foo").unwrap();
803        assert!(rest.is_empty());
804        assert_eq!(sel.name, Some("foo".to_string()));
805        assert!(sel.matchers.is_empty());
806    }
807
808    #[test]
809    fn test_vector_selector_with_underscore() {
810        let (rest, sel) = vector_selector("http_requests_total").unwrap();
811        assert!(rest.is_empty());
812        assert_eq!(sel.name, Some("http_requests_total".to_string()));
813    }
814
815    #[test]
816    fn test_vector_selector_with_colon() {
817        let (rest, sel) = vector_selector("foo:bar:baz").unwrap();
818        assert!(rest.is_empty());
819        assert_eq!(sel.name, Some("foo:bar:baz".to_string()));
820    }
821
822    #[test]
823    fn test_vector_selector_with_label() {
824        let (rest, sel) = vector_selector(r#"foo{bar="baz"}"#).unwrap();
825        assert!(rest.is_empty());
826        assert_eq!(sel.name, Some("foo".to_string()));
827        assert_eq!(sel.matchers.len(), 1);
828        assert_eq!(sel.matchers[0].name, "bar");
829        assert_eq!(sel.matchers[0].value, "baz");
830    }
831
832    #[test]
833    fn test_vector_selector_multiple_labels() {
834        let (rest, sel) = vector_selector(r#"foo{a="b", c="d"}"#).unwrap();
835        assert!(rest.is_empty());
836        assert_eq!(sel.name, Some("foo".to_string()));
837        assert_eq!(sel.matchers.len(), 2);
838        assert_eq!(sel.matchers[0].name, "a");
839        assert_eq!(sel.matchers[1].name, "c");
840    }
841
842    #[test]
843    fn test_vector_selector_trailing_comma() {
844        let (rest, sel) = vector_selector(r#"foo{a="b",}"#).unwrap();
845        assert!(rest.is_empty());
846        assert_eq!(sel.matchers.len(), 1);
847    }
848
849    #[test]
850    fn test_vector_selector_labels_only() {
851        let (rest, sel) = vector_selector(r#"{job="prometheus"}"#).unwrap();
852        assert!(rest.is_empty());
853        assert!(sel.name.is_none());
854        assert_eq!(sel.matchers.len(), 1);
855        assert_eq!(sel.matchers[0].name, "job");
856    }
857
858    #[test]
859    fn test_vector_selector_quoted_metric_name() {
860        let (rest, sel) = vector_selector(r#"{"foo"}"#).unwrap();
861        assert!(rest.is_empty());
862        assert_eq!(sel.name, Some("foo".to_string()));
863        assert!(sel.matchers.is_empty());
864    }
865
866    #[test]
867    fn test_vector_selector_quoted_metric_with_labels() {
868        let (rest, sel) = vector_selector(r#"{"foo", bar="baz"}"#).unwrap();
869        assert!(rest.is_empty());
870        assert_eq!(sel.name, Some("foo".to_string()));
871        assert_eq!(sel.matchers.len(), 1);
872    }
873
874    #[test]
875    fn test_vector_selector_all_operators() {
876        let (rest, sel) = vector_selector(r#"foo{a="b", c!="d", e=~"f", g!~"h"}"#).unwrap();
877        assert!(rest.is_empty());
878        assert_eq!(sel.matchers.len(), 4);
879        assert_eq!(sel.matchers[0].op, LabelMatchOp::Equal);
880        assert_eq!(sel.matchers[1].op, LabelMatchOp::NotEqual);
881        assert_eq!(sel.matchers[2].op, LabelMatchOp::RegexMatch);
882        assert_eq!(sel.matchers[3].op, LabelMatchOp::RegexNotMatch);
883    }
884
885    #[test]
886    fn test_vector_selector_has_non_empty_matcher() {
887        // With metric name - always has non-empty
888        let sel = VectorSelector::new("foo");
889        assert!(sel.has_non_empty_matcher());
890
891        // With non-empty label value
892        let mut sel = VectorSelector::with_matchers(vec![]);
893        sel.add_matcher(LabelMatcher::new("job", LabelMatchOp::Equal, "test"));
894        assert!(sel.has_non_empty_matcher());
895
896        // With only empty matcher
897        let sel =
898            VectorSelector::with_matchers(vec![LabelMatcher::new("x", LabelMatchOp::Equal, "")]);
899        assert!(!sel.has_non_empty_matcher());
900    }
901
902    #[test]
903    fn test_vector_selector_display() {
904        let mut sel = VectorSelector::new("foo");
905        assert_eq!(sel.to_string(), "foo");
906
907        sel.add_matcher(LabelMatcher::new("bar", LabelMatchOp::Equal, "baz"));
908        assert_eq!(sel.to_string(), r#"foo{bar="baz"}"#);
909    }
910
911    #[test]
912    fn test_vector_selector_single_quoted() {
913        let (rest, sel) = vector_selector(r#"foo{bar='baz'}"#).unwrap();
914        assert!(rest.is_empty());
915        assert_eq!(sel.matchers[0].value, "baz");
916    }
917
918    #[test]
919    fn test_vector_selector_backtick() {
920        let (rest, sel) = vector_selector(r#"foo{bar=`baz`}"#).unwrap();
921        assert!(rest.is_empty());
922        assert_eq!(sel.matchers[0].value, "baz");
923    }
924
925    #[test]
926    fn test_vector_selector_keyword_as_metric() {
927        // Keywords can be used as metric names
928        for keyword in [
929            "sum", "min", "max", "avg", "count", "offset", "by", "without",
930        ] {
931            let result = vector_selector(keyword);
932            assert!(
933                result.is_ok(),
934                "Failed to parse keyword as metric: {}",
935                keyword
936            );
937            let (_, sel) = result.unwrap();
938            assert_eq!(sel.name, Some(keyword.to_string()));
939        }
940    }
941
942    #[test]
943    fn test_vector_selector_empty_braces() {
944        // Empty braces should parse but result in no matchers
945        let (rest, sel) = vector_selector("{}").unwrap();
946        assert!(rest.is_empty());
947        assert!(sel.name.is_none());
948        assert!(sel.matchers.is_empty());
949        // Note: validation that this is invalid should happen at a higher level
950    }
951
952    // MatrixSelector tests
953    #[test]
954    fn test_matrix_selector_simple() {
955        let (rest, sel) = matrix_selector("foo[5m]").unwrap();
956        assert!(rest.is_empty());
957        assert_eq!(sel.name(), Some("foo"));
958        assert_eq!(sel.range_millis(), 5 * 60 * 1000);
959    }
960
961    #[test]
962    fn test_matrix_selector_with_labels() {
963        let (rest, sel) = matrix_selector(r#"foo{bar="baz"}[5m]"#).unwrap();
964        assert!(rest.is_empty());
965        assert_eq!(sel.name(), Some("foo"));
966        assert_eq!(sel.matchers().len(), 1);
967        assert_eq!(sel.range_millis(), 5 * 60 * 1000);
968    }
969
970    #[test]
971    fn test_matrix_selector_various_durations() {
972        // Seconds
973        let (_, sel) = matrix_selector("foo[30s]").unwrap();
974        assert_eq!(sel.range_millis(), 30 * 1000);
975
976        // Minutes
977        let (_, sel) = matrix_selector("foo[5m]").unwrap();
978        assert_eq!(sel.range_millis(), 5 * 60 * 1000);
979
980        // Hours
981        let (_, sel) = matrix_selector("foo[1h]").unwrap();
982        assert_eq!(sel.range_millis(), 60 * 60 * 1000);
983
984        // Days
985        let (_, sel) = matrix_selector("foo[1d]").unwrap();
986        assert_eq!(sel.range_millis(), 24 * 60 * 60 * 1000);
987
988        // Weeks
989        let (_, sel) = matrix_selector("foo[1w]").unwrap();
990        assert_eq!(sel.range_millis(), 7 * 24 * 60 * 60 * 1000);
991
992        // Milliseconds
993        let (_, sel) = matrix_selector("foo[100ms]").unwrap();
994        assert_eq!(sel.range_millis(), 100);
995    }
996
997    #[test]
998    fn test_matrix_selector_compound_duration() {
999        // 1h30m = 90 minutes
1000        let (rest, sel) = matrix_selector("foo[1h30m]").unwrap();
1001        assert!(rest.is_empty());
1002        assert_eq!(sel.range_millis(), (60 + 30) * 60 * 1000);
1003    }
1004
1005    #[test]
1006    fn test_matrix_selector_labels_only() {
1007        let (rest, sel) = matrix_selector(r#"{job="prometheus"}[5m]"#).unwrap();
1008        assert!(rest.is_empty());
1009        assert!(sel.name().is_none());
1010        assert_eq!(sel.matchers().len(), 1);
1011    }
1012
1013    #[test]
1014    fn test_matrix_selector_display() {
1015        let sel = MatrixSelector::with_name("foo", Duration::from_secs(300));
1016        assert_eq!(sel.to_string(), "foo[5m]");
1017    }
1018
1019    #[test]
1020    fn test_matrix_selector_display_with_labels() {
1021        let mut vs = VectorSelector::new("foo");
1022        vs.add_matcher(LabelMatcher::new("bar", LabelMatchOp::Equal, "baz"));
1023        let sel = MatrixSelector::new(vs, Duration::from_secs(300));
1024        assert_eq!(sel.to_string(), r#"foo{bar="baz"}[5m]"#);
1025    }
1026
1027    #[test]
1028    fn test_matrix_selector_no_range_fails() {
1029        // Vector selector without range should fail for matrix_selector
1030        let result = matrix_selector("foo");
1031        assert!(result.is_err());
1032    }
1033
1034    #[test]
1035    fn test_matrix_selector_empty_range_fails() {
1036        let result = matrix_selector("foo[]");
1037        assert!(result.is_err());
1038    }
1039
1040    // Offset modifier tests
1041    #[test]
1042    fn test_offset_modifier_basic() {
1043        let (rest, dur) = offset_modifier(" offset 5m").unwrap();
1044        assert!(rest.is_empty());
1045        assert_eq!(dur.as_millis(), 5 * 60 * 1000);
1046    }
1047
1048    #[test]
1049    fn test_offset_modifier_negative() {
1050        let (rest, dur) = offset_modifier(" offset -7m").unwrap();
1051        assert!(rest.is_empty());
1052        assert_eq!(dur.as_millis(), -7 * 60 * 1000);
1053    }
1054
1055    #[test]
1056    fn test_offset_modifier_uppercase() {
1057        let (rest, dur) = offset_modifier(" OFFSET 1h30m").unwrap();
1058        assert!(rest.is_empty());
1059        assert_eq!(dur.as_millis(), 90 * 60 * 1000);
1060    }
1061
1062    #[test]
1063    fn test_offset_modifier_complex_duration() {
1064        let (rest, dur) = offset_modifier(" OFFSET 1m30ms").unwrap();
1065        assert!(rest.is_empty());
1066        assert_eq!(dur.as_millis(), 60 * 1000 + 30);
1067    }
1068
1069    #[test]
1070    fn test_vector_selector_with_offset() {
1071        let (rest, sel) = vector_selector("foo offset 5m").unwrap();
1072        assert!(rest.is_empty());
1073        assert_eq!(sel.name, Some("foo".to_string()));
1074        assert_eq!(sel.offset.unwrap().as_millis(), 5 * 60 * 1000);
1075    }
1076
1077    #[test]
1078    fn test_vector_selector_with_negative_offset() {
1079        let (rest, sel) = vector_selector("foo offset -7m").unwrap();
1080        assert!(rest.is_empty());
1081        assert_eq!(sel.name, Some("foo".to_string()));
1082        assert_eq!(sel.offset.unwrap().as_millis(), -7 * 60 * 1000);
1083    }
1084
1085    #[test]
1086    fn test_vector_selector_with_labels_and_offset() {
1087        let (rest, sel) = vector_selector(r#"foo{bar="baz"} offset 1h"#).unwrap();
1088        assert!(rest.is_empty());
1089        assert_eq!(sel.name, Some("foo".to_string()));
1090        assert_eq!(sel.matchers.len(), 1);
1091        assert_eq!(sel.offset.unwrap().as_millis(), 60 * 60 * 1000);
1092    }
1093
1094    #[test]
1095    fn test_vector_selector_display_with_offset() {
1096        let mut sel = VectorSelector::new("foo");
1097        sel.offset = Some(Duration::from_secs(300));
1098        assert_eq!(sel.to_string(), "foo offset 5m");
1099    }
1100
1101    #[test]
1102    fn test_matrix_selector_with_offset() {
1103        let (rest, sel) = matrix_selector("foo[5m] offset 1h").unwrap();
1104        assert!(rest.is_empty());
1105        assert_eq!(sel.name(), Some("foo"));
1106        assert_eq!(sel.range_millis(), 5 * 60 * 1000);
1107        assert_eq!(sel.offset_millis(), Some(60 * 60 * 1000));
1108    }
1109
1110    #[test]
1111    fn test_matrix_selector_with_labels_and_offset() {
1112        let (rest, sel) = matrix_selector(r#"foo{bar="baz"}[5m] offset 30m"#).unwrap();
1113        assert!(rest.is_empty());
1114        assert_eq!(sel.name(), Some("foo"));
1115        assert_eq!(sel.matchers().len(), 1);
1116        assert_eq!(sel.range_millis(), 5 * 60 * 1000);
1117        assert_eq!(sel.offset_millis(), Some(30 * 60 * 1000));
1118    }
1119
1120    #[test]
1121    fn test_matrix_selector_with_negative_offset() {
1122        let (rest, sel) = matrix_selector("foo[5m] offset -1h").unwrap();
1123        assert!(rest.is_empty());
1124        assert_eq!(sel.offset_millis(), Some(-60 * 60 * 1000));
1125    }
1126
1127    #[test]
1128    fn test_matrix_selector_display_with_offset() {
1129        let mut vs = VectorSelector::new("foo");
1130        vs.offset = Some(Duration::from_secs(3600));
1131        let sel = MatrixSelector::new(vs, Duration::from_secs(300));
1132        assert_eq!(sel.to_string(), "foo[5m] offset 1h");
1133    }
1134
1135    // @ modifier tests
1136    #[test]
1137    fn test_at_modifier_timestamp() {
1138        let (rest, at) = at_modifier(" @ 1603774568").unwrap();
1139        assert!(rest.is_empty());
1140        assert_eq!(at, AtModifier::Timestamp(1_603_774_568_000));
1141    }
1142
1143    #[test]
1144    fn test_at_modifier_negative_timestamp() {
1145        let (rest, at) = at_modifier(" @ -100").unwrap();
1146        assert!(rest.is_empty());
1147        assert_eq!(at, AtModifier::Timestamp(-100_000));
1148    }
1149
1150    #[test]
1151    fn test_at_modifier_float_timestamp() {
1152        let (rest, at) = at_modifier(" @ 3.33").unwrap();
1153        assert!(rest.is_empty());
1154        assert_eq!(at, AtModifier::Timestamp(3_330));
1155    }
1156
1157    #[test]
1158    fn test_at_modifier_start() {
1159        let (rest, at) = at_modifier(" @ start()").unwrap();
1160        assert!(rest.is_empty());
1161        assert_eq!(at, AtModifier::Start);
1162    }
1163
1164    #[test]
1165    fn test_at_modifier_end() {
1166        let (rest, at) = at_modifier(" @ end()").unwrap();
1167        assert!(rest.is_empty());
1168        assert_eq!(at, AtModifier::End);
1169    }
1170
1171    #[test]
1172    fn test_at_modifier_display_timestamp() {
1173        let at = AtModifier::Timestamp(1_603_774_568_000);
1174        assert_eq!(at.to_string(), "@ 1603774568.000");
1175    }
1176
1177    #[test]
1178    fn test_at_modifier_display_start() {
1179        assert_eq!(AtModifier::Start.to_string(), "@ start()");
1180    }
1181
1182    #[test]
1183    fn test_at_modifier_display_end() {
1184        assert_eq!(AtModifier::End.to_string(), "@ end()");
1185    }
1186
1187    #[test]
1188    fn test_vector_selector_with_at() {
1189        let (rest, sel) = vector_selector("foo @ 1603774568").unwrap();
1190        assert!(rest.is_empty());
1191        assert_eq!(sel.name, Some("foo".to_string()));
1192        assert_eq!(sel.at, Some(AtModifier::Timestamp(1_603_774_568_000)));
1193    }
1194
1195    #[test]
1196    fn test_vector_selector_with_at_start() {
1197        let (rest, sel) = vector_selector("foo @ start()").unwrap();
1198        assert!(rest.is_empty());
1199        assert_eq!(sel.at, Some(AtModifier::Start));
1200    }
1201
1202    #[test]
1203    fn test_vector_selector_with_at_and_offset() {
1204        // @ before offset
1205        let (rest, sel) = vector_selector("foo @ 123 offset 5m").unwrap();
1206        assert!(rest.is_empty());
1207        assert_eq!(sel.at, Some(AtModifier::Timestamp(123_000)));
1208        assert_eq!(sel.offset.unwrap().as_millis(), 5 * 60 * 1000);
1209    }
1210
1211    #[test]
1212    fn test_vector_selector_with_offset_and_at() {
1213        // offset before @
1214        let (rest, sel) = vector_selector("foo offset 5m @ 123").unwrap();
1215        assert!(rest.is_empty());
1216        assert_eq!(sel.at, Some(AtModifier::Timestamp(123_000)));
1217        assert_eq!(sel.offset.unwrap().as_millis(), 5 * 60 * 1000);
1218    }
1219
1220    #[test]
1221    fn test_vector_selector_display_with_at() {
1222        let mut sel = VectorSelector::new("foo");
1223        sel.at = Some(AtModifier::Timestamp(123_000));
1224        assert_eq!(sel.to_string(), "foo @ 123.000");
1225    }
1226
1227    #[test]
1228    fn test_vector_selector_display_with_at_and_offset() {
1229        let mut sel = VectorSelector::new("foo");
1230        sel.at = Some(AtModifier::Start);
1231        sel.offset = Some(Duration::from_secs(300));
1232        assert_eq!(sel.to_string(), "foo @ start() offset 5m");
1233    }
1234
1235    #[test]
1236    fn test_matrix_selector_with_at() {
1237        let (rest, sel) = matrix_selector("foo[5m] @ 123").unwrap();
1238        assert!(rest.is_empty());
1239        assert_eq!(sel.at(), Some(&AtModifier::Timestamp(123_000)));
1240    }
1241
1242    #[test]
1243    fn test_matrix_selector_with_at_and_offset() {
1244        let (rest, sel) = matrix_selector("foo[5m] @ 123 offset 1h").unwrap();
1245        assert!(rest.is_empty());
1246        assert_eq!(sel.at(), Some(&AtModifier::Timestamp(123_000)));
1247        assert_eq!(sel.offset_millis(), Some(60 * 60 * 1000));
1248    }
1249
1250    #[test]
1251    fn test_matrix_selector_with_offset_and_at() {
1252        let (rest, sel) = matrix_selector("foo[5m] offset 1h @ 123").unwrap();
1253        assert!(rest.is_empty());
1254        assert_eq!(sel.at(), Some(&AtModifier::Timestamp(123_000)));
1255        assert_eq!(sel.offset_millis(), Some(60 * 60 * 1000));
1256    }
1257
1258    #[test]
1259    fn test_matrix_selector_display_with_at() {
1260        let mut vs = VectorSelector::new("foo");
1261        vs.at = Some(AtModifier::Start);
1262        let sel = MatrixSelector::new(vs, Duration::from_secs(300));
1263        assert_eq!(sel.to_string(), "foo[5m] @ start()");
1264    }
1265
1266    #[test]
1267    fn test_matrix_selector_display_with_at_and_offset() {
1268        let mut vs = VectorSelector::new("foo");
1269        vs.at = Some(AtModifier::Start);
1270        vs.offset = Some(Duration::from_secs(60));
1271        let sel = MatrixSelector::new(vs, Duration::from_secs(300));
1272        assert_eq!(sel.to_string(), "foo[5m] @ start() offset 1m");
1273    }
1274}