1pub mod aggregate;
15pub mod downsample;
16pub mod filter;
17pub mod histogram;
18pub mod project;
19pub mod sort;
20
21pub use aggregate::AggregateExecution;
24pub use downsample::DownsampleExecution;
25pub use filter::FilterExecution;
26pub use harmos_domain::model::storage::query::transform::{FieldPath, Transform};
27pub use histogram::HistogramExecution;
28pub use project::ProjectExecution;
29pub use sort::SortExecution;
30
31use std::sync::Arc;
35
36use arrow_array::{
38 Array, ArrayRef, BinaryArray, BooleanArray, Float32Array, Float64Array, Int8Array, Int16Array, Int32Array, Int64Array, NullArray,
39 RecordBatch, StringArray, StructArray, TimestampNanosecondArray, UInt8Array, UInt16Array, UInt32Array, UInt64Array,
40};
41use arrow_schema::{DataType, Field, Schema, TimeUnit};
42use harmos_domain::model::storage::query::scalar::{ExactNumber, ScalarValue};
43
44use crate::{Error, Result};
46
47pub trait FieldPathExecution {
60 fn field(&self, schema: &Schema) -> Result<Field>;
61
62 fn array<'a>(&self, batch: &'a RecordBatch) -> Result<&'a ArrayRef>;
63
64 fn value(&self, batch: &RecordBatch, row: usize) -> Result<ScalarValue>;
65}
66
67pub trait TransformExecution {
68 fn output_schema(&self, input: &Schema) -> Result<Schema>;
69
70 fn apply(&self, input: &RecordBatch, check: &mut impl FnMut() -> Result<()>) -> Result<RecordBatch>;
71}
72
73impl FieldPathExecution for FieldPath {
76 fn field(&self, schema: &Schema) -> Result<Field> {
77 if self.fields.is_empty() || self.fields.len() > 32 {
78 return Err(invalid("field path requires 1..32 components"));
79 }
80 let mut fields = schema.fields();
81 let mut found = None;
82 let mut nullable = false;
83 for (index, name) in self.fields.iter().enumerate() {
84 let field = fields.iter().find(|field| field.name() == name).ok_or_else(|| invalid("query field does not exist"))?;
85 nullable |= field.is_nullable();
86 found = Some(field.as_ref().clone().with_nullable(nullable));
87 if index + 1 < self.fields.len() {
88 let DataType::Struct(children) = field.data_type() else {
89 return Err(invalid("field path traverses a non-struct value"));
90 };
91 fields = children;
92 }
93 }
94 Ok(found.expect("nonempty field path"))
95 }
96
97 fn array<'a>(&self, batch: &'a RecordBatch) -> Result<&'a ArrayRef> {
98 let first = self.fields.first().ok_or_else(|| invalid("empty field path"))?;
99 let mut array = batch.column_by_name(first).ok_or_else(|| invalid("query field does not exist"))?;
100 for name in self.fields.iter().skip(1) {
101 let structure =
102 array.as_any().downcast_ref::<StructArray>().ok_or_else(|| invalid("field path traverses a non-struct value"))?;
103 array = structure.column_by_name(name).ok_or_else(|| invalid("query child field does not exist"))?;
104 }
105 Ok(array)
106 }
107
108 fn value(&self, batch: &RecordBatch, row: usize) -> Result<ScalarValue> {
109 let first = self.fields.first().ok_or_else(|| invalid("empty field path"))?;
110 let mut array = batch.column_by_name(first).ok_or_else(|| invalid("query field does not exist"))?;
111 if array.is_null(row) {
112 return Ok(ScalarValue::Null);
113 }
114 for name in self.fields.iter().skip(1) {
115 let structure =
116 array.as_any().downcast_ref::<StructArray>().ok_or_else(|| invalid("field path traverses a non-struct value"))?;
117 array = structure.column_by_name(name).ok_or_else(|| invalid("query child field does not exist"))?;
118 if array.is_null(row) {
119 return Ok(ScalarValue::Null);
120 }
121 }
122 scalar(array.as_ref(), row)
123 }
124}
125
126impl TransformExecution for Transform {
127 fn output_schema(&self, input: &Schema) -> Result<Schema> {
128 match self {
129 Self::Filter(value) => value.output_schema(input),
130 Self::Project(value) => value.output_schema(input),
131 Self::Aggregate(value) => value.output_schema(input),
132 Self::Histogram(value) => value.output_schema(input),
133 Self::Downsample(value) => value.output_schema(input),
134 Self::Sort(value) => value.output_schema(input),
135 }
136 }
137
138 fn apply(&self, input: &RecordBatch, check: &mut impl FnMut() -> Result<()>) -> Result<RecordBatch> {
139 match self {
140 Self::Filter(value) => value.apply(input, check),
141 Self::Project(value) => value.apply(input, check),
142 Self::Aggregate(value) => value.apply(input, check),
143 Self::Histogram(value) => value.apply(input, check),
144 Self::Downsample(value) => value.apply(input, check),
145 Self::Sort(value) => value.apply(input, check),
146 }
147 }
148}
149
150pub(crate) fn invalid(reason: &'static str) -> Error {
155 Error::InvalidInput { field: "storage query transform", reason }
156}
157
158pub(super) fn take(input: &RecordBatch, indices: Vec<u64>) -> Result<RecordBatch> {
159 let indices = UInt64Array::from(indices);
160 let columns = input
161 .columns()
162 .iter()
163 .map(|column| arrow_select::take::take(column.as_ref(), &indices, None).map_err(|_| invalid("Arrow row selection failed")))
164 .collect::<Result<Vec<_>>>()?;
165 RecordBatch::try_new_with_options(input.schema(), columns, &arrow_array::RecordBatchOptions::new().with_row_count(Some(indices.len())))
166 .map_err(|_| invalid("Arrow selected batch is invalid"))
167}
168
169pub(super) fn scalar(array: &dyn Array, row: usize) -> Result<ScalarValue> {
170 if array.is_null(row) {
171 return Ok(ScalarValue::Null);
172 }
173 macro_rules! read {
174 ($ty:ty, $variant:ident, $convert:expr) => {
175 if let Some(array) = array.as_any().downcast_ref::<$ty>() {
176 return Ok(ScalarValue::$variant(($convert)(array.value(row))));
177 }
178 };
179 }
180 read!(BooleanArray, Bool, |value| value);
181 read!(Int8Array, Signed, i64::from);
182 read!(Int16Array, Signed, i64::from);
183 read!(Int32Array, Signed, i64::from);
184 read!(Int64Array, Signed, |value| value);
185 read!(UInt8Array, Unsigned, u64::from);
186 read!(UInt16Array, Unsigned, u64::from);
187 read!(UInt32Array, Unsigned, u64::from);
188 read!(UInt64Array, Unsigned, |value| value);
189 read!(Float32Array, Float, f64::from);
190 read!(Float64Array, Float, |value| value);
191 read!(StringArray, Text, str::to_owned);
192 read!(TimestampNanosecondArray, Datetime, i128::from);
193 if let Some(exact) = array.as_any().downcast_ref::<StructArray>() {
194 return exact_value(exact, row);
195 }
196 Err(invalid("field is not a supported scalar"))
197}
198
199pub(super) fn exact_type() -> DataType {
200 DataType::Struct(
201 vec![
202 Arc::new(Field::new("negative", DataType::Boolean, false)),
203 Arc::new(Field::new("numerator", DataType::Binary, false)),
204 Arc::new(Field::new("denominator", DataType::Binary, false)),
205 ]
206 .into(),
207 )
208}
209
210fn exact_value(value: &StructArray, row: usize) -> Result<ScalarValue> {
211 if value.data_type() != &exact_type() {
212 return Err(invalid("struct is not an exact number"));
213 }
214 let sign = value.column(0).as_any().downcast_ref::<BooleanArray>().expect("validated exact sign");
215 let numerator = value.column(1).as_any().downcast_ref::<BinaryArray>().expect("validated exact numerator");
216 let denominator = value.column(2).as_any().downcast_ref::<BinaryArray>().expect("validated exact denominator");
217 let numerator = u128::from_le_bytes(numerator.value(row).try_into().map_err(|_| invalid("exact numerator requires 16 bytes"))?);
218 let denominator = u128::from_le_bytes(denominator.value(row).try_into().map_err(|_| invalid("exact denominator requires 16 bytes"))?);
219 let denominator = std::num::NonZeroU128::new(denominator).ok_or_else(|| invalid("zero exact denominator"))?;
220 let exact = ExactNumber::new(sign.value(row), numerator, denominator);
221 if exact.numerator != numerator || exact.denominator != denominator || exact.negative != sign.value(row) {
222 return Err(invalid("noncanonical exact number"));
223 }
224 Ok(ScalarValue::Exact(exact))
225}
226
227pub(super) fn scalar_type(data_type: &DataType) -> bool {
228 matches!(
229 data_type,
230 DataType::Null
231 | DataType::Boolean
232 | DataType::Int8
233 | DataType::Int16
234 | DataType::Int32
235 | DataType::Int64
236 | DataType::UInt8
237 | DataType::UInt16
238 | DataType::UInt32
239 | DataType::UInt64
240 | DataType::Float32
241 | DataType::Float64
242 | DataType::Utf8
243 | DataType::Timestamp(TimeUnit::Nanosecond, _)
244 ) || data_type == &exact_type()
245}
246
247pub(super) fn numeric_type(data_type: &DataType) -> bool {
248 matches!(
249 data_type,
250 DataType::Int8
251 | DataType::Int16
252 | DataType::Int32
253 | DataType::Int64
254 | DataType::UInt8
255 | DataType::UInt16
256 | DataType::UInt32
257 | DataType::UInt64
258 | DataType::Float32
259 | DataType::Float64
260 ) || data_type == &exact_type()
261}
262
263pub(super) fn values_array(data_type: &DataType, values: &[ScalarValue]) -> Result<ArrayRef> {
264 macro_rules! number {
265 ($array:ty, $variant:ident, $native:ty) => {{
266 let values = values
267 .iter()
268 .map(|value| match value {
269 ScalarValue::$variant(value) => {
270 <$native>::try_from(*value).ok().map(Some).ok_or_else(|| invalid("aggregate output width overflow"))
271 }
272 ScalarValue::Null | ScalarValue::Missing => Ok(None),
273 _ => Err(invalid("aggregate output type mismatch")),
274 })
275 .collect::<Result<Vec<_>>>()?;
276 Arc::new(<$array>::from(values)) as ArrayRef
277 }};
278 }
279 Ok(match data_type {
280 DataType::Null => Arc::new(NullArray::new(values.len())),
281 DataType::Int8 => number!(Int8Array, Signed, i8),
282 DataType::Int16 => number!(Int16Array, Signed, i16),
283 DataType::Int32 => number!(Int32Array, Signed, i32),
284 DataType::Int64 => number!(Int64Array, Signed, i64),
285 DataType::UInt8 => number!(UInt8Array, Unsigned, u8),
286 DataType::UInt16 => number!(UInt16Array, Unsigned, u16),
287 DataType::UInt32 => number!(UInt32Array, Unsigned, u32),
288 DataType::UInt64 => number!(UInt64Array, Unsigned, u64),
289 DataType::Boolean => Arc::new(BooleanArray::from(
290 values
291 .iter()
292 .map(|value| match value {
293 ScalarValue::Bool(value) => Some(*value),
294 _ => None,
295 })
296 .collect::<Vec<_>>(),
297 )),
298 DataType::Float32 => Arc::new(Float32Array::from(
299 values
300 .iter()
301 .map(|value| match value {
302 ScalarValue::Float(value) => Some(*value as f32),
303 _ => None,
304 })
305 .collect::<Vec<_>>(),
306 )),
307 DataType::Float64 => Arc::new(Float64Array::from(
308 values
309 .iter()
310 .map(|value| match value {
311 ScalarValue::Float(value) => Some(*value),
312 _ => None,
313 })
314 .collect::<Vec<_>>(),
315 )),
316 DataType::Utf8 => Arc::new(StringArray::from(
317 values
318 .iter()
319 .map(|value| match value {
320 ScalarValue::Text(value) => Some(value.as_str()),
321 _ => None,
322 })
323 .collect::<Vec<_>>(),
324 )),
325 DataType::Timestamp(TimeUnit::Nanosecond, zone) => {
326 let values = values
327 .iter()
328 .map(|value| match value {
329 ScalarValue::Datetime(value) => i64::try_from(*value).map(Some).map_err(|_| invalid("timestamp output overflow")),
330 ScalarValue::Null | ScalarValue::Missing => Ok(None),
331 _ => Err(invalid("timestamp output type mismatch")),
332 })
333 .collect::<Result<Vec<_>>>()?;
334 Arc::new(TimestampNanosecondArray::from(values).with_timezone_opt(zone.clone()))
335 }
336 data_type if data_type == &exact_type() => {
337 let values = values
338 .iter()
339 .map(|value| match value {
340 ScalarValue::Null | ScalarValue::Missing => Ok(None),
341 value => harmos_domain::model::storage::query::scalar::exact(value)
342 .map(Some)
343 .ok_or_else(|| invalid("exact output type mismatch")),
344 })
345 .collect::<Result<Vec<_>>>()?;
346 let signs = BooleanArray::from(values.iter().map(|value| value.is_some_and(|value| value.negative)).collect::<Vec<_>>());
347 let numerators = values.iter().map(|value| value.map_or(0, |value| value.numerator).to_le_bytes()).collect::<Vec<_>>();
348 let denominators =
349 values.iter().map(|value| value.map_or(1, |value| value.denominator.get()).to_le_bytes()).collect::<Vec<_>>();
350 let numerators = BinaryArray::from_iter_values(numerators.iter().map(|value| value.as_slice()));
351 let denominators = BinaryArray::from_iter_values(denominators.iter().map(|value| value.as_slice()));
352 let DataType::Struct(fields) = exact_type() else { unreachable!() };
353 let nulls = arrow_buffer::NullBuffer::from(values.iter().map(Option::is_some).collect::<Vec<_>>());
354 Arc::new(StructArray::new(fields, vec![Arc::new(signs), Arc::new(numerators), Arc::new(denominators)], Some(nulls)))
355 }
356 _ => return Err(invalid("unsupported scalar output type")),
357 })
358}
359
360