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