harmos_storage/query/transform/
histogram.rs1pub use harmos_domain::model::storage::query::transform::histogram::Histogram;
17
18use crate::storage::query::transform::FieldPathExecution;
21use std::sync::Arc;
23use arrow_array::{Float64Array, RecordBatch, UInt64Array};
25use arrow_schema::{DataType, Field, Schema};
26use super::downsample::number;
28use super::{invalid, numeric_type};
29use crate::Result;
30use harmos_domain::model::storage::query::scalar::ScalarValue;
31
32pub struct Bins {
41 plan: Histogram,
42 schema: Arc<Schema>,
43 counts: Vec<u64>,
44}
45
46pub trait HistogramExecution {
51 fn output_schema(&self, input: &Schema) -> Result<Schema>;
52
53 fn start(&self, input: &Schema) -> Result<Bins>;
54
55 fn apply(&self, input: &RecordBatch, check: &mut impl FnMut() -> Result<()>) -> Result<RecordBatch>;
56}
57
58impl HistogramExecution for Histogram {
61 fn output_schema(&self, input: &Schema) -> Result<Schema> {
62 if self.edges.len() < 2
63 || self.edges.len() > 65536
64 || self.edges.iter().any(|edge| !edge.is_finite())
65 || self.edges.windows(2).any(|pair| pair[0] >= pair[1])
66 {
67 return Err(invalid("histogram requires 2..65536 finite strictly increasing edges"));
68 }
69 if !numeric_type(self.field.field(input)?.data_type()) {
70 return Err(invalid("histogram requires numeric field"));
71 }
72 Ok(Schema::new(vec![
73 Field::new("lower", DataType::Float64, true),
74 Field::new("upper", DataType::Float64, true),
75 Field::new("count", DataType::UInt64, false),
76 ]))
77 }
78
79 fn start(&self, input: &Schema) -> Result<Bins> {
80 Ok(Bins { schema: Arc::new(self.output_schema(input)?), plan: self.clone(), counts: vec![0; self.edges.len() + 1] })
81 }
82
83 fn apply(&self, input: &RecordBatch, check: &mut impl FnMut() -> Result<()>) -> Result<RecordBatch> {
84 let mut bins = self.start(input.schema().as_ref())?;
85 bins.push(input, check)?;
86 bins.finish()
87 }
88}
89
90impl Bins {
91 pub fn retained_bytes(&self) -> usize {
92 self.counts.len().saturating_mul(16).saturating_add(1024)
93 }
94
95 pub fn finish_bytes(&self) -> usize {
96 self.counts.len().saturating_mul(96).saturating_add(4096)
97 }
98
99 pub fn push(&mut self, input: &RecordBatch, check: &mut impl FnMut() -> Result<()>) -> Result<()> {
100 let edges = &self.plan.edges;
101 let mut counts = self.counts.clone();
102 for row in 0..input.num_rows() {
103 check()?;
104 let value = self.plan.field.value(input, row)?;
105 if matches!(value, ScalarValue::Null | ScalarValue::Missing) {
106 continue;
107 }
108 let value = number(&value)?;
109 if !value.is_finite() {
110 continue;
111 }
112 let bin = if value < edges[0] {
113 0
114 } else if value > edges[edges.len() - 1] {
115 edges.len()
116 } else if value == edges[edges.len() - 1] {
117 edges.len() - 1
118 } else {
119 edges.partition_point(|edge| *edge <= value)
120 };
121 counts[bin] = counts[bin].checked_add(1).ok_or_else(|| invalid("histogram count overflow"))?;
122 }
123 self.counts = counts;
124 Ok(())
125 }
126
127 pub fn finish(self) -> Result<RecordBatch> {
128 let lower = std::iter::once(None).chain(self.plan.edges.iter().copied().map(Some)).collect::<Vec<_>>();
129 let upper = self.plan.edges.iter().copied().map(Some).chain(std::iter::once(None)).collect::<Vec<_>>();
130 RecordBatch::try_new(
131 self.schema,
132 vec![Arc::new(Float64Array::from(lower)), Arc::new(Float64Array::from(upper)), Arc::new(UInt64Array::from(self.counts))],
133 )
134 .map_err(|_| invalid("histogram output batch is invalid"))
135 }
136}
137
138