vortex_file/footer/
mod.rs1mod file_layout;
12mod file_statistics;
13mod postscript;
14mod segment;
15
16use std::sync::Arc;
17
18mod serializer;
19pub use serializer::*;
20mod deserializer;
21pub use deserializer::*;
22pub use file_statistics::FileStatistics;
23use flatbuffers::root;
24use itertools::Itertools;
25pub use segment::*;
26use vortex_array::ArrayId;
27use vortex_array::dtype::DType;
28use vortex_buffer::ByteBuffer;
29use vortex_error::VortexResult;
30use vortex_error::vortex_bail;
31use vortex_error::vortex_err;
32use vortex_flatbuffers::FlatBuffer;
33use vortex_flatbuffers::footer as fb;
34use vortex_layout::LayoutEncodingId;
35use vortex_layout::LayoutRef;
36use vortex_layout::layout_from_flatbuffer_with_options;
37use vortex_session::VortexSession;
38use vortex_session::registry::ReadContext;
39
40#[derive(Debug, Clone)]
42pub struct Footer {
43 root_layout: LayoutRef,
44 segments: Arc<[SegmentSpec]>,
45 statistics: Option<FileStatistics>,
46 array_read_ctx: ReadContext,
48 approx_byte_size: Option<usize>,
50}
51
52impl Footer {
53 pub fn new(
54 root_layout: LayoutRef,
55 segments: Arc<[SegmentSpec]>,
56 statistics: Option<FileStatistics>,
57 array_read_ctx: ReadContext,
58 ) -> Self {
59 Self {
60 root_layout,
61 segments,
62 statistics,
63 array_read_ctx,
64 approx_byte_size: None,
65 }
66 }
67
68 pub(crate) fn with_approx_byte_size(mut self, approx_byte_size: usize) -> Self {
69 self.approx_byte_size = Some(approx_byte_size);
70 self
71 }
72
73 pub(crate) fn from_flatbuffer(
75 footer_bytes: FlatBuffer,
76 layout_bytes: FlatBuffer,
77 dtype: DType,
78 statistics: Option<FileStatistics>,
79 session: &VortexSession,
80 ) -> VortexResult<Self> {
81 let approx_byte_size = footer_bytes.len() + layout_bytes.len();
82 let fb_footer = root::<fb::Footer>(&footer_bytes)?;
83
84 let layout_specs = fb_footer.layout_specs();
86 #[expect(clippy::disallowed_methods, reason = "interning a dynamic id")]
87 let layout_ids: Arc<[_]> = layout_specs
88 .iter()
89 .flat_map(|e| e.iter())
90 .map(|encoding| LayoutEncodingId::new(encoding.id()))
91 .collect();
92 let layout_read_ctx = ReadContext::new(layout_ids);
93
94 let array_specs = fb_footer.array_specs();
96 #[expect(clippy::disallowed_methods, reason = "interning a dynamic id")]
97 let array_ids: Arc<[_]> = array_specs
98 .iter()
99 .flat_map(|e| e.iter())
100 .map(|encoding| ArrayId::new(encoding.id()))
101 .collect();
102 let array_read_ctx = ReadContext::new(array_ids);
103
104 let root_layout = layout_from_flatbuffer_with_options(
105 layout_bytes,
106 &dtype,
107 &layout_read_ctx,
108 &array_read_ctx,
109 session,
110 session.allows_unknown(),
111 )?;
112
113 let segments: Arc<[SegmentSpec]> = fb_footer
114 .segment_specs()
115 .ok_or_else(|| vortex_err!("FileLayout missing segment specs"))?
116 .iter()
117 .map(SegmentSpec::try_from)
118 .try_collect()?;
119
120 if !segments.is_sorted_by_key(|segment| segment.offset) {
122 vortex_bail!("Segment offsets are not ordered");
123 }
124
125 Ok(Self {
126 root_layout,
127 segments,
128 statistics,
129 array_read_ctx,
130 approx_byte_size: Some(approx_byte_size),
131 })
132 }
133
134 pub fn layout(&self) -> &LayoutRef {
136 &self.root_layout
137 }
138
139 pub fn segment_map(&self) -> &Arc<[SegmentSpec]> {
141 &self.segments
142 }
143
144 pub fn statistics(&self) -> Option<&FileStatistics> {
146 self.statistics.as_ref()
147 }
148
149 pub fn dtype(&self) -> &DType {
151 self.root_layout.dtype()
152 }
153
154 pub fn approx_byte_size(&self) -> Option<usize> {
156 self.approx_byte_size
157 }
158
159 pub fn row_count(&self) -> u64 {
161 self.root_layout.row_count()
162 }
163
164 pub fn into_serializer(self) -> FooterSerializer {
166 FooterSerializer::new(self)
167 }
168
169 pub fn deserializer(eof_buffer: ByteBuffer, session: VortexSession) -> FooterDeserializer {
171 FooterDeserializer::new(eof_buffer, session)
172 }
173}