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