1use std::num::NonZeroUsize;
7use std::sync::Arc;
8use std::sync::LazyLock;
9
10use vortex_alp::ALP;
11use vortex_alp::ALPRD;
12use vortex_array::ArrayId;
13use vortex_array::VTable;
14use vortex_array::arrays::Bool;
15use vortex_array::arrays::Chunked;
16use vortex_array::arrays::Constant;
17use vortex_array::arrays::Decimal;
18use vortex_array::arrays::Dict;
19use vortex_array::arrays::Extension;
20use vortex_array::arrays::FixedSizeList;
21use vortex_array::arrays::List;
22use vortex_array::arrays::ListView;
23use vortex_array::arrays::Masked;
24use vortex_array::arrays::Null;
25use vortex_array::arrays::Patched;
26use vortex_array::arrays::Primitive;
27use vortex_array::arrays::Struct;
28use vortex_array::arrays::VarBin;
29use vortex_array::arrays::VarBinView;
30use vortex_array::arrays::Variant;
31use vortex_array::arrays::patched::use_experimental_patches;
32use vortex_array::dtype::FieldPath;
33use vortex_btrblocks::BtrBlocksCompressorBuilder;
34use vortex_btrblocks::SchemeExt;
35use vortex_btrblocks::schemes::integer::IntDictScheme;
36use vortex_bytebool::ByteBool;
37use vortex_datetime_parts::DateTimeParts;
38use vortex_decimal_byte_parts::DecimalByteParts;
39use vortex_error::VortexExpect;
40use vortex_fastlanes::BitPacked;
41use vortex_fastlanes::Delta;
42use vortex_fastlanes::FoR;
43use vortex_fastlanes::RLE;
44use vortex_fsst::FSST;
45use vortex_layout::LayoutStrategy;
46use vortex_layout::layouts::buffered::BufferedStrategy;
47use vortex_layout::layouts::chunked::writer::ChunkedLayoutStrategy;
48use vortex_layout::layouts::collect::CollectStrategy;
49use vortex_layout::layouts::compressed::CompressingStrategy;
50use vortex_layout::layouts::compressed::CompressorPlugin;
51use vortex_layout::layouts::dict::writer::DictStrategy;
52use vortex_layout::layouts::flat::writer::FlatLayoutStrategy;
53use vortex_layout::layouts::list::writer::ListLayoutStrategy;
54use vortex_layout::layouts::repartition::RepartitionStrategy;
55use vortex_layout::layouts::repartition::RepartitionWriterOptions;
56use vortex_layout::layouts::table::TableStrategy;
57use vortex_layout::layouts::table::use_experimental_list_layout;
58use vortex_layout::layouts::zoned::writer::ZonedLayoutOptions;
59use vortex_layout::layouts::zoned::writer::ZonedStrategy;
60#[cfg(feature = "unstable_encodings")]
61use vortex_onpair::OnPair;
62use vortex_pco::Pco;
63use vortex_runend::RunEnd;
64use vortex_sequence::Sequence;
65use vortex_sparse::Sparse;
66use vortex_utils::aliases::hash_map::HashMap;
67use vortex_utils::aliases::hash_set::HashSet;
68use vortex_zigzag::ZigZag;
69#[cfg(feature = "zstd")]
70use vortex_zstd::Zstd;
71#[cfg(all(feature = "zstd", feature = "unstable_encodings"))]
72use vortex_zstd::ZstdBuffers;
73
74const ONE_MEG: u64 = 1 << 20;
75
76pub static ALLOWED_ENCODINGS: LazyLock<HashSet<ArrayId>> = LazyLock::new(|| {
81 let mut allowed = HashSet::new();
82
83 allowed.insert(Null.id());
85 allowed.insert(Bool.id());
86 allowed.insert(Primitive.id());
87 allowed.insert(Decimal.id());
88 allowed.insert(VarBin.id());
89 allowed.insert(VarBinView.id());
90 allowed.insert(List.id());
91 allowed.insert(ListView.id());
92 allowed.insert(FixedSizeList.id());
93 allowed.insert(Struct.id());
94 allowed.insert(Extension.id());
95 allowed.insert(Chunked.id());
96 allowed.insert(Constant.id());
97 allowed.insert(Masked.id());
98 allowed.insert(Dict.id());
99 allowed.insert(Variant.id());
100
101 allowed.insert(ALP.id());
103 allowed.insert(ALPRD.id());
104 allowed.insert(BitPacked.id());
105 allowed.insert(ByteBool.id());
106 allowed.insert(DateTimeParts.id());
107 allowed.insert(DecimalByteParts.id());
108 allowed.insert(Delta.id());
109 allowed.insert(FoR.id());
110 allowed.insert(FSST.id());
111 #[cfg(feature = "unstable_encodings")]
112 allowed.insert(OnPair.id());
113 allowed.insert(Pco.id());
114 allowed.insert(RLE.id());
115 allowed.insert(RunEnd.id());
116 allowed.insert(Sequence.id());
117 allowed.insert(Sparse.id());
118 allowed.insert(ZigZag.id());
119
120 if use_experimental_patches() {
123 allowed.insert(Patched.id());
124 }
125
126 #[cfg(feature = "zstd")]
127 allowed.insert(Zstd.id());
128 #[cfg(all(feature = "zstd", feature = "unstable_encodings"))]
129 allowed.insert(ZstdBuffers.id());
130
131 allowed
132});
133
134enum CompressorConfig {
136 BtrBlocks(BtrBlocksCompressorBuilder),
140 Opaque(Arc<dyn CompressorPlugin>),
142}
143
144pub struct WriteStrategyBuilder {
156 compressor: CompressorConfig,
157 row_block_size: usize,
158 field_writers: HashMap<FieldPath, Arc<dyn LayoutStrategy>>,
159 allow_encodings: Option<HashSet<ArrayId>>,
160 flat_strategy: Option<Arc<dyn LayoutStrategy>>,
161 probe_compressor: Option<Arc<dyn CompressorPlugin>>,
162 use_list_layout: bool,
166}
167
168impl Default for WriteStrategyBuilder {
169 fn default() -> Self {
172 Self {
173 compressor: CompressorConfig::BtrBlocks(BtrBlocksCompressorBuilder::default()),
174 row_block_size: 8192,
175 field_writers: HashMap::new(),
176 allow_encodings: Some(ALLOWED_ENCODINGS.clone()),
177 flat_strategy: None,
178 probe_compressor: None,
179 use_list_layout: use_experimental_list_layout(),
180 }
181 }
182}
183
184impl WriteStrategyBuilder {
185 pub fn with_row_block_size(mut self, row_block_size: usize) -> Self {
190 self.row_block_size = row_block_size;
191 self
192 }
193
194 pub fn with_list_layout(mut self) -> Self {
201 self.use_list_layout = true;
202 self
203 }
204
205 pub fn with_field_writer(
210 mut self,
211 field: impl Into<FieldPath>,
212 writer: Arc<dyn LayoutStrategy>,
213 ) -> Self {
214 self.field_writers.insert(field.into(), writer);
215 self
216 }
217
218 pub fn with_allow_encodings(mut self, allow_encodings: HashSet<ArrayId>) -> Self {
223 self.allow_encodings = Some(allow_encodings);
224 self
225 }
226
227 pub fn with_flat_strategy(mut self, flat: Arc<dyn LayoutStrategy>) -> Self {
232 self.flat_strategy = Some(flat);
233 self
234 }
235
236 pub fn with_btrblocks_builder(mut self, builder: BtrBlocksCompressorBuilder) -> Self {
241 self.compressor = CompressorConfig::BtrBlocks(builder);
242 self
243 }
244
245 pub fn with_compressor<C: CompressorPlugin>(mut self, compressor: C) -> Self {
250 self.compressor = CompressorConfig::Opaque(Arc::new(compressor));
251 self
252 }
253
254 pub fn with_probe_compressor<C: CompressorPlugin>(mut self, compressor: C) -> Self {
256 self.probe_compressor = Some(Arc::new(compressor));
257 self
258 }
259
260 pub fn build(self) -> Arc<dyn LayoutStrategy> {
263 let flat: Arc<dyn LayoutStrategy> = if let Some(flat) = self.flat_strategy {
264 flat
265 } else if let Some(allow_encodings) = self.allow_encodings {
266 Arc::new(FlatLayoutStrategy::default().with_allow_encodings(allow_encodings))
267 } else {
268 Arc::new(FlatLayoutStrategy::default())
269 };
270
271 let chunked = ChunkedLayoutStrategy::new(Arc::clone(&flat));
273 let buffered = BufferedStrategy::new(chunked, 2 * ONE_MEG); let data_compressor: Arc<dyn CompressorPlugin> = match &self.compressor {
281 CompressorConfig::BtrBlocks(builder) => Arc::new(
282 builder
283 .clone()
284 .exclude_schemes([IntDictScheme.id()])
285 .build(),
286 ),
287 CompressorConfig::Opaque(compressor) => Arc::clone(compressor),
288 };
289 let compressing = CompressingStrategy::new(buffered, data_compressor);
290
291 let coalescing = RepartitionStrategy::new(
293 compressing,
294 RepartitionWriterOptions {
295 block_size_minimum: ONE_MEG,
302 block_len_multiple: self.row_block_size,
303 block_size_target: Some(ONE_MEG),
304 canonicalize: true,
305 },
306 );
307
308 let stats_compressor: Arc<dyn CompressorPlugin> = match self.compressor {
310 CompressorConfig::BtrBlocks(builder) => Arc::new(builder.build()),
311 CompressorConfig::Opaque(compressor) => compressor,
312 };
313 let compress_then_flat = CompressingStrategy::new(flat, Arc::clone(&stats_compressor));
314
315 let probe_compressor = if let Some(probe_compressor) = self.probe_compressor {
317 probe_compressor
318 } else {
319 Arc::clone(&stats_compressor)
320 };
321 let dict = DictStrategy::new(
322 coalescing.clone(),
323 compress_then_flat.clone(),
324 coalescing,
325 Default::default(),
326 probe_compressor,
327 );
328
329 let row_block_size = NonZeroUsize::new(self.row_block_size).vortex_expect("must be non 0");
330
331 let stats = ZonedStrategy::new(
333 dict,
334 compress_then_flat.clone(),
335 ZonedLayoutOptions {
336 block_size: row_block_size,
337 ..Default::default()
338 },
339 );
340
341 let repartition = RepartitionStrategy::new(
343 stats,
344 RepartitionWriterOptions {
345 block_size_minimum: 0,
347 block_len_multiple: self.row_block_size,
349 block_size_target: None,
350 canonicalize: false,
351 },
352 );
353
354 let validity_strategy = CollectStrategy::new(compress_then_flat.clone());
356
357 let mut table_strategy =
359 TableStrategy::new(Arc::new(validity_strategy), Arc::new(repartition))
360 .with_field_writers(self.field_writers);
361
362 if self.use_list_layout {
363 table_strategy = table_strategy.with_list_layout_factory(
365 move |list_layout: ListLayoutStrategy| -> Arc<dyn LayoutStrategy> {
366 let zoned = ZonedStrategy::new(
367 list_layout,
368 compress_then_flat.clone(),
369 ZonedLayoutOptions {
370 block_size: row_block_size,
371 ..Default::default()
372 },
373 );
374 Arc::new(RepartitionStrategy::new(
375 zoned,
376 RepartitionWriterOptions {
377 block_size_minimum: 0,
378 block_len_multiple: row_block_size.get(),
379 block_size_target: None,
380 canonicalize: false,
381 },
382 ))
383 },
384 );
385 }
386
387 Arc::new(table_strategy)
388 }
389}