vortex_array/aggregate_fn/fns/first/
mod.rs1use vortex_error::VortexResult;
5use vortex_error::vortex_bail;
6use vortex_session::registry::CachedId;
7
8use crate::ArrayRef;
9use crate::Columnar;
10use crate::ExecutionCtx;
11use crate::aggregate_fn::Accumulator;
12use crate::aggregate_fn::AggregateArgs;
13use crate::aggregate_fn::AggregateFnId;
14use crate::aggregate_fn::AggregateFnVTable;
15use crate::aggregate_fn::DynAccumulator;
16use crate::aggregate_fn::EmptyOptions;
17use crate::dtype::DType;
18use crate::scalar::Scalar;
19
20pub fn first(array: &ArrayRef, ctx: &mut ExecutionCtx) -> VortexResult<Scalar> {
24 let mut acc = Accumulator::try_new(First, EmptyOptions, array.dtype().clone())?;
25 acc.accumulate(array, ctx)?;
26 acc.finish()
27}
28
29#[derive(Clone, Debug)]
31pub struct First;
32
33pub struct FirstPartial {
35 value: Option<Scalar>,
37}
38
39impl AggregateFnVTable for First {
40 type Options = EmptyOptions;
41 type Partial = FirstPartial;
42
43 fn id(&self) -> AggregateFnId {
44 static ID: CachedId = CachedId::new("vortex.first");
45 *ID
46 }
47
48 fn serialize(&self, _options: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
49 vortex_bail!("First is not yet serializable");
50 }
51
52 fn return_dtype(&self, _options: &Self::Options, input_dtype: &DType) -> Option<DType> {
53 Some(input_dtype.as_nullable())
54 }
55
56 fn partial_dtype(&self, options: &Self::Options, input_dtype: &DType) -> Option<DType> {
57 self.return_dtype(options, input_dtype)
58 }
59
60 fn empty_partial(
61 &self,
62 _args: AggregateArgs<'_, Self::Options>,
63 ) -> VortexResult<Self::Partial> {
64 Ok(FirstPartial { value: None })
65 }
66
67 fn partial_from_scalar(
68 &self,
69 _args: AggregateArgs<'_, Self::Options>,
70 scalar: Scalar,
71 ) -> VortexResult<Self::Partial> {
72 Ok(FirstPartial {
74 value: (!scalar.is_null()).then_some(scalar),
75 })
76 }
77
78 fn merge_partials(
79 &self,
80 _args: AggregateArgs<'_, Self::Options>,
81 first: Self::Partial,
82 second: Self::Partial,
83 ) -> VortexResult<Self::Partial> {
84 Ok(FirstPartial {
86 value: first.value.or(second.value),
87 })
88 }
89
90 fn to_scalar(
91 &self,
92 args: AggregateArgs<'_, Self::Options>,
93 partial: &Self::Partial,
94 ) -> VortexResult<Scalar> {
95 Ok(match &partial.value {
96 Some(v) => v.clone(),
97 None => Scalar::null(args.return_dtype.clone()),
98 })
99 }
100
101 #[inline]
102 fn is_saturated(
103 &self,
104 _args: AggregateArgs<'_, Self::Options>,
105 partial: &Self::Partial,
106 ) -> bool {
107 partial.value.is_some()
108 }
109
110 fn try_accumulate(
111 &self,
112 _args: AggregateArgs<'_, Self::Options>,
113 partial: &mut Self::Partial,
114 batch: &ArrayRef,
115 ctx: &mut ExecutionCtx,
116 ) -> VortexResult<bool> {
117 if partial.value.is_some() {
118 return Ok(true);
119 }
120 if let Some(idx) = batch.validity()?.execute_mask(batch.len(), ctx)?.first() {
121 let scalar = batch.execute_scalar(idx, ctx)?;
122 partial.value = Some(scalar.into_nullable());
123 }
124 Ok(true)
125 }
126
127 fn accumulate(
128 &self,
129 _args: AggregateArgs<'_, Self::Options>,
130 _partial: &mut Self::Partial,
131 _batch: &Columnar,
132 _ctx: &mut ExecutionCtx,
133 ) -> VortexResult<()> {
134 unreachable!("First::try_accumulate handles all arrays")
135 }
136
137 fn finalize(
138 &self,
139 _args: AggregateArgs<'_, Self::Options>,
140 partials: ArrayRef,
141 ) -> VortexResult<ArrayRef> {
142 Ok(partials)
143 }
144
145 fn finalize_scalar(
146 &self,
147 args: AggregateArgs<'_, Self::Options>,
148 partial: &Self::Partial,
149 ) -> VortexResult<Scalar> {
150 self.to_scalar(args, partial)
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use vortex_buffer::buffer;
157 use vortex_error::VortexResult;
158
159 use crate::IntoArray;
160 use crate::VortexSessionExecute;
161 use crate::aggregate_fn::Accumulator;
162 use crate::aggregate_fn::AggregateDTypes;
163 use crate::aggregate_fn::AggregateFnVTable;
164 use crate::aggregate_fn::DynAccumulator;
165 use crate::aggregate_fn::EmptyOptions;
166 use crate::aggregate_fn::fns::first::First;
167 use crate::aggregate_fn::fns::first::first;
168 use crate::array_session;
169 use crate::arrays::ChunkedArray;
170 use crate::arrays::ConstantArray;
171 use crate::arrays::PrimitiveArray;
172 use crate::arrays::VarBinArray;
173 use crate::dtype::DType;
174 use crate::dtype::Nullability;
175 use crate::dtype::Nullability::Nullable;
176 use crate::dtype::PType;
177 use crate::scalar::Scalar;
178 use crate::validity::Validity;
179
180 #[test]
181 fn first_non_null() -> VortexResult<()> {
182 let array = PrimitiveArray::new(buffer![10i32, 20, 30], Validity::NonNullable).into_array();
183 let mut ctx = array_session().create_execution_ctx();
184 assert_eq!(first(&array, &mut ctx)?, Scalar::primitive(10i32, Nullable));
185 Ok(())
186 }
187
188 #[test]
189 fn first_skips_leading_nulls() -> VortexResult<()> {
190 let array =
191 PrimitiveArray::from_option_iter([None, None, Some(7i32), Some(8)]).into_array();
192 let mut ctx = array_session().create_execution_ctx();
193 assert_eq!(first(&array, &mut ctx)?, Scalar::primitive(7i32, Nullable));
194 Ok(())
195 }
196
197 #[test]
198 fn first_all_null() -> VortexResult<()> {
199 let array = PrimitiveArray::from_option_iter::<i32, _>([None, None, None]).into_array();
200 let mut ctx = array_session().create_execution_ctx();
201 let dtype = DType::Primitive(PType::I32, Nullable);
202 assert_eq!(first(&array, &mut ctx)?, Scalar::null(dtype));
203 Ok(())
204 }
205
206 #[test]
207 fn first_empty() -> VortexResult<()> {
208 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
209 let mut acc = Accumulator::try_new(First, EmptyOptions, dtype)?;
210 let result = acc.finish()?;
211 assert_eq!(result, Scalar::null(DType::Primitive(PType::I32, Nullable)));
212 Ok(())
213 }
214
215 #[test]
216 fn first_constant() -> VortexResult<()> {
217 let array = ConstantArray::new(42i32, 10).into_array();
218 let mut ctx = array_session().create_execution_ctx();
219 assert_eq!(first(&array, &mut ctx)?, Scalar::primitive(42i32, Nullable));
220 Ok(())
221 }
222
223 #[test]
224 fn first_constant_null() -> VortexResult<()> {
225 let dtype = DType::Primitive(PType::I32, Nullable);
226 let array = ConstantArray::new(Scalar::null(dtype.clone()), 10).into_array();
227 let mut ctx = array_session().create_execution_ctx();
228 assert_eq!(first(&array, &mut ctx)?, Scalar::null(dtype));
229 Ok(())
230 }
231
232 #[test]
233 fn first_varbin() -> VortexResult<()> {
234 let array = VarBinArray::from_iter(
235 vec![None, Some("hello"), Some("world")],
236 DType::Utf8(Nullable),
237 )
238 .into_array();
239 let mut ctx = array_session().create_execution_ctx();
240 assert_eq!(first(&array, &mut ctx)?, Scalar::utf8("hello", Nullable));
241 Ok(())
242 }
243
244 #[test]
245 fn first_multi_batch_picks_earliest_non_null() -> VortexResult<()> {
246 let mut ctx = array_session().create_execution_ctx();
247 let dtype = DType::Primitive(PType::I32, Nullable);
248 let mut acc = Accumulator::try_new(First, EmptyOptions, dtype)?;
249
250 let batch1 = PrimitiveArray::from_option_iter::<i32, _>([None, None]).into_array();
252 acc.accumulate(&batch1, &mut ctx)?;
253 assert!(!acc.is_saturated());
254
255 let batch2 = PrimitiveArray::from_option_iter([None, Some(99i32), Some(100)]).into_array();
257 acc.accumulate(&batch2, &mut ctx)?;
258 assert!(acc.is_saturated());
259
260 let batch3 = PrimitiveArray::from_option_iter([Some(1i32)]).into_array();
262 acc.accumulate(&batch3, &mut ctx)?;
263
264 let result = acc.finish()?;
265 assert_eq!(result, Scalar::primitive(99i32, Nullable));
266 Ok(())
267 }
268
269 #[test]
270 fn first_finish_resets_state() -> VortexResult<()> {
271 let mut ctx = array_session().create_execution_ctx();
272 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
273 let mut acc = Accumulator::try_new(First, EmptyOptions, dtype)?;
274
275 let batch1 = PrimitiveArray::new(buffer![10i32, 20], Validity::NonNullable).into_array();
276 acc.accumulate(&batch1, &mut ctx)?;
277 assert_eq!(acc.finish()?, Scalar::primitive(10i32, Nullable));
278
279 let batch2 = PrimitiveArray::new(buffer![3i32, 6, 9], Validity::NonNullable).into_array();
280 acc.accumulate(&batch2, &mut ctx)?;
281 assert_eq!(acc.finish()?, Scalar::primitive(3i32, Nullable));
282 Ok(())
283 }
284
285 #[test]
286 fn first_state_merge() -> VortexResult<()> {
287 let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
288 let owned = AggregateDTypes::try_new(&First, &EmptyOptions, dtype)?;
289 let args = owned.args(&EmptyOptions);
290 let partial_of =
291 |value: i32| First.partial_from_scalar(args, Scalar::primitive(value, Nullable));
292
293 let empty = First.empty_partial(args)?;
294 assert!(!First.is_saturated(args, &empty));
295
296 let five = partial_of(5)?;
298 let seven = partial_of(7)?;
299 let merge = |first, second| First.merge_partials(args, first, second);
300 let state = merge(merge(empty, five)?, seven)?;
301 assert!(First.is_saturated(args, &state));
302 assert_eq!(
303 First.to_scalar(args, &state)?,
304 Scalar::primitive(5i32, Nullable)
305 );
306 Ok(())
307 }
308
309 #[test]
310 fn first_chunked() -> VortexResult<()> {
311 let chunk1 = PrimitiveArray::from_option_iter::<i32, _>([None, None]);
312 let chunk2 = PrimitiveArray::from_option_iter([None, Some(42i32), Some(100)]);
313 let dtype = chunk1.dtype().clone();
314 let chunked = ChunkedArray::try_new(vec![chunk1.into_array(), chunk2.into_array()], dtype)?;
315 let mut ctx = array_session().create_execution_ctx();
316 assert_eq!(
317 first(&chunked.into_array(), &mut ctx)?,
318 Scalar::primitive(42i32, Nullable)
319 );
320 Ok(())
321 }
322}