vortex_layout/layouts/flat/
writer.rs1use async_trait::async_trait;
5use futures::StreamExt;
6use vortex_array::ArrayContext;
7use vortex_array::ArrayId;
8use vortex_array::dtype::DType;
9use vortex_array::expr::stats::Precision;
10use vortex_array::expr::stats::Stat;
11use vortex_array::expr::stats::StatsProvider;
12use vortex_array::normalize::NormalizeOptions;
13use vortex_array::normalize::Operation;
14use vortex_array::scalar::Scalar;
15use vortex_array::scalar::ScalarTruncation;
16use vortex_array::scalar::lower_bound;
17use vortex_array::scalar::upper_bound;
18use vortex_array::serde::SerializeOptions;
19use vortex_array::stats::StatsSetRef;
20use vortex_buffer::BufferString;
21use vortex_buffer::ByteBuffer;
22use vortex_error::VortexExpect;
23use vortex_error::VortexResult;
24use vortex_error::vortex_bail;
25use vortex_session::VortexSession;
26use vortex_session::registry::ReadContext;
27use vortex_utils::aliases::hash_set::HashSet;
28
29use crate::IntoLayout;
30use crate::LayoutRef;
31use crate::LayoutStrategy;
32use crate::children::OwnedLayoutChildren;
33use crate::layouts::chunked::ChunkedLayout;
34use crate::layouts::flat::FlatLayout;
35use crate::layouts::flat::flat_layout_inline_array_node;
36use crate::segments::SegmentSinkRef;
37use crate::sequence::SendableSequentialStream;
38use crate::sequence::SequencePointer;
39
40#[derive(Clone)]
41pub struct FlatLayoutStrategy {
42 pub include_padding: bool,
44 pub max_variable_length_statistics_size: usize,
46 pub allowed_encodings: Option<HashSet<ArrayId>>,
49}
50
51impl Default for FlatLayoutStrategy {
52 fn default() -> Self {
53 Self {
54 include_padding: true,
55 max_variable_length_statistics_size: 64,
56 allowed_encodings: None,
57 }
58 }
59}
60
61impl FlatLayoutStrategy {
62 pub fn with_include_padding(mut self, include_padding: bool) -> Self {
64 self.include_padding = include_padding;
65 self
66 }
67
68 pub fn with_max_variable_length_statistics_size(mut self, size: usize) -> Self {
70 self.max_variable_length_statistics_size = size;
71 self
72 }
73
74 pub fn with_allow_encodings(mut self, allow_encodings: HashSet<ArrayId>) -> Self {
76 self.allowed_encodings = Some(allow_encodings);
77 self
78 }
79}
80
81fn truncate_scalar_stat<F: Fn(Scalar) -> Option<(Scalar, bool)>>(
82 statistics: StatsSetRef<'_>,
83 stat: Stat,
84 truncation: F,
85) {
86 if let Some(sv) = statistics.get(stat).into_inner() {
87 if let Some((truncated_value, truncated)) = truncation(sv) {
88 if truncated && let Some(v) = truncated_value.into_value() {
89 statistics.set(stat, Precision::Inexact(v));
90 }
91 } else {
92 statistics.clear(stat)
93 }
94 }
95}
96
97#[async_trait]
98impl LayoutStrategy for FlatLayoutStrategy {
99 async fn write_stream(
100 &self,
101 ctx: ArrayContext,
102 segment_sink: SegmentSinkRef,
103 mut stream: SendableSequentialStream,
104 _eof: SequencePointer,
105 session: &VortexSession,
106 ) -> VortexResult<LayoutRef> {
107 let ctx = ctx.clone();
108 let Some(chunk) = stream.next().await else {
109 return Ok(ChunkedLayout::new(
111 0,
112 stream.dtype().clone(),
113 OwnedLayoutChildren::layout_children(vec![]),
114 )
115 .into_layout());
116 };
117 let (sequence_id, chunk) = chunk?;
118
119 let row_count = chunk.len() as u64;
120
121 match chunk.dtype() {
122 DType::Utf8(n) => {
123 truncate_scalar_stat(chunk.statistics(), Stat::Min, |v| {
124 lower_bound(
125 BufferString::from_scalar(v)
126 .vortex_expect("utf8 scalar must be a BufferString"),
127 self.max_variable_length_statistics_size,
128 *n,
129 )
130 });
131 truncate_scalar_stat(chunk.statistics(), Stat::Max, |v| {
132 upper_bound(
133 BufferString::from_scalar(v)
134 .vortex_expect("utf8 scalar must be a BufferString"),
135 self.max_variable_length_statistics_size,
136 *n,
137 )
138 });
139 }
140 DType::Binary(n) => {
141 truncate_scalar_stat(chunk.statistics(), Stat::Min, |v| {
142 lower_bound(
143 ByteBuffer::from_scalar(v)
144 .vortex_expect("binary scalar must be a ByteBuffer"),
145 self.max_variable_length_statistics_size,
146 *n,
147 )
148 });
149 truncate_scalar_stat(chunk.statistics(), Stat::Max, |v| {
150 upper_bound(
151 ByteBuffer::from_scalar(v)
152 .vortex_expect("binary scalar must be a ByteBuffer"),
153 self.max_variable_length_statistics_size,
154 *n,
155 )
156 });
157 }
158 _ => {}
159 }
160
161 let chunk = if let Some(allowed) = &self.allowed_encodings {
162 chunk.normalize(&mut NormalizeOptions {
163 allowed,
164 operation: Operation::Error,
165 })?
166 } else {
167 chunk
168 };
169
170 let buffers = chunk.serialize(
171 &ctx,
172 session,
173 &SerializeOptions {
174 offset: 0,
175 include_padding: self.include_padding,
176 },
177 )?;
178 assert!(buffers.len() >= 2);
180 let array_node =
181 flat_layout_inline_array_node().then(|| buffers[buffers.len() - 2].clone());
182 let segment_id = segment_sink.write(sequence_id, buffers).await?;
183
184 let None = stream.next().await else {
185 vortex_bail!("flat layout received stream with more than a single chunk");
186 };
187 Ok(FlatLayout::new_with_metadata(
188 row_count,
189 stream.dtype().clone(),
190 segment_id,
191 ReadContext::new(ctx.to_ids()),
192 array_node,
193 )
194 .into_layout())
195 }
196
197 fn buffered_bytes(&self) -> u64 {
198 0
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use std::sync::Arc;
206
207 use vortex_array::ArrayContext;
208 use vortex_array::ArrayRef;
209 use vortex_array::IntoArray;
210 use vortex_array::MaskFuture;
211 use vortex_array::VortexSessionExecute;
212 use vortex_array::array_session;
213 use vortex_array::arrays::BoolArray;
214 use vortex_array::arrays::Dict;
215 use vortex_array::arrays::DictArray;
216 use vortex_array::arrays::PrimitiveArray;
217 use vortex_array::arrays::StructArray;
218 use vortex_array::arrays::struct_::StructArrayExt;
219 use vortex_array::builders::ArrayBuilder;
220 use vortex_array::builders::VarBinViewBuilder;
221 use vortex_array::dtype::DType;
222 use vortex_array::dtype::FieldName;
223 use vortex_array::dtype::FieldNames;
224 use vortex_array::dtype::Nullability;
225 use vortex_array::expr::root;
226 use vortex_array::expr::stats::Precision;
227 use vortex_array::expr::stats::Stat;
228 use vortex_array::expr::stats::StatsProviderExt;
229 use vortex_array::validity::Validity;
230 use vortex_array::vtable::VTable;
231 use vortex_buffer::BitBufferMut;
232 use vortex_buffer::buffer;
233 use vortex_error::VortexExpect;
234 use vortex_error::VortexResult;
235 use vortex_io::runtime::single::block_on;
236 use vortex_io::session::RuntimeSessionExt;
237 use vortex_mask::AllOr;
238 use vortex_mask::Mask;
239 use vortex_utils::aliases::hash_set::HashSet;
240
241 use crate::LayoutStrategy;
242 use crate::layouts::flat::writer::FlatLayoutStrategy;
243 use crate::segments::TestSegments;
244 use crate::sequence::SequenceId;
245 use crate::sequence::SequentialArrayStreamExt;
246 use crate::test::SESSION;
247
248 #[should_panic]
251 #[test]
252 fn flat_stats() {
253 block_on(|handle| async {
254 let session = SESSION.clone().with_handle(handle);
255 let ctx = ArrayContext::empty();
256 let segments = Arc::new(TestSegments::default());
257 let (ptr, eof) = SequenceId::root().split();
258 let array = PrimitiveArray::new(buffer![1, 2, 3, 4, 5], Validity::AllValid);
259 let layout = FlatLayoutStrategy::default()
260 .write_stream(
261 ctx,
262 Arc::<TestSegments>::clone(&segments),
263 array.into_array().to_array_stream().sequenced(ptr),
264 eof,
265 &session,
266 )
267 .await
268 .unwrap();
269
270 let result = layout
271 .new_reader("".into(), segments, &SESSION, &Default::default())
272 .unwrap()
273 .projection_evaluation(
274 &(0..layout.row_count()),
275 &root(),
276 MaskFuture::new_true(layout.row_count().try_into().unwrap()),
277 )
278 .unwrap()
279 .await
280 .unwrap();
281
282 assert_eq!(
283 result.statistics().get_as::<bool>(Stat::IsSorted),
284 Precision::Exact(true)
285 );
286 })
287 }
288
289 #[test]
290 fn truncates_variable_size_stats() {
291 block_on(|handle| async {
292 let session = SESSION.clone().with_handle(handle);
293 let ctx = ArrayContext::empty();
294 let segments = Arc::new(TestSegments::default());
295 let (ptr, eof) = SequenceId::root().split();
296 let mut builder =
297 VarBinViewBuilder::with_capacity(DType::Utf8(Nullability::NonNullable), 2);
298 builder.append_value("Long value to test that the statistics are actually truncated, it needs a bit of extra padding though");
299 builder.append_value("Another string that's meant to be smaller than the previous value, though still need extra padding");
300 let array = builder.finish();
301 let mut stats_ctx = session.create_execution_ctx();
302 array.statistics().set_iter(
303 array
304 .statistics()
305 .compute_all(&Stat::all().collect::<Vec<_>>(), &mut stats_ctx)
306 .vortex_expect("stats computation should succeed for test array")
307 .into_iter(),
308 );
309
310 let layout = FlatLayoutStrategy::default()
311 .write_stream(
312 ctx,
313 Arc::<TestSegments>::clone(&segments),
314 array.into_array().to_array_stream().sequenced(ptr),
315 eof,
316 &session,
317 )
318 .await
319 .unwrap();
320
321 let result = layout
322 .new_reader("".into(), segments, &SESSION, &Default::default())
323 .unwrap()
324 .projection_evaluation(
325 &(0..layout.row_count()),
326 &root(),
327 MaskFuture::new_true(layout.row_count().try_into().unwrap()),
328 )
329 .unwrap()
330 .await
331 .unwrap();
332
333 assert_eq!(
334 result.statistics().get_as::<String>(Stat::Min),
335 Precision::Inexact(
337 "Another string that's meant to be smaller than the previous valu".to_string()
339 )
340 );
341 assert_eq!(
342 result.statistics().get_as::<String>(Stat::Max),
343 Precision::Inexact(
344 "Long value to test that the statistics are actually truncated, j".to_string()
345 )
346 );
347 })
348 }
349
350 #[test]
351 fn struct_array_round_trip() {
352 block_on(|handle| async {
353 let mut ctx_exec = array_session().create_execution_ctx();
354 let session = SESSION.clone().with_handle(handle);
355 let mut validity_builder = BitBufferMut::with_capacity(2);
356 validity_builder.append(true);
357 validity_builder.append(false);
358 let validity_boolean_buffer = validity_builder.freeze();
359 let validity = Validity::Array(
360 BoolArray::new(validity_boolean_buffer.clone(), Validity::NonNullable).into_array(),
361 );
362 let array = StructArray::try_new(
363 FieldNames::from([FieldName::from("a"), FieldName::from("b")]),
364 vec![
365 buffer![1_u64, 2].into_array(),
366 buffer![3_u64, 4].into_array(),
367 ],
368 2,
369 validity,
370 )
371 .unwrap();
372
373 let ctx = ArrayContext::empty();
374
375 let (layout, segments) = {
377 let segments = Arc::new(TestSegments::default());
378 let (ptr, eof) = SequenceId::root().split();
379 let layout = FlatLayoutStrategy::default()
380 .write_stream(
381 ctx,
382 Arc::<TestSegments>::clone(&segments),
383 array.into_array().to_array_stream().sequenced(ptr),
384 eof,
385 &session,
386 )
387 .await
388 .unwrap();
389
390 (layout, segments)
391 };
392
393 let result: ArrayRef = layout
395 .new_reader("".into(), segments, &SESSION, &Default::default())
396 .unwrap()
397 .projection_evaluation(
398 &(0..layout.row_count()),
399 &root(),
400 MaskFuture::new_true(layout.row_count().try_into().unwrap()),
401 )
402 .unwrap()
403 .await
404 .unwrap();
405
406 assert_eq!(
407 result
408 .validity()
409 .unwrap()
410 .execute_mask(result.len(), &mut ctx_exec)
411 .unwrap()
412 .bit_buffer(),
413 AllOr::Some(&validity_boolean_buffer)
414 );
415 let result_struct = result
416 .clone()
417 .execute::<StructArray>(&mut ctx_exec)
418 .unwrap();
419 let field_a = result_struct
420 .unmasked_field_by_name("a")
421 .unwrap()
422 .clone()
423 .execute::<PrimitiveArray>(&mut ctx_exec)
424 .unwrap();
425 assert_eq!(field_a.as_slice::<u64>(), &[1, 2]);
426 let result_struct_b = result.execute::<StructArray>(&mut ctx_exec).unwrap();
427 let field_b = result_struct_b
428 .unmasked_field_by_name("b")
429 .unwrap()
430 .clone()
431 .execute::<PrimitiveArray>(&mut ctx_exec)
432 .unwrap();
433 assert_eq!(field_b.as_slice::<u64>(), &[3, 4]);
434 })
435 }
436
437 #[test]
438 fn flat_invalid_array_fails() -> VortexResult<()> {
439 block_on(|handle| async {
440 let session = SESSION.clone().with_handle(handle);
441 let prim: PrimitiveArray = (0..10).collect();
442 let filter = prim.filter(Mask::from_indices(10, vec![2, 3]))?;
443
444 let ctx = ArrayContext::empty();
445
446 let (layout, _segments) = {
448 let segments = Arc::new(TestSegments::default());
449 let (ptr, eof) = SequenceId::root().split();
450 let allowed = HashSet::default();
452 let layout = FlatLayoutStrategy::default()
453 .with_allow_encodings(allowed)
454 .write_stream(
455 ctx,
456 Arc::<TestSegments>::clone(&segments),
457 filter.into_array().to_array_stream().sequenced(ptr),
458 eof,
459 &session,
460 )
461 .await;
462
463 (layout, segments)
464 };
465
466 let err = layout.expect_err("expected error");
467 assert!(
468 err.to_string()
469 .contains("normalize forbids encoding (vortex.filter)"),
470 "unexpected error: {err}"
471 );
472
473 Ok(())
474 })
475 }
476
477 #[test]
478 fn flat_valid_array_writes() -> VortexResult<()> {
479 block_on(|handle| async {
480 let session = SESSION.clone().with_handle(handle);
481 let codes: PrimitiveArray = (0u32..10).collect();
482 let values: PrimitiveArray = (0..10).collect();
483 let dict = DictArray::new(codes.into_array(), values.into_array());
484
485 let ctx = ArrayContext::empty();
486
487 let (layout, _segments) = {
489 let segments = Arc::new(TestSegments::default());
490 let (ptr, eof) = SequenceId::root().split();
491 let mut allowed = HashSet::default();
493 allowed.insert(Dict.id());
494 let layout = FlatLayoutStrategy::default()
495 .with_allow_encodings(allowed)
496 .write_stream(
497 ctx,
498 Arc::<TestSegments>::clone(&segments),
499 dict.into_array().to_array_stream().sequenced(ptr),
500 eof,
501 &session,
502 )
503 .await;
504
505 (layout, segments)
506 };
507
508 assert!(layout.is_ok());
509
510 Ok(())
511 })
512 }
513}