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::LayoutStrategyEncodingValidator;
47use vortex_layout::layouts::buffered::BufferedStrategy;
48use vortex_layout::layouts::chunked::writer::ChunkedLayoutStrategy;
49use vortex_layout::layouts::collect::CollectStrategy;
50use vortex_layout::layouts::compressed::CompressingStrategy;
51use vortex_layout::layouts::compressed::CompressorPlugin;
52use vortex_layout::layouts::dict::writer::DictStrategy;
53use vortex_layout::layouts::flat::writer::FlatLayoutStrategy;
54use vortex_layout::layouts::list::writer::ListLayoutStrategy;
55use vortex_layout::layouts::repartition::RepartitionStrategy;
56use vortex_layout::layouts::repartition::RepartitionWriterOptions;
57use vortex_layout::layouts::table::TableStrategy;
58use vortex_layout::layouts::table::use_experimental_list_layout;
59use vortex_layout::layouts::zoned::writer::ZonedLayoutOptions;
60use vortex_layout::layouts::zoned::writer::ZonedStrategy;
61#[cfg(feature = "unstable_encodings")]
62use vortex_onpair::OnPair;
63use vortex_pco::Pco;
64use vortex_runend::RunEnd;
65use vortex_sequence::Sequence;
66use vortex_sparse::Sparse;
67use vortex_utils::aliases::hash_map::HashMap;
68use vortex_utils::aliases::hash_set::HashSet;
69use vortex_zigzag::ZigZag;
70#[cfg(feature = "zstd")]
71use vortex_zstd::Zstd;
72#[cfg(all(feature = "zstd", feature = "unstable_encodings"))]
73use vortex_zstd::ZstdBuffers;
74
75const ONE_MEG: u64 = 1 << 20;
76
77pub static ALLOWED_ENCODINGS: LazyLock<HashSet<ArrayId>> = LazyLock::new(|| {
82 let mut allowed = HashSet::new();
83
84 allowed.insert(Null.id());
86 allowed.insert(Bool.id());
87 allowed.insert(Primitive.id());
88 allowed.insert(Decimal.id());
89 allowed.insert(VarBin.id());
90 allowed.insert(VarBinView.id());
91 allowed.insert(List.id());
92 allowed.insert(ListView.id());
93 allowed.insert(FixedSizeList.id());
94 allowed.insert(Struct.id());
95 allowed.insert(Extension.id());
96 allowed.insert(Chunked.id());
97 allowed.insert(Constant.id());
98 allowed.insert(Masked.id());
99 allowed.insert(Dict.id());
100 allowed.insert(Variant.id());
101
102 allowed.insert(ALP.id());
104 allowed.insert(ALPRD.id());
105 allowed.insert(BitPacked.id());
106 allowed.insert(ByteBool.id());
107 allowed.insert(DateTimeParts.id());
108 allowed.insert(DecimalByteParts.id());
109 allowed.insert(Delta.id());
110 allowed.insert(FoR.id());
111 allowed.insert(FSST.id());
112 #[cfg(feature = "unstable_encodings")]
113 allowed.insert(OnPair.id());
114 allowed.insert(Pco.id());
115 allowed.insert(RLE.id());
116 allowed.insert(RunEnd.id());
117 allowed.insert(Sequence.id());
118 allowed.insert(Sparse.id());
119 allowed.insert(ZigZag.id());
120
121 if use_experimental_patches() {
124 allowed.insert(Patched.id());
125 }
126
127 #[cfg(feature = "zstd")]
128 allowed.insert(Zstd.id());
129 #[cfg(all(feature = "zstd", feature = "unstable_encodings"))]
130 allowed.insert(ZstdBuffers.id());
131
132 allowed
133});
134
135enum CompressorConfig {
137 BtrBlocks(BtrBlocksCompressorBuilder),
141 Opaque(Arc<dyn CompressorPlugin>),
143}
144
145pub struct WriteStrategyBuilder {
157 compressor: CompressorConfig,
158 row_block_size: usize,
159 field_writers: HashMap<FieldPath, Arc<dyn LayoutStrategy>>,
160 allow_encodings: Option<HashSet<ArrayId>>,
161 flat_strategy: Option<Arc<dyn LayoutStrategy>>,
162 probe_compressor: Option<Arc<dyn CompressorPlugin>>,
163 use_list_layout: bool,
167}
168
169impl Default for WriteStrategyBuilder {
170 fn default() -> Self {
173 Self {
174 compressor: CompressorConfig::BtrBlocks(BtrBlocksCompressorBuilder::default()),
175 row_block_size: 8192,
176 field_writers: HashMap::new(),
177 allow_encodings: Some(ALLOWED_ENCODINGS.clone()),
178 flat_strategy: None,
179 probe_compressor: None,
180 use_list_layout: use_experimental_list_layout(),
181 }
182 }
183}
184
185impl WriteStrategyBuilder {
186 pub fn with_row_block_size(mut self, row_block_size: usize) -> Self {
191 self.row_block_size = row_block_size;
192 self
193 }
194
195 pub fn with_list_layout(mut self) -> Self {
202 self.use_list_layout = true;
203 self
204 }
205
206 pub fn with_field_writer(
211 mut self,
212 field: impl Into<FieldPath>,
213 writer: Arc<dyn LayoutStrategy>,
214 ) -> Self {
215 self.field_writers.insert(field.into(), writer);
216 self
217 }
218
219 pub fn with_allow_encodings(mut self, allow_encodings: HashSet<ArrayId>) -> Self {
224 self.allow_encodings = Some(allow_encodings);
225 self
226 }
227
228 pub fn with_flat_strategy(mut self, flat: Arc<dyn LayoutStrategy>) -> Self {
233 self.flat_strategy = Some(flat);
234 self
235 }
236
237 pub fn with_btrblocks_builder(mut self, builder: BtrBlocksCompressorBuilder) -> Self {
242 self.compressor = CompressorConfig::BtrBlocks(builder);
243 self
244 }
245
246 pub fn with_compressor<C: CompressorPlugin>(mut self, compressor: C) -> Self {
251 self.compressor = CompressorConfig::Opaque(Arc::new(compressor));
252 self
253 }
254
255 pub fn with_probe_compressor<C: CompressorPlugin>(mut self, compressor: C) -> Self {
257 self.probe_compressor = Some(Arc::new(compressor));
258 self
259 }
260
261 pub fn build(self) -> Arc<dyn LayoutStrategy> {
264 let flat: Arc<dyn LayoutStrategy> = if let Some(flat) = self.flat_strategy {
265 flat
266 } else {
267 Arc::new(FlatLayoutStrategy::default())
268 };
269 let flat: Arc<dyn LayoutStrategy> = if let Some(allow_encodings) = self.allow_encodings {
270 Arc::new(LayoutStrategyEncodingValidator::new(flat, allow_encodings))
271 } else {
272 flat
273 };
274
275 let chunked = ChunkedLayoutStrategy::new(Arc::clone(&flat));
277 let buffered = BufferedStrategy::new(chunked, 2 * ONE_MEG); let data_compressor: Arc<dyn CompressorPlugin> = match &self.compressor {
285 CompressorConfig::BtrBlocks(builder) => Arc::new(
286 builder
287 .clone()
288 .exclude_schemes([IntDictScheme.id()])
289 .build(),
290 ),
291 CompressorConfig::Opaque(compressor) => Arc::clone(compressor),
292 };
293 let compressing = CompressingStrategy::new(buffered, data_compressor);
294
295 let coalescing = RepartitionStrategy::new(
297 compressing,
298 RepartitionWriterOptions {
299 block_size_minimum: ONE_MEG,
306 block_len_multiple: self.row_block_size,
307 block_size_target: Some(ONE_MEG),
308 canonicalize: true,
309 },
310 );
311
312 let stats_compressor: Arc<dyn CompressorPlugin> = match self.compressor {
314 CompressorConfig::BtrBlocks(builder) => Arc::new(builder.build()),
315 CompressorConfig::Opaque(compressor) => compressor,
316 };
317 let compress_then_flat = CompressingStrategy::new(flat, Arc::clone(&stats_compressor));
318
319 let probe_compressor = if let Some(probe_compressor) = self.probe_compressor {
321 probe_compressor
322 } else {
323 Arc::clone(&stats_compressor)
324 };
325 let dict = DictStrategy::new(
326 coalescing.clone(),
327 compress_then_flat.clone(),
328 coalescing,
329 Default::default(),
330 probe_compressor,
331 );
332
333 let row_block_size = NonZeroUsize::new(self.row_block_size).vortex_expect("must be non 0");
334
335 let stats = ZonedStrategy::new(
337 dict,
338 compress_then_flat.clone(),
339 ZonedLayoutOptions {
340 block_size: row_block_size,
341 ..Default::default()
342 },
343 );
344
345 let repartition = RepartitionStrategy::new(
347 stats,
348 RepartitionWriterOptions {
349 block_size_minimum: 0,
351 block_len_multiple: self.row_block_size,
353 block_size_target: None,
354 canonicalize: false,
355 },
356 );
357
358 let validity_strategy = CollectStrategy::new(compress_then_flat.clone());
360
361 let mut table_strategy =
363 TableStrategy::new(Arc::new(validity_strategy), Arc::new(repartition))
364 .with_field_writers(self.field_writers);
365
366 if self.use_list_layout {
367 table_strategy = table_strategy.with_list_layout_factory(
369 move |list_layout: ListLayoutStrategy| -> Arc<dyn LayoutStrategy> {
370 let zoned = ZonedStrategy::new(
371 list_layout,
372 compress_then_flat.clone(),
373 ZonedLayoutOptions {
374 block_size: row_block_size,
375 ..Default::default()
376 },
377 );
378 Arc::new(RepartitionStrategy::new(
379 zoned,
380 RepartitionWriterOptions {
381 block_size_minimum: 0,
382 block_len_multiple: row_block_size.get(),
383 block_size_target: None,
384 canonicalize: false,
385 },
386 ))
387 },
388 );
389 }
390
391 Arc::new(table_strategy)
392 }
393}