vortex_layout/strategy.rs
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use std::sync::Arc;
5
6use async_trait::async_trait;
7use futures::StreamExt;
8use vortex_array::ArrayContext;
9use vortex_array::ArrayId;
10use vortex_array::normalize::NormalizeOptions;
11use vortex_array::normalize::Operation;
12use vortex_error::VortexResult;
13use vortex_session::VortexSession;
14use vortex_utils::aliases::hash_set::HashSet;
15
16use crate::LayoutRef;
17use crate::segments::SegmentSinkRef;
18use crate::sequence::SendableSequentialStream;
19use crate::sequence::SequencePointer;
20use crate::sequence::SequentialStreamAdapter;
21use crate::sequence::SequentialStreamExt;
22
23// [layout writer]
24/// Writes an ordered array stream into a layout tree and segment sink.
25///
26/// Layout strategies are writer-side extension points. Strategies may repartition, buffer,
27/// collect columns, compute statistics, compress arrays, or delegate to child strategies before
28/// finally emitting segments. They must preserve the logical row order represented by the
29/// [`SequencePointer`]s in the input stream.
30#[async_trait]
31pub trait LayoutStrategy: 'static + Send + Sync {
32 /// Asynchronously process an ordered stream of array chunks, emitting them into a sink and
33 /// returning the [`Layout`][crate::Layout] instance that can be parsed to retrieve the data
34 /// from rest.
35 ///
36 /// This trait uses the `#[async_trait]` attribute to denote that trait objects of this type
37 /// can be `Box`ed or `Arc`ed and shared around. Commonly, these strategies are composed to
38 /// form a operator of operations, each of which modifies the chunk stream in some way before
39 /// passing the data on to a downstream writer.
40 ///
41 /// # Sequencing and EOF
42 ///
43 /// The `stream` parameter is a stream of ordered array chunks, each of which is associated
44 /// with a sequence pointer that indicates its position in the overall array. By passing
45 /// around these pointers (essentially vector clocks), the writer can support concurrent
46 /// and parallel processing while maintaining a deterministic order of data in the file.
47 ///
48 /// The `eof` parameter is a guaranteed to be greater than all sequence pointers in the stream.
49 ///
50 /// Because child strategies can write to the end-of-file pointer, it is very important that
51 /// **all strategies must await all children concurrently**. Otherwise it is possible to
52 /// deadlock if one child is waiting to write to EOF while your strategy is preventing the
53 /// stream from progressing to completion.
54 ///
55 /// # Blocking operations
56 ///
57 /// This is an async trait method, which will return a `BoxFuture` that you can await from
58 /// any runtime. Implementations should avoid directly performing blocking work within the
59 /// `write_stream`, and should instead spawn it onto an appropriate runtime or threadpool
60 /// dedicated to such work.
61 ///
62 /// Such operations are common, and include things like compression and parsing large blobs
63 /// of data, or serializing very large messages to flatbuffers.
64 async fn write_stream(
65 &self,
66 ctx: ArrayContext,
67 segment_sink: SegmentSinkRef,
68 stream: SendableSequentialStream,
69 eof: SequencePointer,
70 session: &VortexSession,
71 ) -> VortexResult<LayoutRef>;
72
73 /// Returns the number of bytes currently buffered by this strategy and any child strategies.
74 ///
75 /// This method allows tracking of data that has been processed by the strategy but not yet
76 /// written to the underlying sink, providing more accurate estimates of final file size
77 /// during write operations.
78 fn buffered_bytes(&self) -> u64 {
79 0
80 }
81}
82
83/// A layout strategy wrapper that rejects arrays containing encodings outside an allow-list.
84///
85/// Canonical encodings are always permitted. Every chunk is recursively validated before it is
86/// passed to the wrapped strategy.
87#[derive(Clone)]
88pub struct LayoutStrategyEncodingValidator {
89 child: Arc<dyn LayoutStrategy>,
90 allowed_encodings: Arc<HashSet<ArrayId>>,
91}
92
93impl LayoutStrategyEncodingValidator {
94 /// Creates a validator around `child` using the supplied encoding allow-list.
95 pub fn new<S: LayoutStrategy>(child: S, allowed_encodings: HashSet<ArrayId>) -> Self {
96 Self {
97 child: Arc::new(child),
98 allowed_encodings: Arc::new(allowed_encodings),
99 }
100 }
101}
102
103#[async_trait]
104impl LayoutStrategy for LayoutStrategyEncodingValidator {
105 async fn write_stream(
106 &self,
107 ctx: ArrayContext,
108 segment_sink: SegmentSinkRef,
109 stream: SendableSequentialStream,
110 eof: SequencePointer,
111 session: &VortexSession,
112 ) -> VortexResult<LayoutRef> {
113 let dtype = stream.dtype().clone();
114 let allowed_encodings = Arc::clone(&self.allowed_encodings);
115 let stream = stream.map(move |chunk| {
116 let (sequence_id, chunk) = chunk?;
117 let chunk = chunk.normalize(&mut NormalizeOptions {
118 allowed: &allowed_encodings,
119 operation: Operation::Error,
120 })?;
121 Ok((sequence_id, chunk))
122 });
123
124 self.child
125 .write_stream(
126 ctx,
127 segment_sink,
128 SequentialStreamAdapter::new(dtype, stream).sendable(),
129 eof,
130 session,
131 )
132 .await
133 }
134
135 fn buffered_bytes(&self) -> u64 {
136 self.child.buffered_bytes()
137 }
138}
139
140#[async_trait]
141impl LayoutStrategy for Arc<dyn LayoutStrategy> {
142 async fn write_stream(
143 &self,
144 ctx: ArrayContext,
145 segment_sink: SegmentSinkRef,
146 stream: SendableSequentialStream,
147 eof: SequencePointer,
148 session: &VortexSession,
149 ) -> VortexResult<LayoutRef> {
150 (**self)
151 .write_stream(ctx, segment_sink, stream, eof, session)
152 .await
153 }
154
155 fn buffered_bytes(&self) -> u64 {
156 (**self).buffered_bytes()
157 }
158}
159// [layout writer]