query/promql/
label_values.rs1use std::time::{SystemTime, UNIX_EPOCH};
16
17use common_time::Timestamp;
18use common_time::timestamp::TimeUnit;
19use datafusion_common::{Column, ScalarValue};
20use datafusion_expr::utils::conjunction;
21use datafusion_expr::{Expr, LogicalPlan, LogicalPlanBuilder, col};
22use snafu::{OptionExt, ResultExt};
23use table::TableRef;
24
25use crate::promql::error::{
26 DataFusionPlanningSnafu, Result, SystemTimeOutOfRangeSnafu, TimeIndexNotFoundSnafu,
27 TimestampOutOfRangeSnafu,
28};
29
30fn millis_since_epoch(time: SystemTime) -> Result<Timestamp> {
36 Ok(Timestamp::new_millisecond(signed_millis_since_epoch(time)?))
37}
38
39pub(crate) fn signed_millis_since_epoch(time: SystemTime) -> Result<i64> {
45 let (millis, before_epoch) = match time.duration_since(UNIX_EPOCH) {
46 Ok(duration) => (duration.as_millis(), false),
47 Err(earlier) => (earlier.duration().as_nanos().div_ceil(1_000_000), true),
48 };
49 signed_millis(millis, before_epoch).with_context(|| SystemTimeOutOfRangeSnafu { time })
50}
51
52fn signed_millis(millis: u128, before_epoch: bool) -> Option<i64> {
54 let millis = i64::try_from(millis).ok()?;
55 Some(if before_epoch { -millis } else { millis })
56}
57
58fn build_time_filter(time_index_expr: Expr, start: Timestamp, end: Timestamp) -> Expr {
59 time_index_expr
60 .clone()
61 .gt_eq(Expr::Literal(timestamp_to_scalar_value(start), None))
62 .and(time_index_expr.lt_eq(Expr::Literal(timestamp_to_scalar_value(end), None)))
63}
64
65fn timestamp_to_scalar_value(timestamp: Timestamp) -> ScalarValue {
66 let value = timestamp.value();
67 match timestamp.unit() {
68 TimeUnit::Second => ScalarValue::TimestampSecond(Some(value), None),
69 TimeUnit::Millisecond => ScalarValue::TimestampMillisecond(Some(value), None),
70 TimeUnit::Microsecond => ScalarValue::TimestampMicrosecond(Some(value), None),
71 TimeUnit::Nanosecond => ScalarValue::TimestampNanosecond(Some(value), None),
72 }
73}
74
75pub fn rewrite_label_values_query(
77 table: TableRef,
78 scan_plan: LogicalPlan,
79 mut conditions: Vec<Expr>,
80 label_name: String,
81 start: SystemTime,
82 end: SystemTime,
83) -> Result<LogicalPlan> {
84 let schema = table.schema();
85 let ts_column = schema
86 .timestamp_column()
87 .with_context(|| TimeIndexNotFoundSnafu {
88 table: table.table_info().full_table_name(),
89 })?;
90 let unit = ts_column
91 .data_type
92 .as_timestamp()
93 .map(|data_type| data_type.unit())
94 .with_context(|| TimeIndexNotFoundSnafu {
95 table: table.table_info().full_table_name(),
96 })?;
97
98 let start = millis_since_epoch(start)?;
100 let start = start.convert_to(unit).context(TimestampOutOfRangeSnafu {
101 timestamp: start.value(),
102 unit,
103 })?;
104 let end = millis_since_epoch(end)?;
105 let end = end.convert_to(unit).context(TimestampOutOfRangeSnafu {
106 timestamp: end.value(),
107 unit,
108 })?;
109 let time_index_expr = col(Column::from_name(ts_column.name.clone()));
110
111 conditions.push(build_time_filter(time_index_expr, start, end));
112 let filter = conjunction(conditions).unwrap();
114
115 let logical_plan = LogicalPlanBuilder::from(scan_plan)
117 .filter(filter)
118 .context(DataFusionPlanningSnafu)?
119 .project(vec![col(Column::from_name(label_name))])
120 .context(DataFusionPlanningSnafu)?
121 .distinct()
122 .context(DataFusionPlanningSnafu)?
123 .build()
124 .context(DataFusionPlanningSnafu)?;
125
126 Ok(logical_plan)
127}
128
129#[cfg(test)]
130mod tests {
131 use std::time::Duration;
132
133 use super::*;
134
135 #[test]
136 fn millis_before_the_epoch_are_negative() {
137 let time = UNIX_EPOCH - Duration::from_millis(1);
140 assert_eq!(millis_since_epoch(time).unwrap().value(), -1);
141 assert_eq!(millis_since_epoch(UNIX_EPOCH).unwrap().value(), 0);
142
143 let time = UNIX_EPOCH + Duration::from_millis(1);
144 assert_eq!(millis_since_epoch(time).unwrap().value(), 1);
145 }
146
147 #[test]
148 fn millis_beyond_i64_are_rejected() {
149 let limit = i64::MAX as u128;
150 assert_eq!(signed_millis(limit, false), Some(i64::MAX));
151 assert_eq!(signed_millis(limit, true), Some(-i64::MAX));
152 assert_eq!(signed_millis(limit + 1, false), None);
153 assert_eq!(signed_millis(limit + 1, true), None);
154
155 if let Some(time) = UNIX_EPOCH.checked_add(Duration::from_millis(i64::MAX as u64 + 1)) {
158 assert!(millis_since_epoch(time).is_err());
159 }
160 }
161}