-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[thrift-remodel] Refactor Thrift encryption and store encodings as bitmask #8587
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
09fc5ea
b430682
a886995
c94c263
591d1ec
6a84472
c5dd00d
8568892
78744ab
34ad674
7d6e13b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ use std::str::FromStr; | |
| use std::{fmt, str}; | ||
|
|
||
| pub use crate::compression::{BrotliLevel, GzipLevel, ZstdLevel}; | ||
| use crate::file::metadata::HeapSize; | ||
| use crate::parquet_thrift::{ | ||
| ElementType, FieldType, ReadThrift, ThriftCompactInputProtocol, ThriftCompactOutputProtocol, | ||
| WriteThrift, WriteThriftField, | ||
|
|
@@ -724,6 +725,123 @@ impl FromStr for Encoding { | |
| } | ||
| } | ||
|
|
||
| /// A bitmask representing the [`Encoding`]s employed while encoding a Parquet column chunk. | ||
| /// | ||
| /// The Parquet [`ColumnMetaData`] struct contains an array that indicates what encodings were | ||
| /// used when writing that column chunk. For memory and performance reasons, this crate reduces | ||
| /// that array to bitmask, where each bit position represents a different [`Encoding`]. This | ||
| /// struct contains that bitmask, and provides methods to interact with the data. | ||
| /// | ||
| /// # Example | ||
| /// ```no_run | ||
| /// # use parquet::file::metadata::ParquetMetaDataReader; | ||
| /// # use parquet::basic::Encoding; | ||
| /// # fn open_parquet_file(path: &str) -> std::fs::File { unimplemented!(); } | ||
| /// // read parquet metadata from a file | ||
| /// let file = open_parquet_file("some_path.parquet"); | ||
| /// let mut reader = ParquetMetaDataReader::new(); | ||
| /// reader.try_parse(&file).unwrap(); | ||
| /// let metadata = reader.finish().unwrap(); | ||
| /// | ||
| /// // find the encodings used by the first column chunk in the first row group | ||
| /// let col_meta = metadata.row_group(0).column(0); | ||
| /// let encodings = col_meta.encodings_mask(); | ||
| /// | ||
| /// // check to see if a particular encoding was used | ||
| /// let used_rle = encodings.is_set(Encoding::RLE); | ||
| /// | ||
| /// // check to see if all of a set of encodings were used | ||
| /// let used_all = encodings.all_set([Encoding::RLE, Encoding::PLAIN].iter()); | ||
| /// | ||
| /// // convert mask to a Vec<Encoding> | ||
| /// let encodings_vec = encodings.encodings().collect::<Vec<_>>(); | ||
| /// ``` | ||
| /// | ||
| /// [`ColumnMetaData`]: https://github.com/apache/parquet-format/blob/9fd57b59e0ce1a82a69237dcf8977d3e72a2965d/src/main/thrift/parquet.thrift#L875 | ||
| #[derive(Debug, Clone, Default, PartialEq, Eq)] | ||
| pub struct EncodingMask(i32); | ||
|
|
||
| impl EncodingMask { | ||
| const MAX_ENCODING: i32 = Encoding::BYTE_STREAM_SPLIT as i32; | ||
|
|
||
| /// Create a new `EncodingMask` from an integer. | ||
| pub fn new(val: i32) -> Self { | ||
| Self(val) | ||
| } | ||
|
|
||
| /// Return an integer representation of this `EncodingMask`. | ||
| pub fn as_i32(&self) -> i32 { | ||
| self.0 | ||
| } | ||
|
|
||
| /// Create a new `EncodingMask` from a collection of [`Encoding`]s. | ||
| pub fn new_from_encodings<'a>(encodings: impl Iterator<Item = &'a Encoding>) -> Self { | ||
| let mut mask = 0; | ||
| for &e in encodings { | ||
| mask |= 1 << (e as i32); | ||
| } | ||
| Self(mask) | ||
| } | ||
|
|
||
| /// Test if a given [`Encoding`] is present in this mask. | ||
| pub fn is_set(&self, val: Encoding) -> bool { | ||
| self.0 & (1 << (val as i32)) != 0 | ||
| } | ||
|
|
||
| /// Test if all [`Encoding`]s in a given set are present in this mask. | ||
| pub fn all_set<'a>(&self, mut encodings: impl Iterator<Item = &'a Encoding>) -> bool { | ||
| encodings.all(|&e| self.is_set(e)) | ||
| } | ||
|
|
||
| /// Return an iterator over all [`Encoding`]s present in this mask. | ||
| pub fn encodings(&self) -> impl Iterator<Item = Encoding> { | ||
| Self::mask_to_encodings_iter(self.0) | ||
| } | ||
|
|
||
| fn mask_to_encodings_iter(mask: i32) -> impl Iterator<Item = Encoding> { | ||
| (0..=Self::MAX_ENCODING) | ||
| .filter(move |i| mask & (1 << i) != 0) | ||
| .map(i32_to_encoding) | ||
| } | ||
| } | ||
|
|
||
| impl HeapSize for EncodingMask { | ||
| fn heap_size(&self) -> usize { | ||
| 0 // no heap allocations | ||
| } | ||
| } | ||
|
|
||
| impl<'a, R: ThriftCompactInputProtocol<'a>> ReadThrift<'a, R> for EncodingMask { | ||
| fn read_thrift(prot: &mut R) -> Result<Self> { | ||
| let mut mask = 0; | ||
|
|
||
| let list_ident = prot.read_list_begin()?; | ||
| for _ in 0..list_ident.size { | ||
| let val = i32::read_thrift(prot)?; | ||
| if (0..=Self::MAX_ENCODING).contains(&val) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been thinking about this representation some more since I proposed it. The main usecase is to detect whether any used encoding is not yet supported. I think this means we also need to handle bits outside the current
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks @jhorstmann, that's an excellent point. I think for each enum/union we should figure out what we want to do when we encounter an unknown field. For now they all (with the exception of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, and I was wondering if |
||
| mask |= 1 << val; | ||
| } | ||
| } | ||
| Ok(Self(mask)) | ||
| } | ||
| } | ||
|
|
||
| #[allow(deprecated)] | ||
|
alamb marked this conversation as resolved.
|
||
| fn i32_to_encoding(val: i32) -> Encoding { | ||
| match val { | ||
| 0 => Encoding::PLAIN, | ||
| 2 => Encoding::PLAIN_DICTIONARY, | ||
| 3 => Encoding::RLE, | ||
| 4 => Encoding::BIT_PACKED, | ||
| 5 => Encoding::DELTA_BINARY_PACKED, | ||
| 6 => Encoding::DELTA_LENGTH_BYTE_ARRAY, | ||
| 7 => Encoding::DELTA_BYTE_ARRAY, | ||
| 8 => Encoding::RLE_DICTIONARY, | ||
| 9 => Encoding::BYTE_STREAM_SPLIT, | ||
| _ => panic!("Impossible encoding {val}"), | ||
| } | ||
| } | ||
|
|
||
| // ---------------------------------------------------------------------- | ||
| // Mirrors thrift enum `CompressionCodec` | ||
|
|
||
|
|
@@ -2409,4 +2527,38 @@ mod tests { | |
| assert_eq!(EdgeInterpolationAlgorithm::ANDOYER.to_string(), "ANDOYER"); | ||
| assert_eq!(EdgeInterpolationAlgorithm::KARNEY.to_string(), "KARNEY"); | ||
| } | ||
|
|
||
| fn encodings_roundtrip(mut encodings: Vec<Encoding>) { | ||
| encodings.sort(); | ||
| let mask = EncodingMask::new_from_encodings(encodings.iter()); | ||
| assert!(mask.all_set(encodings.iter())); | ||
| let v = mask.encodings().collect::<Vec<_>>(); | ||
| assert_eq!(v, encodings); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_encoding_roundtrip() { | ||
| encodings_roundtrip( | ||
| [ | ||
| Encoding::RLE, | ||
| Encoding::PLAIN, | ||
| Encoding::DELTA_BINARY_PACKED, | ||
| ] | ||
| .into(), | ||
| ); | ||
| encodings_roundtrip([Encoding::RLE_DICTIONARY, Encoding::PLAIN_DICTIONARY].into()); | ||
| encodings_roundtrip([].into()); | ||
| let encodings = [ | ||
| Encoding::PLAIN, | ||
| Encoding::BIT_PACKED, | ||
| Encoding::RLE, | ||
| Encoding::DELTA_BINARY_PACKED, | ||
| Encoding::DELTA_BYTE_ARRAY, | ||
| Encoding::DELTA_LENGTH_BYTE_ARRAY, | ||
| Encoding::PLAIN_DICTIONARY, | ||
| Encoding::RLE_DICTIONARY, | ||
| Encoding::BYTE_STREAM_SPLIT, | ||
| ]; | ||
| encodings_roundtrip(encodings.into()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️