parquet/file/properties.rs
1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Configuration via [`WriterProperties`] and [`ReaderProperties`]
19use crate::basic::{Compression, Encoding};
20use crate::compression::{CodecOptions, CodecOptionsBuilder};
21#[cfg(feature = "encryption")]
22use crate::encryption::encrypt::FileEncryptionProperties;
23use crate::file::metadata::{KeyValue, SortingColumn};
24use crate::schema::types::ColumnPath;
25use std::str::FromStr;
26use std::{collections::HashMap, sync::Arc};
27
28/// Default value for [`WriterProperties::data_page_size_limit`]
29pub const DEFAULT_PAGE_SIZE: usize = 1024 * 1024;
30/// Default value for [`WriterProperties::write_batch_size`]
31pub const DEFAULT_WRITE_BATCH_SIZE: usize = 1024;
32/// Default value for [`WriterProperties::writer_version`]
33pub const DEFAULT_WRITER_VERSION: WriterVersion = WriterVersion::PARQUET_1_0;
34/// Default value for [`WriterProperties::compression`]
35pub const DEFAULT_COMPRESSION: Compression = Compression::UNCOMPRESSED;
36/// Default value for [`WriterProperties::dictionary_enabled`]
37pub const DEFAULT_DICTIONARY_ENABLED: bool = true;
38/// Default value for [`WriterProperties::dictionary_page_size_limit`]
39pub const DEFAULT_DICTIONARY_PAGE_SIZE_LIMIT: usize = DEFAULT_PAGE_SIZE;
40/// Default value for [`WriterProperties::data_page_row_count_limit`]
41pub const DEFAULT_DATA_PAGE_ROW_COUNT_LIMIT: usize = 20_000;
42/// Default value for [`WriterProperties::statistics_enabled`]
43pub const DEFAULT_STATISTICS_ENABLED: EnabledStatistics = EnabledStatistics::Page;
44/// Default value for [`WriterProperties::write_page_header_statistics`]
45pub const DEFAULT_WRITE_PAGE_HEADER_STATISTICS: bool = false;
46/// Default value for [`WriterProperties::max_row_group_row_count`]
47pub const DEFAULT_MAX_ROW_GROUP_ROW_COUNT: usize = 1024 * 1024;
48/// Default value for [`WriterProperties::bloom_filter_position`]
49pub const DEFAULT_BLOOM_FILTER_POSITION: BloomFilterPosition = BloomFilterPosition::AfterRowGroup;
50/// Default value for [`WriterProperties::created_by`]
51pub const DEFAULT_CREATED_BY: &str = concat!("parquet-rs version ", env!("CARGO_PKG_VERSION"));
52/// Default value for [`WriterProperties::column_index_truncate_length`]
53pub const DEFAULT_COLUMN_INDEX_TRUNCATE_LENGTH: Option<usize> = Some(64);
54/// Default value for [`BloomFilterProperties::fpp`]
55pub const DEFAULT_BLOOM_FILTER_FPP: f64 = 0.05;
56/// Default value for [`BloomFilterProperties::ndv`].
57///
58/// Note: this is only the fallback default used when constructing [`BloomFilterProperties`]
59/// directly. When using [`WriterPropertiesBuilder`], columns with bloom filters enabled
60/// but without an explicit NDV will have their NDV resolved at build time to
61/// [`WriterProperties::max_row_group_row_count`], which may differ from this constant
62/// if the user configured a custom row group size.
63pub const DEFAULT_BLOOM_FILTER_NDV: u64 = DEFAULT_MAX_ROW_GROUP_ROW_COUNT as u64;
64/// Default values for [`WriterProperties::statistics_truncate_length`]
65pub const DEFAULT_STATISTICS_TRUNCATE_LENGTH: Option<usize> = Some(64);
66/// Default value for [`WriterProperties::offset_index_disabled`]
67pub const DEFAULT_OFFSET_INDEX_DISABLED: bool = false;
68/// Default values for [`WriterProperties::coerce_types`]
69pub const DEFAULT_COERCE_TYPES: bool = false;
70/// Default value for [`WriterProperties::data_page_v2_compression_ratio_threshold`]
71pub const DEFAULT_DATA_PAGE_V2_COMPRESSION_RATIO_THRESHOLD: f64 = 1.0;
72/// Default minimum chunk size for content-defined chunking: 256 KiB.
73pub const DEFAULT_CDC_MIN_CHUNK_SIZE: usize = 256 * 1024;
74/// Default maximum chunk size for content-defined chunking: 1024 KiB.
75pub const DEFAULT_CDC_MAX_CHUNK_SIZE: usize = 1024 * 1024;
76/// Default normalization level for content-defined chunking.
77pub const DEFAULT_CDC_NORM_LEVEL: i32 = 0;
78
79/// EXPERIMENTAL: Options for content-defined chunking (CDC).
80///
81/// Content-defined chunking is an experimental feature that optimizes parquet
82/// files for content addressable storage (CAS) systems by writing data pages
83/// according to content-defined chunk boundaries. This allows for more
84/// efficient deduplication of data across files, hence more efficient network
85/// transfers and storage.
86///
87/// Each content-defined chunk is written as a separate parquet data page. The
88/// following options control the chunks' size and the chunking process. Note
89/// that the chunk size is calculated based on the logical value of the data,
90/// before any encoding or compression is applied.
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub struct CdcOptions {
93 /// Minimum chunk size in bytes, default is 256 KiB.
94 /// The rolling hash will not be updated until this size is reached for each chunk.
95 /// Note that all data sent through the hash function is counted towards the chunk
96 /// size, including definition and repetition levels if present.
97 pub min_chunk_size: usize,
98 /// Maximum chunk size in bytes, default is 1024 KiB.
99 /// The chunker will create a new chunk whenever the chunk size exceeds this value.
100 /// Note that the parquet writer has a related [`data_page_size_limit`] property that
101 /// controls the maximum size of a parquet data page after encoding. While setting
102 /// `data_page_size_limit` to a smaller value than `max_chunk_size` doesn't affect
103 /// the chunking effectiveness, it results in more small parquet data pages.
104 ///
105 /// [`data_page_size_limit`]: WriterPropertiesBuilder::set_data_page_size_limit
106 pub max_chunk_size: usize,
107 /// Number of bit adjustment to the gearhash mask in order to center the chunk size
108 /// around the average size more aggressively, default is 0.
109 /// Increasing the normalization level increases the probability of finding a chunk,
110 /// improving the deduplication ratio, but also increasing the number of small chunks
111 /// resulting in many small parquet data pages. The default value provides a good
112 /// balance between deduplication ratio and fragmentation.
113 /// Use norm_level=1 or norm_level=2 to reach a higher deduplication ratio at the
114 /// expense of fragmentation. Negative values can also be used to reduce the
115 /// probability of finding a chunk, resulting in larger chunks and fewer data pages.
116 /// Note that values outside [-3, 3] are not recommended, prefer using the default
117 /// value of 0 for most use cases.
118 pub norm_level: i32,
119}
120
121impl Default for CdcOptions {
122 fn default() -> Self {
123 Self {
124 min_chunk_size: DEFAULT_CDC_MIN_CHUNK_SIZE,
125 max_chunk_size: DEFAULT_CDC_MAX_CHUNK_SIZE,
126 norm_level: DEFAULT_CDC_NORM_LEVEL,
127 }
128 }
129}
130
131/// Parquet writer version.
132///
133/// Basic constant, which is not part of the Thrift definition.
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135#[allow(non_camel_case_types)]
136pub enum WriterVersion {
137 /// Parquet format version 1.0
138 PARQUET_1_0,
139 /// Parquet format version 2.0
140 PARQUET_2_0,
141}
142
143impl WriterVersion {
144 /// Returns writer version as `i32`.
145 pub fn as_num(&self) -> i32 {
146 match self {
147 WriterVersion::PARQUET_1_0 => 1,
148 WriterVersion::PARQUET_2_0 => 2,
149 }
150 }
151}
152
153impl FromStr for WriterVersion {
154 type Err = String;
155
156 fn from_str(s: &str) -> Result<Self, Self::Err> {
157 match s {
158 "PARQUET_1_0" | "parquet_1_0" => Ok(WriterVersion::PARQUET_1_0),
159 "PARQUET_2_0" | "parquet_2_0" => Ok(WriterVersion::PARQUET_2_0),
160 _ => Err(format!("Invalid writer version: {s}")),
161 }
162 }
163}
164
165/// Where in the file [`ArrowWriter`](crate::arrow::arrow_writer::ArrowWriter) should
166/// write Bloom filters
167///
168/// Basic constant, which is not part of the Thrift definition.
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub enum BloomFilterPosition {
171 /// Write Bloom Filters of each row group right after the row group
172 ///
173 /// This saves memory by writing it as soon as it is computed, at the cost
174 /// of data locality for readers
175 AfterRowGroup,
176 /// Write Bloom Filters at the end of the file
177 ///
178 /// This allows better data locality for readers, at the cost of memory usage
179 /// for writers.
180 End,
181}
182
183/// Reference counted writer properties.
184pub type WriterPropertiesPtr = Arc<WriterProperties>;
185
186/// Resolved state of [`WriterPropertiesBuilder::set_offset_index_disabled`].
187///
188/// When a user disables offset indexes but page-level statistics are enabled,
189/// the setting is overridden (offset indexes remain enabled). This enum
190/// preserves the user's original intent so that a round-trip through
191/// `WriterPropertiesBuilder` does not lose it.
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193enum OffsetIndexSetting {
194 /// Offset indexes are enabled (the default).
195 Enabled,
196 /// User disabled offset indexes and no page-level statistics override it.
197 Disabled,
198 /// User disabled offset indexes, but page-level statistics require them,
199 /// so they remain enabled.
200 DisabledOverridden,
201}
202
203/// Configuration settings for writing parquet files.
204///
205/// Use [`Self::builder`] to create a [`WriterPropertiesBuilder`] to change settings.
206///
207/// # Example
208///
209/// ```rust
210/// # use parquet::{
211/// # basic::{Compression, Encoding},
212/// # file::properties::*,
213/// # schema::types::ColumnPath,
214/// # };
215/// #
216/// // Create properties with default configuration.
217/// let props = WriterProperties::default();
218///
219/// // Use properties builder to set certain options and assemble the configuration.
220/// let props = WriterProperties::builder()
221/// .set_writer_version(WriterVersion::PARQUET_1_0)
222/// .set_encoding(Encoding::PLAIN)
223/// .set_column_encoding(ColumnPath::from("col1"), Encoding::DELTA_BINARY_PACKED)
224/// .set_compression(Compression::SNAPPY)
225/// .build();
226///
227/// assert_eq!(props.writer_version(), WriterVersion::PARQUET_1_0);
228/// assert_eq!(
229/// props.encoding(&ColumnPath::from("col1")),
230/// Some(Encoding::DELTA_BINARY_PACKED)
231/// );
232/// assert_eq!(
233/// props.encoding(&ColumnPath::from("col2")),
234/// Some(Encoding::PLAIN)
235/// );
236/// ```
237#[derive(Debug, Clone)]
238pub struct WriterProperties {
239 data_page_row_count_limit: usize,
240 write_batch_size: usize,
241 max_row_group_row_count: Option<usize>,
242 max_row_group_bytes: Option<usize>,
243 bloom_filter_position: BloomFilterPosition,
244 writer_version: WriterVersion,
245 created_by: String,
246 offset_index_setting: OffsetIndexSetting,
247 pub(crate) key_value_metadata: Option<Vec<KeyValue>>,
248 default_column_properties: ColumnProperties,
249 column_properties: HashMap<ColumnPath, ColumnProperties>,
250 sorting_columns: Option<Vec<SortingColumn>>,
251 column_index_truncate_length: Option<usize>,
252 statistics_truncate_length: Option<usize>,
253 coerce_types: bool,
254 content_defined_chunking: Option<CdcOptions>,
255 #[cfg(feature = "encryption")]
256 pub(crate) file_encryption_properties: Option<Arc<FileEncryptionProperties>>,
257}
258
259impl Default for WriterProperties {
260 fn default() -> Self {
261 Self::builder().build()
262 }
263}
264
265impl WriterProperties {
266 /// Create a new [`WriterProperties`] with the default settings
267 ///
268 /// See [`WriterProperties::builder`] for customising settings
269 pub fn new() -> Self {
270 Self::default()
271 }
272
273 /// Returns a new default [`WriterPropertiesBuilder`] for creating writer
274 /// properties.
275 pub fn builder() -> WriterPropertiesBuilder {
276 WriterPropertiesBuilder::default()
277 }
278
279 /// Converts this [`WriterProperties`] into a [`WriterPropertiesBuilder`]
280 /// Used for mutating existing property settings
281 pub fn into_builder(self) -> WriterPropertiesBuilder {
282 self.into()
283 }
284
285 /// Returns data page size limit.
286 ///
287 /// Note: this is a best effort limit based on the write batch size
288 ///
289 /// For more details see [`WriterPropertiesBuilder::set_data_page_size_limit`]
290 pub fn data_page_size_limit(&self) -> usize {
291 self.default_column_properties
292 .data_page_size_limit()
293 .unwrap_or(DEFAULT_PAGE_SIZE)
294 }
295
296 /// Returns data page size limit for a specific column.
297 ///
298 /// Takes precedence over [`Self::data_page_size_limit`].
299 ///
300 /// Note: this is a best effort limit based on the write batch size.
301 pub fn column_data_page_size_limit(&self, col: &ColumnPath) -> usize {
302 self.column_properties
303 .get(col)
304 .and_then(|c| c.data_page_size_limit())
305 .or_else(|| self.default_column_properties.data_page_size_limit())
306 .unwrap_or(DEFAULT_PAGE_SIZE)
307 }
308
309 /// Returns dictionary page size limit.
310 ///
311 /// Note: this is a best effort limit based on the write batch size
312 ///
313 /// For more details see [`WriterPropertiesBuilder::set_dictionary_page_size_limit`]
314 pub fn dictionary_page_size_limit(&self) -> usize {
315 self.default_column_properties
316 .dictionary_page_size_limit()
317 .unwrap_or(DEFAULT_DICTIONARY_PAGE_SIZE_LIMIT)
318 }
319
320 /// Returns dictionary page size limit for a specific column.
321 pub fn column_dictionary_page_size_limit(&self, col: &ColumnPath) -> usize {
322 self.column_properties
323 .get(col)
324 .and_then(|c| c.dictionary_page_size_limit())
325 .or_else(|| self.default_column_properties.dictionary_page_size_limit())
326 .unwrap_or(DEFAULT_DICTIONARY_PAGE_SIZE_LIMIT)
327 }
328
329 /// Returns the maximum page row count
330 ///
331 /// Note: this is a best effort limit based on the write batch size
332 ///
333 /// For more details see [`WriterPropertiesBuilder::set_data_page_row_count_limit`]
334 pub fn data_page_row_count_limit(&self) -> usize {
335 self.data_page_row_count_limit
336 }
337
338 /// Returns configured batch size for writes.
339 ///
340 /// When writing a batch of data, this setting allows to split it internally into
341 /// smaller batches so we can better estimate the size of a page currently being
342 /// written.
343 ///
344 /// For more details see [`WriterPropertiesBuilder::set_write_batch_size`]
345 pub fn write_batch_size(&self) -> usize {
346 self.write_batch_size
347 }
348
349 /// Returns maximum number of rows in a row group, or `usize::MAX` if unlimited.
350 ///
351 /// For more details see [`WriterPropertiesBuilder::set_max_row_group_size`]
352 #[deprecated(since = "58.0.0", note = "Use `max_row_group_row_count` instead")]
353 pub fn max_row_group_size(&self) -> usize {
354 self.max_row_group_row_count.unwrap_or(usize::MAX)
355 }
356
357 /// Returns maximum number of rows in a row group, or `None` if unlimited.
358 ///
359 /// For more details see [`WriterPropertiesBuilder::set_max_row_group_row_count`]
360 pub fn max_row_group_row_count(&self) -> Option<usize> {
361 self.max_row_group_row_count
362 }
363
364 /// Returns maximum size of a row group in bytes, or `None` if unlimited.
365 ///
366 /// For more details see [`WriterPropertiesBuilder::set_max_row_group_bytes`]
367 pub fn max_row_group_bytes(&self) -> Option<usize> {
368 self.max_row_group_bytes
369 }
370
371 /// Returns bloom filter position.
372 ///
373 /// For more details see [`WriterPropertiesBuilder::set_bloom_filter_position`]
374 pub fn bloom_filter_position(&self) -> BloomFilterPosition {
375 self.bloom_filter_position
376 }
377
378 /// Returns configured writer version.
379 ///
380 /// For more details see [`WriterPropertiesBuilder::set_writer_version`]
381 pub fn writer_version(&self) -> WriterVersion {
382 self.writer_version
383 }
384
385 /// Returns `created_by` string.
386 ///
387 /// For more details see [`WriterPropertiesBuilder::set_created_by`]
388 pub fn created_by(&self) -> &str {
389 &self.created_by
390 }
391
392 /// Returns `true` if offset index writing is disabled.
393 ///
394 /// For more details see [`WriterPropertiesBuilder::set_offset_index_disabled`]
395 pub fn offset_index_disabled(&self) -> bool {
396 matches!(self.offset_index_setting, OffsetIndexSetting::Disabled)
397 }
398
399 /// Returns `key_value_metadata` KeyValue pairs.
400 ///
401 /// For more details see [`WriterPropertiesBuilder::set_key_value_metadata`]
402 pub fn key_value_metadata(&self) -> Option<&Vec<KeyValue>> {
403 self.key_value_metadata.as_ref()
404 }
405
406 /// Returns sorting columns.
407 ///
408 /// For more details see [`WriterPropertiesBuilder::set_sorting_columns`]
409 pub fn sorting_columns(&self) -> Option<&Vec<SortingColumn>> {
410 self.sorting_columns.as_ref()
411 }
412
413 /// Returns the maximum length of truncated min/max values in the column index.
414 ///
415 /// `None` if truncation is disabled, must be greater than 0 otherwise.
416 ///
417 /// For more details see [`WriterPropertiesBuilder::set_column_index_truncate_length`]
418 pub fn column_index_truncate_length(&self) -> Option<usize> {
419 self.column_index_truncate_length
420 }
421
422 /// Returns the maximum length of truncated min/max values in [`Statistics`].
423 ///
424 /// `None` if truncation is disabled, must be greater than 0 otherwise.
425 ///
426 /// For more details see [`WriterPropertiesBuilder::set_statistics_truncate_length`]
427 ///
428 /// [`Statistics`]: crate::file::statistics::Statistics
429 pub fn statistics_truncate_length(&self) -> Option<usize> {
430 self.statistics_truncate_length
431 }
432
433 /// Returns `true` if type coercion is enabled.
434 ///
435 /// For more details see [`WriterPropertiesBuilder::set_coerce_types`]
436 pub fn coerce_types(&self) -> bool {
437 self.coerce_types
438 }
439
440 /// EXPERIMENTAL: Returns content-defined chunking options, or `None` if CDC is disabled.
441 ///
442 /// For more details see [`WriterPropertiesBuilder::set_content_defined_chunking`]
443 pub fn content_defined_chunking(&self) -> Option<&CdcOptions> {
444 self.content_defined_chunking.as_ref()
445 }
446
447 /// Returns the compression ratio threshold at or above which a Data Page v2's
448 /// compressed values are discarded in favor of writing the values uncompressed.
449 ///
450 /// For more details see [`WriterPropertiesBuilder::set_data_page_v2_compression_ratio_threshold`]
451 pub fn data_page_v2_compression_ratio_threshold(&self) -> f64 {
452 self.default_column_properties
453 .data_page_v2_compression_ratio_threshold()
454 .unwrap_or(DEFAULT_DATA_PAGE_V2_COMPRESSION_RATIO_THRESHOLD)
455 }
456
457 /// Returns the Data Page v2 compression ratio threshold for a specific column.
458 ///
459 /// Takes precedence over [`Self::data_page_v2_compression_ratio_threshold`].
460 pub fn column_data_page_v2_compression_ratio_threshold(&self, col: &ColumnPath) -> f64 {
461 self.column_properties
462 .get(col)
463 .and_then(|c| c.data_page_v2_compression_ratio_threshold())
464 .or_else(|| {
465 self.default_column_properties
466 .data_page_v2_compression_ratio_threshold()
467 })
468 .unwrap_or(DEFAULT_DATA_PAGE_V2_COMPRESSION_RATIO_THRESHOLD)
469 }
470
471 /// Returns encoding for a data page, when dictionary encoding is enabled.
472 ///
473 /// This is not configurable.
474 #[inline]
475 pub fn dictionary_data_page_encoding(&self) -> Encoding {
476 // PLAIN_DICTIONARY encoding is deprecated in writer version 1.
477 // Dictionary values are encoded using RLE_DICTIONARY encoding.
478 Encoding::RLE_DICTIONARY
479 }
480
481 /// Returns encoding for dictionary page, when dictionary encoding is enabled.
482 ///
483 /// This is not configurable.
484 #[inline]
485 pub fn dictionary_page_encoding(&self) -> Encoding {
486 // PLAIN_DICTIONARY is deprecated in writer version 1.
487 // Dictionary is encoded using plain encoding.
488 Encoding::PLAIN
489 }
490
491 /// Returns encoding for a column, if set.
492 ///
493 /// In case when dictionary is enabled, returns fallback encoding.
494 ///
495 /// If encoding is not set, then column writer will choose the best encoding
496 /// based on the column type.
497 pub fn encoding(&self, col: &ColumnPath) -> Option<Encoding> {
498 self.column_properties
499 .get(col)
500 .and_then(|c| c.encoding())
501 .or_else(|| self.default_column_properties.encoding())
502 }
503
504 /// Returns compression codec for a column.
505 ///
506 /// For more details see [`WriterPropertiesBuilder::set_column_compression`]
507 pub fn compression(&self, col: &ColumnPath) -> Compression {
508 self.column_properties
509 .get(col)
510 .and_then(|c| c.compression())
511 .or_else(|| self.default_column_properties.compression())
512 .unwrap_or(DEFAULT_COMPRESSION)
513 }
514
515 /// Returns `true` if dictionary encoding is enabled for a column.
516 ///
517 /// For more details see [`WriterPropertiesBuilder::set_dictionary_enabled`]
518 pub fn dictionary_enabled(&self, col: &ColumnPath) -> bool {
519 self.column_properties
520 .get(col)
521 .and_then(|c| c.dictionary_enabled())
522 .or_else(|| self.default_column_properties.dictionary_enabled())
523 .unwrap_or(DEFAULT_DICTIONARY_ENABLED)
524 }
525
526 /// Returns which statistics are written for a column.
527 ///
528 /// For more details see [`WriterPropertiesBuilder::set_statistics_enabled`]
529 pub fn statistics_enabled(&self, col: &ColumnPath) -> EnabledStatistics {
530 self.column_properties
531 .get(col)
532 .and_then(|c| c.statistics_enabled())
533 .or_else(|| self.default_column_properties.statistics_enabled())
534 .unwrap_or(DEFAULT_STATISTICS_ENABLED)
535 }
536
537 /// Returns `true` if [`Statistics`] are to be written to the page header for a column.
538 ///
539 /// For more details see [`WriterPropertiesBuilder::set_write_page_header_statistics`]
540 ///
541 /// [`Statistics`]: crate::file::statistics::Statistics
542 pub fn write_page_header_statistics(&self, col: &ColumnPath) -> bool {
543 self.column_properties
544 .get(col)
545 .and_then(|c| c.write_page_header_statistics())
546 .or_else(|| {
547 self.default_column_properties
548 .write_page_header_statistics()
549 })
550 .unwrap_or(DEFAULT_WRITE_PAGE_HEADER_STATISTICS)
551 }
552
553 /// Returns the [`BloomFilterProperties`] for the given column
554 ///
555 /// Returns `None` if bloom filter is disabled
556 ///
557 /// For more details see [`WriterPropertiesBuilder::set_column_bloom_filter_enabled`]
558 pub fn bloom_filter_properties(&self, col: &ColumnPath) -> Option<&BloomFilterProperties> {
559 self.column_properties
560 .get(col)
561 .and_then(|c| c.bloom_filter_properties())
562 .or_else(|| self.default_column_properties.bloom_filter_properties())
563 }
564
565 /// Return file encryption properties
566 ///
567 /// For more details see [`WriterPropertiesBuilder::with_file_encryption_properties`]
568 #[cfg(feature = "encryption")]
569 pub fn file_encryption_properties(&self) -> Option<&Arc<FileEncryptionProperties>> {
570 self.file_encryption_properties.as_ref()
571 }
572}
573
574/// Builder for [`WriterProperties`] Parquet writer configuration.
575///
576/// See example on [`WriterProperties`]
577#[derive(Debug, Clone)]
578pub struct WriterPropertiesBuilder {
579 data_page_row_count_limit: usize,
580 write_batch_size: usize,
581 max_row_group_row_count: Option<usize>,
582 max_row_group_bytes: Option<usize>,
583 bloom_filter_position: BloomFilterPosition,
584 writer_version: WriterVersion,
585 created_by: String,
586 offset_index_disabled: bool,
587 key_value_metadata: Option<Vec<KeyValue>>,
588 default_column_properties: ColumnProperties,
589 column_properties: HashMap<ColumnPath, ColumnProperties>,
590 sorting_columns: Option<Vec<SortingColumn>>,
591 column_index_truncate_length: Option<usize>,
592 statistics_truncate_length: Option<usize>,
593 coerce_types: bool,
594 content_defined_chunking: Option<CdcOptions>,
595 #[cfg(feature = "encryption")]
596 file_encryption_properties: Option<Arc<FileEncryptionProperties>>,
597}
598
599impl Default for WriterPropertiesBuilder {
600 /// Returns default state of the builder.
601 fn default() -> Self {
602 Self {
603 data_page_row_count_limit: DEFAULT_DATA_PAGE_ROW_COUNT_LIMIT,
604 write_batch_size: DEFAULT_WRITE_BATCH_SIZE,
605 max_row_group_row_count: Some(DEFAULT_MAX_ROW_GROUP_ROW_COUNT),
606 max_row_group_bytes: None,
607 bloom_filter_position: DEFAULT_BLOOM_FILTER_POSITION,
608 writer_version: DEFAULT_WRITER_VERSION,
609 created_by: DEFAULT_CREATED_BY.to_string(),
610 offset_index_disabled: DEFAULT_OFFSET_INDEX_DISABLED,
611 key_value_metadata: None,
612 default_column_properties: Default::default(),
613 column_properties: HashMap::new(),
614 sorting_columns: None,
615 column_index_truncate_length: DEFAULT_COLUMN_INDEX_TRUNCATE_LENGTH,
616 statistics_truncate_length: DEFAULT_STATISTICS_TRUNCATE_LENGTH,
617 coerce_types: DEFAULT_COERCE_TYPES,
618 content_defined_chunking: None,
619 #[cfg(feature = "encryption")]
620 file_encryption_properties: None,
621 }
622 }
623}
624
625impl WriterPropertiesBuilder {
626 /// Finalizes the configuration and returns immutable writer properties struct.
627 pub fn build(self) -> WriterProperties {
628 // Pre-compute offset_index_setting
629 let offset_index_setting = if self.offset_index_disabled {
630 let default_page_stats_enabled = self.default_column_properties.statistics_enabled()
631 == Some(EnabledStatistics::Page);
632 let column_page_stats_enabled = self.column_properties.iter().any(|path_props| {
633 path_props.1.statistics_enabled() == Some(EnabledStatistics::Page)
634 });
635 if default_page_stats_enabled || column_page_stats_enabled {
636 OffsetIndexSetting::DisabledOverridden
637 } else {
638 OffsetIndexSetting::Disabled
639 }
640 } else {
641 OffsetIndexSetting::Enabled
642 };
643
644 // Resolve bloom filter NDV for columns where it wasn't explicitly set:
645 // default to max_row_group_row_count so the filter is never undersized.
646 let default_ndv = self
647 .max_row_group_row_count
648 .unwrap_or(DEFAULT_MAX_ROW_GROUP_ROW_COUNT) as u64;
649 let mut default_column_properties = self.default_column_properties;
650 default_column_properties.resolve_bloom_filter_ndv(default_ndv);
651 let mut column_properties = self.column_properties;
652 for props in column_properties.values_mut() {
653 props.resolve_bloom_filter_ndv(default_ndv);
654 }
655
656 WriterProperties {
657 data_page_row_count_limit: self.data_page_row_count_limit,
658 write_batch_size: self.write_batch_size,
659 max_row_group_row_count: self.max_row_group_row_count,
660 max_row_group_bytes: self.max_row_group_bytes,
661 bloom_filter_position: self.bloom_filter_position,
662 writer_version: self.writer_version,
663 created_by: self.created_by,
664 offset_index_setting,
665 key_value_metadata: self.key_value_metadata,
666 default_column_properties,
667 column_properties,
668 sorting_columns: self.sorting_columns,
669 column_index_truncate_length: self.column_index_truncate_length,
670 statistics_truncate_length: self.statistics_truncate_length,
671 coerce_types: self.coerce_types,
672 content_defined_chunking: self.content_defined_chunking,
673 #[cfg(feature = "encryption")]
674 file_encryption_properties: self.file_encryption_properties,
675 }
676 }
677
678 // ----------------------------------------------------------------------
679 // Writer properties related to a file
680
681 /// Sets the `WriterVersion` written into the parquet metadata (defaults to [`PARQUET_1_0`]
682 /// via [`DEFAULT_WRITER_VERSION`])
683 ///
684 /// This value can determine what features some readers will support.
685 ///
686 /// [`PARQUET_1_0`]: [WriterVersion::PARQUET_1_0]
687 pub fn set_writer_version(mut self, value: WriterVersion) -> Self {
688 self.writer_version = value;
689 self
690 }
691
692 /// Sets best effort maximum number of rows in a data page (defaults to `20_000`
693 /// via [`DEFAULT_DATA_PAGE_ROW_COUNT_LIMIT`]).
694 ///
695 /// The parquet writer will attempt to limit the number of rows in
696 /// each `DataPage` to this value. Reducing this value will result
697 /// in larger parquet files, but may improve the effectiveness of
698 /// page index based predicate pushdown during reading.
699 ///
700 /// Note: this is a best effort limit based on value of
701 /// [`set_write_batch_size`](Self::set_write_batch_size).
702 pub fn set_data_page_row_count_limit(mut self, value: usize) -> Self {
703 self.data_page_row_count_limit = value;
704 self
705 }
706
707 /// Sets write batch size (defaults to 1024 via [`DEFAULT_WRITE_BATCH_SIZE`]).
708 ///
709 /// For performance reasons, data for each column is written in
710 /// batches of this size.
711 ///
712 /// Additional limits such as such as
713 /// [`set_data_page_row_count_limit`](Self::set_data_page_row_count_limit)
714 /// are checked between batches, and thus the write batch size value acts as an
715 /// upper-bound on the enforcement granularity of other limits.
716 pub fn set_write_batch_size(mut self, value: usize) -> Self {
717 self.write_batch_size = value;
718 self
719 }
720
721 /// Sets maximum number of rows in a row group (defaults to `1024 * 1024`
722 /// via [`DEFAULT_MAX_ROW_GROUP_ROW_COUNT`]).
723 ///
724 /// # Panics
725 /// If the value is set to 0.
726 #[deprecated(since = "58.0.0", note = "Use `set_max_row_group_row_count` instead")]
727 pub fn set_max_row_group_size(mut self, value: usize) -> Self {
728 assert!(value > 0, "Cannot have a 0 max row group size");
729 self.max_row_group_row_count = Some(value);
730 self
731 }
732
733 /// Sets maximum number of rows in a row group, or `None` for unlimited.
734 ///
735 /// If both `max_row_group_row_count` and `max_row_group_bytes` are set,
736 /// the row group with the smaller limit will be produced.
737 ///
738 /// # Panics
739 /// If the value is `Some(0)`.
740 pub fn set_max_row_group_row_count(mut self, value: Option<usize>) -> Self {
741 assert_ne!(value, Some(0), "Cannot have a 0 max row group row count");
742 self.max_row_group_row_count = value;
743 self
744 }
745
746 /// Sets maximum size of a row group in bytes, or `None` for unlimited.
747 ///
748 /// Row groups are flushed when their estimated encoded size exceeds this threshold.
749 /// This is similar to the official Java implementation for `parquet.block.size`'s behavior.
750 ///
751 /// If both `max_row_group_row_count` and `max_row_group_bytes` are set,
752 /// the row group with the smaller limit will be produced.
753 ///
754 /// # Panics
755 /// If the value is `Some(0)`.
756 pub fn set_max_row_group_bytes(mut self, value: Option<usize>) -> Self {
757 assert_ne!(value, Some(0), "Cannot have a 0 max row group bytes");
758 self.max_row_group_bytes = value;
759 self
760 }
761
762 /// Sets where in the final file Bloom Filters are written (defaults to [`AfterRowGroup`]
763 /// via [`DEFAULT_BLOOM_FILTER_POSITION`])
764 ///
765 /// [`AfterRowGroup`]: BloomFilterPosition::AfterRowGroup
766 pub fn set_bloom_filter_position(mut self, value: BloomFilterPosition) -> Self {
767 self.bloom_filter_position = value;
768 self
769 }
770
771 /// Sets "created by" property (defaults to `parquet-rs version <VERSION>` via
772 /// [`DEFAULT_CREATED_BY`]).
773 ///
774 /// This is a string that will be written into the file metadata
775 pub fn set_created_by(mut self, value: String) -> Self {
776 self.created_by = value;
777 self
778 }
779
780 /// Sets whether the writing of offset indexes is disabled (defaults to `false` via
781 /// [`DEFAULT_OFFSET_INDEX_DISABLED`]).
782 ///
783 /// If statistics level is set to [`Page`] this setting will be overridden with `false`.
784 ///
785 /// Note: As the offset indexes are useful for accessing data by row number,
786 /// they are always written by default, regardless of whether other statistics
787 /// are enabled. Disabling this metadata may result in a degradation in read
788 /// performance, so use this option with care.
789 ///
790 /// [`Page`]: EnabledStatistics::Page
791 pub fn set_offset_index_disabled(mut self, value: bool) -> Self {
792 self.offset_index_disabled = value;
793 self
794 }
795
796 /// Sets "key_value_metadata" property (defaults to `None`).
797 pub fn set_key_value_metadata(mut self, value: Option<Vec<KeyValue>>) -> Self {
798 self.key_value_metadata = value;
799 self
800 }
801
802 /// Sets sorting order of rows in the row group if any (defaults to `None`).
803 pub fn set_sorting_columns(mut self, value: Option<Vec<SortingColumn>>) -> Self {
804 self.sorting_columns = value;
805 self
806 }
807
808 /// Sets the max length of min/max value fields when writing the column
809 /// [`Index`] (defaults to `Some(64)` via [`DEFAULT_COLUMN_INDEX_TRUNCATE_LENGTH`]).
810 ///
811 /// This can be used to prevent columns with very long values (hundreds of
812 /// bytes long) from causing the parquet metadata to become huge.
813 ///
814 /// # Notes
815 ///
816 /// The column [`Index`] is written when [`Self::set_statistics_enabled`] is
817 /// set to [`EnabledStatistics::Page`].
818 ///
819 /// * If `Some`, must be greater than 0, otherwise will panic
820 /// * If `None`, there's no effective limit.
821 ///
822 /// [`Index`]: crate::file::page_index::column_index::ColumnIndexMetaData
823 pub fn set_column_index_truncate_length(mut self, max_length: Option<usize>) -> Self {
824 if let Some(value) = max_length {
825 assert!(
826 value > 0,
827 "Cannot have a 0 column index truncate length. If you wish to disable min/max value truncation, set it to `None`."
828 );
829 }
830
831 self.column_index_truncate_length = max_length;
832 self
833 }
834
835 /// Sets the max length of min/max value fields in row group and data page header
836 /// [`Statistics`] (defaults to `Some(64)` via [`DEFAULT_STATISTICS_TRUNCATE_LENGTH`]).
837 ///
838 /// # Notes
839 /// Row group [`Statistics`] are written when [`Self::set_statistics_enabled`] is
840 /// set to [`EnabledStatistics::Chunk`] or [`EnabledStatistics::Page`]. Data page header
841 /// [`Statistics`] are written when [`Self::set_statistics_enabled`] is set to
842 /// [`EnabledStatistics::Page`].
843 ///
844 /// * If `Some`, must be greater than 0, otherwise will panic
845 /// * If `None`, there's no effective limit.
846 ///
847 /// # See also
848 /// Truncation of Page Index statistics is controlled separately via
849 /// [`WriterPropertiesBuilder::set_column_index_truncate_length`]
850 ///
851 /// [`Statistics`]: crate::file::statistics::Statistics
852 pub fn set_statistics_truncate_length(mut self, max_length: Option<usize>) -> Self {
853 if let Some(value) = max_length {
854 assert!(
855 value > 0,
856 "Cannot have a 0 statistics truncate length. If you wish to disable min/max value truncation, set it to `None`."
857 );
858 }
859
860 self.statistics_truncate_length = max_length;
861 self
862 }
863
864 /// Should the writer coerce types to parquet native types (defaults to `false` via
865 /// [`DEFAULT_COERCE_TYPES`]).
866 ///
867 /// Leaving this option the default `false` will ensure the exact same data
868 /// written to parquet using this library will be read.
869 ///
870 /// Setting this option to `true` will result in parquet files that can be
871 /// read by more readers, but potentially lose information in the process.
872 ///
873 /// * Types such as [`DataType::Date64`], which have no direct corresponding
874 /// Parquet type, may be stored with lower precision.
875 ///
876 /// * The internal field names of `List` and `Map` types will be renamed if
877 /// necessary to match what is required by the newest Parquet specification.
878 ///
879 /// See [`ArrowToParquetSchemaConverter::with_coerce_types`] for more details
880 ///
881 /// [`DataType::Date64`]: arrow_schema::DataType::Date64
882 /// [`ArrowToParquetSchemaConverter::with_coerce_types`]: crate::arrow::ArrowSchemaConverter::with_coerce_types
883 pub fn set_coerce_types(mut self, coerce_types: bool) -> Self {
884 self.coerce_types = coerce_types;
885 self
886 }
887
888 /// EXPERIMENTAL: Sets content-defined chunking options, or disables CDC with `None`.
889 ///
890 /// When enabled, data page boundaries are determined by a rolling hash of the
891 /// column values, so unchanged data produces identical byte sequences across
892 /// file versions. This enables efficient deduplication on content-addressable
893 /// storage systems.
894 ///
895 /// Only supported through the Arrow writer interface ([`ArrowWriter`]).
896 ///
897 /// # Panics
898 ///
899 /// Panics if `min_chunk_size == 0` or `max_chunk_size <= min_chunk_size`.
900 ///
901 /// [`ArrowWriter`]: crate::arrow::arrow_writer::ArrowWriter
902 pub fn set_content_defined_chunking(mut self, options: Option<CdcOptions>) -> Self {
903 if let Some(ref options) = options {
904 assert!(
905 options.min_chunk_size > 0,
906 "min_chunk_size must be positive"
907 );
908 assert!(
909 options.max_chunk_size > options.min_chunk_size,
910 "max_chunk_size ({}) must be greater than min_chunk_size ({})",
911 options.max_chunk_size,
912 options.min_chunk_size
913 );
914 }
915 self.content_defined_chunking = options;
916 self
917 }
918
919 /// Sets the default compression ratio threshold at or above which a Data Page
920 /// v2's compressed values are discarded in favor of writing the values
921 /// uncompressed, for all columns (defaults to `1.0` via
922 /// [`DEFAULT_DATA_PAGE_V2_COMPRESSION_RATIO_THRESHOLD`]).
923 ///
924 /// When writing a Data Page v2 with a configured compression codec, the writer
925 /// first compresses the values and then compares the compressed size to the
926 /// uncompressed size. If `compressed_size >= uncompressed_size * threshold`, the
927 /// compressed buffer is discarded and the values are written uncompressed for
928 /// that page (the page's `is_compressed` flag is set to `false`).
929 ///
930 /// The default of `1.0` preserves the historical behavior of only keeping
931 /// compression when it strictly reduces the size. Setting a value below `1.0`
932 /// requires a minimum amount of size reduction to keep the compressed page —
933 /// for example `0.9` requires at least a 10% reduction. Setting a value above
934 /// `1.0` keeps the compressed buffer even if it's somewhat larger than the
935 /// uncompressed values.
936 ///
937 /// This setting only affects Data Page v2; Data Page v1 always stores the
938 /// compressor's output regardless of the resulting size.
939 ///
940 /// # Panics
941 /// If `value` is not finite or is not strictly positive.
942 pub fn set_data_page_v2_compression_ratio_threshold(mut self, value: f64) -> Self {
943 self.default_column_properties
944 .set_data_page_v2_compression_ratio_threshold(value);
945 self
946 }
947
948 /// Sets FileEncryptionProperties (defaults to `None`)
949 #[cfg(feature = "encryption")]
950 pub fn with_file_encryption_properties(
951 mut self,
952 file_encryption_properties: Arc<FileEncryptionProperties>,
953 ) -> Self {
954 self.file_encryption_properties = Some(file_encryption_properties);
955 self
956 }
957
958 // ----------------------------------------------------------------------
959 // Setters for any column (global)
960
961 /// Sets default encoding for all columns.
962 ///
963 /// If dictionary is not enabled, this is treated as a primary encoding for all
964 /// columns. In case when dictionary is enabled for any column, this value is
965 /// considered to be a fallback encoding for that column.
966 ///
967 /// # Panics
968 ///
969 /// if dictionary encoding is specified, regardless of dictionary
970 /// encoding flag being set.
971 pub fn set_encoding(mut self, value: Encoding) -> Self {
972 self.default_column_properties.set_encoding(value);
973 self
974 }
975
976 /// Sets default compression codec for all columns (default to [`UNCOMPRESSED`] via
977 /// [`DEFAULT_COMPRESSION`]).
978 ///
979 /// [`UNCOMPRESSED`]: Compression::UNCOMPRESSED
980 pub fn set_compression(mut self, value: Compression) -> Self {
981 self.default_column_properties.set_compression(value);
982 self
983 }
984
985 /// Sets default flag to enable/disable dictionary encoding for all columns (defaults to `true`
986 /// via [`DEFAULT_DICTIONARY_ENABLED`]).
987 ///
988 /// Use this method to set dictionary encoding, instead of explicitly specifying
989 /// encoding in `set_encoding` method.
990 pub fn set_dictionary_enabled(mut self, value: bool) -> Self {
991 self.default_column_properties.set_dictionary_enabled(value);
992 self
993 }
994
995 /// Sets best effort maximum dictionary page size, in bytes (defaults to `1024 * 1024`
996 /// via [`DEFAULT_DICTIONARY_PAGE_SIZE_LIMIT`]).
997 ///
998 /// The parquet writer will attempt to limit the size of each
999 /// `DataPage` used to store dictionaries to this many
1000 /// bytes. Reducing this value will result in larger parquet
1001 /// files, but may improve the effectiveness of page index based
1002 /// predicate pushdown during reading.
1003 ///
1004 /// Note: this is a best effort limit based on value of
1005 /// [`set_write_batch_size`](Self::set_write_batch_size).
1006 pub fn set_dictionary_page_size_limit(mut self, value: usize) -> Self {
1007 self.default_column_properties
1008 .set_dictionary_page_size_limit(value);
1009 self
1010 }
1011
1012 /// Sets best effort maximum size of a data page in bytes (defaults to `1024 * 1024`
1013 /// via [`DEFAULT_PAGE_SIZE`]).
1014 ///
1015 /// The parquet writer will attempt to limit the sizes of each
1016 /// `DataPage` to this many bytes. Reducing this value will result
1017 /// in larger parquet files, but may improve the effectiveness of
1018 /// page index based predicate pushdown during reading.
1019 ///
1020 /// Note: this is a best effort limit based on value of
1021 /// [`set_write_batch_size`](Self::set_write_batch_size).
1022 pub fn set_data_page_size_limit(mut self, value: usize) -> Self {
1023 self.default_column_properties
1024 .set_data_page_size_limit(value);
1025 self
1026 }
1027
1028 /// Sets default [`EnabledStatistics`] level for all columns (defaults to [`Page`] via
1029 /// [`DEFAULT_STATISTICS_ENABLED`]).
1030 ///
1031 /// [`Page`]: EnabledStatistics::Page
1032 pub fn set_statistics_enabled(mut self, value: EnabledStatistics) -> Self {
1033 self.default_column_properties.set_statistics_enabled(value);
1034 self
1035 }
1036
1037 /// enable/disable writing [`Statistics`] in the page header
1038 /// (defaults to `false` via [`DEFAULT_WRITE_PAGE_HEADER_STATISTICS`]).
1039 ///
1040 /// Only applicable if [`Page`] level statistics are gathered.
1041 ///
1042 /// Setting this value to `true` can greatly increase the size of the resulting Parquet
1043 /// file while yielding very little added benefit. Most modern Parquet implementations
1044 /// will use the min/max values stored in the [`ParquetColumnIndex`] rather than
1045 /// those in the page header.
1046 ///
1047 /// # Note
1048 ///
1049 /// Prior to version 56.0.0, the `parquet` crate always wrote these
1050 /// statistics (the equivalent of setting this option to `true`). This was
1051 /// changed in 56.0.0 to follow the recommendation in the Parquet
1052 /// specification. See [issue #7580] for more details.
1053 ///
1054 /// [`Statistics`]: crate::file::statistics::Statistics
1055 /// [`ParquetColumnIndex`]: crate::file::metadata::ParquetColumnIndex
1056 /// [`Page`]: EnabledStatistics::Page
1057 /// [issue #7580]: https://github.com/apache/arrow-rs/issues/7580
1058 pub fn set_write_page_header_statistics(mut self, value: bool) -> Self {
1059 self.default_column_properties
1060 .set_write_page_header_statistics(value);
1061 self
1062 }
1063
1064 /// Sets if bloom filter should be written for all columns (defaults to `false`).
1065 ///
1066 /// # Notes
1067 ///
1068 /// * If the bloom filter is enabled previously then it is a no-op.
1069 ///
1070 /// * If the bloom filter is not enabled, default values for ndv and fpp
1071 /// value are used used. See [`set_bloom_filter_ndv`] and
1072 /// [`set_bloom_filter_fpp`] to further adjust the ndv and fpp.
1073 ///
1074 /// [`set_bloom_filter_ndv`]: Self::set_bloom_filter_ndv
1075 /// [`set_bloom_filter_fpp`]: Self::set_bloom_filter_fpp
1076 pub fn set_bloom_filter_enabled(mut self, value: bool) -> Self {
1077 self.default_column_properties
1078 .set_bloom_filter_enabled(value);
1079 self
1080 }
1081
1082 /// Sets the default target bloom filter false positive probability (fpp)
1083 /// for all columns (defaults to `0.05` via [`DEFAULT_BLOOM_FILTER_FPP`]).
1084 ///
1085 /// Implicitly enables bloom writing, as if [`set_bloom_filter_enabled`] had
1086 /// been called.
1087 ///
1088 /// [`set_bloom_filter_enabled`]: Self::set_bloom_filter_enabled
1089 pub fn set_bloom_filter_fpp(mut self, value: f64) -> Self {
1090 self.default_column_properties.set_bloom_filter_fpp(value);
1091 self
1092 }
1093
1094 /// Sets default maximum expected number of distinct values (ndv) for bloom filter
1095 /// for all columns (defaults to [`DEFAULT_BLOOM_FILTER_NDV`]).
1096 ///
1097 /// The bloom filter is initially sized for this many distinct values at the
1098 /// configured FPP, then folded down after all values are inserted to achieve
1099 /// optimal size. A good heuristic is to set this to the expected number of rows
1100 /// in the row group.
1101 ///
1102 /// Implicitly enables bloom writing, as if [`set_bloom_filter_enabled`] had
1103 /// been called.
1104 ///
1105 /// [`set_bloom_filter_enabled`]: Self::set_bloom_filter_enabled
1106 pub fn set_bloom_filter_ndv(mut self, value: u64) -> Self {
1107 self.default_column_properties.set_bloom_filter_ndv(value);
1108 self
1109 }
1110
1111 // ----------------------------------------------------------------------
1112 // Setters for a specific column
1113
1114 /// Helper method to get existing or new mutable reference of column properties.
1115 #[inline]
1116 fn get_mut_props(&mut self, col: ColumnPath) -> &mut ColumnProperties {
1117 self.column_properties.entry(col).or_default()
1118 }
1119
1120 /// Sets encoding for a specific column.
1121 ///
1122 /// Takes precedence over [`Self::set_encoding`].
1123 ///
1124 /// If dictionary is not enabled, this is treated as a primary encoding for this
1125 /// column. In case when dictionary is enabled for this column, either through
1126 /// global defaults or explicitly, this value is considered to be a fallback
1127 /// encoding for this column.
1128 ///
1129 /// # Panics
1130 /// If user tries to set dictionary encoding here, regardless of dictionary
1131 /// encoding flag being set.
1132 pub fn set_column_encoding(mut self, col: ColumnPath, value: Encoding) -> Self {
1133 self.get_mut_props(col).set_encoding(value);
1134 self
1135 }
1136
1137 /// Sets compression codec for a specific column.
1138 ///
1139 /// Takes precedence over [`Self::set_compression`].
1140 pub fn set_column_compression(mut self, col: ColumnPath, value: Compression) -> Self {
1141 self.get_mut_props(col).set_compression(value);
1142 self
1143 }
1144
1145 /// Sets flag to enable/disable dictionary encoding for a specific column.
1146 ///
1147 /// Takes precedence over [`Self::set_dictionary_enabled`].
1148 pub fn set_column_dictionary_enabled(mut self, col: ColumnPath, value: bool) -> Self {
1149 self.get_mut_props(col).set_dictionary_enabled(value);
1150 self
1151 }
1152
1153 /// Sets dictionary page size limit for a specific column.
1154 ///
1155 /// Takes precedence over [`Self::set_dictionary_page_size_limit`].
1156 pub fn set_column_dictionary_page_size_limit(mut self, col: ColumnPath, value: usize) -> Self {
1157 self.get_mut_props(col)
1158 .set_dictionary_page_size_limit(value);
1159 self
1160 }
1161
1162 /// Sets data page size limit for a specific column.
1163 ///
1164 /// Takes precedence over [`Self::set_data_page_size_limit`].
1165 pub fn set_column_data_page_size_limit(mut self, col: ColumnPath, value: usize) -> Self {
1166 self.get_mut_props(col).set_data_page_size_limit(value);
1167 self
1168 }
1169
1170 /// Sets [`EnabledStatistics`] level for a specific column.
1171 ///
1172 /// Takes precedence over [`Self::set_statistics_enabled`].
1173 pub fn set_column_statistics_enabled(
1174 mut self,
1175 col: ColumnPath,
1176 value: EnabledStatistics,
1177 ) -> Self {
1178 self.get_mut_props(col).set_statistics_enabled(value);
1179 self
1180 }
1181
1182 /// Sets whether to write [`Statistics`] in the page header for a specific column.
1183 ///
1184 /// Takes precedence over [`Self::set_write_page_header_statistics`].
1185 ///
1186 /// [`Statistics`]: crate::file::statistics::Statistics
1187 pub fn set_column_write_page_header_statistics(mut self, col: ColumnPath, value: bool) -> Self {
1188 self.get_mut_props(col)
1189 .set_write_page_header_statistics(value);
1190 self
1191 }
1192
1193 /// Sets whether a bloom filter should be written for a specific column.
1194 ///
1195 /// Takes precedence over [`Self::set_bloom_filter_enabled`].
1196 pub fn set_column_bloom_filter_enabled(mut self, col: ColumnPath, value: bool) -> Self {
1197 self.get_mut_props(col).set_bloom_filter_enabled(value);
1198 self
1199 }
1200
1201 /// Sets the false positive probability for bloom filter for a specific column.
1202 ///
1203 /// Takes precedence over [`Self::set_bloom_filter_fpp`].
1204 pub fn set_column_bloom_filter_fpp(mut self, col: ColumnPath, value: f64) -> Self {
1205 self.get_mut_props(col).set_bloom_filter_fpp(value);
1206 self
1207 }
1208
1209 /// Sets the number of distinct values for bloom filter for a specific column.
1210 ///
1211 /// Takes precedence over [`Self::set_bloom_filter_ndv`].
1212 pub fn set_column_bloom_filter_ndv(mut self, col: ColumnPath, value: u64) -> Self {
1213 self.get_mut_props(col).set_bloom_filter_ndv(value);
1214 self
1215 }
1216
1217 /// Sets the Data Page v2 compression ratio threshold for a specific column.
1218 ///
1219 /// Takes precedence over [`Self::set_data_page_v2_compression_ratio_threshold`].
1220 ///
1221 /// # Panics
1222 /// If `value` is not finite or is not strictly positive.
1223 pub fn set_column_data_page_v2_compression_ratio_threshold(
1224 mut self,
1225 col: ColumnPath,
1226 value: f64,
1227 ) -> Self {
1228 self.get_mut_props(col)
1229 .set_data_page_v2_compression_ratio_threshold(value);
1230 self
1231 }
1232}
1233
1234impl From<WriterProperties> for WriterPropertiesBuilder {
1235 fn from(props: WriterProperties) -> Self {
1236 WriterPropertiesBuilder {
1237 data_page_row_count_limit: props.data_page_row_count_limit,
1238 write_batch_size: props.write_batch_size,
1239 max_row_group_row_count: props.max_row_group_row_count,
1240 max_row_group_bytes: props.max_row_group_bytes,
1241 bloom_filter_position: props.bloom_filter_position,
1242 writer_version: props.writer_version,
1243 created_by: props.created_by,
1244 offset_index_disabled: !matches!(
1245 props.offset_index_setting,
1246 OffsetIndexSetting::Enabled
1247 ),
1248 key_value_metadata: props.key_value_metadata,
1249 default_column_properties: props.default_column_properties,
1250 column_properties: props.column_properties,
1251 sorting_columns: props.sorting_columns,
1252 column_index_truncate_length: props.column_index_truncate_length,
1253 statistics_truncate_length: props.statistics_truncate_length,
1254 coerce_types: props.coerce_types,
1255 content_defined_chunking: props.content_defined_chunking,
1256 #[cfg(feature = "encryption")]
1257 file_encryption_properties: props.file_encryption_properties,
1258 }
1259 }
1260}
1261
1262/// Controls the level of statistics to be computed by the writer and stored in
1263/// the parquet file.
1264///
1265/// Enabling statistics makes the resulting Parquet file larger and requires
1266/// more time to read the parquet footer.
1267///
1268/// Statistics can be used to improve query performance by pruning row groups
1269/// and pages during query execution if the query engine supports evaluating the
1270/// predicate using the statistics.
1271#[derive(Debug, Clone, Copy, Eq, PartialEq)]
1272pub enum EnabledStatistics {
1273 /// Compute no statistics.
1274 None,
1275 /// Compute column chunk-level statistics but not page-level.
1276 ///
1277 /// Setting this option will store one set of statistics for each relevant
1278 /// column for each row group. The more row groups written, the more
1279 /// statistics will be stored.
1280 Chunk,
1281 /// Compute page-level and column chunk-level statistics.
1282 ///
1283 /// Setting this option will store one set of statistics for each relevant
1284 /// column for each row group. In addition, this will enable the writing
1285 /// of the column index (the offset index is always written regardless of
1286 /// this setting). See [`ParquetColumnIndex`] for
1287 /// more information.
1288 ///
1289 /// [`ParquetColumnIndex`]: crate::file::metadata::ParquetColumnIndex
1290 Page,
1291}
1292
1293impl FromStr for EnabledStatistics {
1294 type Err = String;
1295
1296 fn from_str(s: &str) -> Result<Self, Self::Err> {
1297 match s {
1298 "NONE" | "none" => Ok(EnabledStatistics::None),
1299 "CHUNK" | "chunk" => Ok(EnabledStatistics::Chunk),
1300 "PAGE" | "page" => Ok(EnabledStatistics::Page),
1301 _ => Err(format!("Invalid statistics arg: {s}")),
1302 }
1303 }
1304}
1305
1306impl Default for EnabledStatistics {
1307 fn default() -> Self {
1308 DEFAULT_STATISTICS_ENABLED
1309 }
1310}
1311
1312/// Controls the bloom filter to be computed by the writer.
1313///
1314/// The bloom filter is initially sized for `ndv` distinct values at the given `fpp`, then
1315/// automatically folded down after all values are inserted to achieve optimal size while
1316/// maintaining the target `fpp`. See [`Sbbf::fold_to_target_fpp`] for details on the
1317/// folding algorithm.
1318///
1319/// [`Sbbf::fold_to_target_fpp`]: crate::bloom_filter::Sbbf::fold_to_target_fpp
1320#[derive(Debug, Clone, PartialEq)]
1321pub struct BloomFilterProperties {
1322 /// False positive probability. This should be always between 0 and 1 exclusive. Defaults to [`DEFAULT_BLOOM_FILTER_FPP`].
1323 ///
1324 /// You should set this value by calling [`WriterPropertiesBuilder::set_bloom_filter_fpp`].
1325 ///
1326 /// The bloom filter data structure is a trade of between disk and memory space versus fpp, the
1327 /// smaller the fpp, the more memory and disk space is required, thus setting it to a reasonable value
1328 /// e.g. 0.1, 0.05, or 0.001 is recommended.
1329 ///
1330 /// This value also serves as the target FPP for bloom filter folding: after all values
1331 /// are inserted, the filter is folded down to the smallest size that still meets this FPP.
1332 pub fpp: f64,
1333 /// Maximum expected number of distinct values. Defaults to [`DEFAULT_BLOOM_FILTER_NDV`].
1334 ///
1335 /// You should set this value by calling [`WriterPropertiesBuilder::set_bloom_filter_ndv`].
1336 ///
1337 /// When not explicitly set via the builder, this defaults to
1338 /// [`max_row_group_row_count`](WriterProperties::max_row_group_row_count) (resolved at
1339 /// build time). The bloom filter is initially sized for this many distinct values at the
1340 /// given `fpp`, then folded down after insertion to achieve optimal size. A good heuristic
1341 /// is to set this to the expected number of rows in the row group. If fewer distinct values
1342 /// are actually written, the filter will be automatically compacted via folding.
1343 ///
1344 /// Thus the only negative side of overestimating this value is that the bloom filter
1345 /// will use more memory during writing than necessary, but it will not affect the final
1346 /// bloom filter size on disk.
1347 ///
1348 /// If you wish to reduce memory usage during writing and are able to make a reasonable estimate
1349 /// of the number of distinct values in a row group, it is recommended to set this value explicitly
1350 /// rather than relying on the default dynamic sizing based on `max_row_group_row_count`.
1351 /// If you do set this value explicitly it is probably best to set it for each column
1352 /// individually via [`WriterPropertiesBuilder::set_column_bloom_filter_ndv`] rather than globally,
1353 /// since different columns may have different numbers of distinct values.
1354 pub ndv: u64,
1355}
1356
1357impl Default for BloomFilterProperties {
1358 fn default() -> Self {
1359 BloomFilterProperties {
1360 fpp: DEFAULT_BLOOM_FILTER_FPP,
1361 ndv: DEFAULT_BLOOM_FILTER_NDV,
1362 }
1363 }
1364}
1365
1366/// Container for column properties that can be changed as part of writer.
1367///
1368/// If a field is `None`, it means that no specific value has been set for this column,
1369/// so some subsequent or default value must be used.
1370#[derive(Debug, Clone, Default, PartialEq)]
1371struct ColumnProperties {
1372 encoding: Option<Encoding>,
1373 codec: Option<Compression>,
1374 data_page_size_limit: Option<usize>,
1375 dictionary_page_size_limit: Option<usize>,
1376 dictionary_enabled: Option<bool>,
1377 statistics_enabled: Option<EnabledStatistics>,
1378 write_page_header_statistics: Option<bool>,
1379 /// bloom filter related properties
1380 bloom_filter_properties: Option<BloomFilterProperties>,
1381 /// Whether the bloom filter NDV was explicitly set by the user
1382 bloom_filter_ndv_is_set: bool,
1383 data_page_v2_compression_ratio_threshold: Option<f64>,
1384}
1385
1386impl ColumnProperties {
1387 /// Sets encoding for this column.
1388 ///
1389 /// If dictionary is not enabled, this is treated as a primary encoding for a column.
1390 /// In case when dictionary is enabled for a column, this value is considered to
1391 /// be a fallback encoding.
1392 ///
1393 /// Panics if user tries to set dictionary encoding here, regardless of dictionary
1394 /// encoding flag being set. Use `set_dictionary_enabled` method to enable dictionary
1395 /// for a column.
1396 fn set_encoding(&mut self, value: Encoding) {
1397 if value == Encoding::PLAIN_DICTIONARY || value == Encoding::RLE_DICTIONARY {
1398 panic!("Dictionary encoding can not be used as fallback encoding");
1399 }
1400 self.encoding = Some(value);
1401 }
1402
1403 /// Sets compression codec for this column.
1404 fn set_compression(&mut self, value: Compression) {
1405 self.codec = Some(value);
1406 }
1407
1408 /// Sets data page size limit for this column.
1409 fn set_data_page_size_limit(&mut self, value: usize) {
1410 self.data_page_size_limit = Some(value);
1411 }
1412
1413 /// Sets whether dictionary encoding is enabled for this column.
1414 fn set_dictionary_enabled(&mut self, enabled: bool) {
1415 self.dictionary_enabled = Some(enabled);
1416 }
1417
1418 /// Sets dictionary page size limit for this column.
1419 fn set_dictionary_page_size_limit(&mut self, value: usize) {
1420 self.dictionary_page_size_limit = Some(value);
1421 }
1422
1423 /// Sets the statistics level for this column.
1424 fn set_statistics_enabled(&mut self, enabled: EnabledStatistics) {
1425 self.statistics_enabled = Some(enabled);
1426 }
1427
1428 /// Sets whether to write statistics in the page header for this column.
1429 fn set_write_page_header_statistics(&mut self, enabled: bool) {
1430 self.write_page_header_statistics = Some(enabled);
1431 }
1432
1433 /// If `value` is `true`, sets bloom filter properties to default values if not previously set,
1434 /// otherwise it is a no-op.
1435 /// If `value` is `false`, resets bloom filter properties to `None`.
1436 fn set_bloom_filter_enabled(&mut self, value: bool) {
1437 if value && self.bloom_filter_properties.is_none() {
1438 self.bloom_filter_properties = Some(Default::default())
1439 } else if !value {
1440 self.bloom_filter_properties = None
1441 }
1442 }
1443
1444 /// Sets the false positive probability for bloom filter for this column, and implicitly enables
1445 /// bloom filter if not previously enabled.
1446 ///
1447 /// # Panics
1448 ///
1449 /// Panics if the `value` is not between 0 and 1 exclusive
1450 fn set_bloom_filter_fpp(&mut self, value: f64) {
1451 assert!(
1452 value > 0. && value < 1.0,
1453 "fpp must be between 0 and 1 exclusive, got {value}"
1454 );
1455
1456 self.bloom_filter_properties
1457 .get_or_insert_with(Default::default)
1458 .fpp = value;
1459 }
1460
1461 /// Sets the maximum expected number of distinct (unique) values for bloom filter for this
1462 /// column, and implicitly enables bloom filter if not previously enabled.
1463 fn set_bloom_filter_ndv(&mut self, value: u64) {
1464 self.bloom_filter_properties
1465 .get_or_insert_with(Default::default)
1466 .ndv = value;
1467 self.bloom_filter_ndv_is_set = true;
1468 }
1469
1470 /// Sets the Data Page v2 compression ratio threshold for this column.
1471 ///
1472 /// # Panics
1473 /// If `value` is not finite or is not strictly positive.
1474 fn set_data_page_v2_compression_ratio_threshold(&mut self, value: f64) {
1475 assert!(
1476 value.is_finite() && value > 0.0,
1477 "data_page_v2_compression_ratio_threshold must be a positive finite number, got {value}"
1478 );
1479 self.data_page_v2_compression_ratio_threshold = Some(value);
1480 }
1481
1482 /// Returns optional encoding for this column.
1483 fn encoding(&self) -> Option<Encoding> {
1484 self.encoding
1485 }
1486
1487 /// Returns optional compression codec for this column.
1488 fn compression(&self) -> Option<Compression> {
1489 self.codec
1490 }
1491
1492 /// Returns `Some(true)` if dictionary encoding is enabled for this column, if
1493 /// disabled then returns `Some(false)`. If result is `None`, then no setting has
1494 /// been provided.
1495 fn dictionary_enabled(&self) -> Option<bool> {
1496 self.dictionary_enabled
1497 }
1498
1499 /// Returns optional dictionary page size limit for this column.
1500 fn dictionary_page_size_limit(&self) -> Option<usize> {
1501 self.dictionary_page_size_limit
1502 }
1503
1504 /// Returns optional data page size limit for this column.
1505 fn data_page_size_limit(&self) -> Option<usize> {
1506 self.data_page_size_limit
1507 }
1508
1509 /// Returns optional statistics level requested for this column. If result is `None`,
1510 /// then no setting has been provided.
1511 fn statistics_enabled(&self) -> Option<EnabledStatistics> {
1512 self.statistics_enabled
1513 }
1514
1515 /// Returns `Some(true)` if [`Statistics`] are to be written to the page header for this
1516 /// column.
1517 ///
1518 /// [`Statistics`]: crate::file::statistics::Statistics
1519 fn write_page_header_statistics(&self) -> Option<bool> {
1520 self.write_page_header_statistics
1521 }
1522
1523 /// Returns the bloom filter properties, or `None` if not enabled
1524 fn bloom_filter_properties(&self) -> Option<&BloomFilterProperties> {
1525 self.bloom_filter_properties.as_ref()
1526 }
1527
1528 /// Returns optional Data Page v2 compression ratio threshold for this column.
1529 fn data_page_v2_compression_ratio_threshold(&self) -> Option<f64> {
1530 self.data_page_v2_compression_ratio_threshold
1531 }
1532
1533 /// If bloom filter is enabled and NDV was not explicitly set, resolve it to the
1534 /// given `default_ndv` (typically derived from `max_row_group_row_count`).
1535 fn resolve_bloom_filter_ndv(&mut self, default_ndv: u64) {
1536 if !self.bloom_filter_ndv_is_set {
1537 if let Some(ref mut bf) = self.bloom_filter_properties {
1538 bf.ndv = default_ndv;
1539 }
1540 }
1541 }
1542}
1543
1544/// Reference counted reader properties.
1545pub type ReaderPropertiesPtr = Arc<ReaderProperties>;
1546
1547const DEFAULT_READ_BLOOM_FILTER: bool = false;
1548const DEFAULT_READ_PAGE_STATS: bool = false;
1549
1550/// Configuration settings for reading parquet files.
1551///
1552/// All properties are immutable and `Send` + `Sync`.
1553/// Use [`ReaderPropertiesBuilder`] to assemble these properties.
1554///
1555/// # Example
1556///
1557/// ```rust
1558/// use parquet::file::properties::ReaderProperties;
1559///
1560/// // Create properties with default configuration.
1561/// let props = ReaderProperties::builder().build();
1562///
1563/// // Use properties builder to set certain options and assemble the configuration.
1564/// let props = ReaderProperties::builder()
1565/// .set_backward_compatible_lz4(false)
1566/// .build();
1567/// ```
1568pub struct ReaderProperties {
1569 codec_options: CodecOptions,
1570 read_bloom_filter: bool,
1571 read_page_stats: bool,
1572}
1573
1574impl ReaderProperties {
1575 /// Returns builder for reader properties with default values.
1576 pub fn builder() -> ReaderPropertiesBuilder {
1577 ReaderPropertiesBuilder::with_defaults()
1578 }
1579
1580 /// Returns codec options.
1581 pub(crate) fn codec_options(&self) -> &CodecOptions {
1582 &self.codec_options
1583 }
1584
1585 /// Returns whether to read bloom filter
1586 pub(crate) fn read_bloom_filter(&self) -> bool {
1587 self.read_bloom_filter
1588 }
1589
1590 /// Returns whether to read page level statistics
1591 pub(crate) fn read_page_stats(&self) -> bool {
1592 self.read_page_stats
1593 }
1594}
1595
1596/// Builder for parquet file reader configuration. See example on
1597/// [`ReaderProperties`]
1598pub struct ReaderPropertiesBuilder {
1599 codec_options_builder: CodecOptionsBuilder,
1600 read_bloom_filter: Option<bool>,
1601 read_page_stats: Option<bool>,
1602}
1603
1604/// Reader properties builder.
1605impl ReaderPropertiesBuilder {
1606 /// Returns default state of the builder.
1607 fn with_defaults() -> Self {
1608 Self {
1609 codec_options_builder: CodecOptionsBuilder::default(),
1610 read_bloom_filter: None,
1611 read_page_stats: None,
1612 }
1613 }
1614
1615 /// Finalizes the configuration and returns immutable reader properties struct.
1616 pub fn build(self) -> ReaderProperties {
1617 ReaderProperties {
1618 codec_options: self.codec_options_builder.build(),
1619 read_bloom_filter: self.read_bloom_filter.unwrap_or(DEFAULT_READ_BLOOM_FILTER),
1620 read_page_stats: self.read_page_stats.unwrap_or(DEFAULT_READ_PAGE_STATS),
1621 }
1622 }
1623
1624 /// Enable/disable backward compatible LZ4.
1625 ///
1626 /// If backward compatible LZ4 is enable, on LZ4_HADOOP error it will fallback
1627 /// to the older versions LZ4 algorithms. That is LZ4_FRAME, for backward compatibility
1628 /// with files generated by older versions of this library, and LZ4_RAW, for backward
1629 /// compatibility with files generated by older versions of parquet-cpp.
1630 ///
1631 /// If backward compatible LZ4 is disabled, on LZ4_HADOOP error it will return the error.
1632 pub fn set_backward_compatible_lz4(mut self, value: bool) -> Self {
1633 self.codec_options_builder = self
1634 .codec_options_builder
1635 .set_backward_compatible_lz4(value);
1636 self
1637 }
1638
1639 /// Enable/disable reading bloom filter
1640 ///
1641 /// If reading bloom filter is enabled, bloom filter will be read from the file.
1642 /// If reading bloom filter is disabled, bloom filter will not be read from the file.
1643 ///
1644 /// By default bloom filter is set to be read.
1645 pub fn set_read_bloom_filter(mut self, value: bool) -> Self {
1646 self.read_bloom_filter = Some(value);
1647 self
1648 }
1649
1650 /// Enable/disable reading page-level statistics
1651 ///
1652 /// If set to `true`, then the reader will decode and populate the [`Statistics`] for
1653 /// each page, if present.
1654 /// If set to `false`, then the reader will skip decoding the statistics.
1655 ///
1656 /// By default statistics will not be decoded.
1657 ///
1658 /// [`Statistics`]: crate::file::statistics::Statistics
1659 pub fn set_read_page_statistics(mut self, value: bool) -> Self {
1660 self.read_page_stats = Some(value);
1661 self
1662 }
1663}
1664
1665#[cfg(test)]
1666mod tests {
1667 use super::*;
1668
1669 #[test]
1670 fn test_writer_version() {
1671 assert_eq!(WriterVersion::PARQUET_1_0.as_num(), 1);
1672 assert_eq!(WriterVersion::PARQUET_2_0.as_num(), 2);
1673 }
1674
1675 #[test]
1676 fn test_writer_properties_default_settings() {
1677 let props = WriterProperties::default();
1678 assert_eq!(props.data_page_size_limit(), DEFAULT_PAGE_SIZE);
1679 assert_eq!(
1680 props.dictionary_page_size_limit(),
1681 DEFAULT_DICTIONARY_PAGE_SIZE_LIMIT
1682 );
1683 assert_eq!(props.write_batch_size(), DEFAULT_WRITE_BATCH_SIZE);
1684 assert_eq!(
1685 props.max_row_group_row_count(),
1686 Some(DEFAULT_MAX_ROW_GROUP_ROW_COUNT)
1687 );
1688 assert_eq!(props.max_row_group_bytes(), None);
1689 assert_eq!(props.bloom_filter_position(), DEFAULT_BLOOM_FILTER_POSITION);
1690 assert_eq!(props.writer_version(), DEFAULT_WRITER_VERSION);
1691 assert_eq!(props.created_by(), DEFAULT_CREATED_BY);
1692 assert_eq!(props.key_value_metadata(), None);
1693 assert_eq!(props.encoding(&ColumnPath::from("col")), None);
1694 assert_eq!(
1695 props.compression(&ColumnPath::from("col")),
1696 DEFAULT_COMPRESSION
1697 );
1698 assert_eq!(
1699 props.dictionary_enabled(&ColumnPath::from("col")),
1700 DEFAULT_DICTIONARY_ENABLED
1701 );
1702 assert_eq!(
1703 props.statistics_enabled(&ColumnPath::from("col")),
1704 DEFAULT_STATISTICS_ENABLED
1705 );
1706 assert!(
1707 props
1708 .bloom_filter_properties(&ColumnPath::from("col"))
1709 .is_none()
1710 );
1711 }
1712
1713 #[test]
1714 fn test_writer_properties_dictionary_encoding() {
1715 // dictionary encoding is not configurable, and it should be the same for both
1716 // writer version 1 and 2.
1717 for version in &[WriterVersion::PARQUET_1_0, WriterVersion::PARQUET_2_0] {
1718 let props = WriterProperties::builder()
1719 .set_writer_version(*version)
1720 .build();
1721 assert_eq!(props.dictionary_page_encoding(), Encoding::PLAIN);
1722 assert_eq!(
1723 props.dictionary_data_page_encoding(),
1724 Encoding::RLE_DICTIONARY
1725 );
1726 }
1727 }
1728
1729 #[test]
1730 #[should_panic(expected = "Dictionary encoding can not be used as fallback encoding")]
1731 fn test_writer_properties_panic_when_plain_dictionary_is_fallback() {
1732 // Should panic when user specifies dictionary encoding as fallback encoding.
1733 WriterProperties::builder()
1734 .set_encoding(Encoding::PLAIN_DICTIONARY)
1735 .build();
1736 }
1737
1738 #[test]
1739 #[should_panic(expected = "Dictionary encoding can not be used as fallback encoding")]
1740 fn test_writer_properties_panic_when_rle_dictionary_is_fallback() {
1741 // Should panic when user specifies dictionary encoding as fallback encoding.
1742 WriterProperties::builder()
1743 .set_encoding(Encoding::RLE_DICTIONARY)
1744 .build();
1745 }
1746
1747 #[test]
1748 #[should_panic(expected = "Dictionary encoding can not be used as fallback encoding")]
1749 fn test_writer_properties_panic_when_dictionary_is_enabled() {
1750 WriterProperties::builder()
1751 .set_dictionary_enabled(true)
1752 .set_column_encoding(ColumnPath::from("col"), Encoding::RLE_DICTIONARY)
1753 .build();
1754 }
1755
1756 #[test]
1757 #[should_panic(expected = "Dictionary encoding can not be used as fallback encoding")]
1758 fn test_writer_properties_panic_when_dictionary_is_disabled() {
1759 WriterProperties::builder()
1760 .set_dictionary_enabled(false)
1761 .set_column_encoding(ColumnPath::from("col"), Encoding::RLE_DICTIONARY)
1762 .build();
1763 }
1764
1765 #[test]
1766 fn test_writer_properties_builder() {
1767 let props = WriterProperties::builder()
1768 // file settings
1769 .set_writer_version(WriterVersion::PARQUET_2_0)
1770 .set_data_page_size_limit(10)
1771 .set_dictionary_page_size_limit(20)
1772 .set_write_batch_size(30)
1773 .set_max_row_group_row_count(Some(40))
1774 .set_created_by("default".to_owned())
1775 .set_key_value_metadata(Some(vec![KeyValue::new(
1776 "key".to_string(),
1777 "value".to_string(),
1778 )]))
1779 // global column settings
1780 .set_encoding(Encoding::DELTA_BINARY_PACKED)
1781 .set_compression(Compression::GZIP(Default::default()))
1782 .set_dictionary_enabled(false)
1783 .set_statistics_enabled(EnabledStatistics::None)
1784 // specific column settings
1785 .set_column_encoding(ColumnPath::from("col"), Encoding::RLE)
1786 .set_column_compression(ColumnPath::from("col"), Compression::SNAPPY)
1787 .set_column_dictionary_enabled(ColumnPath::from("col"), true)
1788 .set_column_statistics_enabled(ColumnPath::from("col"), EnabledStatistics::Chunk)
1789 .set_column_bloom_filter_enabled(ColumnPath::from("col"), true)
1790 .set_column_bloom_filter_ndv(ColumnPath::from("col"), 100_u64)
1791 .set_column_bloom_filter_fpp(ColumnPath::from("col"), 0.1)
1792 .build();
1793
1794 fn test_props(props: &WriterProperties) {
1795 assert_eq!(props.writer_version(), WriterVersion::PARQUET_2_0);
1796 assert_eq!(props.data_page_size_limit(), 10);
1797 assert_eq!(props.dictionary_page_size_limit(), 20);
1798 assert_eq!(props.write_batch_size(), 30);
1799 assert_eq!(props.max_row_group_row_count(), Some(40));
1800 assert_eq!(props.created_by(), "default");
1801 assert_eq!(
1802 props.key_value_metadata(),
1803 Some(&vec![
1804 KeyValue::new("key".to_string(), "value".to_string(),)
1805 ])
1806 );
1807
1808 assert_eq!(
1809 props.encoding(&ColumnPath::from("a")),
1810 Some(Encoding::DELTA_BINARY_PACKED)
1811 );
1812 assert_eq!(
1813 props.compression(&ColumnPath::from("a")),
1814 Compression::GZIP(Default::default())
1815 );
1816 assert!(!props.dictionary_enabled(&ColumnPath::from("a")));
1817 assert_eq!(
1818 props.statistics_enabled(&ColumnPath::from("a")),
1819 EnabledStatistics::None
1820 );
1821
1822 assert_eq!(
1823 props.encoding(&ColumnPath::from("col")),
1824 Some(Encoding::RLE)
1825 );
1826 assert_eq!(
1827 props.compression(&ColumnPath::from("col")),
1828 Compression::SNAPPY
1829 );
1830 assert!(props.dictionary_enabled(&ColumnPath::from("col")));
1831 assert_eq!(
1832 props.statistics_enabled(&ColumnPath::from("col")),
1833 EnabledStatistics::Chunk
1834 );
1835 assert_eq!(
1836 props.bloom_filter_properties(&ColumnPath::from("col")),
1837 Some(&BloomFilterProperties { fpp: 0.1, ndv: 100 })
1838 );
1839 }
1840
1841 // Test direct build of properties
1842 test_props(&props);
1843
1844 // Test that into_builder() gives the same result
1845 let props_into_builder_and_back = props.into_builder().build();
1846 test_props(&props_into_builder_and_back);
1847 }
1848
1849 #[test]
1850 fn test_writer_properties_builder_partial_defaults() {
1851 let props = WriterProperties::builder()
1852 .set_encoding(Encoding::DELTA_BINARY_PACKED)
1853 .set_compression(Compression::GZIP(Default::default()))
1854 .set_bloom_filter_enabled(true)
1855 .set_column_encoding(ColumnPath::from("col"), Encoding::RLE)
1856 .build();
1857
1858 assert_eq!(
1859 props.encoding(&ColumnPath::from("col")),
1860 Some(Encoding::RLE)
1861 );
1862 assert_eq!(
1863 props.compression(&ColumnPath::from("col")),
1864 Compression::GZIP(Default::default())
1865 );
1866 assert_eq!(
1867 props.dictionary_enabled(&ColumnPath::from("col")),
1868 DEFAULT_DICTIONARY_ENABLED
1869 );
1870 assert_eq!(
1871 props.bloom_filter_properties(&ColumnPath::from("col")),
1872 Some(&BloomFilterProperties {
1873 fpp: DEFAULT_BLOOM_FILTER_FPP,
1874 ndv: DEFAULT_BLOOM_FILTER_NDV,
1875 })
1876 );
1877 }
1878
1879 #[test]
1880 #[allow(deprecated)]
1881 fn test_writer_properties_deprecated_max_row_group_size_still_works() {
1882 let props = WriterProperties::builder()
1883 .set_max_row_group_size(42)
1884 .build();
1885
1886 assert_eq!(props.max_row_group_row_count(), Some(42));
1887 assert_eq!(props.max_row_group_size(), 42);
1888 }
1889
1890 #[test]
1891 #[should_panic(expected = "Cannot have a 0 max row group row count")]
1892 fn test_writer_properties_panic_on_zero_row_group_row_count() {
1893 let _ = WriterProperties::builder().set_max_row_group_row_count(Some(0));
1894 }
1895
1896 #[test]
1897 #[should_panic(expected = "Cannot have a 0 max row group bytes")]
1898 fn test_writer_properties_panic_on_zero_row_group_bytes() {
1899 let _ = WriterProperties::builder().set_max_row_group_bytes(Some(0));
1900 }
1901
1902 #[test]
1903 fn test_writer_properties_bloom_filter_ndv_fpp_set() {
1904 assert_eq!(
1905 WriterProperties::builder()
1906 .build()
1907 .bloom_filter_properties(&ColumnPath::from("col")),
1908 None
1909 );
1910 assert_eq!(
1911 WriterProperties::builder()
1912 .set_bloom_filter_ndv(100)
1913 .build()
1914 .bloom_filter_properties(&ColumnPath::from("col")),
1915 Some(&BloomFilterProperties {
1916 fpp: DEFAULT_BLOOM_FILTER_FPP,
1917 ndv: 100,
1918 })
1919 );
1920 assert_eq!(
1921 WriterProperties::builder()
1922 .set_bloom_filter_fpp(0.1)
1923 .build()
1924 .bloom_filter_properties(&ColumnPath::from("col")),
1925 Some(&BloomFilterProperties {
1926 fpp: 0.1,
1927 ndv: DEFAULT_BLOOM_FILTER_NDV,
1928 })
1929 );
1930 }
1931
1932 #[test]
1933 fn test_writer_properties_column_data_page_v2_compression_ratio_threshold() {
1934 let props = WriterProperties::builder()
1935 .set_data_page_v2_compression_ratio_threshold(0.5)
1936 .set_column_data_page_v2_compression_ratio_threshold(ColumnPath::from("col"), 0.1)
1937 .build();
1938
1939 assert_eq!(props.data_page_v2_compression_ratio_threshold(), 0.5);
1940 assert_eq!(
1941 props.column_data_page_v2_compression_ratio_threshold(&ColumnPath::from("col")),
1942 0.1
1943 );
1944 assert_eq!(
1945 props.column_data_page_v2_compression_ratio_threshold(&ColumnPath::from("other")),
1946 0.5
1947 );
1948 }
1949
1950 #[test]
1951 #[should_panic(
1952 expected = "data_page_v2_compression_ratio_threshold must be a positive finite number"
1953 )]
1954 fn test_writer_properties_panic_on_invalid_data_page_v2_compression_ratio_threshold() {
1955 WriterProperties::builder()
1956 .set_data_page_v2_compression_ratio_threshold(0.0)
1957 .build();
1958 }
1959
1960 #[test]
1961 fn test_writer_properties_column_dictionary_page_size_limit() {
1962 let props = WriterProperties::builder()
1963 .set_dictionary_page_size_limit(100)
1964 .set_column_dictionary_page_size_limit(ColumnPath::from("col"), 10)
1965 .build();
1966
1967 assert_eq!(props.dictionary_page_size_limit(), 100);
1968 assert_eq!(
1969 props.column_dictionary_page_size_limit(&ColumnPath::from("col")),
1970 10
1971 );
1972 assert_eq!(
1973 props.column_dictionary_page_size_limit(&ColumnPath::from("other")),
1974 100
1975 );
1976 }
1977
1978 #[test]
1979 fn test_writer_properties_column_data_page_size_limit() {
1980 let props = WriterProperties::builder()
1981 .set_data_page_size_limit(100)
1982 .set_column_data_page_size_limit(ColumnPath::from("col"), 10)
1983 .build();
1984
1985 assert_eq!(props.data_page_size_limit(), 100);
1986 assert_eq!(
1987 props.column_data_page_size_limit(&ColumnPath::from("col")),
1988 10
1989 );
1990 assert_eq!(
1991 props.column_data_page_size_limit(&ColumnPath::from("other")),
1992 100
1993 );
1994 }
1995
1996 #[test]
1997 fn test_reader_properties_default_settings() {
1998 let props = ReaderProperties::builder().build();
1999
2000 let codec_options = CodecOptionsBuilder::default()
2001 .set_backward_compatible_lz4(true)
2002 .build();
2003
2004 assert_eq!(props.codec_options(), &codec_options);
2005 assert!(!props.read_bloom_filter());
2006 }
2007
2008 #[test]
2009 fn test_reader_properties_builder() {
2010 let props = ReaderProperties::builder()
2011 .set_backward_compatible_lz4(false)
2012 .build();
2013
2014 let codec_options = CodecOptionsBuilder::default()
2015 .set_backward_compatible_lz4(false)
2016 .build();
2017
2018 assert_eq!(props.codec_options(), &codec_options);
2019 }
2020
2021 #[test]
2022 fn test_parse_writerversion() {
2023 let mut writer_version = "PARQUET_1_0".parse::<WriterVersion>().unwrap();
2024 assert_eq!(writer_version, WriterVersion::PARQUET_1_0);
2025 writer_version = "PARQUET_2_0".parse::<WriterVersion>().unwrap();
2026 assert_eq!(writer_version, WriterVersion::PARQUET_2_0);
2027
2028 // test lowercase
2029 writer_version = "parquet_1_0".parse::<WriterVersion>().unwrap();
2030 assert_eq!(writer_version, WriterVersion::PARQUET_1_0);
2031
2032 // test invalid version
2033 match "PARQUET_-1_0".parse::<WriterVersion>() {
2034 Ok(_) => panic!("Should not be able to parse PARQUET_-1_0"),
2035 Err(e) => {
2036 assert_eq!(e, "Invalid writer version: PARQUET_-1_0");
2037 }
2038 }
2039 }
2040
2041 #[test]
2042 fn test_parse_enabledstatistics() {
2043 let mut enabled_statistics = "NONE".parse::<EnabledStatistics>().unwrap();
2044 assert_eq!(enabled_statistics, EnabledStatistics::None);
2045 enabled_statistics = "CHUNK".parse::<EnabledStatistics>().unwrap();
2046 assert_eq!(enabled_statistics, EnabledStatistics::Chunk);
2047 enabled_statistics = "PAGE".parse::<EnabledStatistics>().unwrap();
2048 assert_eq!(enabled_statistics, EnabledStatistics::Page);
2049
2050 // test lowercase
2051 enabled_statistics = "none".parse::<EnabledStatistics>().unwrap();
2052 assert_eq!(enabled_statistics, EnabledStatistics::None);
2053
2054 //test invalid statistics
2055 match "ChunkAndPage".parse::<EnabledStatistics>() {
2056 Ok(_) => panic!("Should not be able to parse ChunkAndPage"),
2057 Err(e) => {
2058 assert_eq!(e, "Invalid statistics arg: ChunkAndPage");
2059 }
2060 }
2061 }
2062
2063 #[test]
2064 fn test_cdc_options_equality() {
2065 let opts = CdcOptions::default();
2066 assert_eq!(opts, CdcOptions::default());
2067
2068 let custom = CdcOptions {
2069 min_chunk_size: 1024,
2070 max_chunk_size: 8192,
2071 norm_level: 1,
2072 };
2073 assert_eq!(custom, custom);
2074 assert_ne!(opts, custom);
2075 }
2076}