vortex_file/footer/
serializer.rs1use std::sync::Arc;
5
6use vortex_buffer::ByteBuffer;
7use vortex_error::VortexExpect;
8use vortex_error::VortexResult;
9use vortex_error::vortex_err;
10use vortex_flatbuffers::FlatBuffer;
11use vortex_flatbuffers::FlatBufferRoot;
12use vortex_flatbuffers::WriteFlatBuffer;
13use vortex_flatbuffers::WriteFlatBufferExt;
14use vortex_layout::LayoutContext;
15use vortex_session::registry::ReadContext;
16use vortex_utils::aliases::hash_map::HashMap;
17
18use crate::EOF_SIZE;
19use crate::Footer;
20use crate::MAGIC_BYTES;
21use crate::MAX_POSTSCRIPT_SIZE;
22use crate::VERSION;
23use crate::footer::SegmentSpec;
24use crate::footer::file_layout::FooterFlatBufferWriter;
25use crate::footer::postscript::Postscript;
26use crate::footer::postscript::PostscriptMetadata;
27use crate::footer::postscript::PostscriptSegment;
28
29pub struct FooterSerializer {
31 footer: Footer,
32 metadata: HashMap<String, ByteBuffer>,
33 exclude_dtype: bool,
34 offset: u64,
35 layout_ctx: LayoutContext,
36}
37
38impl FooterSerializer {
39 pub(super) fn new(footer: Footer) -> Self {
40 Self {
41 footer,
42 metadata: HashMap::default(),
43 exclude_dtype: false,
44 offset: 0,
45 layout_ctx: LayoutContext::default(),
46 }
47 }
48
49 pub fn with_layout_context(mut self, layout_ctx: LayoutContext) -> Self {
55 self.layout_ctx = layout_ctx;
56 self
57 }
58
59 pub fn with_offset(mut self, offset: u64) -> Self {
64 self.offset = offset;
65 self
66 }
67
68 pub fn exclude_dtype(mut self) -> Self {
71 self.exclude_dtype = true;
72 self
73 }
74
75 pub fn with_exclude_dtype(mut self, exclude_dtype: bool) -> Self {
78 self.exclude_dtype = exclude_dtype;
79 self
80 }
81
82 pub(crate) fn with_metadata_segments(mut self, metadata: HashMap<String, ByteBuffer>) -> Self {
83 self.metadata = metadata;
84 self
85 }
86
87 pub fn serialize(self) -> VortexResult<Vec<ByteBuffer>> {
90 self.serialize_with_metadata()
91 .map(|(buffers, _metadata, _approx_byte_size)| buffers)
92 }
93
94 pub(crate) fn serialize_with_metadata(
95 mut self,
96 ) -> VortexResult<(Vec<ByteBuffer>, super::MetadataSegments, usize)> {
97 let mut buffers = vec![];
98
99 let (metadata_segments, metadata_locators) = if self.metadata.is_empty() {
100 let locators = self
101 .footer
102 .metadata_segments()
103 .map(|(key, segment)| (key.to_string(), *segment))
104 .collect::<Vec<_>>();
105 let segments = locators
106 .iter()
107 .map(|(key, segment)| PostscriptMetadata {
108 key: key.clone(),
109 segment: PostscriptSegment::from(segment),
110 })
111 .collect();
112 (segments, locators)
113 } else {
114 let mut metadata = self.metadata.into_iter().collect::<Vec<_>>();
115 metadata.sort_unstable_by(|(left, _), (right, _)| left.cmp(right));
117
118 let mut segments = Vec::with_capacity(metadata.len());
119 let mut locators = Vec::with_capacity(metadata.len());
120 for (key, metadata) in metadata {
121 let (metadata_buffers, segment) = write_buffer(&mut self.offset, metadata)?;
122 buffers.extend(metadata_buffers);
123 locators.push((key.clone(), SegmentSpec::from(&segment)));
124 segments.push(PostscriptMetadata { key, segment });
125 }
126 (segments, locators)
127 };
128 let metadata_storage_bytes = buffers.iter().map(ByteBuffer::len).sum::<usize>();
129
130 let dtype_segment = if self.exclude_dtype {
131 None
132 } else {
133 let (buffer, dtype_segment) = write_flatbuffer(&mut self.offset, self.footer.dtype())?;
134 buffers.push(buffer);
135 Some(dtype_segment)
136 };
137
138 let layout_ctx = self.layout_ctx.clone();
141
142 let (buffer, layout_segment) = write_flatbuffer(
143 &mut self.offset,
144 &self.footer.layout().flatbuffer_writer(&layout_ctx),
145 )?;
146 buffers.push(buffer);
147
148 let statistics_segment = match self.footer.statistics() {
149 None => None,
150 Some(stats) if stats.stats_sets().is_empty() => None,
151 Some(stats) => {
152 let (buffer, stats_segment) = write_flatbuffer(&mut self.offset, stats)?;
153 buffers.push(buffer);
154 Some(stats_segment)
155 }
156 };
157
158 let (buffer, footer_segment) = write_flatbuffer(
159 &mut self.offset,
160 &FooterFlatBufferWriter {
161 ctx: self.footer.array_read_ctx.clone(),
162 layout_ctx: ReadContext::new(layout_ctx.to_ids()),
163 segment_specs: Arc::clone(&self.footer.segments),
164 },
165 )?;
166 buffers.push(buffer);
167
168 let postscript = Postscript {
170 dtype: dtype_segment,
171 layout: layout_segment,
172 statistics: statistics_segment,
173 footer: footer_segment,
174 metadata: metadata_segments,
175 };
176 let postscript_buffer = postscript.write_flatbuffer_bytes()?;
177 if postscript_buffer.len() > MAX_POSTSCRIPT_SIZE as usize {
178 Err(vortex_err!(
179 "Postscript is too large ({} bytes); max postscript size is {}",
180 postscript_buffer.len(),
181 MAX_POSTSCRIPT_SIZE
182 ))?;
183 }
184
185 let postscript_len = u16::try_from(postscript_buffer.len())
186 .vortex_expect("Postscript already verified to fit into u16");
187 buffers.push(postscript_buffer.into_inner());
188
189 let mut eof = [0u8; EOF_SIZE];
191 eof[0..2].copy_from_slice(&VERSION.to_le_bytes());
192 eof[2..4].copy_from_slice(&postscript_len.to_le_bytes());
193 eof[4..8].copy_from_slice(&MAGIC_BYTES);
194 buffers.push(ByteBuffer::copy_from(eof));
195
196 let approx_byte_size =
197 buffers.iter().map(ByteBuffer::len).sum::<usize>() - metadata_storage_bytes;
198 Ok((buffers, metadata_locators.into(), approx_byte_size))
199 }
200}
201
202impl From<&PostscriptSegment> for SegmentSpec {
203 fn from(value: &PostscriptSegment) -> Self {
204 Self {
205 offset: value.offset,
206 length: value.length,
207 alignment: value.alignment,
208 }
209 }
210}
211
212impl From<&SegmentSpec> for PostscriptSegment {
213 fn from(value: &SegmentSpec) -> Self {
214 Self {
215 offset: value.offset,
216 length: value.length,
217 alignment: value.alignment,
218 }
219 }
220}
221
222fn write_flatbuffer<F: FlatBufferRoot + WriteFlatBuffer>(
223 offset: &mut u64,
224 flatbuffer: &F,
225) -> VortexResult<(ByteBuffer, PostscriptSegment)> {
226 let buffer = flatbuffer.write_flatbuffer_bytes()?;
227 let length = u32::try_from(buffer.len())
228 .map_err(|_| vortex_err!("flatbuffer length exceeds maximum u32"))?;
229
230 let segment = PostscriptSegment {
231 offset: *offset,
232 length,
233 alignment: FlatBuffer::alignment(),
234 };
235
236 *offset += u64::from(length);
237
238 Ok((buffer.into_inner(), segment))
239}
240
241fn write_buffer(
242 offset: &mut u64,
243 buffer: ByteBuffer,
244) -> VortexResult<(Vec<ByteBuffer>, PostscriptSegment)> {
245 let length = u32::try_from(buffer.len())
246 .map_err(|_| vortex_err!("metadata segment length exceeds maximum u32"))?;
247 let alignment = buffer.alignment();
248
249 let padding = offset.next_multiple_of(alignment.as_usize() as u64) - *offset;
250 let segment_offset = *offset + padding;
251
252 let segment = PostscriptSegment {
253 offset: segment_offset,
254 length,
255 alignment,
256 };
257
258 *offset += padding + u64::from(length);
259
260 let mut buffers = Vec::with_capacity(if padding == 0 { 1 } else { 2 });
261 if padding > 0 {
262 buffers.push(ByteBuffer::zeroed(padding as usize));
263 }
264 buffers.push(buffer);
265
266 Ok((buffers, segment))
267}