1mod field_sizes;
12mod file_layout;
13mod file_statistics;
14mod postscript;
15mod segment;
16
17use std::sync::Arc;
18
19mod serializer;
20pub use serializer::*;
21mod deserializer;
22pub use deserializer::*;
23pub use field_sizes::CompressedFieldSizes;
24pub use file_statistics::FileStatistics;
25use flatbuffers::root;
26use itertools::Itertools;
27pub use segment::*;
28use vortex_array::ArrayId;
29use vortex_array::dtype::DType;
30use vortex_array::flatbuffers::FlatBuffer;
31use vortex_buffer::ByteBuffer;
32use vortex_error::VortexResult;
33use vortex_error::vortex_bail;
34use vortex_error::vortex_err;
35use vortex_layout::LayoutEncodingId;
36use vortex_layout::LayoutRef;
37use vortex_layout::layout_from_flatbuffer_with_options;
38use vortex_session::VortexSession;
39use vortex_session::registry::ReadContext;
40
41use crate::flatbuffers::footer as fb;
42
43pub(crate) const MAX_METADATA_SEGMENTS: usize = 16;
46
47pub(crate) const MAX_METADATA_KEY_BYTES: usize = 64;
53
54pub(crate) type MetadataSegments = Arc<[(String, SegmentSpec)]>;
56
57#[derive(Debug, Clone)]
59pub struct Footer {
60 root_layout: LayoutRef,
61 segments: Arc<[SegmentSpec]>,
62 statistics: Option<FileStatistics>,
63 metadata: Arc<[(String, SegmentSpec)]>,
64 segments_with_metadata: Arc<[SegmentSpec]>,
67 segments_end: Option<u64>,
72 array_read_ctx: ReadContext,
74 approx_byte_size: Option<usize>,
76}
77
78impl Footer {
79 pub fn new(
80 root_layout: LayoutRef,
81 segments: Arc<[SegmentSpec]>,
82 statistics: Option<FileStatistics>,
83 array_read_ctx: ReadContext,
84 ) -> Self {
85 Self {
86 root_layout,
87 segments_with_metadata: Arc::clone(&segments),
88 segments_end: segments_end(&segments),
89 segments,
90 statistics,
91 metadata: Arc::from([]),
92 array_read_ctx,
93 approx_byte_size: None,
94 }
95 }
96
97 pub(crate) fn with_approx_byte_size(mut self, approx_byte_size: usize) -> Self {
98 self.approx_byte_size = Some(approx_byte_size);
99 self
100 }
101
102 pub(crate) fn from_flatbuffer(
104 footer_bytes: &[u8],
105 layout_bytes: FlatBuffer,
106 dtype: DType,
107 statistics: Option<FileStatistics>,
108 metadata: Arc<[(String, SegmentSpec)]>,
109 session: &VortexSession,
110 ) -> VortexResult<Self> {
111 let metadata_bytes: usize = metadata
112 .iter()
113 .map(|(key, _segment)| key.len() + size_of::<SegmentSpec>())
114 .sum();
115 let approx_byte_size = footer_bytes.len() + layout_bytes.len() + metadata_bytes;
116 let fb_footer = root::<fb::Footer>(footer_bytes)?;
117
118 let layout_specs = fb_footer.layout_specs();
120 #[expect(clippy::disallowed_methods, reason = "interning a dynamic id")]
121 let layout_ids: Arc<[_]> = layout_specs
122 .iter()
123 .flat_map(|e| e.iter())
124 .map(|encoding| LayoutEncodingId::new(encoding.id()))
125 .collect();
126 let layout_read_ctx = ReadContext::new(layout_ids);
127
128 let array_specs = fb_footer.array_specs();
130 #[expect(clippy::disallowed_methods, reason = "interning a dynamic id")]
131 let array_ids: Arc<[_]> = array_specs
132 .iter()
133 .flat_map(|e| e.iter())
134 .map(|encoding| ArrayId::new(encoding.id()))
135 .collect();
136 let array_read_ctx = ReadContext::new(array_ids);
137
138 let root_layout = layout_from_flatbuffer_with_options(
139 layout_bytes,
140 &dtype,
141 &layout_read_ctx,
142 &array_read_ctx,
143 session,
144 session.allows_unknown(),
145 )?;
146
147 let segments: Arc<[SegmentSpec]> = fb_footer
148 .segment_specs()
149 .ok_or_else(|| vortex_err!("FileLayout missing segment specs"))?
150 .iter()
151 .map(SegmentSpec::try_from)
152 .try_collect()?;
153
154 if !segments.is_sorted_by_key(|segment| segment.offset) {
156 vortex_bail!("Segment offsets are not ordered");
157 }
158
159 Ok(Self {
160 root_layout,
161 segments_with_metadata: segments_with_metadata(&segments, &metadata),
162 segments_end: segments_end(&segments),
163 segments,
164 statistics,
165 metadata,
166 array_read_ctx,
167 approx_byte_size: Some(approx_byte_size),
168 })
169 }
170
171 pub fn layout(&self) -> &LayoutRef {
173 &self.root_layout
174 }
175
176 pub fn segment_map(&self) -> &Arc<[SegmentSpec]> {
178 &self.segments
179 }
180
181 pub fn statistics(&self) -> Option<&FileStatistics> {
183 self.statistics.as_ref()
184 }
185
186 pub fn metadata_segments(&self) -> impl Iterator<Item = (&str, &SegmentSpec)> {
188 self.metadata
189 .iter()
190 .map(|(key, segment)| (key.as_str(), segment))
191 }
192
193 pub fn metadata_segment(&self, key: &str) -> Option<&SegmentSpec> {
195 self.metadata
196 .iter()
197 .find_map(|(candidate, segment)| (candidate == key).then_some(segment))
198 }
199
200 pub(crate) fn with_metadata_segments(mut self, metadata: Arc<[(String, SegmentSpec)]>) -> Self {
201 self.segments_with_metadata = segments_with_metadata(&self.segments, &metadata);
202 self.metadata = metadata;
203 self
204 }
205
206 pub(crate) fn segment_specs_with_metadata(&self) -> Arc<[SegmentSpec]> {
207 Arc::clone(&self.segments_with_metadata)
208 }
209
210 pub fn compressed_field_sizes(&self) -> VortexResult<CompressedFieldSizes> {
216 CompressedFieldSizes::try_new(&self.root_layout, &self.segments)
217 }
218
219 pub fn dtype(&self) -> &DType {
221 self.root_layout.dtype()
222 }
223
224 pub fn approx_byte_size(&self) -> Option<usize> {
226 self.approx_byte_size
227 }
228
229 pub fn row_count(&self) -> u64 {
231 self.root_layout.row_count()
232 }
233
234 pub(crate) fn validate_file_size(&self, file_size: u64) -> VortexResult<()> {
236 if self.segments_end.is_some_and(|end| end <= file_size) {
237 return Ok(());
238 }
239 validate_segments_within_file(&self.segments, file_size)
241 }
242
243 pub fn into_serializer(self) -> FooterSerializer {
245 FooterSerializer::new(self)
246 }
247
248 pub fn deserializer(eof_buffer: ByteBuffer, session: VortexSession) -> FooterDeserializer {
250 FooterDeserializer::new(eof_buffer, session)
251 }
252}
253
254fn segments_with_metadata(
255 segments: &Arc<[SegmentSpec]>,
256 metadata: &[(String, SegmentSpec)],
257) -> Arc<[SegmentSpec]> {
258 if metadata.is_empty() {
259 return Arc::clone(segments);
260 }
261 segments
262 .iter()
263 .copied()
264 .chain(metadata.iter().map(|(_, segment)| *segment))
265 .collect()
266}
267
268fn segments_end(segments: &[SegmentSpec]) -> Option<u64> {
269 segments.iter().try_fold(0u64, |end, segment| {
270 Some(end.max(segment.offset.checked_add(u64::from(segment.length))?))
271 })
272}
273
274fn validate_segments_within_file(segments: &[SegmentSpec], file_size: u64) -> VortexResult<()> {
280 for segment in segments {
281 let within_file = segment
282 .offset
283 .checked_add(segment.length as u64)
284 .is_some_and(|end| end <= file_size);
285 if !within_file {
286 vortex_bail!(
287 "Segment at offset {} with length {} extends past the end of the \
288 {file_size}-byte file",
289 segment.offset,
290 segment.length,
291 );
292 }
293 }
294 Ok(())
295}
296
297#[cfg(test)]
298mod tests {
299 use vortex_buffer::Alignment;
300
301 use super::*;
302
303 fn segment(offset: u64, length: u32) -> SegmentSpec {
304 SegmentSpec {
305 offset,
306 length,
307 alignment: Alignment::none(),
308 }
309 }
310
311 #[test]
312 fn accepts_segments_within_file() -> VortexResult<()> {
313 validate_segments_within_file(&[segment(0, 100), segment(100, 50)], 150)?;
314 Ok(())
315 }
316
317 #[test]
318 fn rejects_segment_extending_past_end_of_file() {
319 let err =
320 validate_segments_within_file(&[segment(0, 100), segment(100, 51)], 150).unwrap_err();
321 assert!(err.to_string().contains("past the end"), "{err}");
322 }
323
324 #[test]
325 fn rejects_segment_offset_length_overflow() {
326 let err = validate_segments_within_file(&[segment(u64::MAX, 1)], u64::MAX).unwrap_err();
327 assert!(err.to_string().contains("past the end"), "{err}");
328 }
329
330 #[test]
331 fn segments_end_covers_overlapping_segments() {
332 assert_eq!(
336 segments_end(&[segment(0, 300), segment(100, 50)]),
337 Some(300)
338 );
339 assert_eq!(segments_end(&[]), Some(0));
340 assert_eq!(segments_end(&[segment(0, 1), segment(u64::MAX, 1)]), None);
341 }
342
343 #[test]
344 fn segments_with_metadata_appends_metadata_segments() {
345 let segments: Arc<[SegmentSpec]> = Arc::from([segment(0, 10), segment(10, 20)]);
346
347 let without = segments_with_metadata(&segments, &[]);
348 assert!(Arc::ptr_eq(&without, &segments));
349
350 let with = segments_with_metadata(&segments, &[("key".to_string(), segment(30, 5))]);
351 let spans: Vec<_> = with.iter().map(|s| (s.offset, s.length)).collect();
352 assert_eq!(spans, [(0, 10), (10, 20), (30, 5)]);
353 }
354}