This is an automated email from the ASF dual-hosted git repository.
tustvold pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
The following commit(s) were added to refs/heads/master by this push:
new 7b94b08c7 Support compression levels (#3847)
7b94b08c7 is described below
commit 7b94b08c7d98e1f449955e2f31a94b871dc3e78e
Author: bold <[email protected]>
AuthorDate: Tue Mar 14 13:10:47 2023 +0100
Support compression levels (#3847)
* Support zstd compression levels
* Support gzip compression levels
* Fix tests
* Support brotli compression level
* Fix tests
* Add tests for all supported compression levels
---
parquet/src/basic.rs | 52 +++++++----
parquet/src/bin/parquet-fromcsv.rs | 21 +++--
parquet/src/bin/parquet-layout.rs | 6 +-
parquet/src/bin/parquet-rewrite.rs | 6 +-
parquet/src/compression.rs | 182 +++++++++++++++++++++++++++++++------
parquet/src/file/properties.rs | 11 ++-
6 files changed, 217 insertions(+), 61 deletions(-)
diff --git a/parquet/src/basic.rs b/parquet/src/basic.rs
index e971c8632..9f4f4ee1d 100644
--- a/parquet/src/basic.rs
+++ b/parquet/src/basic.rs
@@ -20,6 +20,7 @@
use std::{fmt, str};
+use crate::compression::{BrotliLevel, GzipLevel, ZstdLevel};
use crate::format as parquet;
use crate::errors::{ParquetError, Result};
@@ -286,11 +287,11 @@ pub enum Encoding {
pub enum Compression {
UNCOMPRESSED,
SNAPPY,
- GZIP,
+ GZIP(GzipLevel),
LZO,
- BROTLI,
+ BROTLI(BrotliLevel),
LZ4,
- ZSTD,
+ ZSTD(ZstdLevel),
LZ4_RAW,
}
@@ -830,11 +831,11 @@ impl TryFrom<parquet::CompressionCodec> for Compression {
Ok(match value {
parquet::CompressionCodec::UNCOMPRESSED =>
Compression::UNCOMPRESSED,
parquet::CompressionCodec::SNAPPY => Compression::SNAPPY,
- parquet::CompressionCodec::GZIP => Compression::GZIP,
+ parquet::CompressionCodec::GZIP =>
Compression::GZIP(Default::default()),
parquet::CompressionCodec::LZO => Compression::LZO,
- parquet::CompressionCodec::BROTLI => Compression::BROTLI,
+ parquet::CompressionCodec::BROTLI =>
Compression::BROTLI(Default::default()),
parquet::CompressionCodec::LZ4 => Compression::LZ4,
- parquet::CompressionCodec::ZSTD => Compression::ZSTD,
+ parquet::CompressionCodec::ZSTD =>
Compression::ZSTD(Default::default()),
parquet::CompressionCodec::LZ4_RAW => Compression::LZ4_RAW,
_ => {
return Err(general_err!(
@@ -851,11 +852,11 @@ impl From<Compression> for parquet::CompressionCodec {
match value {
Compression::UNCOMPRESSED =>
parquet::CompressionCodec::UNCOMPRESSED,
Compression::SNAPPY => parquet::CompressionCodec::SNAPPY,
- Compression::GZIP => parquet::CompressionCodec::GZIP,
+ Compression::GZIP(_) => parquet::CompressionCodec::GZIP,
Compression::LZO => parquet::CompressionCodec::LZO,
- Compression::BROTLI => parquet::CompressionCodec::BROTLI,
+ Compression::BROTLI(_) => parquet::CompressionCodec::BROTLI,
Compression::LZ4 => parquet::CompressionCodec::LZ4,
- Compression::ZSTD => parquet::CompressionCodec::ZSTD,
+ Compression::ZSTD(_) => parquet::CompressionCodec::ZSTD,
Compression::LZ4_RAW => parquet::CompressionCodec::LZ4_RAW,
}
}
@@ -1783,11 +1784,20 @@ mod tests {
fn test_display_compression() {
assert_eq!(Compression::UNCOMPRESSED.to_string(), "UNCOMPRESSED");
assert_eq!(Compression::SNAPPY.to_string(), "SNAPPY");
- assert_eq!(Compression::GZIP.to_string(), "GZIP");
+ assert_eq!(
+ Compression::GZIP(Default::default()).to_string(),
+ "GZIP(GzipLevel(6))"
+ );
assert_eq!(Compression::LZO.to_string(), "LZO");
- assert_eq!(Compression::BROTLI.to_string(), "BROTLI");
+ assert_eq!(
+ Compression::BROTLI(Default::default()).to_string(),
+ "BROTLI(BrotliLevel(1))"
+ );
assert_eq!(Compression::LZ4.to_string(), "LZ4");
- assert_eq!(Compression::ZSTD.to_string(), "ZSTD");
+ assert_eq!(
+ Compression::ZSTD(Default::default()).to_string(),
+ "ZSTD(ZstdLevel(1))"
+ );
}
#[test]
@@ -1802,7 +1812,7 @@ mod tests {
);
assert_eq!(
Compression::try_from(parquet::CompressionCodec::GZIP).unwrap(),
- Compression::GZIP
+ Compression::GZIP(Default::default())
);
assert_eq!(
Compression::try_from(parquet::CompressionCodec::LZO).unwrap(),
@@ -1810,7 +1820,7 @@ mod tests {
);
assert_eq!(
Compression::try_from(parquet::CompressionCodec::BROTLI).unwrap(),
- Compression::BROTLI
+ Compression::BROTLI(Default::default())
);
assert_eq!(
Compression::try_from(parquet::CompressionCodec::LZ4).unwrap(),
@@ -1818,7 +1828,7 @@ mod tests {
);
assert_eq!(
Compression::try_from(parquet::CompressionCodec::ZSTD).unwrap(),
- Compression::ZSTD
+ Compression::ZSTD(Default::default())
);
}
@@ -1832,14 +1842,20 @@ mod tests {
parquet::CompressionCodec::SNAPPY,
Compression::SNAPPY.into()
);
- assert_eq!(parquet::CompressionCodec::GZIP, Compression::GZIP.into());
+ assert_eq!(
+ parquet::CompressionCodec::GZIP,
+ Compression::GZIP(Default::default()).into()
+ );
assert_eq!(parquet::CompressionCodec::LZO, Compression::LZO.into());
assert_eq!(
parquet::CompressionCodec::BROTLI,
- Compression::BROTLI.into()
+ Compression::BROTLI(Default::default()).into()
);
assert_eq!(parquet::CompressionCodec::LZ4, Compression::LZ4.into());
- assert_eq!(parquet::CompressionCodec::ZSTD, Compression::ZSTD.into());
+ assert_eq!(
+ parquet::CompressionCodec::ZSTD,
+ Compression::ZSTD(Default::default()).into()
+ );
}
#[test]
diff --git a/parquet/src/bin/parquet-fromcsv.rs
b/parquet/src/bin/parquet-fromcsv.rs
index b1de492f5..0a9950e9c 100644
--- a/parquet/src/bin/parquet-fromcsv.rs
+++ b/parquet/src/bin/parquet-fromcsv.rs
@@ -213,11 +213,11 @@ fn compression_from_str(cmp: &str) -> Result<Compression,
String> {
match cmp.to_uppercase().as_str() {
"UNCOMPRESSED" => Ok(Compression::UNCOMPRESSED),
"SNAPPY" => Ok(Compression::SNAPPY),
- "GZIP" => Ok(Compression::GZIP),
+ "GZIP" => Ok(Compression::GZIP(Default::default())),
"LZO" => Ok(Compression::LZO),
- "BROTLI" => Ok(Compression::BROTLI),
+ "BROTLI" => Ok(Compression::BROTLI(Default::default())),
"LZ4" => Ok(Compression::LZ4),
- "ZSTD" => Ok(Compression::ZSTD),
+ "ZSTD" => Ok(Compression::ZSTD(Default::default())),
v => Err(
format!("Unknown compression {v} : possible values UNCOMPRESSED,
SNAPPY, GZIP, LZO, BROTLI, LZ4, ZSTD \n\nFor more information try --help")
)
@@ -507,15 +507,24 @@ mod tests {
let args = parse_args(vec!["--parquet-compression",
"snappy"]).unwrap();
assert_eq!(args.parquet_compression, Compression::SNAPPY);
let args = parse_args(vec!["--parquet-compression", "gzip"]).unwrap();
- assert_eq!(args.parquet_compression, Compression::GZIP);
+ assert_eq!(
+ args.parquet_compression,
+ Compression::GZIP(Default::default())
+ );
let args = parse_args(vec!["--parquet-compression", "lzo"]).unwrap();
assert_eq!(args.parquet_compression, Compression::LZO);
let args = parse_args(vec!["--parquet-compression", "lz4"]).unwrap();
assert_eq!(args.parquet_compression, Compression::LZ4);
let args = parse_args(vec!["--parquet-compression",
"brotli"]).unwrap();
- assert_eq!(args.parquet_compression, Compression::BROTLI);
+ assert_eq!(
+ args.parquet_compression,
+ Compression::BROTLI(Default::default())
+ );
let args = parse_args(vec!["--parquet-compression", "zstd"]).unwrap();
- assert_eq!(args.parquet_compression, Compression::ZSTD);
+ assert_eq!(
+ args.parquet_compression,
+ Compression::ZSTD(Default::default())
+ );
}
#[test]
diff --git a/parquet/src/bin/parquet-layout.rs
b/parquet/src/bin/parquet-layout.rs
index 7a685d206..7278c718c 100644
--- a/parquet/src/bin/parquet-layout.rs
+++ b/parquet/src/bin/parquet-layout.rs
@@ -184,11 +184,11 @@ fn compression(compression: Compression) ->
Option<&'static str> {
match compression {
Compression::UNCOMPRESSED => None,
Compression::SNAPPY => Some("snappy"),
- Compression::GZIP => Some("gzip"),
+ Compression::GZIP(_) => Some("gzip"),
Compression::LZO => Some("lzo"),
- Compression::BROTLI => Some("brotli"),
+ Compression::BROTLI(_) => Some("brotli"),
Compression::LZ4 => Some("lz4"),
- Compression::ZSTD => Some("zstd"),
+ Compression::ZSTD(_) => Some("zstd"),
Compression::LZ4_RAW => Some("lz4_raw"),
}
}
diff --git a/parquet/src/bin/parquet-rewrite.rs
b/parquet/src/bin/parquet-rewrite.rs
index cd60225ca..57e8885c3 100644
--- a/parquet/src/bin/parquet-rewrite.rs
+++ b/parquet/src/bin/parquet-rewrite.rs
@@ -79,11 +79,11 @@ impl From<CompressionArgs> for Compression {
match value {
CompressionArgs::None => Self::UNCOMPRESSED,
CompressionArgs::Snappy => Self::SNAPPY,
- CompressionArgs::Gzip => Self::GZIP,
+ CompressionArgs::Gzip => Self::GZIP(Default::default()),
CompressionArgs::Lzo => Self::LZO,
- CompressionArgs::Brotli => Self::BROTLI,
+ CompressionArgs::Brotli => Self::BROTLI(Default::default()),
CompressionArgs::Lz4 => Self::LZ4,
- CompressionArgs::Zstd => Self::ZSTD,
+ CompressionArgs::Zstd => Self::ZSTD(Default::default()),
CompressionArgs::Lz4Raw => Self::LZ4_RAW,
}
}
diff --git a/parquet/src/compression.rs b/parquet/src/compression.rs
index 4ee321609..4c4057e7a 100644
--- a/parquet/src/compression.rs
+++ b/parquet/src/compression.rs
@@ -121,6 +121,26 @@ impl CodecOptionsBuilder {
}
}
+/// Defines valid compression levels.
+pub(crate) trait CompressionLevel<T: std::fmt::Display + std::cmp::PartialOrd>
{
+ const MINIMUM_LEVEL: T;
+ const MAXIMUM_LEVEL: T;
+
+ /// Tests if the provided compression level is valid.
+ fn is_valid_level(level: T) -> Result<()> {
+ let compression_range = Self::MINIMUM_LEVEL..=Self::MAXIMUM_LEVEL;
+ if compression_range.contains(&level) {
+ Ok(())
+ } else {
+ Err(ParquetError::General(format!(
+ "valid compression range {}..={} exceeded.",
+ compression_range.start(),
+ compression_range.end()
+ )))
+ }
+ }
+}
+
/// Given the compression type `codec`, returns a codec used to compress and
decompress
/// bytes for the compression type.
/// This returns `None` if the codec type is `UNCOMPRESSED`.
@@ -130,9 +150,9 @@ pub fn create_codec(
) -> Result<Option<Box<dyn Codec>>> {
match codec {
#[cfg(any(feature = "brotli", test))]
- CodecType::BROTLI => Ok(Some(Box::new(BrotliCodec::new()))),
+ CodecType::BROTLI(level) =>
Ok(Some(Box::new(BrotliCodec::new(level)))),
#[cfg(any(feature = "flate2", test))]
- CodecType::GZIP => Ok(Some(Box::new(GZipCodec::new()))),
+ CodecType::GZIP(level) => Ok(Some(Box::new(GZipCodec::new(level)))),
#[cfg(any(feature = "snap", test))]
CodecType::SNAPPY => Ok(Some(Box::new(SnappyCodec::new()))),
#[cfg(any(feature = "lz4", test))]
@@ -140,7 +160,7 @@ pub fn create_codec(
_options.backward_compatible_lz4,
)))),
#[cfg(any(feature = "zstd", test))]
- CodecType::ZSTD => Ok(Some(Box::new(ZSTDCodec::new()))),
+ CodecType::ZSTD(level) => Ok(Some(Box::new(ZSTDCodec::new(level)))),
#[cfg(any(feature = "lz4", test))]
CodecType::LZ4_RAW => Ok(Some(Box::new(LZ4RawCodec::new()))),
CodecType::UNCOMPRESSED => Ok(None),
@@ -214,13 +234,17 @@ mod gzip_codec {
use crate::compression::Codec;
use crate::errors::Result;
+ use super::GzipLevel;
+
/// Codec for GZIP compression algorithm.
- pub struct GZipCodec {}
+ pub struct GZipCodec {
+ level: GzipLevel,
+ }
impl GZipCodec {
/// Creates new GZIP compression codec.
- pub(crate) fn new() -> Self {
- Self {}
+ pub(crate) fn new(level: GzipLevel) -> Self {
+ Self { level }
}
}
@@ -236,7 +260,8 @@ mod gzip_codec {
}
fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) ->
Result<()> {
- let mut encoder = write::GzEncoder::new(output_buf,
Compression::default());
+ let mut encoder =
+ write::GzEncoder::new(output_buf,
Compression::new(self.level.0));
encoder.write_all(input_buf)?;
encoder.try_finish().map_err(|e| e.into())
}
@@ -245,6 +270,37 @@ mod gzip_codec {
#[cfg(any(feature = "flate2", test))]
pub use gzip_codec::*;
+/// Represents a valid gzip compression level.
+#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
+pub struct GzipLevel(u32);
+
+impl Default for GzipLevel {
+ fn default() -> Self {
+ // The default as of miniz_oxide 0.5.1 is 6 for compression level
+ // (miniz_oxide::deflate::CompressionLevel::DefaultLevel)
+ Self(6)
+ }
+}
+
+impl CompressionLevel<u32> for GzipLevel {
+ const MINIMUM_LEVEL: u32 = 0;
+ const MAXIMUM_LEVEL: u32 = 10;
+}
+
+impl GzipLevel {
+ /// Attempts to create a gzip compression level.
+ ///
+ /// Compression levels must be valid (i.e. be acceptable for
[`flate2::Compression`]).
+ pub fn try_new(level: u32) -> Result<Self> {
+ Self::is_valid_level(level).map(|_| Self(level))
+ }
+
+ /// Returns the compression level.
+ pub fn compression_level(&self) -> u32 {
+ self.0
+ }
+}
+
#[cfg(any(feature = "brotli", test))]
mod brotli_codec {
@@ -253,17 +309,20 @@ mod brotli_codec {
use crate::compression::Codec;
use crate::errors::Result;
+ use super::BrotliLevel;
+
const BROTLI_DEFAULT_BUFFER_SIZE: usize = 4096;
- const BROTLI_DEFAULT_COMPRESSION_QUALITY: u32 = 1; // supported levels 0-9
const BROTLI_DEFAULT_LG_WINDOW_SIZE: u32 = 22; // recommended between 20-22
/// Codec for Brotli compression algorithm.
- pub struct BrotliCodec {}
+ pub struct BrotliCodec {
+ level: BrotliLevel,
+ }
impl BrotliCodec {
/// Creates new Brotli compression codec.
- pub(crate) fn new() -> Self {
- Self {}
+ pub(crate) fn new(level: BrotliLevel) -> Self {
+ Self { level }
}
}
@@ -284,7 +343,7 @@ mod brotli_codec {
let mut encoder = brotli::CompressorWriter::new(
output_buf,
BROTLI_DEFAULT_BUFFER_SIZE,
- BROTLI_DEFAULT_COMPRESSION_QUALITY,
+ self.level.0,
BROTLI_DEFAULT_LG_WINDOW_SIZE,
);
encoder.write_all(input_buf)?;
@@ -295,6 +354,35 @@ mod brotli_codec {
#[cfg(any(feature = "brotli", test))]
pub use brotli_codec::*;
+/// Represents a valid brotli compression level.
+#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
+pub struct BrotliLevel(u32);
+
+impl Default for BrotliLevel {
+ fn default() -> Self {
+ Self(1)
+ }
+}
+
+impl CompressionLevel<u32> for BrotliLevel {
+ const MINIMUM_LEVEL: u32 = 0;
+ const MAXIMUM_LEVEL: u32 = 11;
+}
+
+impl BrotliLevel {
+ /// Attempts to create a brotli compression level.
+ ///
+ /// Compression levels must be valid.
+ pub fn try_new(level: u32) -> Result<Self> {
+ Self::is_valid_level(level).map(|_| Self(level))
+ }
+
+ /// Returns the compression level.
+ pub fn compression_level(&self) -> u32 {
+ self.0
+ }
+}
+
#[cfg(any(feature = "lz4", test))]
mod lz4_codec {
use std::io::{Read, Write};
@@ -357,22 +445,21 @@ pub use lz4_codec::*;
mod zstd_codec {
use std::io::{self, Write};
- use crate::compression::Codec;
+ use crate::compression::{Codec, ZstdLevel};
use crate::errors::Result;
/// Codec for Zstandard compression algorithm.
- pub struct ZSTDCodec {}
+ pub struct ZSTDCodec {
+ level: ZstdLevel,
+ }
impl ZSTDCodec {
/// Creates new Zstandard compression codec.
- pub(crate) fn new() -> Self {
- Self {}
+ pub(crate) fn new(level: ZstdLevel) -> Self {
+ Self { level }
}
}
- /// Compression level (1-21) for ZSTD. Choose 1 here for better
compression speed.
- const ZSTD_COMPRESSION_LEVEL: i32 = 1;
-
impl Codec for ZSTDCodec {
fn decompress(
&mut self,
@@ -388,7 +475,7 @@ mod zstd_codec {
}
fn compress(&mut self, input_buf: &[u8], output_buf: &mut Vec<u8>) ->
Result<()> {
- let mut encoder = zstd::Encoder::new(output_buf,
ZSTD_COMPRESSION_LEVEL)?;
+ let mut encoder = zstd::Encoder::new(output_buf, self.level.0)?;
encoder.write_all(input_buf)?;
match encoder.finish() {
Ok(_) => Ok(()),
@@ -400,6 +487,37 @@ mod zstd_codec {
#[cfg(any(feature = "zstd", test))]
pub use zstd_codec::*;
+/// Represents a valid zstd compression level.
+#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
+pub struct ZstdLevel(i32);
+
+impl CompressionLevel<i32> for ZstdLevel {
+ // zstd binds to C, and hence zstd::compression_level_range() is not const
as this calls the
+ // underlying C library.
+ const MINIMUM_LEVEL: i32 = 1;
+ const MAXIMUM_LEVEL: i32 = 22;
+}
+
+impl ZstdLevel {
+ /// Attempts to create a zstd compression level from a given compression
level.
+ ///
+ /// Compression levels must be valid (i.e. be acceptable for
[`zstd::compression_level_range`]).
+ pub fn try_new(level: i32) -> Result<Self> {
+ Self::is_valid_level(level).map(|_| Self(level))
+ }
+
+ /// Returns the compression level.
+ pub fn compression_level(&self) -> i32 {
+ self.0
+ }
+}
+
+impl Default for ZstdLevel {
+ fn default() -> Self {
+ Self(1)
+ }
+}
+
#[cfg(any(feature = "lz4", test))]
mod lz4_raw_codec {
use crate::compression::Codec;
@@ -647,7 +765,8 @@ mod lz4_hadoop_codec {
let compressed_size = compressed_size as u32;
let uncompressed_size = input_buf.len() as u32;
output_buf[..SIZE_U32].copy_from_slice(&uncompressed_size.to_be_bytes());
-
output_buf[SIZE_U32..PREFIX_LEN].copy_from_slice(&compressed_size.to_be_bytes());
+ output_buf[SIZE_U32..PREFIX_LEN]
+ .copy_from_slice(&compressed_size.to_be_bytes());
Ok(())
}
@@ -742,14 +861,20 @@ mod tests {
#[test]
fn test_codec_gzip() {
- test_codec_with_size(CodecType::GZIP);
- test_codec_without_size(CodecType::GZIP);
+ for level in GzipLevel::MINIMUM_LEVEL..=GzipLevel::MAXIMUM_LEVEL {
+ let level = GzipLevel::try_new(level).unwrap();
+ test_codec_with_size(CodecType::GZIP(level));
+ test_codec_without_size(CodecType::GZIP(level));
+ }
}
#[test]
fn test_codec_brotli() {
- test_codec_with_size(CodecType::BROTLI);
- test_codec_without_size(CodecType::BROTLI);
+ for level in BrotliLevel::MINIMUM_LEVEL..=BrotliLevel::MAXIMUM_LEVEL {
+ let level = BrotliLevel::try_new(level).unwrap();
+ test_codec_with_size(CodecType::BROTLI(level));
+ test_codec_without_size(CodecType::BROTLI(level));
+ }
}
#[test]
@@ -759,8 +884,11 @@ mod tests {
#[test]
fn test_codec_zstd() {
- test_codec_with_size(CodecType::ZSTD);
- test_codec_without_size(CodecType::ZSTD);
+ for level in ZstdLevel::MINIMUM_LEVEL..=ZstdLevel::MAXIMUM_LEVEL {
+ let level = ZstdLevel::try_new(level).unwrap();
+ test_codec_with_size(CodecType::ZSTD(level));
+ test_codec_without_size(CodecType::ZSTD(level));
+ }
}
#[test]
diff --git a/parquet/src/file/properties.rs b/parquet/src/file/properties.rs
index 2ce0050c9..1d6f38dcd 100644
--- a/parquet/src/file/properties.rs
+++ b/parquet/src/file/properties.rs
@@ -937,7 +937,7 @@ mod tests {
)]))
// global column settings
.set_encoding(Encoding::DELTA_BINARY_PACKED)
- .set_compression(Compression::GZIP)
+ .set_compression(Compression::GZIP(Default::default()))
.set_dictionary_enabled(false)
.set_statistics_enabled(EnabledStatistics::None)
.set_max_statistics_size(50)
@@ -972,7 +972,10 @@ mod tests {
props.encoding(&ColumnPath::from("a")),
Some(Encoding::DELTA_BINARY_PACKED)
);
- assert_eq!(props.compression(&ColumnPath::from("a")),
Compression::GZIP);
+ assert_eq!(
+ props.compression(&ColumnPath::from("a")),
+ Compression::GZIP(Default::default())
+ );
assert!(!props.dictionary_enabled(&ColumnPath::from("a")));
assert_eq!(
props.statistics_enabled(&ColumnPath::from("a")),
@@ -1004,7 +1007,7 @@ mod tests {
fn test_writer_properties_builder_partial_defaults() {
let props = WriterProperties::builder()
.set_encoding(Encoding::DELTA_BINARY_PACKED)
- .set_compression(Compression::GZIP)
+ .set_compression(Compression::GZIP(Default::default()))
.set_bloom_filter_enabled(true)
.set_column_encoding(ColumnPath::from("col"), Encoding::RLE)
.build();
@@ -1015,7 +1018,7 @@ mod tests {
);
assert_eq!(
props.compression(&ColumnPath::from("col")),
- Compression::GZIP
+ Compression::GZIP(Default::default())
);
assert_eq!(
props.dictionary_enabled(&ColumnPath::from("col")),