1use std::sync::Arc;
9
10use async_trait::async_trait;
11use futures::StreamExt;
12use futures::TryStreamExt;
13use futures::future::try_join;
14use futures::future::try_join_all;
15use futures::pin_mut;
16use itertools::Itertools;
17use vortex_array::ArrayContext;
18use vortex_array::ArrayRef;
19use vortex_array::IntoArray;
20use vortex_array::VortexSessionExecute;
21use vortex_array::arrays::StructArray;
22use vortex_array::arrays::struct_::StructArrayExt;
23use vortex_array::dtype::DType;
24use vortex_array::dtype::Field;
25use vortex_array::dtype::FieldName;
26use vortex_array::dtype::FieldPath;
27use vortex_array::dtype::Nullability;
28use vortex_error::VortexError;
29use vortex_error::VortexResult;
30use vortex_error::vortex_bail;
31use vortex_io::kanal_ext::KanalExt;
32use vortex_io::session::RuntimeSessionExt;
33use vortex_session::VortexSession;
34use vortex_utils::aliases::DefaultHashBuilder;
35use vortex_utils::aliases::hash_map::HashMap;
36use vortex_utils::aliases::hash_set::HashSet;
37
38use crate::IntoLayout;
39use crate::LayoutRef;
40use crate::LayoutStrategy;
41use crate::layouts::struct_::StructLayout;
42use crate::segments::SegmentSinkRef;
43use crate::sequence::SendableSequentialStream;
44use crate::sequence::SequenceId;
45use crate::sequence::SequencePointer;
46use crate::sequence::SequentialStreamAdapter;
47use crate::sequence::SequentialStreamExt;
48
49pub struct TableStrategy {
52 leaf_writers: HashMap<FieldPath, Arc<dyn LayoutStrategy>>,
54 validity: Arc<dyn LayoutStrategy>,
56 fallback: Arc<dyn LayoutStrategy>,
58}
59
60impl TableStrategy {
61 pub fn new(validity: Arc<dyn LayoutStrategy>, fallback: Arc<dyn LayoutStrategy>) -> Self {
79 Self {
80 leaf_writers: Default::default(),
81 validity,
82 fallback,
83 }
84 }
85
86 pub fn with_field_writer(
115 mut self,
116 field_path: impl Into<FieldPath>,
117 writer: Arc<dyn LayoutStrategy>,
118 ) -> Self {
119 self.leaf_writers
120 .insert(self.validate_path(field_path.into()), writer);
121 self
122 }
123
124 pub fn with_field_writers(
128 mut self,
129 writers: impl IntoIterator<Item = (FieldPath, Arc<dyn LayoutStrategy>)>,
130 ) -> Self {
131 for (field_path, strategy) in writers {
132 self.leaf_writers
133 .insert(self.validate_path(field_path), strategy);
134 }
135 self
136 }
137
138 pub fn with_default_strategy(mut self, default: Arc<dyn LayoutStrategy>) -> Self {
140 self.fallback = default;
141 self
142 }
143
144 pub fn with_validity_strategy(mut self, validity: Arc<dyn LayoutStrategy>) -> Self {
146 self.validity = validity;
147 self
148 }
149}
150
151impl TableStrategy {
152 fn descend(&self, field: &Field) -> Self {
154 let mut new_writers = HashMap::with_capacity(self.leaf_writers.len());
157
158 for (field_path, strategy) in &self.leaf_writers {
159 if field_path.starts_with_field(field)
160 && let Some(subpath) = field_path.clone().step_into()
161 {
162 new_writers.insert(subpath, Arc::clone(strategy));
163 }
164 }
165
166 Self {
167 leaf_writers: new_writers,
168 validity: Arc::clone(&self.validity),
169 fallback: Arc::clone(&self.fallback),
170 }
171 }
172
173 fn validate_path(&self, path: FieldPath) -> FieldPath {
174 assert!(
175 !path.is_root(),
176 "Do not set override as a root strategy, instead set the default strategy"
177 );
178
179 for field_path in self.leaf_writers.keys() {
182 assert!(
183 !path.overlap(field_path),
184 "Override for field_path {path} conflicts with existing override for {field_path}"
185 );
186 }
187
188 path
189 }
190}
191
192#[async_trait]
194impl LayoutStrategy for TableStrategy {
195 async fn write_stream(
196 &self,
197 ctx: ArrayContext,
198 segment_sink: SegmentSinkRef,
199 stream: SendableSequentialStream,
200 mut eof: SequencePointer,
201 session: &VortexSession,
202 ) -> VortexResult<LayoutRef> {
203 let dtype = stream.dtype().clone();
204
205 if !dtype.is_struct() {
207 return self
208 .fallback
209 .write_stream(ctx, segment_sink, stream, eof, session)
210 .await;
211 }
212
213 let struct_dtype = dtype.as_struct_fields();
214
215 if HashSet::<_, DefaultHashBuilder>::from_iter(struct_dtype.names().iter()).len()
217 != struct_dtype.names().len()
218 {
219 vortex_bail!("StructLayout must have unique field names");
220 }
221 let is_nullable = dtype.is_nullable();
222
223 if struct_dtype.nfields() == 0 && !is_nullable {
226 let row_count = stream
227 .try_fold(
228 0u64,
229 |acc, (_, arr)| async move { Ok(acc + arr.len() as u64) },
230 )
231 .await?;
232 return Ok(StructLayout::new(row_count, dtype, vec![]).into_layout());
233 }
234
235 let columns_session = session.clone();
237 let columns_vec_stream = stream.map(move |chunk| {
238 let (sequence_id, chunk) = chunk?;
239 let mut sequence_pointer = sequence_id.descend();
240 let mut ctx = columns_session.create_execution_ctx();
241 let struct_chunk = chunk.clone().execute::<StructArray>(&mut ctx)?;
242 let mut columns: Vec<(SequenceId, ArrayRef)> = Vec::new();
243 if is_nullable {
244 columns.push((
245 sequence_pointer.advance(),
246 chunk
247 .validity()?
248 .execute_mask(chunk.len(), &mut ctx)?
249 .into_array(),
250 ));
251 }
252
253 columns.extend(
254 struct_chunk
255 .iter_unmasked_fields()
256 .map(|field| (sequence_pointer.advance(), field.clone())),
257 );
258
259 Ok(columns)
260 });
261
262 let mut stream_count = struct_dtype.nfields();
263 if is_nullable {
264 stream_count += 1;
265 }
266
267 let (column_streams_tx, column_streams_rx): (Vec<_>, Vec<_>) =
268 (0..stream_count).map(|_| kanal::bounded_async(1)).unzip();
269
270 let handle = session.handle();
273 let fanout_fut = async move {
274 pin_mut!(columns_vec_stream);
275 while let Some(result) = columns_vec_stream.next().await {
276 match result {
277 Ok(columns) => {
278 for (tx, column) in column_streams_tx.iter().zip_eq(columns.into_iter()) {
279 if tx.send(Ok(column)).await.is_err() {
280 vortex_bail!(
281 "table column writer finished before all chunks were sent"
282 );
283 }
284 }
285 }
286 Err(e) => {
287 let e: Arc<VortexError> = Arc::new(e);
288 for tx in column_streams_tx.iter() {
289 let _ = tx.send(Err(VortexError::from(Arc::clone(&e)))).await;
290 }
291 return Err(VortexError::from(e));
292 }
293 }
294 }
295 Ok(())
296 };
297
298 let column_dtypes: Vec<DType> = if is_nullable {
300 std::iter::once(DType::Bool(Nullability::NonNullable))
301 .chain(struct_dtype.fields())
302 .collect()
303 } else {
304 struct_dtype.fields().collect()
305 };
306
307 let column_names: Vec<FieldName> = if is_nullable {
308 std::iter::once(FieldName::from("__validity"))
309 .chain(struct_dtype.names().iter().cloned())
310 .collect()
311 } else {
312 struct_dtype.names().iter().cloned().collect()
313 };
314
315 let layout_futures: Vec<_> = column_dtypes
316 .into_iter()
317 .zip_eq(column_streams_rx)
318 .zip_eq(column_names)
319 .enumerate()
320 .map(move |(index, ((dtype, recv), name))| {
321 let column_stream =
322 SequentialStreamAdapter::new(dtype.clone(), recv.into_stream().boxed())
323 .sendable();
324 let child_eof = eof.split_off();
325 let field = Field::Name(name.clone());
326 let session = session.clone();
327 let ctx = ctx.clone();
328 let segment_sink = Arc::clone(&segment_sink);
329 handle.spawn_nested(move |h| {
330 let validity = Arc::clone(&self.validity);
331 let writer = self
333 .leaf_writers
334 .get(&FieldPath::from_name(name))
335 .cloned()
336 .unwrap_or_else(|| {
337 if dtype.is_struct() {
338 Arc::new(self.descend(&field))
340 } else {
341 Arc::clone(&self.fallback)
343 }
344 });
345 let session = session.with_handle(h);
346
347 async move {
348 if index == 0 && is_nullable {
352 validity
353 .write_stream(ctx, segment_sink, column_stream, child_eof, &session)
354 .await
355 } else {
356 writer
358 .write_stream(ctx, segment_sink, column_stream, child_eof, &session)
359 .await
360 }
361 }
362 })
363 })
364 .collect();
365
366 let (_success, column_layouts) = try_join(fanout_fut, try_join_all(layout_futures)).await?;
367 let row_count = column_layouts.first().map(|l| l.row_count()).unwrap_or(0);
370 Ok(StructLayout::new(row_count, dtype, column_layouts).into_layout())
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use std::sync::Arc;
377 use std::task::Poll;
378
379 use vortex_array::ArrayContext;
380 use vortex_array::ArrayRef;
381 use vortex_array::dtype::DType;
382 use vortex_array::dtype::FieldPath;
383 use vortex_array::dtype::Nullability;
384 use vortex_array::dtype::PType;
385 use vortex_array::dtype::StructFields;
386 use vortex_array::field_path;
387 use vortex_error::VortexResult;
388 use vortex_io::runtime::single::block_on;
389 use vortex_io::session::RuntimeSessionExt;
390
391 use crate::LayoutStrategy;
392 use crate::layouts::flat::writer::FlatLayoutStrategy;
393 use crate::layouts::table::TableStrategy;
394 use crate::segments::TestSegments;
395 use crate::sequence::SequenceId;
396 use crate::sequence::SequentialStreamAdapter;
397 use crate::sequence::SequentialStreamExt;
398 use crate::test::SESSION;
399
400 #[test]
401 #[should_panic(
402 expected = "Override for field_path $a.$b conflicts with existing override for $a.$b.$c"
403 )]
404 fn test_overlapping_paths_fail() {
405 let flat = Arc::new(FlatLayoutStrategy::default());
406
407 let path = TableStrategy::new(
409 Arc::<FlatLayoutStrategy>::clone(&flat),
410 Arc::<FlatLayoutStrategy>::clone(&flat),
411 )
412 .with_field_writer(field_path!(a.b.c), Arc::<FlatLayoutStrategy>::clone(&flat));
413
414 let _path = path.with_field_writer(field_path!(a.b), flat);
416 }
417
418 #[test]
419 #[should_panic(
420 expected = "Do not set override as a root strategy, instead set the default strategy"
421 )]
422 fn test_root_override() {
423 let flat = Arc::new(FlatLayoutStrategy::default());
424 let _strategy = TableStrategy::new(
425 Arc::<FlatLayoutStrategy>::clone(&flat),
426 Arc::<FlatLayoutStrategy>::clone(&flat),
427 )
428 .with_field_writer(FieldPath::root(), flat);
429 }
430
431 #[test]
432 #[should_panic(expected = "panic while transposing table stream")]
433 fn table_fanout_panic_propagates() {
434 let ctx = ArrayContext::empty();
435 let segments = Arc::new(TestSegments::default());
436 let (_, eof) = SequenceId::root().split();
437 let dtype = DType::Struct(
438 StructFields::from_iter([(
439 "a",
440 DType::Primitive(PType::I32, Nullability::NonNullable),
441 )]),
442 Nullability::NonNullable,
443 );
444 let stream =
445 futures::stream::poll_fn(|_| -> Poll<Option<VortexResult<(SequenceId, ArrayRef)>>> {
446 panic!("panic while transposing table stream");
447 });
448 let strategy = TableStrategy::new(
449 Arc::new(FlatLayoutStrategy::default()),
450 Arc::new(FlatLayoutStrategy::default()),
451 );
452
453 block_on(|handle| async move {
454 let session = SESSION.clone().with_handle(handle);
455 strategy
456 .write_stream(
457 ctx,
458 segments,
459 SequentialStreamAdapter::new(dtype, stream).sendable(),
460 eof,
461 &session,
462 )
463 .await
464 .unwrap();
465 });
466 }
467}