Skip to main content

vortex_array/scalar_fn/fns/
merge.rs

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