1use std::fmt::Display;
5use std::fmt::Formatter;
6use std::hash::Hash;
7use std::sync::Arc;
8
9use itertools::Itertools as _;
10use vortex_error::VortexExpect;
11use vortex_error::VortexResult;
12use vortex_error::vortex_bail;
13use vortex_session::VortexSession;
14use vortex_session::registry::CachedId;
15use vortex_utils::aliases::hash_set::HashSet;
16
17use crate::ArrayRef;
18use crate::ExecutionCtx;
19use crate::IntoArray as _;
20use crate::arrays::StructArray;
21use crate::arrays::struct_::StructArrayExt;
22use crate::dtype::DType;
23use crate::dtype::FieldNames;
24use crate::dtype::Nullability;
25use crate::dtype::StructFields;
26use crate::scalar_fn::Arity;
27use crate::scalar_fn::ChildName;
28use crate::scalar_fn::ExecutionArgs;
29use crate::scalar_fn::ReduceNode;
30use crate::scalar_fn::ScalarFnId;
31use crate::scalar_fn::ScalarFnVTable;
32use crate::scalar_fn::ScalarFnVTableExt;
33use crate::scalar_fn::fns::get_item::GetItem;
34use crate::scalar_fn::fns::pack::Pack;
35use crate::scalar_fn::fns::pack::PackOptions;
36use crate::validity::Validity;
37
38#[derive(Clone)]
45pub struct Merge;
46
47impl ScalarFnVTable for Merge {
48 type Options = DuplicateHandling;
49
50 fn id(&self) -> ScalarFnId {
51 static ID: CachedId = CachedId::new("vortex.merge");
52 *ID
53 }
54
55 fn serialize(&self, instance: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
56 Ok(Some(match instance {
57 DuplicateHandling::RightMost => vec![0x00],
58 DuplicateHandling::Error => vec![0x01],
59 }))
60 }
61
62 fn deserialize(
63 &self,
64 _metadata: &[u8],
65 _session: &VortexSession,
66 ) -> VortexResult<Self::Options> {
67 let instance = match _metadata {
68 [0x00] => DuplicateHandling::RightMost,
69 [0x01] => DuplicateHandling::Error,
70 _ => {
71 vortex_bail!("invalid metadata for Merge expression");
72 }
73 };
74 Ok(instance)
75 }
76
77 fn arity(&self, _options: &Self::Options) -> Arity {
78 Arity::Variadic { min: 0, max: None }
79 }
80
81 fn child_name(&self, _instance: &Self::Options, child_idx: usize) -> ChildName {
82 ChildName::from(Arc::from(format!("{}", child_idx)))
83 }
84
85 fn return_dtype(&self, options: &Self::Options, arg_dtypes: &[DType]) -> VortexResult<DType> {
86 let mut field_names = Vec::new();
87 let mut arrays = Vec::new();
88 let mut merge_nullability = Nullability::NonNullable;
89 let mut duplicate_names = HashSet::<_>::new();
90
91 for dtype in arg_dtypes {
92 let Some(fields) = dtype.as_struct_fields_opt() else {
93 vortex_bail!("merge expects struct input");
94 };
95 if dtype.is_nullable() {
96 vortex_bail!("merge expects non-nullable input");
97 }
98
99 merge_nullability |= dtype.nullability();
100
101 for (field_name, field_dtype) in fields.names().iter().zip_eq(fields.fields()) {
102 if let Some(idx) = field_names.iter().position(|name| name == field_name) {
103 duplicate_names.insert(field_name.clone());
104 arrays[idx] = field_dtype;
105 } else {
106 field_names.push(field_name.clone());
107 arrays.push(field_dtype);
108 }
109 }
110 }
111
112 if options == &DuplicateHandling::Error && !duplicate_names.is_empty() {
113 vortex_bail!(
114 "merge: duplicate fields in children: {}",
115 duplicate_names.into_iter().format(", ")
116 )
117 }
118
119 Ok(DType::Struct(
120 StructFields::new(FieldNames::from(field_names), arrays),
121 merge_nullability,
122 ))
123 }
124
125 fn execute(
126 &self,
127 options: &Self::Options,
128 args: &dyn ExecutionArgs,
129 ctx: &mut ExecutionCtx,
130 ) -> VortexResult<ArrayRef> {
131 let mut field_names = Vec::new();
133 let mut arrays = Vec::new();
134 let mut duplicate_names = HashSet::<_>::new();
135
136 for i in 0..args.num_inputs() {
137 let array = args.get(i)?.execute::<StructArray>(ctx)?;
138 if array.dtype().is_nullable() {
139 vortex_bail!("merge expects non-nullable input");
140 }
141
142 for (field_name, field_array) in array
143 .names()
144 .iter()
145 .zip_eq(array.iter_unmasked_fields().cloned())
146 {
147 if let Some(idx) = field_names.iter().position(|name| name == field_name) {
149 duplicate_names.insert(field_name.clone());
150 arrays[idx] = field_array;
151 } else {
152 field_names.push(field_name.clone());
153 arrays.push(field_array);
154 }
155 }
156 }
157
158 if options == &DuplicateHandling::Error && !duplicate_names.is_empty() {
159 vortex_bail!(
160 "merge: duplicate fields in children: {}",
161 duplicate_names.into_iter().format(", ")
162 )
163 }
164
165 let validity = Validity::NonNullable;
167 let len = args.row_count();
168 Ok(
169 StructArray::try_new(FieldNames::from(field_names), arrays, len, validity)?
170 .into_array(),
171 )
172 }
173
174 fn reduce<T: ReduceNode>(&self, options: &Self::Options, node: &T) -> VortexResult<Option<T>> {
175 let mut names = Vec::with_capacity(node.child_count() * 2);
176 let mut fields: Vec<T> = Vec::with_capacity(node.child_count() * 2);
177 let mut duplicate_names = HashSet::<_>::new();
178
179 for child in (0..node.child_count()).map(|i| node.child(i)) {
180 let child_dtype = child.node_dtype()?;
181 if !child_dtype.is_struct() || child_dtype.is_nullable() {
182 vortex_bail!(
183 "Merge child must return a non-nullable struct dtype, got {}",
184 child_dtype
185 )
186 }
187
188 let child_dtype = child_dtype
189 .as_struct_fields_opt()
190 .vortex_expect("expected struct");
191
192 for name in child_dtype.names().iter() {
193 let field = GetItem::apply_now(&child, name)?;
198
199 if let Some(idx) = names.iter().position(|n| n == name) {
200 duplicate_names.insert(name.clone());
201 fields[idx] = field;
202 } else {
203 names.push(name.clone());
204 fields.push(field);
205 }
206 }
207
208 if options == &DuplicateHandling::Error && !duplicate_names.is_empty() {
209 vortex_bail!(
210 "merge: duplicate fields in children: {}",
211 duplicate_names.into_iter().format(", ")
212 )
213 }
214 }
215
216 let pack_expr = node.new_node(
217 Pack.bind(PackOptions {
218 names: FieldNames::from(names),
219 nullability: node.node_dtype()?.nullability(),
220 }),
221 &fields,
222 )?;
223
224 Ok(Some(pack_expr))
225 }
226
227 fn is_strict(&self, _options: &Self::Options) -> bool {
228 true
229 }
230
231 fn is_infallible(&self, instance: &Self::Options) -> bool {
232 !matches!(instance, DuplicateHandling::Error)
233 }
234}
235
236#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
238pub enum DuplicateHandling {
239 RightMost,
241 #[default]
243 Error,
244}
245
246impl Display for DuplicateHandling {
247 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
248 match self {
249 DuplicateHandling::RightMost => write!(f, "RightMost"),
250 DuplicateHandling::Error => write!(f, "Error"),
251 }
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use vortex_buffer::buffer;
258 use vortex_error::VortexResult;
259 use vortex_error::vortex_bail;
260
261 use crate::ArrayRef;
262 use crate::IntoArray;
263 use crate::VortexSessionExecute;
264 use crate::array_session;
265 use crate::arrays::PrimitiveArray;
266 use crate::arrays::struct_::StructArrayExt;
267 use crate::assert_arrays_eq;
268 use crate::dtype::DType;
269 use crate::dtype::Nullability::NonNullable;
270 use crate::dtype::PType::I32;
271 use crate::dtype::PType::I64;
272 use crate::dtype::PType::U32;
273 use crate::dtype::PType::U64;
274 use crate::expr::Expression;
275 use crate::expr::get_item;
276 use crate::expr::merge;
277 use crate::expr::merge_opts;
278 use crate::expr::root;
279 use crate::scalar_fn::fns::merge::DuplicateHandling;
280 use crate::scalar_fn::fns::merge::StructArray;
281 use crate::scalar_fn::fns::pack::Pack;
282
283 fn field_array(array: &ArrayRef, field_path: &[&str]) -> VortexResult<ArrayRef> {
284 let mut ctx = array_session().create_execution_ctx();
285 let mut field_path = field_path.iter();
286
287 let Some(field) = field_path.next() else {
288 vortex_bail!("empty field path");
289 };
290
291 let mut array = array
292 .clone()
293 .execute::<StructArray>(&mut ctx)?
294 .unmasked_field_by_name(field)?
295 .clone();
296 for field in field_path {
297 let next = array
298 .clone()
299 .execute::<StructArray>(&mut ctx)?
300 .unmasked_field_by_name(field)?
301 .clone();
302 array = next;
303 }
304 Ok(array)
305 }
306
307 #[test]
308 pub fn test_merge_right_most() {
309 let mut ctx = array_session().create_execution_ctx();
310 let expr = merge_opts(
311 vec![
312 get_item("0", root()),
313 get_item("1", root()),
314 get_item("2", root()),
315 ],
316 DuplicateHandling::RightMost,
317 );
318
319 let test_array = StructArray::from_fields(&[
320 (
321 "0",
322 StructArray::from_fields(&[
323 ("a", buffer![0, 0, 0].into_array()),
324 ("b", buffer![1, 1, 1].into_array()),
325 ])
326 .unwrap()
327 .into_array(),
328 ),
329 (
330 "1",
331 StructArray::from_fields(&[
332 ("b", buffer![2, 2, 2].into_array()),
333 ("c", buffer![3, 3, 3].into_array()),
334 ])
335 .unwrap()
336 .into_array(),
337 ),
338 (
339 "2",
340 StructArray::from_fields(&[
341 ("d", buffer![4, 4, 4].into_array()),
342 ("e", buffer![5, 5, 5].into_array()),
343 ])
344 .unwrap()
345 .into_array(),
346 ),
347 ])
348 .unwrap()
349 .into_array();
350 let actual_array = test_array.apply(&expr).unwrap();
351
352 assert_eq!(
353 actual_array.dtype().as_struct_fields().names(),
354 ["a", "b", "c", "d", "e"]
355 );
356
357 assert_arrays_eq!(
358 field_array(&actual_array, &["a"]).unwrap(),
359 PrimitiveArray::from_iter([0i32, 0, 0]),
360 &mut ctx
361 );
362 assert_arrays_eq!(
363 field_array(&actual_array, &["b"]).unwrap(),
364 PrimitiveArray::from_iter([2i32, 2, 2]),
365 &mut ctx
366 );
367 assert_arrays_eq!(
368 field_array(&actual_array, &["c"]).unwrap(),
369 PrimitiveArray::from_iter([3i32, 3, 3]),
370 &mut ctx
371 );
372 assert_arrays_eq!(
373 field_array(&actual_array, &["d"]).unwrap(),
374 PrimitiveArray::from_iter([4i32, 4, 4]),
375 &mut ctx
376 );
377 assert_arrays_eq!(
378 field_array(&actual_array, &["e"]).unwrap(),
379 PrimitiveArray::from_iter([5i32, 5, 5]),
380 &mut ctx
381 );
382 }
383
384 #[test]
385 #[should_panic(expected = "merge: duplicate fields in children")]
386 pub fn test_merge_error_on_dupe_return_dtype() {
387 let expr = merge_opts(
388 vec![get_item("0", root()), get_item("1", root())],
389 DuplicateHandling::Error,
390 );
391 let test_array = StructArray::try_from_iter([
392 (
393 "0",
394 StructArray::try_from_iter([("a", buffer![1]), ("b", buffer![1])]).unwrap(),
395 ),
396 (
397 "1",
398 StructArray::try_from_iter([("c", buffer![1]), ("b", buffer![1])]).unwrap(),
399 ),
400 ])
401 .unwrap()
402 .into_array();
403
404 expr.return_dtype(test_array.dtype()).unwrap();
405 }
406
407 #[test]
408 #[should_panic(expected = "merge: duplicate fields in children")]
409 pub fn test_merge_error_on_dupe_evaluate() {
410 let expr = merge_opts(
411 vec![get_item("0", root()), get_item("1", root())],
412 DuplicateHandling::Error,
413 );
414 let test_array = StructArray::try_from_iter([
415 (
416 "0",
417 StructArray::try_from_iter([("a", buffer![1]), ("b", buffer![1])]).unwrap(),
418 ),
419 (
420 "1",
421 StructArray::try_from_iter([("c", buffer![1]), ("b", buffer![1])]).unwrap(),
422 ),
423 ])
424 .unwrap()
425 .into_array();
426
427 test_array.apply(&expr).unwrap();
428 }
429
430 #[test]
431 pub fn test_empty_merge() {
432 let expr = merge(Vec::<Expression>::new());
433
434 let test_array = StructArray::from_fields(&[("a", buffer![0, 1, 2].into_array())])
435 .unwrap()
436 .into_array();
437 let actual_array = test_array.clone().apply(&expr).unwrap();
438 assert_eq!(actual_array.len(), test_array.len());
439 let expected = StructArray::try_new(
440 Default::default(),
441 vec![],
442 3,
443 crate::validity::Validity::NonNullable,
444 )
445 .unwrap();
446 assert_arrays_eq!(
447 actual_array,
448 expected,
449 &mut array_session().create_execution_ctx()
450 );
451 }
452
453 #[test]
454 pub fn test_nested_merge() {
455 let expr = merge_opts(
458 vec![get_item("0", root()), get_item("1", root())],
459 DuplicateHandling::RightMost,
460 );
461
462 let test_array = StructArray::from_fields(&[
463 (
464 "0",
465 StructArray::from_fields(&[(
466 "a",
467 StructArray::from_fields(&[
468 ("x", buffer![0, 0, 0].into_array()),
469 ("y", buffer![1, 1, 1].into_array()),
470 ])
471 .unwrap()
472 .into_array(),
473 )])
474 .unwrap()
475 .into_array(),
476 ),
477 (
478 "1",
479 StructArray::from_fields(&[(
480 "a",
481 StructArray::from_fields(&[("x", buffer![0, 0, 0].into_array())])
482 .unwrap()
483 .into_array(),
484 )])
485 .unwrap()
486 .into_array(),
487 ),
488 ])
489 .unwrap()
490 .into_array();
491 let mut ctx = array_session().create_execution_ctx();
492 let actual_array = test_array
493 .apply(&expr)
494 .unwrap()
495 .execute::<StructArray>(&mut ctx)
496 .unwrap();
497
498 let inner_struct = actual_array.unmasked_field_by_name("a").unwrap();
499 assert_eq!(
500 inner_struct
501 .dtype()
502 .as_struct_fields()
503 .names()
504 .iter()
505 .map(|name| name.as_ref())
506 .collect::<Vec<_>>(),
507 vec!["x"]
508 );
509 assert_arrays_eq!(
510 inner_struct,
511 StructArray::from_fields(&[("x", buffer![0i32, 0, 0].into_array())]).unwrap(),
512 &mut ctx
513 );
514 }
515
516 #[test]
517 pub fn test_merge_order() {
518 let mut ctx = array_session().create_execution_ctx();
519 let expr = merge(vec![get_item("0", root()), get_item("1", root())]);
520
521 let test_array = StructArray::from_fields(&[
522 (
523 "0",
524 StructArray::from_fields(&[
525 ("a", buffer![0, 0, 0].into_array()),
526 ("c", buffer![1, 1, 1].into_array()),
527 ])
528 .unwrap()
529 .into_array(),
530 ),
531 (
532 "1",
533 StructArray::from_fields(&[
534 ("b", buffer![2, 2, 2].into_array()),
535 ("d", buffer![3, 3, 3].into_array()),
536 ])
537 .unwrap()
538 .into_array(),
539 ),
540 ])
541 .unwrap()
542 .into_array();
543 let actual_array = test_array.apply(&expr).unwrap();
544
545 assert_eq!(
546 actual_array.dtype().as_struct_fields().names(),
547 ["a", "c", "b", "d"]
548 );
549 let expected = StructArray::from_fields(&[
550 ("a", buffer![0i32, 0, 0].into_array()),
551 ("c", buffer![1i32, 1, 1].into_array()),
552 ("b", buffer![2i32, 2, 2].into_array()),
553 ("d", buffer![3i32, 3, 3].into_array()),
554 ])
555 .unwrap();
556 assert_arrays_eq!(actual_array, expected, &mut ctx);
557 }
558
559 #[test]
560 pub fn test_display() {
561 let expr = merge([get_item("struct1", root()), get_item("struct2", root())]);
562 assert_eq!(
563 expr.to_string(),
564 "vortex.merge($.struct1, $.struct2, opts=Error)"
565 );
566
567 let expr2 = merge(vec![get_item("a", root())]);
568 assert_eq!(expr2.to_string(), "vortex.merge($.a, opts=Error)");
569 }
570
571 #[test]
572 fn test_remove_merge() {
573 let dtype = DType::struct_(
574 [
575 ("0", DType::struct_([("a", I32), ("b", I64)], NonNullable)),
576 ("1", DType::struct_([("b", U32), ("c", U64)], NonNullable)),
577 ],
578 NonNullable,
579 );
580
581 let e = merge_opts(
582 [get_item("0", root()), get_item("1", root())],
583 DuplicateHandling::RightMost,
584 );
585
586 let result = e.bind(&dtype).unwrap().optimize().unwrap();
587
588 assert!(result.is::<Pack>());
589 assert_eq!(
590 result.dtype(),
591 &DType::struct_([("a", I32), ("b", U32), ("c", U64)], NonNullable)
592 );
593 }
594}