Skip to main content

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]