vortex_layout/layouts/struct_/
writer.rs1use std::sync::Arc;
15
16use async_trait::async_trait;
17use futures::StreamExt;
18use futures::TryStreamExt;
19use futures::future::try_join;
20use futures::future::try_join_all;
21use futures::pin_mut;
22use itertools::Itertools;
23use vortex_array::ArrayRef;
24use vortex_array::IntoArray;
25use vortex_array::VortexSessionExecute;
26use vortex_array::arrays::StructArray;
27use vortex_array::arrays::struct_::StructArrayExt;
28use vortex_array::dtype::DType;
29use vortex_array::dtype::FieldName;
30use vortex_array::dtype::Nullability;
31use vortex_error::VortexError;
32use vortex_error::VortexResult;
33use vortex_error::vortex_bail;
34use vortex_io::kanal_ext::KanalExt;
35use vortex_io::session::RuntimeSessionExt;
36use vortex_session::VortexSession;
37use vortex_utils::aliases::DefaultHashBuilder;
38use vortex_utils::aliases::hash_map::HashMap;
39use vortex_utils::aliases::hash_set::HashSet;
40
41use crate::LayoutRef;
42use crate::LayoutStrategy;
43use crate::LayoutWriterContext;
44use crate::layouts::struct_::StructLayout;
45use crate::segments::SegmentSinkRef;
46use crate::sequence::SendableSequentialStream;
47use crate::sequence::SequenceId;
48use crate::sequence::SequencePointer;
49use crate::sequence::SequentialStreamAdapter;
50use crate::sequence::SequentialStreamExt;
51
52#[derive(Clone)]
63pub struct StructStrategy {
64 field_writers: HashMap<FieldName, Arc<dyn LayoutStrategy>>,
67 default: Arc<dyn LayoutStrategy>,
69 validity: Arc<dyn LayoutStrategy>,
71}
72
73impl StructStrategy {
74 pub fn new(validity: Arc<dyn LayoutStrategy>, default: Arc<dyn LayoutStrategy>) -> Self {
77 Self {
78 field_writers: HashMap::default(),
79 default,
80 validity,
81 }
82 }
83
84 pub fn with_field_writer(
86 mut self,
87 name: impl Into<FieldName>,
88 writer: Arc<dyn LayoutStrategy>,
89 ) -> Self {
90 self.field_writers.insert(name.into(), writer);
91 self
92 }
93
94 pub fn with_field_writers(
96 mut self,
97 writers: impl IntoIterator<Item = (FieldName, Arc<dyn LayoutStrategy>)>,
98 ) -> Self {
99 self.field_writers.extend(writers);
100 self
101 }
102}
103
104#[async_trait]
105impl LayoutStrategy for StructStrategy {
106 async fn write_stream(
107 &self,
108 ctx: LayoutWriterContext,
109 segment_sink: SegmentSinkRef,
110 stream: SendableSequentialStream,
111 mut eof: SequencePointer,
112 session: &VortexSession,
113 ) -> VortexResult<LayoutRef> {
114 let dtype = stream.dtype().clone();
115
116 let Some(struct_dtype) = dtype.as_struct_fields_opt() else {
117 vortex_bail!("StructStrategy can only write struct-typed streams, got {dtype}");
118 };
119
120 if HashSet::<_, DefaultHashBuilder>::from_iter(struct_dtype.names().iter()).len()
122 != struct_dtype.names().len()
123 {
124 vortex_bail!("StructLayout must have unique field names");
125 }
126 let is_nullable = dtype.is_nullable();
127
128 if struct_dtype.nfields() == 0 && !is_nullable {
131 let row_count = stream
132 .try_fold(
133 0u64,
134 |acc, (_, arr)| async move { Ok(acc + arr.len() as u64) },
135 )
136 .await?;
137 return Ok(StructLayout::new(row_count, dtype, vec![]).into_layout());
138 }
139
140 let columns_session = session.clone();
142 let columns_vec_stream = stream.map(move |chunk| {
143 let (sequence_id, chunk) = chunk?;
144 let mut sequence_pointer = sequence_id.descend();
145 let mut ctx = columns_session.create_execution_ctx();
146 let struct_chunk = chunk.clone().execute::<StructArray>(&mut ctx)?;
147 let mut columns: Vec<(SequenceId, ArrayRef)> = Vec::new();
148 if is_nullable {
149 columns.push((
150 sequence_pointer.advance(),
151 chunk
152 .validity()?
153 .execute_mask(chunk.len(), &mut ctx)?
154 .into_array(),
155 ));
156 }
157
158 columns.extend(
159 struct_chunk
160 .iter_unmasked_fields()
161 .map(|field| (sequence_pointer.advance(), field.clone())),
162 );
163
164 Ok(columns)
165 });
166
167 let mut stream_count = struct_dtype.nfields();
168 if is_nullable {
169 stream_count += 1;
170 }
171
172 let (column_streams_tx, column_streams_rx): (Vec<_>, Vec<_>) =
173 (0..stream_count).map(|_| kanal::bounded_async(1)).unzip();
174
175 let handle = session.handle();
178 let fanout_fut = async move {
179 pin_mut!(columns_vec_stream);
180 while let Some(result) = columns_vec_stream.next().await {
181 match result {
182 Ok(columns) => {
183 for (tx, column) in column_streams_tx.iter().zip_eq(columns.into_iter()) {
184 if tx.send(Ok(column)).await.is_err() {
185 vortex_bail!(
186 "struct column writer finished before all chunks were sent"
187 );
188 }
189 }
190 }
191 Err(e) => {
192 let e: Arc<VortexError> = Arc::new(e);
193 for tx in column_streams_tx.iter() {
194 let _ = tx.send(Err(VortexError::from(Arc::clone(&e)))).await;
195 }
196 return Err(VortexError::from(e));
197 }
198 }
199 }
200 Ok(())
201 };
202
203 let column_dtypes: Vec<DType> = if is_nullable {
205 std::iter::once(DType::Bool(Nullability::NonNullable))
206 .chain(struct_dtype.fields())
207 .collect()
208 } else {
209 struct_dtype.fields().collect()
210 };
211
212 let column_names: Vec<FieldName> = if is_nullable {
213 std::iter::once(FieldName::from("__validity"))
214 .chain(struct_dtype.names().iter().cloned())
215 .collect()
216 } else {
217 struct_dtype.names().iter().cloned().collect()
218 };
219
220 let layout_futures: Vec<_> = column_dtypes
221 .into_iter()
222 .zip_eq(column_streams_rx)
223 .zip_eq(column_names)
224 .enumerate()
225 .map(move |(index, ((dtype, recv), name))| {
226 let column_stream =
227 SequentialStreamAdapter::new(dtype, recv.into_stream().boxed()).sendable();
228 let child_eof = eof.split_off();
229 let session = session.clone();
230 let ctx = ctx.clone();
231 let segment_sink = Arc::clone(&segment_sink);
232 handle.spawn_nested(move |h| {
233 let writer = if index == 0 && is_nullable {
236 Arc::clone(&self.validity)
237 } else {
238 self.field_writers
239 .get(&name)
240 .cloned()
241 .unwrap_or_else(|| Arc::clone(&self.default))
242 };
243 let session = session.with_handle(h);
244
245 async move {
246 writer
247 .write_stream(ctx, segment_sink, column_stream, child_eof, &session)
248 .await
249 }
250 })
251 })
252 .collect();
253
254 let (_success, column_layouts) = try_join(fanout_fut, try_join_all(layout_futures)).await?;
255 let row_count = column_layouts.first().map(|l| l.row_count()).unwrap_or(0);
258 Ok(StructLayout::new(row_count, dtype, column_layouts).into_layout())
259 }
260}