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