Skip to main content

vortex_layout/
layout.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::any::Any;
5use std::fmt::Debug;
6use std::fmt::Display;
7use std::fmt::Formatter;
8use std::sync::Arc;
9
10use itertools::Itertools;
11use vortex_array::SerializeMetadata;
12use vortex_array::dtype::DType;
13use vortex_array::dtype::FieldName;
14use vortex_error::VortexExpect;
15use vortex_error::VortexResult;
16use vortex_error::vortex_err;
17use vortex_session::VortexSession;
18use vortex_session::registry::Id;
19
20use crate::LayoutEncodingId;
21use crate::LayoutEncodingRef;
22use crate::LayoutReaderContext;
23use crate::LayoutReaderRef;
24use crate::VTable;
25use crate::display::DisplayLayoutTree;
26use crate::display::display_tree_with_segment_sizes;
27use crate::segments::SegmentId;
28use crate::segments::SegmentSource;
29
30/// A unique identifier for a layout encoding.
31pub type LayoutId = Id;
32
33/// Shared, erased handle to a layout tree node.
34pub type LayoutRef = Arc<dyn Layout>;
35
36/// Erased layout tree node.
37///
38/// A layout is immutable metadata describing how row data is organized into child layouts and
39/// physical byte segments. It is self-describing only when paired with the session registries used
40/// to deserialize its encoding ids and the segment source used to read bytes.
41pub trait Layout: 'static + Send + Sync + Debug + private::Sealed {
42    /// Returns this layout as [`Any`] for downcasting through the `dyn Layout` helpers.
43    fn as_any(&self) -> &dyn Any;
44
45    /// Converts an owned layout reference into erased [`Any`] for `Arc` downcasting.
46    fn as_any_arc(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
47
48    /// Clone this layout as an erased [`LayoutRef`].
49    fn to_layout(&self) -> LayoutRef;
50
51    /// Returns the [`crate::LayoutEncoding`] for this layout.
52    fn encoding(&self) -> LayoutEncodingRef;
53
54    /// The number of rows in this layout.
55    fn row_count(&self) -> u64;
56
57    /// The logical dtype of this layout when evaluated at the root scope.
58    fn dtype(&self) -> &DType;
59
60    /// The number of children in this layout.
61    fn nchildren(&self) -> usize;
62
63    /// Get the child at the given index.
64    ///
65    /// Child ordering and meaning are encoding-specific and described by
66    /// [`child_type`](Self::child_type).
67    fn child(&self, idx: usize) -> VortexResult<LayoutRef>;
68
69    /// Get the relative row offset of the child at the given index, returning `None` for
70    /// any auxiliary children, e.g. dictionary values, zone maps, etc.
71    fn child_type(&self, idx: usize) -> LayoutChildType;
72
73    /// Serialize this layout's encoding metadata.
74    fn metadata(&self) -> Vec<u8>;
75
76    /// Get the segment IDs directly referenced by this layout node.
77    fn segment_ids(&self) -> Vec<SegmentId>;
78
79    /// Construct a new reader for this layout.
80    ///
81    /// - `name` — human-readable label for this reader, propagated to child readers
82    ///   (typically by appending a path component) and surfaced in tracing/debug output.
83    /// - `segment_source` — source of segment bytes for this and any descendant readers
84    ///   constructed from the returned reader; recursive callers should pass the same
85    ///   source through.
86    /// - `session` — the [`VortexSession`] hosting the encoding/scalar/layout registries
87    ///   and execution context the reader needs at evaluation time.
88    /// - `ctx` — id-keyed dependency registry threaded through reader construction (see
89    ///   [`LayoutReaderContext`]). Top-level callers (file open, tests) typically pass
90    ///   `&LayoutReaderContext::new()`; recursive callers inside layout implementations
91    ///   must propagate the `ctx` they were handed so ancestor-published values reach
92    ///   descendants.
93    fn new_reader(
94        &self,
95        name: Arc<str>,
96        segment_source: Arc<dyn SegmentSource>,
97        session: &VortexSession,
98        ctx: &LayoutReaderContext,
99    ) -> VortexResult<LayoutReaderRef>;
100}
101
102/// Conversion into an erased [`LayoutRef`].
103pub trait IntoLayout {
104    /// Converts this type into a [`LayoutRef`].
105    fn into_layout(self) -> LayoutRef;
106}
107
108/// A type that allows us to identify how a layout child relates to its parent.
109#[derive(Debug, Clone, PartialEq, Eq)]
110pub enum LayoutChildType {
111    /// A layout child that retains the same schema and row offset position in the dataset.
112    Transparent(Arc<str>),
113    /// A layout child that provides auxiliary data, e.g. dictionary values, zone maps, etc.
114    /// Contains a human-readable name of the child.
115    Auxiliary(Arc<str>),
116    /// A layout child that represents a row-based chunk of data.
117    /// Contains the chunk index and relative row offset of the child.
118    Chunk((usize, u64)),
119    /// A layout child that represents a single field of data.
120    /// Contains the field name of the child.
121    Field(FieldName),
122}
123
124impl LayoutChildType {
125    /// Returns the name of this child.
126    pub fn name(&self) -> Arc<str> {
127        match self {
128            LayoutChildType::Chunk((idx, _offset)) => format!("[{idx}]").into(),
129            LayoutChildType::Auxiliary(name) => Arc::clone(name),
130            LayoutChildType::Transparent(name) => Arc::clone(name),
131            LayoutChildType::Field(name) => name.clone().into(),
132        }
133    }
134
135    /// Returns the relative row offset of this child.
136    /// For auxiliary children, this is `None`.
137    pub fn row_offset(&self) -> Option<u64> {
138        match self {
139            LayoutChildType::Chunk((_idx, offset)) => Some(*offset),
140            LayoutChildType::Auxiliary(_) => None,
141            LayoutChildType::Transparent(_) => Some(0),
142            LayoutChildType::Field(_) => Some(0),
143        }
144    }
145}
146
147impl dyn Layout + '_ {
148    /// The ID of the encoding for this layout.
149    pub fn encoding_id(&self) -> LayoutEncodingId {
150        self.encoding().id()
151    }
152
153    /// The children of this layout.
154    pub fn children(&self) -> VortexResult<Vec<LayoutRef>> {
155        (0..self.nchildren()).map(|i| self.child(i)).try_collect()
156    }
157
158    /// The child types of this layout.
159    pub fn child_types(&self) -> impl Iterator<Item = LayoutChildType> {
160        (0..self.nchildren()).map(|i| self.child_type(i))
161    }
162
163    /// The names of the children of this layout.
164    pub fn child_names(&self) -> impl Iterator<Item = Arc<str>> {
165        self.child_types().map(|child| child.name())
166    }
167
168    /// The row offsets of the children of this layout, where `None` indicates an auxiliary child.
169    pub fn child_row_offsets(&self) -> impl Iterator<Item = Option<u64>> {
170        self.child_types().map(|child| child.row_offset())
171    }
172
173    pub fn is<V: VTable>(&self) -> bool {
174        self.as_opt::<V>().is_some()
175    }
176
177    /// Downcast a layout to a specific type.
178    pub fn as_<V: VTable>(&self) -> &V::Layout {
179        self.as_opt::<V>().vortex_expect("Failed to downcast")
180    }
181
182    /// Downcast a layout to a specific type.
183    pub fn as_opt<V: VTable>(&self) -> Option<&V::Layout> {
184        self.as_any()
185            .downcast_ref::<LayoutAdapter<V>>()
186            .map(|adapter| &adapter.0)
187    }
188
189    /// Downcast a layout to a specific type.
190    pub fn into<V: VTable>(self: Arc<Self>) -> Arc<V::Layout> {
191        let layout_adapter = self
192            .as_any_arc()
193            .downcast::<LayoutAdapter<V>>()
194            .map_err(|_| vortex_err!("Invalid layout type"))
195            .vortex_expect("Invalid layout type");
196
197        // SAFETY: LayoutAdapter<V> is #[repr(transparent)] (see line 192) which guarantees
198        // it has the same memory layout as V::Layout. The downcast above ensures we have
199        // the correct type. This transmute is safe because both Arc types point to data
200        // with identical layout and alignment.
201        unsafe { std::mem::transmute::<Arc<LayoutAdapter<V>>, Arc<V::Layout>>(layout_adapter) }
202    }
203
204    /// Depth-first traversal of the layout and its children.
205    pub fn depth_first_traversal(&self) -> impl Iterator<Item = VortexResult<LayoutRef>> {
206        /// A depth-first pre-order iterator over a layout.
207        struct ChildrenIterator {
208            stack: Vec<LayoutRef>,
209        }
210
211        impl Iterator for ChildrenIterator {
212            type Item = VortexResult<LayoutRef>;
213
214            fn next(&mut self) -> Option<Self::Item> {
215                let next = self.stack.pop()?;
216                let Ok(children) = next.children() else {
217                    return Some(Ok(next));
218                };
219                for child in children.into_iter().rev() {
220                    self.stack.push(child);
221                }
222                Some(Ok(next))
223            }
224        }
225
226        ChildrenIterator {
227            stack: vec![self.to_layout()],
228        }
229    }
230
231    /// Display the layout as a tree structure.
232    pub fn display_tree(&self) -> DisplayLayoutTree {
233        DisplayLayoutTree::new(self.to_layout(), false)
234    }
235
236    /// Display the layout as a tree structure with optional verbose metadata.
237    pub fn display_tree_verbose(&self, verbose: bool) -> DisplayLayoutTree {
238        DisplayLayoutTree::new(self.to_layout(), verbose)
239    }
240
241    /// Display the layout as a tree structure, fetching segment buffer sizes from the segment source.
242    ///
243    /// # Warning
244    ///
245    /// This function performs IO to fetch each segment's buffer. For layouts with
246    /// many segments, this may result in significant IO overhead.
247    pub async fn display_tree_with_segments(
248        &self,
249        segment_source: Arc<dyn SegmentSource>,
250    ) -> VortexResult<DisplayLayoutTree> {
251        display_tree_with_segment_sizes(self.to_layout(), segment_source).await
252    }
253}
254
255/// Display the encoding, dtype, row count, and segment IDs of this layout.
256impl Display for dyn Layout + '_ {
257    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
258        let segment_ids = self.segment_ids();
259        if segment_ids.is_empty() {
260            write!(
261                f,
262                "{}({}, rows={})",
263                self.encoding_id(),
264                self.dtype(),
265                self.row_count()
266            )
267        } else {
268            write!(
269                f,
270                "{}({}, rows={}, segments=[{}])",
271                self.encoding_id(),
272                self.dtype(),
273                self.row_count(),
274                segment_ids
275                    .iter()
276                    .map(|s| format!("{}", **s))
277                    .collect::<Vec<_>>()
278                    .join(", ")
279            )
280        }
281    }
282}
283
284#[repr(transparent)]
285pub struct LayoutAdapter<V: VTable>(V::Layout);
286
287impl<V: VTable> Debug for LayoutAdapter<V> {
288    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
289        self.0.fmt(f)
290    }
291}
292
293impl<V: VTable> Layout for LayoutAdapter<V> {
294    fn as_any(&self) -> &dyn Any {
295        self
296    }
297
298    fn as_any_arc(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
299        self
300    }
301
302    fn to_layout(&self) -> LayoutRef {
303        Arc::new(LayoutAdapter::<V>(self.0.clone()))
304    }
305
306    fn encoding(&self) -> LayoutEncodingRef {
307        V::encoding(&self.0)
308    }
309
310    fn row_count(&self) -> u64 {
311        V::row_count(&self.0)
312    }
313
314    fn dtype(&self) -> &DType {
315        V::dtype(&self.0)
316    }
317
318    fn nchildren(&self) -> usize {
319        V::nchildren(&self.0)
320    }
321
322    fn child(&self, idx: usize) -> VortexResult<LayoutRef> {
323        V::child(&self.0, idx)
324    }
325
326    fn child_type(&self, idx: usize) -> LayoutChildType {
327        V::child_type(&self.0, idx)
328    }
329
330    fn metadata(&self) -> Vec<u8> {
331        V::metadata(&self.0).serialize()
332    }
333
334    fn segment_ids(&self) -> Vec<SegmentId> {
335        V::segment_ids(&self.0)
336    }
337
338    fn new_reader(
339        &self,
340        name: Arc<str>,
341        segment_source: Arc<dyn SegmentSource>,
342        session: &VortexSession,
343        ctx: &LayoutReaderContext,
344    ) -> VortexResult<LayoutReaderRef> {
345        V::new_reader(&self.0, name, segment_source, session, ctx)
346    }
347}
348
349mod private {
350    use super::*;
351    use crate::layouts::foreign::ForeignLayout;
352
353    pub trait Sealed {}
354
355    impl<V: VTable> Sealed for LayoutAdapter<V> {}
356    impl Sealed for ForeignLayout {}
357}
358
359#[cfg(test)]
360mod tests {
361    use rstest::rstest;
362    use vortex_session::registry::ReadContext;
363
364    use super::*;
365
366    #[test]
367    fn test_layout_child_type_name() {
368        // Test Chunk variant
369        let chunk = LayoutChildType::Chunk((5, 100));
370        assert_eq!(chunk.name().as_ref(), "[5]");
371
372        // Test Field variant
373        let field = LayoutChildType::Field(FieldName::from("customer_id"));
374        assert_eq!(field.name().as_ref(), "customer_id");
375
376        // Test Auxiliary variant
377        let aux = LayoutChildType::Auxiliary(Arc::from("zone_map"));
378        assert_eq!(aux.name().as_ref(), "zone_map");
379
380        // Test Transparent variant
381        let transparent = LayoutChildType::Transparent(Arc::from("compressed"));
382        assert_eq!(transparent.name().as_ref(), "compressed");
383    }
384
385    #[test]
386    fn test_layout_child_type_row_offset() {
387        // Chunk should return the offset
388        let chunk = LayoutChildType::Chunk((0, 42));
389        assert_eq!(chunk.row_offset(), Some(42));
390
391        // Field should return 0
392        let field = LayoutChildType::Field(FieldName::from("field1"));
393        assert_eq!(field.row_offset(), Some(0));
394
395        // Auxiliary should return None
396        let aux = LayoutChildType::Auxiliary(Arc::from("metadata"));
397        assert_eq!(aux.row_offset(), None);
398
399        // Transparent should return 0
400        let transparent = LayoutChildType::Transparent(Arc::from("wrapper"));
401        assert_eq!(transparent.row_offset(), Some(0));
402    }
403
404    #[test]
405    fn test_layout_child_type_equality() {
406        // Test Chunk equality
407        let chunk1 = LayoutChildType::Chunk((1, 100));
408        let chunk2 = LayoutChildType::Chunk((1, 100));
409        let chunk3 = LayoutChildType::Chunk((2, 100));
410        let chunk4 = LayoutChildType::Chunk((1, 200));
411
412        assert_eq!(chunk1, chunk2);
413        assert_ne!(chunk1, chunk3);
414        assert_ne!(chunk1, chunk4);
415
416        // Test Field equality
417        let field1 = LayoutChildType::Field(FieldName::from("name"));
418        let field2 = LayoutChildType::Field(FieldName::from("name"));
419        let field3 = LayoutChildType::Field(FieldName::from("age"));
420
421        assert_eq!(field1, field2);
422        assert_ne!(field1, field3);
423
424        // Test Auxiliary equality
425        let aux1 = LayoutChildType::Auxiliary(Arc::from("stats"));
426        let aux2 = LayoutChildType::Auxiliary(Arc::from("stats"));
427        let aux3 = LayoutChildType::Auxiliary(Arc::from("index"));
428
429        assert_eq!(aux1, aux2);
430        assert_ne!(aux1, aux3);
431
432        // Test Transparent equality
433        let trans1 = LayoutChildType::Transparent(Arc::from("enc"));
434        let trans2 = LayoutChildType::Transparent(Arc::from("enc"));
435        let trans3 = LayoutChildType::Transparent(Arc::from("dec"));
436
437        assert_eq!(trans1, trans2);
438        assert_ne!(trans1, trans3);
439
440        // Test cross-variant inequality
441        assert_ne!(chunk1, field1);
442        assert_ne!(field1, aux1);
443        assert_ne!(aux1, trans1);
444    }
445
446    #[rstest]
447    #[case(LayoutChildType::Chunk((0, 0)), "[0]", Some(0))]
448    #[case(LayoutChildType::Chunk((999, 1000000)), "[999]", Some(1000000))]
449    #[case(LayoutChildType::Field(FieldName::from("")), "", Some(0))]
450    #[case(
451        LayoutChildType::Field(FieldName::from("very_long_field_name_that_is_quite_lengthy")),
452        "very_long_field_name_that_is_quite_lengthy",
453        Some(0)
454    )]
455    #[case(LayoutChildType::Auxiliary(Arc::from("aux")), "aux", None)]
456    #[case(LayoutChildType::Transparent(Arc::from("t")), "t", Some(0))]
457    fn test_layout_child_type_parameterized(
458        #[case] child_type: LayoutChildType,
459        #[case] expected_name: &str,
460        #[case] expected_offset: Option<u64>,
461    ) {
462        assert_eq!(child_type.name().as_ref(), expected_name);
463        assert_eq!(child_type.row_offset(), expected_offset);
464    }
465
466    #[test]
467    fn test_chunk_with_different_indices_and_offsets() {
468        let chunks = [
469            LayoutChildType::Chunk((0, 0)),
470            LayoutChildType::Chunk((1, 100)),
471            LayoutChildType::Chunk((2, 200)),
472            LayoutChildType::Chunk((100, 10000)),
473        ];
474
475        for chunk in chunks.iter() {
476            let name = chunk.name();
477            assert!(name.starts_with('['));
478            assert!(name.ends_with(']'));
479
480            if let LayoutChildType::Chunk((idx, offset)) = chunk {
481                assert_eq!(name.as_ref(), format!("[{}]", idx));
482                assert_eq!(chunk.row_offset(), Some(*offset));
483            }
484        }
485    }
486
487    #[test]
488    fn test_field_names_with_special_characters() {
489        let special_fields: Vec<Arc<str>> = vec![
490            Arc::from("field-with-dashes"),
491            Arc::from("field_with_underscores"),
492            Arc::from("field.with.dots"),
493            Arc::from("field::with::colons"),
494            Arc::from("field/with/slashes"),
495            Arc::from("field@with#symbols"),
496        ];
497
498        for field_name in special_fields {
499            let field = LayoutChildType::Field(Arc::clone(&field_name).into());
500            assert_eq!(field.name(), field_name);
501            assert_eq!(field.row_offset(), Some(0));
502        }
503    }
504
505    #[test]
506    fn test_struct_layout_display() {
507        use vortex_array::dtype::Nullability::NonNullable;
508        use vortex_array::dtype::PType;
509        use vortex_array::dtype::StructFields;
510
511        use crate::IntoLayout;
512        use crate::layouts::chunked::ChunkedLayout;
513        use crate::layouts::dict::DictLayout;
514        use crate::layouts::flat::FlatLayout;
515        use crate::layouts::struct_::StructLayout;
516        use crate::segments::SegmentId;
517
518        let ctx = ReadContext::new([]);
519
520        // Create a flat layout for dict values (utf8 strings)
521        let dict_values =
522            FlatLayout::new(3, DType::Utf8(NonNullable), SegmentId::from(0), ctx.clone())
523                .into_layout();
524
525        // Test flat layout display shows segment
526        assert_eq!(
527            format!("{}", dict_values),
528            "vortex.flat(utf8, rows=3, segments=[0])"
529        );
530
531        // Create a flat layout for dict codes
532        let dict_codes = FlatLayout::new(
533            10,
534            DType::Primitive(PType::U16, NonNullable),
535            SegmentId::from(1),
536            ctx.clone(),
537        )
538        .into_layout();
539
540        // Test flat layout display shows segment
541        assert_eq!(
542            format!("{}", dict_codes),
543            "vortex.flat(u16, rows=10, segments=[1])"
544        );
545
546        // Create dict layout (column "name")
547        let dict_layout =
548            DictLayout::new(Arc::clone(&dict_values), Arc::clone(&dict_codes)).into_layout();
549
550        // Test dict layout display (no direct segments)
551        assert_eq!(format!("{}", dict_layout), "vortex.dict(utf8, rows=10)");
552
553        // Create flat layouts for chunks
554        let chunk1 = FlatLayout::new(
555            5,
556            DType::Primitive(PType::I64, NonNullable),
557            SegmentId::from(2),
558            ctx.clone(),
559        )
560        .into_layout();
561
562        let chunk2 = FlatLayout::new(
563            5,
564            DType::Primitive(PType::I64, NonNullable),
565            SegmentId::from(3),
566            ctx,
567        )
568        .into_layout();
569
570        // Create chunked layout (column "value")
571        let chunked_layout = ChunkedLayout::new(
572            10,
573            DType::Primitive(PType::I64, NonNullable),
574            crate::OwnedLayoutChildren::layout_children(vec![
575                Arc::clone(&chunk1),
576                Arc::clone(&chunk2),
577            ]),
578        )
579        .into_layout();
580
581        // Test chunked layout display (no direct segments)
582        assert_eq!(
583            format!("{}", chunked_layout),
584            "vortex.chunked(i64, rows=10)"
585        );
586
587        // Test chunk displays show segments
588        assert_eq!(
589            format!("{}", chunk1),
590            "vortex.flat(i64, rows=5, segments=[2])"
591        );
592        assert_eq!(
593            format!("{}", chunk2),
594            "vortex.flat(i64, rows=5, segments=[3])"
595        );
596
597        // Create struct layout with two fields
598        let field_names: Vec<Arc<str>> = vec!["name".into(), "value".into()];
599        let struct_dtype = DType::Struct(
600            StructFields::new(
601                field_names.into(),
602                vec![
603                    DType::Utf8(NonNullable),
604                    DType::Primitive(PType::I64, NonNullable),
605                ],
606            ),
607            NonNullable,
608        );
609
610        let struct_layout =
611            StructLayout::new(10, struct_dtype, vec![dict_layout, chunked_layout]).into_layout();
612
613        println!("{}", struct_layout.display_tree_verbose(true));
614
615        // Test Display impl for struct (no direct segments)
616        assert_eq!(
617            format!("{}", struct_layout),
618            "vortex.struct({name=utf8, value=i64}, rows=10)"
619        );
620    }
621}