1mod aggr_over_time;
16mod changes;
17mod deriv;
18mod double_exponential_smoothing;
19mod edge_count;
20mod extrapolate_rate;
21mod idelta;
22mod native_histogram;
23mod predict_linear;
24mod quantile;
25mod quantile_aggr;
26mod resets;
27mod round;
28#[cfg(test)]
29mod test_util;
30
31pub use aggr_over_time::{
32 AbsentOverTime, AvgOverTime, CountOverTime, LastOverTime, MaxOverTime, MinOverTime,
33 PresentOverTime, StddevOverTime, StdvarOverTime, SumOverTime,
34};
35pub use changes::Changes;
36use datafusion::arrow::array::{
37 ArrayRef, DictionaryArray, Float64Array, TimestampMillisecondArray,
38};
39use datafusion::error::DataFusionError;
40use datafusion::physical_plan::ColumnarValue;
41use datatypes::arrow::array::Array;
42use datatypes::arrow::datatypes::{DataType, Int64Type};
43pub use deriv::Deriv;
44pub use double_exponential_smoothing::DoubleExponentialSmoothing;
45pub use extrapolate_rate::{Delta, Increase, Rate};
46pub use idelta::IDelta;
47pub use native_histogram::{
48 MixedRange, NativeHistogramAbsentOverTime, NativeHistogramAdd, NativeHistogramAggAvg,
49 NativeHistogramAggSum, NativeHistogramAvg, NativeHistogramAvgOverTime, NativeHistogramChanges,
50 NativeHistogramCount, NativeHistogramCountOverTime, NativeHistogramDelta,
51 NativeHistogramDivScalar, NativeHistogramDrop, NativeHistogramEq, NativeHistogramFraction,
52 NativeHistogramIDelta, NativeHistogramIRate, NativeHistogramIncrease,
53 NativeHistogramLastOverTime, NativeHistogramMulScalar, NativeHistogramNeg,
54 NativeHistogramNotEq, NativeHistogramPresentOverTime, NativeHistogramQuantile,
55 NativeHistogramRate, NativeHistogramResets, NativeHistogramScalarMul, NativeHistogramStddev,
56 NativeHistogramStdvar, NativeHistogramSub, NativeHistogramSum, NativeHistogramSumOverTime,
57 NativeHistogramToString, PromqlFloatToString,
58};
59pub use predict_linear::PredictLinear;
60pub use quantile::QuantileOverTime;
61pub use quantile_aggr::{QUANTILE_NAME, quantile_udaf};
62pub use resets::Resets;
63pub use round::Round;
64
65use crate::range_array::RangeArray;
66
67pub(crate) fn extract_array(columnar_value: &ColumnarValue) -> Result<ArrayRef, DataFusionError> {
71 match columnar_value {
72 ColumnarValue::Array(array) => Ok(array.clone()),
73 ColumnarValue::Scalar(scalar) => Ok(scalar.to_array_of_size(1)?),
74 }
75}
76
77pub(crate) fn extract_range_dict(
79 columnar_value: &ColumnarValue,
80 func_name: &str,
81 arg_name: &str,
82 expected_value_type: &DataType,
83) -> Result<DictionaryArray<Int64Type>, DataFusionError> {
84 let array = extract_array(columnar_value)?;
85 let dict = array
86 .as_any()
87 .downcast_ref::<DictionaryArray<Int64Type>>()
88 .ok_or_else(|| {
89 DataFusionError::Execution(format!(
90 "{func_name}: expect {arg_name} as DictionaryArray<Int64>, found {}",
91 array.data_type()
92 ))
93 })?
94 .clone();
95
96 if &dict.value_type() != expected_value_type {
97 return Err(DataFusionError::Execution(format!(
98 "{func_name}: expect {arg_name} values of type {expected_value_type}, found {}",
99 dict.value_type()
100 )));
101 }
102
103 RangeArray::try_new(dict.clone()).map_err(DataFusionError::from)?;
104 Ok(dict)
105}
106
107pub(crate) fn extract_range_array(
109 columnar_value: &ColumnarValue,
110) -> Result<RangeArray, DataFusionError> {
111 let array = extract_array(columnar_value)?;
112 let dict = array
113 .as_any()
114 .downcast_ref::<DictionaryArray<Int64Type>>()
115 .ok_or_else(|| {
116 DataFusionError::Execution(format!(
117 "expected DictionaryArray<Int64>, found {}",
118 array.data_type()
119 ))
120 })?
121 .clone();
122 RangeArray::try_new(dict).map_err(DataFusionError::from)
123}
124
125pub(crate) fn compensated_sum_inc(inc: f64, sum: f64, mut compensation: f64) -> (f64, f64) {
132 let new_sum = sum + inc;
133 if sum.abs() >= inc.abs() {
134 compensation += (sum - new_sum) + inc;
135 } else {
136 compensation += (inc - new_sum) + sum;
137 }
138 (new_sum, compensation)
139}
140
141pub(crate) fn linear_regression(
145 times: &TimestampMillisecondArray,
146 values: &Float64Array,
147 intercept_time: i64,
148) -> (Option<f64>, Option<f64>) {
149 linear_regression_slice(times.values(), values, 0, values.len(), intercept_time)
150}
151
152pub(crate) fn linear_regression_slice(
153 times: &[i64],
154 values: &Float64Array,
155 offset: usize,
156 len: usize,
157 intercept_time: i64,
158) -> (Option<f64>, Option<f64>) {
159 linear_regression_slices(times, offset, values, offset, len, intercept_time)
160}
161
162pub(crate) fn linear_regression_slices(
163 times: &[i64],
164 time_offset: usize,
165 values: &Float64Array,
166 value_offset: usize,
167 len: usize,
168 intercept_time: i64,
169) -> (Option<f64>, Option<f64>) {
170 let raw_values = values.values();
171 let has_nulls = values.null_count() > 0;
172 let mut count: f64 = 0.0;
173 let mut sum_x: f64 = 0.0;
174 let mut sum_y: f64 = 0.0;
175 let mut sum_xy: f64 = 0.0;
176 let mut sum_x2: f64 = 0.0;
177 let mut comp_x: f64 = 0.0;
178 let mut comp_y: f64 = 0.0;
179 let mut comp_xy: f64 = 0.0;
180 let mut comp_x2: f64 = 0.0;
181
182 let mut const_y = true;
183 let mut init_y = None;
184
185 for i in 0..len {
186 let time_idx = time_offset + i;
187 let value_idx = value_offset + i;
188 if has_nulls && values.is_null(value_idx) {
189 continue;
190 }
191 let value = raw_values[value_idx];
192 let time = times[time_idx] as f64;
193 let initial = init_y.get_or_insert(value);
194 if const_y && count > 0.0 && value != *initial {
195 const_y = false;
196 }
197 count += 1.0;
198 let x = (time - intercept_time as f64) / 1e3f64;
199 (sum_x, comp_x) = compensated_sum_inc(x, sum_x, comp_x);
200 (sum_y, comp_y) = compensated_sum_inc(value, sum_y, comp_y);
201 (sum_xy, comp_xy) = compensated_sum_inc(x * value, sum_xy, comp_xy);
202 (sum_x2, comp_x2) = compensated_sum_inc(x * x, sum_x2, comp_x2);
203 }
204
205 if count < 2.0 {
206 return (None, None);
207 }
208
209 if const_y {
210 let init_y = init_y.unwrap();
211 if !init_y.is_finite() {
212 return (None, None);
213 }
214 return (Some(0.0), Some(init_y));
215 }
216
217 sum_x += comp_x;
218 sum_y += comp_y;
219 sum_xy += comp_xy;
220 sum_x2 += comp_x2;
221
222 let cov_xy = sum_xy - sum_x * sum_y / count;
223 let var_x = sum_x2 - sum_x * sum_x / count;
224
225 let slope = cov_xy / var_x;
226 let intercept = sum_y / count - slope * sum_x / count;
227
228 (Some(slope), Some(intercept))
229}
230
231#[cfg(test)]
232mod test {
233 use std::sync::Arc;
234
235 use datafusion::physical_plan::ColumnarValue;
236 use datatypes::arrow::array::Int64Array;
237 use datatypes::arrow::datatypes::Int64Type;
238
239 use super::*;
240 use crate::range_array::RangeArray;
241
242 #[test]
243 fn calculate_linear_regression_none() {
244 let ts_array = TimestampMillisecondArray::from_iter(
245 [
246 0i64, 300, 600, 900, 1200, 1500, 1800, 2100, 2400, 2700, 3000,
247 ]
248 .into_iter()
249 .map(Some),
250 );
251 let values_array = Float64Array::from_iter([
252 1.0 / 0.0,
253 1.0 / 0.0,
254 1.0 / 0.0,
255 1.0 / 0.0,
256 1.0 / 0.0,
257 1.0 / 0.0,
258 1.0 / 0.0,
259 1.0 / 0.0,
260 1.0 / 0.0,
261 1.0 / 0.0,
262 ]);
263 let (slope, intercept) = linear_regression(&ts_array, &values_array, ts_array.value(0));
264 assert_eq!(slope, None);
265 assert_eq!(intercept, None);
266 }
267
268 #[test]
269 fn calculate_linear_regression_value_is_const() {
270 let ts_array = TimestampMillisecondArray::from_iter(
271 [
272 0i64, 300, 600, 900, 1200, 1500, 1800, 2100, 2400, 2700, 3000,
273 ]
274 .into_iter()
275 .map(Some),
276 );
277 let values_array =
278 Float64Array::from_iter([10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0]);
279 let (slope, intercept) = linear_regression(&ts_array, &values_array, ts_array.value(0));
280 assert_eq!(slope, Some(0.0));
281 assert_eq!(intercept, Some(10.0));
282 }
283
284 #[test]
285 fn calculate_linear_regression() {
286 let ts_array = TimestampMillisecondArray::from_iter(
287 [
288 0i64, 300, 600, 900, 1200, 1500, 1800, 2100, 2400, 2700, 3000,
289 ]
290 .into_iter()
291 .map(Some),
292 );
293 let values_array = Float64Array::from_iter([
294 0.0, 10.0, 20.0, 30.0, 40.0, 0.0, 10.0, 20.0, 30.0, 40.0, 50.0,
295 ]);
296 let (slope, intercept) = linear_regression(&ts_array, &values_array, ts_array.value(0));
297 assert_eq!(slope, Some(10.606060606060607));
298 assert_eq!(intercept, Some(6.818181818181815));
299
300 let (slope, intercept) = linear_regression(&ts_array, &values_array, 3000);
301 assert_eq!(slope, Some(10.606060606060607));
302 assert_eq!(intercept, Some(38.63636363636364));
303 }
304
305 #[test]
306 fn calculate_linear_regression_value_have_none() {
307 let ts_array = TimestampMillisecondArray::from_iter(
308 [
309 0i64, 300, 600, 900, 1200, 1350, 1500, 1800, 2100, 2400, 2550, 2700, 3000,
310 ]
311 .into_iter()
312 .map(Some),
313 );
314 let values_array: Float64Array = [
315 Some(0.0),
316 Some(10.0),
317 Some(20.0),
318 Some(30.0),
319 Some(40.0),
320 None,
321 Some(0.0),
322 Some(10.0),
323 Some(20.0),
324 Some(30.0),
325 None,
326 Some(40.0),
327 Some(50.0),
328 ]
329 .into_iter()
330 .collect();
331 let (slope, intercept) = linear_regression(&ts_array, &values_array, ts_array.value(0));
332 assert_eq!(slope, Some(10.606060606060607));
333 assert_eq!(intercept, Some(6.818181818181815));
334 }
335
336 #[test]
337 fn calculate_linear_regression_value_all_none() {
338 let ts_array = TimestampMillisecondArray::from_iter([0i64, 300, 600].into_iter().map(Some));
339 let values_array: Float64Array = [None, None, None].into_iter().collect();
340 let (slope, intercept) = linear_regression(&ts_array, &values_array, ts_array.value(0));
341 assert_eq!(slope, None);
342 assert_eq!(intercept, None);
343 }
344
345 #[test]
347 fn test_kahan_sum() {
348 let inputs = vec![1.0, 10.0f64.powf(100.0), 1.0, -10.0f64.powf(100.0)];
349
350 let mut sum = 0.0;
351 let mut c = 0f64;
352
353 for v in inputs {
354 (sum, c) = compensated_sum_inc(v, sum, c);
355 }
356 assert_eq!(sum + c, 2.0)
357 }
358
359 #[test]
360 fn extract_range_array_rejects_external_dictionary_with_null_keys() {
361 let keys = Int64Array::from_iter([Some(0), None]);
362 let values = Arc::new(Float64Array::from_iter([1.0, 2.0]));
363 let dict = DictionaryArray::<Int64Type>::try_new(keys, values).unwrap();
364
365 let err = extract_range_array(&ColumnarValue::Array(Arc::new(dict))).unwrap_err();
366 assert!(err.to_string().contains("Empty range is not expected"));
367 }
368
369 #[test]
370 fn extract_range_array_accepts_internal_packed_ranges() {
371 let values = Arc::new(Float64Array::from_iter([1.0, 2.0, 3.0]));
372 let range_array = RangeArray::from_ranges(values, [(0, 2), (1, 2)]).unwrap();
373
374 let extracted =
375 extract_range_array(&ColumnarValue::Array(Arc::new(range_array.into_dict()))).unwrap();
376
377 assert_eq!(extracted.get_offset_length(0), Some((0, 2)));
378 assert_eq!(extracted.get_offset_length(1), Some((1, 2)));
379 }
380}