Skip to content

Commit 5ceebd9

Browse files
authored
Fix column encoding so it matches the underlying madness (#58)
1 parent be7908d commit 5ceebd9

4 files changed

Lines changed: 291 additions & 15 deletions

File tree

src/commands/transform.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ pub async fn run(args: TransformCommand) -> Result<()> {
4747
parquet_statistics,
4848
parquet_writer_version,
4949
parquet_no_dictionary,
50+
parquet_column_dictionary,
51+
parquet_column_no_dictionary,
5052
parquet_encoding,
5153
parquet_column_encoding,
5254
parquet_sorted_metadata,
@@ -200,6 +202,8 @@ pub async fn run(args: TransformCommand) -> Result<()> {
200202
parquet_statistics,
201203
parquet_writer_version,
202204
parquet_no_dictionary,
205+
parquet_column_dictionary,
206+
parquet_column_no_dictionary,
203207
parquet_encoding,
204208
parquet_column_encoding,
205209
bloom_filter,
@@ -338,6 +342,8 @@ fn create_sink_factory(
338342
parquet_statistics: Option<ParquetStatistics>,
339343
parquet_writer_version: Option<ParquetWriterVersion>,
340344
parquet_no_dictionary: bool,
345+
parquet_column_dictionary: Vec<String>,
346+
parquet_column_no_dictionary: Vec<String>,
341347
parquet_encoding: Option<ParquetEncoding>,
342348
parquet_column_encoding: Vec<ColumnEncodingConfig>,
343349
parquet_bloom_filter: BloomFilterConfig,
@@ -380,6 +386,8 @@ fn create_sink_factory(
380386
options = options.with_no_dictionary(true);
381387
}
382388
options = options
389+
.with_column_dictionary(parquet_column_dictionary.clone())
390+
.with_column_no_dictionary(parquet_column_no_dictionary.clone())
383391
.with_encoding(parquet_encoding)
384392
.with_column_encodings(parquet_column_encoding.clone())
385393
.with_bloom_filters(parquet_bloom_filter.clone());

src/lib.rs

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -792,31 +792,63 @@ pub struct TransformCommand {
792792
#[arg(long, value_enum)]
793793
pub parquet_writer_version: Option<ParquetWriterVersion>,
794794

795-
/// Disable dictionary encoding for Parquet columns.
796-
#[arg(long)]
795+
/// Disable dictionary encoding globally for all Parquet columns.
796+
///
797+
/// Dictionary encoding builds a dictionary of unique values and stores references to it,
798+
/// which is very effective for low-cardinality columns (few unique values). When disabled,
799+
/// columns use their data page encoding directly.
800+
///
801+
/// Default: dictionary encoding is enabled.
802+
///
803+
/// Use --parquet-column-dictionary or --parquet-column-no-dictionary to override
804+
/// this setting for specific columns.
805+
#[arg(long, verbatim_doc_comment)]
797806
pub parquet_no_dictionary: bool,
798807

799-
/// Encoding for Parquet column data pages.
808+
/// Enable dictionary encoding for specific columns. Can be specified multiple times.
800809
///
801-
/// If dictionary encoding is disabled (via --parquet-no-dictionary), this is the primary
802-
/// encoding for all columns. If dictionary encoding is enabled (the default), this is the
803-
/// fallback encoding when dictionary encoding falls back (e.g., dictionary size exceeded).
810+
/// Overrides --parquet-no-dictionary for the named columns, enabling dictionary encoding
811+
/// even when it's globally disabled.
804812
///
805-
/// Options: plain, rle, delta-binary-packed, delta-length-byte-array, delta-byte-array, byte-stream-split
806-
#[arg(long, value_enum)]
813+
/// Useful when most columns have high cardinality (dictionary disabled globally) but
814+
/// a few columns have low cardinality and would benefit from dictionary encoding.
815+
#[arg(long, value_name = "COLUMN", verbatim_doc_comment)]
816+
pub parquet_column_dictionary: Vec<String>,
817+
818+
/// Disable dictionary encoding for specific columns. Can be specified multiple times.
819+
///
820+
/// Overrides the default (dictionary enabled) for the named columns.
821+
///
822+
/// Useful for high-cardinality columns like UUIDs or timestamps where dictionary
823+
/// encoding adds overhead without compression benefit.
824+
#[arg(long, value_name = "COLUMN", verbatim_doc_comment)]
825+
pub parquet_column_no_dictionary: Vec<String>,
826+
827+
/// Data page encoding for Parquet columns.
828+
///
829+
/// This encoding is used for column data pages. Its role depends on dictionary encoding:
830+
/// - Dictionary enabled (default): this is the fallback encoding, used when the dictionary
831+
/// becomes too large or is inefficient for the data.
832+
/// - Dictionary disabled: this is the primary encoding for all data.
833+
///
834+
/// If not specified, the writer automatically selects an encoding based on column type
835+
/// and writer version. With Parquet v2: integers use delta-binary-packed, strings use
836+
/// delta-byte-array, booleans use rle. With Parquet v1: everything uses plain.
837+
///
838+
/// Options: plain, rle, delta-binary-packed, delta-length-byte-array, delta-byte-array,
839+
/// byte-stream-split
840+
#[arg(long, value_enum, verbatim_doc_comment)]
807841
pub parquet_encoding: Option<ParquetEncoding>,
808842

809-
/// Set encoding for specific columns. Can be specified multiple times.
810-
/// Overrides --parquet-encoding for the named column.
843+
/// Set data page encoding for specific columns. Can be specified multiple times.
811844
///
812-
/// If dictionary encoding is disabled for this column, this is the primary encoding.
813-
/// If dictionary encoding is enabled (the default), this is the fallback encoding
814-
/// when dictionary encoding falls back (e.g., dictionary size exceeded).
845+
/// Overrides --parquet-encoding for the named column. See --parquet-encoding for
846+
/// how this interacts with dictionary encoding.
815847
///
816848
/// Format: COLUMN=ENCODING
817849
///
818-
/// Encoding options: plain, rle, delta-binary-packed, delta-length-byte-array,
819-
/// delta-byte-array, byte-stream-split
850+
/// Options: plain, rle, delta-binary-packed, delta-length-byte-array, delta-byte-array,
851+
/// byte-stream-split
820852
///
821853
/// Examples:
822854
/// --parquet-column-encoding id=delta-binary-packed

src/sinks/parquet/mod.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub struct ParquetSinkOptions {
3232
bloom_filters: BloomFilterConfig,
3333
statistics: ParquetStatistics,
3434
no_dictionary: bool,
35+
column_dictionary: Vec<String>,
36+
column_no_dictionary: Vec<String>,
3537
writer_version: ParquetWriterVersion,
3638
encoding: Option<ParquetEncoding>,
3739
column_encodings: Vec<ColumnEncodingConfig>,
@@ -56,6 +58,8 @@ impl ParquetSinkOptions {
5658
bloom_filters: BloomFilterConfig::default(),
5759
statistics: ParquetStatistics::default(),
5860
no_dictionary: false,
61+
column_dictionary: Vec::new(),
62+
column_no_dictionary: Vec::new(),
5963
writer_version: ParquetWriterVersion::default(),
6064
encoding: None,
6165
column_encodings: Vec::new(),
@@ -104,6 +108,16 @@ impl ParquetSinkOptions {
104108
self
105109
}
106110

111+
pub fn with_column_dictionary(mut self, column_dictionary: Vec<String>) -> Self {
112+
self.column_dictionary = column_dictionary;
113+
self
114+
}
115+
116+
pub fn with_column_no_dictionary(mut self, column_no_dictionary: Vec<String>) -> Self {
117+
self.column_no_dictionary = column_no_dictionary;
118+
self
119+
}
120+
107121
pub fn with_writer_version(mut self, writer_version: ParquetWriterVersion) -> Self {
108122
self.writer_version = writer_version;
109123
self
@@ -153,6 +167,12 @@ impl ParquetSink {
153167
.set_statistics_enabled(options.statistics.into())
154168
.set_dictionary_enabled(!options.no_dictionary);
155169

170+
writer_builder = Self::apply_column_dictionary(
171+
writer_builder,
172+
&options.column_dictionary,
173+
&options.column_no_dictionary,
174+
);
175+
156176
writer_builder =
157177
Self::apply_encodings(writer_builder, &options.encoding, &options.column_encodings);
158178

@@ -187,6 +207,24 @@ impl ParquetSink {
187207
Ok(sink)
188208
}
189209

210+
fn apply_column_dictionary(
211+
mut builder: WriterPropertiesBuilder,
212+
column_dictionary: &[String],
213+
column_no_dictionary: &[String],
214+
) -> WriterPropertiesBuilder {
215+
for col_name in column_dictionary {
216+
let col_path = ColumnPath::from(col_name.as_str());
217+
builder = builder.set_column_dictionary_enabled(col_path, true);
218+
}
219+
220+
for col_name in column_no_dictionary {
221+
let col_path = ColumnPath::from(col_name.as_str());
222+
builder = builder.set_column_dictionary_enabled(col_path, false);
223+
}
224+
225+
builder
226+
}
227+
190228
fn apply_encodings(
191229
mut builder: WriterPropertiesBuilder,
192230
default_encoding: &Option<ParquetEncoding>,

0 commit comments

Comments
 (0)