1use std::fmt::Display;
16use std::sync::Arc;
17
18use datafusion::arrow::array::{Float64Array, Float64Builder, TimestampMillisecondArray};
19use datafusion::arrow::datatypes::TimeUnit;
20use datafusion::common::DataFusionError;
21use datafusion::logical_expr::{ScalarUDF, Volatility};
22use datafusion::physical_plan::ColumnarValue;
23use datafusion_expr::create_udf;
24use datatypes::arrow::array::Array;
25use datatypes::arrow::datatypes::DataType;
26
27use crate::error;
28use crate::functions::extract_array;
29use crate::range_array::RangeArray;
30
31#[derive(Debug)]
34pub struct IDelta<const IS_RATE: bool> {}
35
36impl<const IS_RATE: bool> IDelta<IS_RATE> {
37 pub const fn name() -> &'static str {
38 if IS_RATE { "prom_irate" } else { "prom_idelta" }
39 }
40
41 pub fn scalar_udf() -> ScalarUDF {
42 create_udf(
43 Self::name(),
44 Self::input_type(),
45 Self::return_type(),
46 Volatility::Volatile,
47 Arc::new(Self::calc) as _,
48 )
49 }
50
51 fn input_type() -> Vec<DataType> {
53 vec![
54 RangeArray::convert_data_type(DataType::Timestamp(TimeUnit::Millisecond, None)),
55 RangeArray::convert_data_type(DataType::Float64),
56 ]
57 }
58
59 fn return_type() -> DataType {
60 DataType::Float64
61 }
62
63 fn calc(input: &[ColumnarValue]) -> Result<ColumnarValue, DataFusionError> {
64 assert_eq!(input.len(), 2);
66 let ts_array = extract_array(&input[0])?;
67 let value_array = extract_array(&input[1])?;
68
69 let ts_range: RangeArray = RangeArray::try_new(ts_array.to_data().into())?;
70 let value_range: RangeArray = RangeArray::try_new(value_array.to_data().into())?;
71 error::ensure(
72 ts_range.len() == value_range.len(),
73 DataFusionError::Execution(format!(
74 "{}: input arrays should have the same length, found {} and {}",
75 Self::name(),
76 ts_range.len(),
77 value_range.len()
78 )),
79 )?;
80 error::ensure(
81 ts_range.value_type() == DataType::Timestamp(TimeUnit::Millisecond, None),
82 DataFusionError::Execution(format!(
83 "{}: expect TimestampMillisecond as time index array's type, found {}",
84 Self::name(),
85 ts_range.value_type()
86 )),
87 )?;
88 error::ensure(
89 value_range.value_type() == DataType::Float64,
90 DataFusionError::Execution(format!(
91 "{}: expect Float64 as value array's type, found {}",
92 Self::name(),
93 value_range.value_type()
94 )),
95 )?;
96
97 let ts_values = ts_range.values();
98 let ts_values = ts_values
99 .as_any()
100 .downcast_ref::<TimestampMillisecondArray>()
101 .unwrap()
102 .values();
103
104 let value_array = value_range.values();
105 let value_array = value_array.as_any().downcast_ref::<Float64Array>().unwrap();
106 let has_nulls = value_array.null_count() > 0;
109 let value_values = value_array.values();
110
111 let mut result_builder = Float64Builder::with_capacity(ts_range.len());
112
113 for index in 0..ts_range.len() {
114 let (ts_offset, len) = ts_range.get_offset_length(index).unwrap();
115 let (value_offset, value_len) = value_range.get_offset_length(index).unwrap();
116 error::ensure(
117 len == value_len,
118 DataFusionError::Execution(format!(
119 "{}: input arrays should have the same length, found {} and {}",
120 Self::name(),
121 len,
122 value_len
123 )),
124 )?;
125 let (last_position, prev_position) = if has_nulls {
126 match last_two_samples(value_array, value_offset, len) {
127 Some(positions) => positions,
128 None => {
129 result_builder.append_null();
130 continue;
131 }
132 }
133 } else {
134 if len < 2 {
135 result_builder.append_null();
136 continue;
137 }
138 (len - 1, len - 2)
139 };
140
141 let last_offset = ts_offset + last_position;
142 let prev_offset = ts_offset + prev_position;
143 let sampled_interval_ms = ts_values[last_offset] - ts_values[prev_offset];
144 if sampled_interval_ms == 0 {
146 result_builder.append_null();
147 continue;
148 }
149 let sampled_interval = sampled_interval_ms as f64 / 1000.0;
150
151 let last_value = value_values[value_offset + last_position];
152 let prev_value = value_values[value_offset + prev_position];
153
154 if !IS_RATE {
155 result_builder.append_value(last_value - prev_value);
156 continue;
157 }
158
159 let result_value = if last_value < prev_value {
160 last_value
162 } else {
163 last_value - prev_value
164 };
165
166 result_builder.append_value(result_value / sampled_interval);
167 }
168
169 let result = ColumnarValue::Array(Arc::new(result_builder.finish()));
170 Ok(result)
171 }
172}
173
174fn last_two_samples(values: &Float64Array, offset: usize, len: usize) -> Option<(usize, usize)> {
177 let mut last = None;
178 for position in (0..len).rev() {
179 if values.is_null(offset + position) {
180 continue;
181 }
182 match last {
183 None => last = Some(position),
184 Some(last) => return Some((last, position)),
185 }
186 }
187 None
188}
189
190impl<const IS_RATE: bool> Display for IDelta<IS_RATE> {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 write!(f, "PromQL Idelta Function (is_rate: {IS_RATE})",)
193 }
194}
195
196#[cfg(test)]
197mod test {
198
199 use datafusion::arrow::buffer::NullBuffer;
200
201 use super::*;
202 use crate::functions::test_util::simple_range_udf_runner;
203
204 #[test]
205 fn basic_idelta_and_irate() {
206 let ts_array = Arc::new(TimestampMillisecondArray::from_iter(
207 [1000i64, 3000, 5000, 7000, 9000, 11000, 13000, 15000, 17000]
208 .into_iter()
209 .map(Some),
210 ));
211 let ts_ranges = [(0, 2), (0, 5), (1, 1), (3, 3), (8, 1), (9, 0)];
212
213 let values_array = Arc::new(Float64Array::from_iter([
214 1.0, 2.0, 3.0, 5.0, 0.0, 6.0, 7.0, 8.0, 9.0,
215 ]));
216 let values_ranges = [(0, 2), (0, 5), (1, 1), (3, 3), (8, 1), (9, 0)];
217
218 let ts_range_array = RangeArray::from_ranges(ts_array.clone(), ts_ranges).unwrap();
220 let value_range_array =
221 RangeArray::from_ranges(values_array.clone(), values_ranges).unwrap();
222 simple_range_udf_runner(
223 IDelta::<false>::scalar_udf(),
224 ts_range_array,
225 value_range_array,
226 vec![],
227 vec![Some(1.0), Some(-5.0), None, Some(6.0), None, None],
228 );
229
230 let ts_range_array = RangeArray::from_ranges(ts_array, ts_ranges).unwrap();
232 let value_range_array = RangeArray::from_ranges(values_array, values_ranges).unwrap();
233 simple_range_udf_runner(
234 IDelta::<true>::scalar_udf(),
235 ts_range_array,
236 value_range_array,
237 vec![],
238 vec![Some(0.5), Some(0.0), None, Some(3.0), None, None],
240 );
241 }
242
243 #[test]
244 fn idelta_uses_last_two_samples_not_last_two_slots() {
245 let ts_array = Arc::new(TimestampMillisecondArray::from_iter_values([
246 0i64, 1000, 2000, 3000,
247 ]));
248 let values_array = Arc::new(Float64Array::new(
250 vec![1.0, 4.0, 100.0, 200.0].into(),
251 Some(NullBuffer::from_iter([true, true, false, false])),
252 ));
253 let ranges = [(0, 4), (2, 2), (3, 1)];
254
255 simple_range_udf_runner(
256 IDelta::<false>::scalar_udf(),
257 RangeArray::from_ranges(ts_array.clone(), ranges).unwrap(),
258 RangeArray::from_ranges(values_array.clone(), ranges).unwrap(),
259 vec![],
260 vec![Some(3.0), None, None],
261 );
262
263 simple_range_udf_runner(
264 IDelta::<true>::scalar_udf(),
265 RangeArray::from_ranges(ts_array, ranges).unwrap(),
266 RangeArray::from_ranges(values_array, ranges).unwrap(),
267 vec![],
268 vec![Some(3.0), None, None],
269 );
270 }
271
272 #[test]
273 fn idelta_skips_samples_in_same_millisecond() {
274 let ts_array = Arc::new(TimestampMillisecondArray::from_iter_values([
276 999i64, 999, 999, 1000,
277 ]));
278 let values_array = Arc::new(Float64Array::from_iter([10.0, 13.0, 999.0, 1000.0]));
279 let ranges = [(0, 3), (2, 2)];
280
281 simple_range_udf_runner(
282 IDelta::<false>::scalar_udf(),
283 RangeArray::from_ranges(ts_array.clone(), ranges).unwrap(),
284 RangeArray::from_ranges(values_array.clone(), ranges).unwrap(),
285 vec![],
286 vec![None, Some(1.0)],
287 );
288
289 simple_range_udf_runner(
290 IDelta::<true>::scalar_udf(),
291 RangeArray::from_ranges(ts_array, ranges).unwrap(),
292 RangeArray::from_ranges(values_array, ranges).unwrap(),
293 vec![],
294 vec![None, Some(1000.0)],
295 );
296 }
297}