1use arrow::array::FixedSizeBinaryArray;
2use arrow::array::{Array as _, ListArray as ArrowListArray};
3use arrow::buffer::ScalarBuffer as ArrowScalarBuffer;
4use itertools::{Itertools as _, izip};
5use nohash_hasher::IntMap;
6
7use re_arrow_util::ArrowArrayDowncastRef as _;
8
9use crate::{Chunk, ChunkError, ChunkId, ChunkResult, TimeColumn, chunk::ChunkComponents};
10
11impl Chunk {
14 pub fn concatenated(&self, rhs: &Self) -> ChunkResult<Self> {
23 re_tracing::profile_function!(format!(
24 "lhs={} rhs={}",
25 re_format::format_uint(self.num_rows()),
26 re_format::format_uint(rhs.num_rows())
27 ));
28
29 let cl = self;
30 let cr = rhs;
31
32 if !cl.concatenable(cr) {
33 return Err(ChunkError::Malformed {
34 reason: format!("cannot concatenate incompatible Chunks:\n{cl}\n{cr}"),
35 });
36 }
37
38 let Some((_cl0, cl1)) = cl.row_id_range() else {
39 return Ok(cr.clone()); };
41 let Some((cr0, _cr1)) = cr.row_id_range() else {
42 return Ok(cl.clone());
43 };
44
45 let is_sorted = cl.is_sorted && cr.is_sorted && cl1 <= cr0;
46
47 let row_ids = {
48 re_tracing::profile_scope!("row_ids");
49
50 let row_ids = re_arrow_util::concat_arrays(&[&cl.row_ids, &cr.row_ids])?;
51 #[expect(clippy::unwrap_used)]
52 row_ids
54 .downcast_array_ref::<FixedSizeBinaryArray>()
55 .unwrap()
56 .clone()
57 };
58
59 let timelines = {
61 re_tracing::profile_scope!("timelines");
62 izip!(self.timelines.iter(), rhs.timelines.iter())
63 .filter_map(
64 |((lhs_timeline, lhs_time_chunk), (rhs_timeline, rhs_time_chunk))| {
65 debug_assert_eq!(lhs_timeline, rhs_timeline);
66 lhs_time_chunk
67 .concatenated(rhs_time_chunk)
68 .map(|time_column| (*lhs_timeline, time_column))
69 },
70 )
71 .collect()
72 };
73
74 let lhs_per_desc: IntMap<_, _> = cl
75 .components
76 .iter()
77 .map(|(component_desc, list_array)| (component_desc.clone(), list_array))
78 .collect();
79 let rhs_per_desc: IntMap<_, _> = cr
80 .components
81 .iter()
82 .map(|(component_desc, list_array)| (component_desc.clone(), list_array))
83 .collect();
84
85 let mut components = ChunkComponents({
87 re_tracing::profile_scope!("components (r2l)");
88 lhs_per_desc
89 .iter()
90 .filter_map(|(component_desc, &lhs_list_array)| {
91 re_tracing::profile_scope!(component_desc.to_string());
92 if let Some(&rhs_list_array) = rhs_per_desc.get(component_desc) {
93 re_tracing::profile_scope!(format!(
94 "concat (lhs={} rhs={})",
95 re_format::format_uint(lhs_list_array.values().len()),
96 re_format::format_uint(rhs_list_array.values().len()),
97 ));
98
99 let list_array =
100 re_arrow_util::concat_arrays(&[lhs_list_array, rhs_list_array]).ok()?;
101 let list_array = list_array.downcast_array_ref::<ArrowListArray>()?.clone();
102
103 Some((component_desc.clone(), list_array))
104 } else {
105 re_tracing::profile_scope!("pad");
106 Some((
107 component_desc.clone(),
108 re_arrow_util::pad_list_array_back(
109 lhs_list_array,
110 self.num_rows() + rhs.num_rows(),
111 ),
112 ))
113 }
114 })
115 .collect()
116 });
117
118 {
120 re_tracing::profile_scope!("components (l2r)");
121 let rhs = rhs_per_desc
122 .iter()
123 .filter_map(|(component_desc, &rhs_list_array)| {
124 if components.contains_key(component_desc) {
125 return None;
127 }
128
129 re_tracing::profile_scope!(component_desc.to_string());
130
131 if let Some(&lhs_list_array) = lhs_per_desc.get(component_desc) {
132 re_tracing::profile_scope!(format!(
133 "concat (lhs={} rhs={})",
134 re_format::format_uint(lhs_list_array.values().len()),
135 re_format::format_uint(rhs_list_array.values().len()),
136 ));
137
138 let list_array =
139 re_arrow_util::concat_arrays(&[lhs_list_array, rhs_list_array]).ok()?;
140 let list_array = list_array.downcast_array_ref::<ArrowListArray>()?.clone();
141
142 Some((component_desc.clone(), list_array))
143 } else {
144 re_tracing::profile_scope!("pad");
145 Some((
146 component_desc.clone(),
147 re_arrow_util::pad_list_array_front(
148 rhs_list_array,
149 self.num_rows() + rhs.num_rows(),
150 ),
151 ))
152 }
153 })
154 .collect_vec();
155 components.extend(rhs);
156 }
157
158 let chunk = Self {
159 id: ChunkId::new(),
160 entity_path: cl.entity_path.clone(),
161 heap_size_bytes: Default::default(),
162 is_sorted,
163 row_ids,
164 timelines,
165 components,
166 };
167
168 chunk.sanity_check()?;
169
170 Ok(chunk)
171 }
172
173 #[inline]
175 pub fn overlaps_on_row_id(&self, rhs: &Self) -> bool {
176 let cl = self;
177 let cr = rhs;
178
179 let Some((cl0, cl1)) = cl.row_id_range() else {
180 return false;
181 };
182 let Some((cr0, cr1)) = cr.row_id_range() else {
183 return false;
184 };
185
186 cl0 <= cr1 && cr0 <= cl1
187 }
188
189 #[inline]
193 pub fn overlaps_on_time(&self, rhs: &Self) -> bool {
194 self.timelines.iter().any(|(timeline, cl_time_chunk)| {
195 if let Some(cr_time_chunk) = rhs.timelines.get(timeline) {
196 cl_time_chunk
197 .time_range()
198 .intersects(cr_time_chunk.time_range())
199 } else {
200 false
201 }
202 })
203 }
204
205 #[inline]
207 pub fn same_entity_paths(&self, rhs: &Self) -> bool {
208 self.entity_path() == rhs.entity_path()
209 }
210
211 #[inline]
213 pub fn same_timelines(&self, rhs: &Self) -> bool {
214 self.timelines.len() == rhs.timelines.len()
215 && self.timelines.keys().collect_vec() == rhs.timelines.keys().collect_vec()
216 }
217
218 #[inline]
221 pub fn same_datatypes(&self, rhs: &Self) -> bool {
222 self.components
223 .iter()
224 .all(|(component_desc, lhs_list_array)| {
225 if let Some(rhs_list_array) = rhs.components.get(component_desc) {
226 lhs_list_array.data_type() == rhs_list_array.data_type()
227 } else {
228 true
229 }
230 })
231 }
232
233 #[inline]
240 pub fn concatenable(&self, rhs: &Self) -> bool {
241 self.same_entity_paths(rhs) && self.same_timelines(rhs) && self.same_datatypes(rhs)
242 }
243}
244
245impl TimeColumn {
246 pub fn concatenated(&self, rhs: &Self) -> Option<Self> {
253 if self.timeline != rhs.timeline {
254 return None;
255 }
256 re_tracing::profile_function!();
257
258 let is_sorted =
259 self.is_sorted && rhs.is_sorted && self.time_range.max() <= rhs.time_range.min();
260
261 let time_range = self.time_range.union(rhs.time_range);
262
263 let times = self
264 .times_raw()
265 .iter()
266 .chain(rhs.times_raw())
267 .copied()
268 .collect_vec();
269 let times = ArrowScalarBuffer::from(times);
270
271 Some(Self {
272 timeline: self.timeline,
273 times,
274 is_sorted,
275 time_range,
276 })
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 use re_log_types::example_components::{MyColor, MyLabel, MyPoint, MyPoint64, MyPoints};
285
286 use crate::{Chunk, RowId, Timeline};
287
288 #[test]
289 fn homogeneous() -> anyhow::Result<()> {
290 let entity_path = "my/entity";
291
292 let row_id1 = RowId::new();
293 let row_id2 = RowId::new();
294 let row_id3 = RowId::new();
295 let row_id4 = RowId::new();
296 let row_id5 = RowId::new();
297
298 let timepoint1 = [
299 (Timeline::log_time(), 1000),
300 (Timeline::new_sequence("frame"), 1),
301 ];
302 let timepoint2 = [
303 (Timeline::log_time(), 1032),
304 (Timeline::new_sequence("frame"), 3),
305 ];
306 let timepoint3 = [
307 (Timeline::log_time(), 1064),
308 (Timeline::new_sequence("frame"), 5),
309 ];
310 let timepoint4 = [
311 (Timeline::log_time(), 1096),
312 (Timeline::new_sequence("frame"), 7),
313 ];
314 let timepoint5 = [
315 (Timeline::log_time(), 1128),
316 (Timeline::new_sequence("frame"), 9),
317 ];
318
319 let points1 = &[MyPoint::new(1.0, 1.0), MyPoint::new(2.0, 2.0)];
320 let points3 = &[
321 MyPoint::new(3.0, 3.0),
322 MyPoint::new(4.0, 4.0),
323 MyPoint::new(5.0, 5.0),
324 ];
325 let points5 = &[MyPoint::new(6.0, 7.0)];
326
327 let colors2 = &[MyColor::from_rgb(1, 1, 1)];
328 let colors4 = &[MyColor::from_rgb(2, 2, 2), MyColor::from_rgb(3, 3, 3)];
329
330 let labels2 = &[
331 MyLabel("a".into()),
332 MyLabel("b".into()),
333 MyLabel("c".into()),
334 ];
335 let labels5 = &[MyLabel("d".into())];
336
337 let chunk1 = Chunk::builder(entity_path)
338 .with_component_batches(
339 row_id1,
340 timepoint1,
341 [(MyPoints::descriptor_points(), points1 as _)],
342 )
343 .with_component_batches(
344 row_id2,
345 timepoint2,
346 [
347 (MyPoints::descriptor_colors(), colors2 as _),
348 (MyPoints::descriptor_labels(), labels2 as _),
349 ],
350 )
351 .with_component_batches(
352 row_id3,
353 timepoint3,
354 [(MyPoints::descriptor_points(), points3 as _)],
355 )
356 .build()?;
357
358 let chunk2 = Chunk::builder(entity_path)
359 .with_component_batches(
360 row_id4,
361 timepoint4,
362 [(MyPoints::descriptor_colors(), colors4 as _)],
363 )
364 .with_component_batches(
365 row_id5,
366 timepoint5,
367 [
368 (MyPoints::descriptor_points(), points5 as _),
369 (MyPoints::descriptor_labels(), labels5 as _),
370 ],
371 )
372 .build()?;
373
374 eprintln!("chunk1:\n{chunk1}");
375 eprintln!("chunk2:\n{chunk2}");
376
377 {
378 assert!(chunk1.concatenable(&chunk2));
379
380 let got = chunk1.concatenated(&chunk2).unwrap();
381 let expected = Chunk::builder_with_id(got.id(), entity_path)
382 .with_sparse_component_batches(
383 row_id1,
384 timepoint1,
385 [
386 (MyPoints::descriptor_points(), Some(points1 as _)),
387 (MyPoints::descriptor_colors(), None),
388 (MyPoints::descriptor_labels(), None),
389 ],
390 )
391 .with_sparse_component_batches(
392 row_id2,
393 timepoint2,
394 [
395 (MyPoints::descriptor_points(), None),
396 (MyPoints::descriptor_colors(), Some(colors2 as _)),
397 (MyPoints::descriptor_labels(), Some(labels2 as _)),
398 ],
399 )
400 .with_sparse_component_batches(
401 row_id3,
402 timepoint3,
403 [
404 (MyPoints::descriptor_points(), Some(points3 as _)),
405 (MyPoints::descriptor_colors(), None),
406 (MyPoints::descriptor_labels(), None),
407 ],
408 )
409 .with_sparse_component_batches(
410 row_id4,
411 timepoint4,
412 [
413 (MyPoints::descriptor_points(), None),
414 (MyPoints::descriptor_colors(), Some(colors4 as _)),
415 (MyPoints::descriptor_labels(), None),
416 ],
417 )
418 .with_sparse_component_batches(
419 row_id5,
420 timepoint5,
421 [
422 (MyPoints::descriptor_points(), Some(points5 as _)),
423 (MyPoints::descriptor_colors(), None),
424 (MyPoints::descriptor_labels(), Some(labels5 as _)),
425 ],
426 )
427 .build()?;
428
429 eprintln!("got:\n{got}");
430 eprintln!("expected:\n{expected}");
431
432 assert_eq!(
433 expected,
434 got,
435 "{}",
436 similar_asserts::SimpleDiff::from_str(
437 &format!("{got}"),
438 &format!("{expected}"),
439 "got",
440 "expected",
441 ),
442 );
443
444 assert!(got.is_sorted());
445 assert!(got.is_time_sorted());
446 }
447 {
448 assert!(chunk2.concatenable(&chunk1));
449
450 let got = chunk2.concatenated(&chunk1).unwrap();
451 let expected = Chunk::builder_with_id(got.id(), entity_path)
452 .with_sparse_component_batches(
453 row_id4,
454 timepoint4,
455 [
456 (MyPoints::descriptor_points(), None),
457 (MyPoints::descriptor_colors(), Some(colors4 as _)),
458 (MyPoints::descriptor_labels(), None),
459 ],
460 )
461 .with_sparse_component_batches(
462 row_id5,
463 timepoint5,
464 [
465 (MyPoints::descriptor_points(), Some(points5 as _)),
466 (MyPoints::descriptor_colors(), None),
467 (MyPoints::descriptor_labels(), Some(labels5 as _)),
468 ],
469 )
470 .with_sparse_component_batches(
471 row_id1,
472 timepoint1,
473 [
474 (MyPoints::descriptor_points(), Some(points1 as _)),
475 (MyPoints::descriptor_colors(), None),
476 (MyPoints::descriptor_labels(), None),
477 ],
478 )
479 .with_sparse_component_batches(
480 row_id2,
481 timepoint2,
482 [
483 (MyPoints::descriptor_points(), None),
484 (MyPoints::descriptor_colors(), Some(colors2 as _)),
485 (MyPoints::descriptor_labels(), Some(labels2 as _)),
486 ],
487 )
488 .with_sparse_component_batches(
489 row_id3,
490 timepoint3,
491 [
492 (MyPoints::descriptor_points(), Some(points3 as _)),
493 (MyPoints::descriptor_colors(), None),
494 (MyPoints::descriptor_labels(), None),
495 ],
496 )
497 .build()?;
498
499 eprintln!("got:\n{got}");
500 eprintln!("expected:\n{expected}");
501
502 assert_eq!(
503 expected,
504 got,
505 "{}",
506 similar_asserts::SimpleDiff::from_str(
507 &format!("{got}"),
508 &format!("{expected}"),
509 "got",
510 "expected",
511 ),
512 );
513
514 assert!(!got.is_sorted());
515 assert!(!got.is_time_sorted());
516 }
517
518 Ok(())
519 }
520
521 #[test]
522 fn heterogeneous() -> anyhow::Result<()> {
523 let entity_path = "my/entity";
524
525 let row_id1 = RowId::new();
526 let row_id2 = RowId::new();
527 let row_id3 = RowId::new();
528 let row_id4 = RowId::new();
529 let row_id5 = RowId::new();
530
531 let timepoint1 = [
532 (Timeline::log_time(), 1000),
533 (Timeline::new_sequence("frame"), 1),
534 ];
535 let timepoint2 = [
536 (Timeline::log_time(), 1032),
537 (Timeline::new_sequence("frame"), 3),
538 ];
539 let timepoint3 = [
540 (Timeline::log_time(), 1064),
541 (Timeline::new_sequence("frame"), 5),
542 ];
543 let timepoint4 = [
544 (Timeline::log_time(), 1096),
545 (Timeline::new_sequence("frame"), 7),
546 ];
547 let timepoint5 = [
548 (Timeline::log_time(), 1128),
549 (Timeline::new_sequence("frame"), 9),
550 ];
551
552 let points1 = &[MyPoint::new(1.0, 1.0), MyPoint::new(2.0, 2.0)];
553 let points3 = &[MyPoint::new(6.0, 7.0)];
554
555 let colors4 = &[MyColor::from_rgb(1, 1, 1)];
556 let colors5 = &[MyColor::from_rgb(2, 2, 2), MyColor::from_rgb(3, 3, 3)];
557
558 let labels1 = &[MyLabel("a".into())];
559 let labels2 = &[MyLabel("b".into())];
560 let labels3 = &[MyLabel("c".into())];
561 let labels4 = &[MyLabel("d".into())];
562 let labels5 = &[MyLabel("e".into())];
563
564 let chunk1 = Chunk::builder(entity_path)
565 .with_component_batches(
566 row_id1,
567 timepoint1,
568 [
569 (MyPoints::descriptor_points(), points1 as _),
570 (MyPoints::descriptor_labels(), labels1 as _),
571 ],
572 )
573 .with_component_batches(
574 row_id2,
575 timepoint2,
576 [(MyPoints::descriptor_labels(), labels2 as _)],
577 )
578 .with_component_batches(
579 row_id3,
580 timepoint3,
581 [
582 (MyPoints::descriptor_points(), points3 as _),
583 (MyPoints::descriptor_labels(), labels3 as _),
584 ],
585 )
586 .build()?;
587
588 let chunk2 = Chunk::builder(entity_path)
589 .with_component_batches(
590 row_id4,
591 timepoint4,
592 [
593 (MyPoints::descriptor_colors(), colors4 as _),
594 (MyPoints::descriptor_labels(), labels4 as _),
595 ],
596 )
597 .with_component_batches(
598 row_id5,
599 timepoint5,
600 [
601 (MyPoints::descriptor_colors(), colors5 as _),
602 (MyPoints::descriptor_labels(), labels5 as _),
603 ],
604 )
605 .build()?;
606
607 eprintln!("chunk1:\n{chunk1}");
608 eprintln!("chunk2:\n{chunk2}");
609
610 {
611 assert!(chunk1.concatenable(&chunk2));
612
613 let got = chunk1.concatenated(&chunk2).unwrap();
614 let expected = Chunk::builder_with_id(got.id(), entity_path)
615 .with_sparse_component_batches(
616 row_id1,
617 timepoint1,
618 [
619 (MyPoints::descriptor_points(), Some(points1 as _)),
620 (MyPoints::descriptor_colors(), None),
621 (MyPoints::descriptor_labels(), Some(labels1 as _)),
622 ],
623 )
624 .with_sparse_component_batches(
625 row_id2,
626 timepoint2,
627 [
628 (MyPoints::descriptor_points(), None),
629 (MyPoints::descriptor_colors(), None),
630 (MyPoints::descriptor_labels(), Some(labels2 as _)),
631 ],
632 )
633 .with_sparse_component_batches(
634 row_id3,
635 timepoint3,
636 [
637 (MyPoints::descriptor_points(), Some(points3 as _)),
638 (MyPoints::descriptor_colors(), None),
639 (MyPoints::descriptor_labels(), Some(labels3 as _)),
640 ],
641 )
642 .with_sparse_component_batches(
643 row_id4,
644 timepoint4,
645 [
646 (MyPoints::descriptor_points(), None),
647 (MyPoints::descriptor_colors(), Some(colors4 as _)),
648 (MyPoints::descriptor_labels(), Some(labels4 as _)),
649 ],
650 )
651 .with_sparse_component_batches(
652 row_id5,
653 timepoint5,
654 [
655 (MyPoints::descriptor_points(), None),
656 (MyPoints::descriptor_colors(), Some(colors5 as _)),
657 (MyPoints::descriptor_labels(), Some(labels5 as _)),
658 ],
659 )
660 .build()?;
661
662 eprintln!("got:\n{got}");
663 eprintln!("expected:\n{expected}");
664
665 assert_eq!(
666 expected,
667 got,
668 "{}",
669 similar_asserts::SimpleDiff::from_str(
670 &format!("{got}"),
671 &format!("{expected}"),
672 "got",
673 "expected",
674 ),
675 );
676
677 assert!(got.is_sorted());
678 assert!(got.is_time_sorted());
679 }
680 {
681 assert!(chunk2.concatenable(&chunk1));
682
683 let got = chunk2.concatenated(&chunk1).unwrap();
684 let expected = Chunk::builder_with_id(got.id(), entity_path)
685 .with_sparse_component_batches(
686 row_id4,
687 timepoint4,
688 [
689 (MyPoints::descriptor_points(), None),
690 (MyPoints::descriptor_colors(), Some(colors4 as _)),
691 (MyPoints::descriptor_labels(), Some(labels4 as _)),
692 ],
693 )
694 .with_sparse_component_batches(
695 row_id5,
696 timepoint5,
697 [
698 (MyPoints::descriptor_points(), None),
699 (MyPoints::descriptor_colors(), Some(colors5 as _)),
700 (MyPoints::descriptor_labels(), Some(labels5 as _)),
701 ],
702 )
703 .with_sparse_component_batches(
704 row_id1,
705 timepoint1,
706 [
707 (MyPoints::descriptor_points(), Some(points1 as _)),
708 (MyPoints::descriptor_colors(), None),
709 (MyPoints::descriptor_labels(), Some(labels1 as _)),
710 ],
711 )
712 .with_sparse_component_batches(
713 row_id2,
714 timepoint2,
715 [
716 (MyPoints::descriptor_points(), None),
717 (MyPoints::descriptor_colors(), None),
718 (MyPoints::descriptor_labels(), Some(labels2 as _)),
719 ],
720 )
721 .with_sparse_component_batches(
722 row_id3,
723 timepoint3,
724 [
725 (MyPoints::descriptor_points(), Some(points3 as _)),
726 (MyPoints::descriptor_colors(), None),
727 (MyPoints::descriptor_labels(), Some(labels3 as _)),
728 ],
729 )
730 .build()?;
731
732 eprintln!("got:\n{got}");
733 eprintln!("expected:\n{expected}");
734
735 assert_eq!(
736 expected,
737 got,
738 "{}",
739 similar_asserts::SimpleDiff::from_str(
740 &format!("{got}"),
741 &format!("{expected}"),
742 "got",
743 "expected",
744 ),
745 );
746
747 assert!(!got.is_sorted());
748 assert!(!got.is_time_sorted());
749 }
750
751 Ok(())
752 }
753
754 #[test]
755 fn malformed() -> anyhow::Result<()> {
756 {
758 let entity_path1 = "ent1";
759 let entity_path2 = "ent2";
760
761 let row_id1 = RowId::new();
762 let row_id2 = RowId::new();
763
764 let timepoint1 = [
765 (Timeline::log_time(), 1000),
766 (Timeline::new_sequence("frame"), 1),
767 ];
768 let timepoint2 = [
769 (Timeline::log_time(), 1032),
770 (Timeline::new_sequence("frame"), 3),
771 ];
772
773 let points1 = &[MyPoint::new(1.0, 1.0)];
774 let points2 = &[MyPoint::new(2.0, 2.0)];
775
776 let chunk1 = Chunk::builder(entity_path1)
777 .with_component_batches(
778 row_id1,
779 timepoint1,
780 [(MyPoints::descriptor_points(), points1 as _)],
781 )
782 .build()?;
783
784 let chunk2 = Chunk::builder(entity_path2)
785 .with_component_batches(
786 row_id2,
787 timepoint2,
788 [(MyPoints::descriptor_points(), points2 as _)],
789 )
790 .build()?;
791
792 assert!(matches!(
793 chunk1.concatenated(&chunk2),
794 Err(ChunkError::Malformed { .. })
795 ));
796 assert!(matches!(
797 chunk2.concatenated(&chunk1),
798 Err(ChunkError::Malformed { .. })
799 ));
800 }
801
802 {
804 let entity_path = "ent";
805
806 let row_id1 = RowId::new();
807 let row_id2 = RowId::new();
808
809 let timepoint1 = [(Timeline::new_sequence("frame"), 1)];
810 let timepoint2 = [(Timeline::log_time(), 1032)];
811
812 let points1 = &[MyPoint::new(1.0, 1.0)];
813 let points2 = &[MyPoint::new(2.0, 2.0)];
814
815 let chunk1 = Chunk::builder(entity_path)
816 .with_component_batches(
817 row_id1,
818 timepoint1,
819 [(MyPoints::descriptor_points(), points1 as _)],
820 )
821 .build()?;
822
823 let chunk2 = Chunk::builder(entity_path)
824 .with_component_batches(
825 row_id2,
826 timepoint2,
827 [(MyPoints::descriptor_points(), points2 as _)],
828 )
829 .build()?;
830
831 assert!(matches!(
832 chunk1.concatenated(&chunk2),
833 Err(ChunkError::Malformed { .. })
834 ));
835 assert!(matches!(
836 chunk2.concatenated(&chunk1),
837 Err(ChunkError::Malformed { .. })
838 ));
839 }
840
841 {
843 let entity_path = "ent";
844
845 let row_id1 = RowId::new();
846 let row_id2 = RowId::new();
847
848 let timepoint1 = [(Timeline::new_sequence("frame"), 1)];
849 let timepoint2 = [(Timeline::new_sequence("frame"), 2)];
850
851 let points32bit =
852 <MyPoint as re_types_core::ComponentBatch>::to_arrow(&MyPoint::new(1.0, 1.0))?;
853 let points64bit =
854 <MyPoint64 as re_types_core::ComponentBatch>::to_arrow(&MyPoint64::new(1.0, 1.0))?;
855
856 let chunk1 = Chunk::builder(entity_path)
857 .with_row(
858 row_id1,
859 timepoint1,
860 [
861 (MyPoints::descriptor_points(), points32bit), ],
863 )
864 .build()?;
865
866 let chunk2 = Chunk::builder(entity_path)
867 .with_row(
868 row_id2,
869 timepoint2,
870 [
871 (MyPoints::descriptor_points(), points64bit), ],
873 )
874 .build()?;
875
876 assert!(matches!(
877 chunk1.concatenated(&chunk2),
878 Err(ChunkError::Malformed { .. })
879 ));
880 assert!(matches!(
881 chunk2.concatenated(&chunk1),
882 Err(ChunkError::Malformed { .. })
883 ));
884 }
885
886 Ok(())
887 }
888}