1use std::env;
5use std::sync::LazyLock;
6
7use flatbuffers::FlatBufferBuilder;
8use flatbuffers::VerifierOptions;
9use flatbuffers::WIPOffset;
10use flatbuffers::root_with_opts;
11use vortex_array::dtype::DType;
12use vortex_error::VortexResult;
13use vortex_error::vortex_err;
14use vortex_flatbuffers::FlatBuffer;
15use vortex_flatbuffers::FlatBufferRoot;
16use vortex_flatbuffers::WriteFlatBuffer;
17use vortex_flatbuffers::layout;
18use vortex_session::VortexSession;
19use vortex_session::registry::ReadContext;
20
21use crate::Layout;
22use crate::LayoutBuildContext;
23use crate::LayoutContext;
24use crate::LayoutRef;
25use crate::children::ViewedLayoutChildren;
26use crate::layouts::foreign::new_foreign_layout;
27use crate::segments::SegmentId;
28use crate::session::LayoutSessionExt;
29
30static LAYOUT_VERIFIER: LazyLock<VerifierOptions> = LazyLock::new(|| {
31 VerifierOptions {
32 max_tables: env::var("VORTEX_MAX_LAYOUT_TABLES")
34 .ok()
35 .and_then(|lmt| lmt.parse::<usize>().ok())
36 .unwrap_or(1000000),
37 max_depth: env::var("VORTEX_MAX_LAYOUT_DEPTH")
38 .ok()
39 .and_then(|lmt| lmt.parse::<usize>().ok())
40 .unwrap_or(64),
41 max_apparent_size: 1 << 31,
43 ignore_missing_null_terminator: false,
44 }
45});
46
47pub fn layout_from_flatbuffer(
49 flatbuffer: FlatBuffer,
50 dtype: &DType,
51 layout_ctx: &ReadContext,
52 ctx: &ReadContext,
53 session: &VortexSession,
54) -> VortexResult<LayoutRef> {
55 layout_from_flatbuffer_with_options(flatbuffer, dtype, layout_ctx, ctx, session, false)
56}
57
58pub fn layout_from_flatbuffer_with_options(
60 flatbuffer: FlatBuffer,
61 dtype: &DType,
62 layout_ctx: &ReadContext,
63 ctx: &ReadContext,
64 session: &VortexSession,
65 allow_unknown: bool,
66) -> VortexResult<LayoutRef> {
67 let layout_session = session.layouts();
68 let layouts = layout_session.registry();
69 let fb_layout = root_with_opts::<layout::Layout>(&LAYOUT_VERIFIER, &flatbuffer)?;
70 let encoding_id = layout_ctx
71 .resolve(fb_layout.encoding())
72 .ok_or_else(|| vortex_err!("Invalid encoding ID: {}", fb_layout.encoding()))?;
73 let encoding = layouts.find(&encoding_id);
74
75 if encoding.is_none() && allow_unknown {
76 return foreign_layout_from_fb(fb_layout, dtype, layout_ctx);
77 }
78 let encoding =
79 encoding.ok_or_else(|| vortex_err!("Invalid encoding ID: {}", fb_layout.encoding()))?;
80
81 let viewed_children = unsafe {
83 ViewedLayoutChildren::new_unchecked(
84 flatbuffer.clone(),
85 fb_layout._tab.loc(),
86 ctx.clone(),
87 layout_ctx.clone(),
88 layouts.clone(),
89 allow_unknown,
90 session.clone(),
91 )
92 };
93
94 let build_ctx = LayoutBuildContext {
95 session,
96 array_read_ctx: ctx,
97 };
98 let layout = encoding.build(
99 dtype,
100 fb_layout.row_count(),
101 fb_layout
102 .metadata()
103 .map(|m| m.bytes())
104 .unwrap_or_else(|| &[]),
105 fb_layout
106 .segments()
107 .unwrap_or_default()
108 .iter()
109 .map(SegmentId::from)
110 .collect(),
111 &viewed_children,
112 &build_ctx,
113 )?;
114
115 Ok(layout)
116}
117
118fn foreign_layout_from_fb(
119 fb_layout: layout::Layout<'_>,
120 dtype: &DType,
121 layout_ctx: &ReadContext,
122) -> VortexResult<LayoutRef> {
123 let encoding_id = layout_ctx
124 .resolve(fb_layout.encoding())
125 .ok_or_else(|| vortex_err!("Invalid encoding ID: {}", fb_layout.encoding()))?;
126
127 let children = fb_layout
128 .children()
129 .unwrap_or_default()
130 .iter()
131 .map(|child| foreign_layout_from_fb(child, dtype, layout_ctx))
132 .collect::<VortexResult<Vec<_>>>()?;
133
134 Ok(new_foreign_layout(
135 encoding_id,
136 dtype.clone(),
137 fb_layout.row_count(),
138 fb_layout
139 .metadata()
140 .map(|m| m.bytes().to_vec())
141 .unwrap_or_default(),
142 fb_layout
143 .segments()
144 .unwrap_or_default()
145 .iter()
146 .map(SegmentId::from)
147 .collect(),
148 children,
149 ))
150}
151
152impl dyn Layout + '_ {
153 pub fn flatbuffer_writer<'a>(
155 &'a self,
156 ctx: &'a LayoutContext,
157 ) -> impl WriteFlatBuffer<Target<'a> = layout::Layout<'a>> + FlatBufferRoot + 'a {
158 LayoutFlatBufferWriter { layout: self, ctx }
159 }
160}
161
162struct LayoutFlatBufferWriter<'a> {
164 layout: &'a dyn Layout,
165 ctx: &'a LayoutContext,
166}
167
168impl FlatBufferRoot for LayoutFlatBufferWriter<'_> {}
169
170impl WriteFlatBuffer for LayoutFlatBufferWriter<'_> {
171 type Target<'fb> = layout::Layout<'fb>;
172
173 fn write_flatbuffer<'fb>(
174 &self,
175 fbb: &mut FlatBufferBuilder<'fb>,
176 ) -> VortexResult<WIPOffset<Self::Target<'fb>>> {
177 let child_layouts = self.layout.children()?;
179 let children = child_layouts
180 .iter()
181 .map(|layout| {
182 LayoutFlatBufferWriter {
183 layout: layout.as_ref(),
184 ctx: self.ctx,
185 }
186 .write_flatbuffer(fbb)
187 })
188 .collect::<VortexResult<Vec<_>>>()?;
189 let children = (!children.is_empty()).then(|| fbb.create_vector(&children));
190
191 let metadata = self.layout.metadata();
193 let metadata = (!metadata.is_empty()).then(|| fbb.create_vector(&metadata));
194
195 let segments = self
196 .layout
197 .segment_ids()
198 .into_iter()
199 .map(|s| *s)
200 .collect::<Vec<_>>();
201 let segments = (!segments.is_empty()).then(|| fbb.create_vector(&segments));
202
203 let encoding = self.ctx.intern(&self.layout.encoding_id()).ok_or_else(|| {
205 vortex_err!(
206 "Failed to intern layout encoding ID: {}",
207 self.layout.encoding_id()
208 )
209 })?;
210
211 Ok(layout::Layout::create(
212 fbb,
213 &layout::LayoutArgs {
214 encoding,
215 row_count: self.layout.row_count(),
216 metadata,
217 children,
218 segments,
219 },
220 ))
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use flatbuffers::FlatBufferBuilder;
227 use vortex_array::dtype::DType;
228 use vortex_array::dtype::Nullability;
229 use vortex_flatbuffers::layout as fbl;
230 use vortex_session::registry::ReadContext;
231
232 use super::layout_from_flatbuffer_with_options;
233 use crate::LayoutEncodingId;
234 use crate::session::LayoutSession;
235
236 #[expect(clippy::disallowed_methods, reason = "test-only id")]
237 #[test]
238 fn unknown_layout_encoding_allow_unknown() {
239 let mut fbb = FlatBufferBuilder::new();
240
241 let child_metadata = fbb.create_vector(&[9u8]);
242 let child = fbl::Layout::create(
243 &mut fbb,
244 &fbl::LayoutArgs {
245 encoding: 1,
246 row_count: 3,
247 metadata: Some(child_metadata),
248 children: None,
249 segments: None,
250 },
251 );
252
253 let children = fbb.create_vector(&[child]);
254 let metadata = fbb.create_vector(&[1u8, 2, 3]);
255 let segments = fbb.create_vector(&[7u32]);
256 let root = fbl::Layout::create(
257 &mut fbb,
258 &fbl::LayoutArgs {
259 encoding: 0,
260 row_count: 10,
261 metadata: Some(metadata),
262 children: Some(children),
263 segments: Some(segments),
264 },
265 );
266 fbb.finish_minimal(root);
267 let (buf, start) = fbb.collapse();
268 let layout_buffer = vortex_flatbuffers::FlatBuffer::align_from(
269 vortex_buffer::ByteBuffer::from(buf).slice(start..),
270 );
271
272 let layout_ctx = ReadContext::new([
273 LayoutEncodingId::new("vortex.test.foreign_layout"),
274 LayoutEncodingId::new("vortex.test.foreign_child_layout"),
275 ]);
276 let array_ctx = ReadContext::new([]);
277 let session = vortex_array::array_session().with::<LayoutSession>();
278
279 let layout = layout_from_flatbuffer_with_options(
280 layout_buffer,
281 &DType::Variant(Nullability::Nullable),
282 &layout_ctx,
283 &array_ctx,
284 &session,
285 true,
286 )
287 .unwrap();
288
289 assert_eq!(layout.encoding_id().as_ref(), "vortex.test.foreign_layout");
290 assert_eq!(layout.row_count(), 10);
291 assert_eq!(layout.metadata(), vec![1, 2, 3]);
292 assert_eq!(layout.segment_ids().len(), 1);
293 assert_eq!(*layout.segment_ids()[0], 7);
294 assert_eq!(layout.nchildren(), 1);
295
296 let child = layout.child(0).unwrap();
297 assert_eq!(
298 child.encoding_id().as_ref(),
299 "vortex.test.foreign_child_layout"
300 );
301 assert_eq!(child.metadata(), vec![9]);
302 }
303}