1use std::sync::Arc;
7use std::sync::LazyLock;
8
9use vortex_alp::ALP;
10use vortex_alp::ALPRD;
11use vortex_array::ArrayId;
12use vortex_array::VTable;
13use vortex_array::arrays::Bool;
14use vortex_array::arrays::Chunked;
15use vortex_array::arrays::Constant;
16use vortex_array::arrays::Decimal;
17use vortex_array::arrays::Dict;
18use vortex_array::arrays::Extension;
19use vortex_array::arrays::FixedSizeList;
20use vortex_array::arrays::List;
21use vortex_array::arrays::ListView;
22use vortex_array::arrays::Masked;
23use vortex_array::arrays::Null;
24use vortex_array::arrays::Patched;
25use vortex_array::arrays::Primitive;
26use vortex_array::arrays::Struct;
27use vortex_array::arrays::VarBin;
28use vortex_array::arrays::VarBinView;
29use vortex_array::arrays::patched::use_experimental_patches;
30use vortex_array::dtype::FieldPath;
31use vortex_btrblocks::BtrBlocksCompressorBuilder;
32use vortex_btrblocks::SchemeExt;
33use vortex_btrblocks::schemes::integer::IntDictScheme;
34use vortex_bytebool::ByteBool;
35use vortex_datetime_parts::DateTimeParts;
36use vortex_decimal_byte_parts::DecimalByteParts;
37use vortex_fastlanes::BitPacked;
38use vortex_fastlanes::Delta;
39use vortex_fastlanes::FoR;
40use vortex_fastlanes::RLE;
41use vortex_fsst::FSST;
42use vortex_layout::LayoutStrategy;
43use vortex_layout::layouts::buffered::BufferedStrategy;
44use vortex_layout::layouts::chunked::writer::ChunkedLayoutStrategy;
45use vortex_layout::layouts::collect::CollectStrategy;
46use vortex_layout::layouts::compressed::CompressingStrategy;
47use vortex_layout::layouts::compressed::CompressorPlugin;
48use vortex_layout::layouts::dict::writer::DictStrategy;
49use vortex_layout::layouts::flat::writer::FlatLayoutStrategy;
50use vortex_layout::layouts::repartition::RepartitionStrategy;
51use vortex_layout::layouts::repartition::RepartitionWriterOptions;
52use vortex_layout::layouts::table::TableStrategy;
53use vortex_layout::layouts::zoned::writer::ZonedLayoutOptions;
54use vortex_layout::layouts::zoned::writer::ZonedStrategy;
55use vortex_pco::Pco;
56use vortex_runend::RunEnd;
57use vortex_sequence::Sequence;
58use vortex_sparse::Sparse;
59use vortex_utils::aliases::hash_map::HashMap;
60use vortex_utils::aliases::hash_set::HashSet;
61use vortex_zigzag::ZigZag;
62#[cfg(feature = "zstd")]
63use vortex_zstd::Zstd;
64#[cfg(all(feature = "zstd", feature = "unstable_encodings"))]
65use vortex_zstd::ZstdBuffers;
66
67const ONE_MEG: u64 = 1 << 20;
68
69pub static ALLOWED_ENCODINGS: LazyLock<HashSet<ArrayId>> = LazyLock::new(|| {
74 let mut allowed = HashSet::new();
75
76 allowed.insert(Null.id());
78 allowed.insert(Bool.id());
79 allowed.insert(Primitive.id());
80 allowed.insert(Decimal.id());
81 allowed.insert(VarBin.id());
82 allowed.insert(VarBinView.id());
83 allowed.insert(List.id());
84 allowed.insert(ListView.id());
85 allowed.insert(FixedSizeList.id());
86 allowed.insert(Struct.id());
87 allowed.insert(Extension.id());
88 allowed.insert(Chunked.id());
89 allowed.insert(Constant.id());
90 allowed.insert(Masked.id());
91 allowed.insert(Dict.id());
92
93 allowed.insert(ALP.id());
95 allowed.insert(ALPRD.id());
96 allowed.insert(BitPacked.id());
97 allowed.insert(ByteBool.id());
98 allowed.insert(DateTimeParts.id());
99 allowed.insert(DecimalByteParts.id());
100 allowed.insert(Delta.id());
101 allowed.insert(FoR.id());
102 allowed.insert(FSST.id());
103 allowed.insert(Pco.id());
104 allowed.insert(RLE.id());
105 allowed.insert(RunEnd.id());
106 allowed.insert(Sequence.id());
107 allowed.insert(Sparse.id());
108 allowed.insert(ZigZag.id());
109
110 if use_experimental_patches() {
113 allowed.insert(Patched.id());
114 }
115
116 #[cfg(feature = "zstd")]
117 allowed.insert(Zstd.id());
118 #[cfg(all(feature = "zstd", feature = "unstable_encodings"))]
119 allowed.insert(ZstdBuffers.id());
120
121 allowed
122});
123
124enum CompressorConfig {
126 BtrBlocks(BtrBlocksCompressorBuilder),
130 Opaque(Arc<dyn CompressorPlugin>),
132}
133
134pub struct WriteStrategyBuilder {
141 compressor: CompressorConfig,
142 row_block_size: usize,
143 field_writers: HashMap<FieldPath, Arc<dyn LayoutStrategy>>,
144 allow_encodings: Option<HashSet<ArrayId>>,
145 flat_strategy: Option<Arc<dyn LayoutStrategy>>,
146}
147
148impl Default for WriteStrategyBuilder {
149 fn default() -> Self {
152 Self {
153 compressor: CompressorConfig::BtrBlocks(BtrBlocksCompressorBuilder::default()),
154 row_block_size: 8192,
155 field_writers: HashMap::new(),
156 allow_encodings: Some(ALLOWED_ENCODINGS.clone()),
157 flat_strategy: None,
158 }
159 }
160}
161
162impl WriteStrategyBuilder {
163 pub fn with_row_block_size(mut self, row_block_size: usize) -> Self {
165 self.row_block_size = row_block_size;
166 self
167 }
168
169 pub fn with_field_writer(
172 mut self,
173 field: impl Into<FieldPath>,
174 writer: Arc<dyn LayoutStrategy>,
175 ) -> Self {
176 self.field_writers.insert(field.into(), writer);
177 self
178 }
179
180 pub fn with_allow_encodings(mut self, allow_encodings: HashSet<ArrayId>) -> Self {
182 self.allow_encodings = Some(allow_encodings);
183 self
184 }
185
186 pub fn with_flat_strategy(mut self, flat: Arc<dyn LayoutStrategy>) -> Self {
191 self.flat_strategy = Some(flat);
192 self
193 }
194
195 pub fn with_btrblocks_builder(mut self, builder: BtrBlocksCompressorBuilder) -> Self {
200 self.compressor = CompressorConfig::BtrBlocks(builder);
201 self
202 }
203
204 pub fn with_compressor<C: CompressorPlugin>(mut self, compressor: C) -> Self {
208 self.compressor = CompressorConfig::Opaque(Arc::new(compressor));
209 self
210 }
211
212 pub fn build(self) -> Arc<dyn LayoutStrategy> {
215 let flat: Arc<dyn LayoutStrategy> = if let Some(flat) = self.flat_strategy {
216 flat
217 } else if let Some(allow_encodings) = self.allow_encodings {
218 Arc::new(FlatLayoutStrategy::default().with_allow_encodings(allow_encodings))
219 } else {
220 Arc::new(FlatLayoutStrategy::default())
221 };
222
223 let chunked = ChunkedLayoutStrategy::new(Arc::clone(&flat));
225 let buffered = BufferedStrategy::new(chunked, 2 * ONE_MEG); let data_compressor: Arc<dyn CompressorPlugin> = match &self.compressor {
233 CompressorConfig::BtrBlocks(builder) => Arc::new(
234 builder
235 .clone()
236 .exclude_schemes([IntDictScheme.id()])
237 .build(),
238 ),
239 CompressorConfig::Opaque(compressor) => Arc::clone(compressor),
240 };
241 let compressing = CompressingStrategy::new(buffered, data_compressor);
242
243 let coalescing = RepartitionStrategy::new(
245 compressing,
246 RepartitionWriterOptions {
247 block_size_minimum: ONE_MEG,
254 block_len_multiple: self.row_block_size,
255 block_size_target: Some(ONE_MEG),
256 canonicalize: true,
257 },
258 );
259
260 let stats_compressor: Arc<dyn CompressorPlugin> = match self.compressor {
262 CompressorConfig::BtrBlocks(builder) => Arc::new(builder.build()),
263 CompressorConfig::Opaque(compressor) => compressor,
264 };
265 let compress_then_flat = CompressingStrategy::new(flat, stats_compressor);
266
267 let dict = DictStrategy::new(
269 coalescing.clone(),
270 compress_then_flat.clone(),
271 coalescing,
272 Default::default(),
273 );
274
275 let stats = ZonedStrategy::new(
277 dict,
278 compress_then_flat.clone(),
279 ZonedLayoutOptions {
280 block_size: self.row_block_size,
281 ..Default::default()
282 },
283 );
284
285 let repartition = RepartitionStrategy::new(
287 stats,
288 RepartitionWriterOptions {
289 block_size_minimum: 0,
291 block_len_multiple: self.row_block_size,
293 block_size_target: None,
294 canonicalize: false,
295 },
296 );
297
298 let validity_strategy = CollectStrategy::new(compress_then_flat);
300
301 let table_strategy = TableStrategy::new(Arc::new(validity_strategy), Arc::new(repartition))
303 .with_field_writers(self.field_writers);
304
305 Arc::new(table_strategy)
306 }
307}