This is an automated email from the ASF dual-hosted git repository.
nicholasjiang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git
The following commit(s) were added to refs/heads/main by this push:
new 4275c6b feat(spec): Impl the ManifestFile read functionality (#69)
4275c6b is described below
commit 4275c6b70731e181e9f1c92fac737584a17f6c81
Author: WenjunMin <[email protected]>
AuthorDate: Mon Sep 9 19:55:47 2024 +0800
feat(spec): Impl the ManifestFile read functionality (#69)
---
crates/paimon/Cargo.toml | 1 +
crates/paimon/src/spec/data_file.rs | 44 ++++--
crates/paimon/src/spec/manifest_entry.rs | 130 +++++++++++++++++
crates/paimon/src/spec/manifest_file_meta.rs | 59 +-------
crates/paimon/src/spec/manifest_list.rs | 111 --------------
crates/paimon/src/spec/mod.rs | 7 +-
crates/paimon/src/spec/objects_file.rs | 162 +++++++++++++++++++++
crates/paimon/src/spec/stats.rs | 77 ++++++++++
...manifest-8ded1f09-fcda-489e-9167-582ac0f9f846-0 | Bin 0 -> 1787 bytes
9 files changed, 403 insertions(+), 188 deletions(-)
diff --git a/crates/paimon/Cargo.toml b/crates/paimon/Cargo.toml
index 959475f..ceb6100 100644
--- a/crates/paimon/Cargo.toml
+++ b/crates/paimon/Cargo.toml
@@ -44,6 +44,7 @@ serde = { version = "1", features = ["derive"] }
serde_bytes = "0.11.15"
serde_json = "1.0.120"
serde_with = "3.9.0"
+serde_repr = "0.1"
snafu = "0.8.3"
typed-builder = "^0.19"
opendal = { version = "0.49", features = ["services-fs"] }
diff --git a/crates/paimon/src/spec/data_file.rs
b/crates/paimon/src/spec/data_file.rs
index 37165e6..17fab24 100644
--- a/crates/paimon/src/spec/data_file.rs
+++ b/crates/paimon/src/spec/data_file.rs
@@ -15,7 +15,9 @@
// specific language governing permissions and limitations
// under the License.
-use crate::spec::RowType;
+use crate::spec::stats::BinaryTableStats;
+use chrono::serde::ts_milliseconds::deserialize as from_millis;
+use chrono::serde::ts_milliseconds::serialize as to_millis;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
@@ -48,12 +50,6 @@ impl BinaryRow {
}
}
-/// TODO: implement me.
-/// The statistics for columns, supports the following stats.
-///
-/// Impl References:
<https://github.com/apache/paimon/blob/release-0.8.2/paimon-core/src/main/java/org/apache/paimon/stats/SimpleStats.java>
-type SimpleStats = ();
-
/// The Source of a file.
/// TODO: move me to the manifest module.
///
@@ -72,25 +68,43 @@ pub enum FileSource {
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DataFileMeta {
+ #[serde(rename = "_FILE_NAME")]
pub file_name: String,
+ #[serde(rename = "_FILE_SIZE")]
pub file_size: i64,
// row_count tells the total number of rows (including add & delete) in
this file.
+ #[serde(rename = "_ROW_COUNT")]
pub row_count: i64,
- pub min_key: BinaryRow,
- pub max_key: BinaryRow,
- pub key_stats: SimpleStats,
- pub value_stats: SimpleStats,
+ #[serde(rename = "_MIN_KEY", with = "serde_bytes")]
+ pub min_key: Vec<u8>,
+ #[serde(rename = "_MAX_KEY", with = "serde_bytes")]
+ pub max_key: Vec<u8>,
+ #[serde(rename = "_KEY_STATS")]
+ pub key_stats: BinaryTableStats,
+ #[serde(rename = "_VALUE_STATS")]
+ pub value_stats: BinaryTableStats,
+ #[serde(rename = "_MIN_SEQUENCE_NUMBER")]
pub min_sequence_number: i64,
+ #[serde(rename = "_MAX_SEQUENCE_NUMBER")]
pub max_sequence_number: i64,
+ #[serde(rename = "_SCHEMA_ID")]
pub schema_id: i64,
+ #[serde(rename = "_LEVEL")]
pub level: i32,
+ #[serde(rename = "_EXTRA_FILES")]
pub extra_files: Vec<String>,
+ #[serde(
+ rename = "_CREATION_TIME",
+ serialize_with = "to_millis",
+ deserialize_with = "from_millis"
+ )]
pub creation_time: DateTime<Utc>,
+ #[serde(rename = "_DELETE_ROW_COUNT")]
// rowCount = add_row_count + delete_row_count.
pub delete_row_count: Option<i64>,
// file index filter bytes, if it is small, store in data file meta
+ #[serde(rename = "_EMBEDDED_FILE_INDEX", with = "serde_bytes")]
pub embedded_index: Option<Vec<u8>>,
- pub file_source: Option<FileSource>,
}
impl Display for DataFileMeta {
@@ -99,7 +113,5 @@ impl Display for DataFileMeta {
}
}
-impl DataFileMeta {
- // TODO: implement me
- pub const SCHEMA: RowType = RowType::new(vec![]);
-}
+#[allow(dead_code)]
+impl DataFileMeta {}
diff --git a/crates/paimon/src/spec/manifest_entry.rs
b/crates/paimon/src/spec/manifest_entry.rs
new file mode 100644
index 0000000..848d19b
--- /dev/null
+++ b/crates/paimon/src/spec/manifest_entry.rs
@@ -0,0 +1,130 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use crate::spec::DataFileMeta;
+use serde::Deserialize;
+use serde_repr::{Deserialize_repr, Serialize_repr};
+use serde_with::serde_derive::Serialize;
+
+/// The same {@link Identifier} indicates that the {@link ManifestEntry}
refers to the same data file.
+///
+/// Impl Reference:
<https://github.com/apache/paimon/blob/release-0.8.2/paimon-core/src/main/java/org/apache/paimon/manifest/FileEntry.java#L58>
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub struct Identifier {
+ pub partition: Vec<u8>,
+ pub bucket: i32,
+ pub level: i32,
+ pub file_name: String,
+}
+
+/// Kind of a file.
+/// Impl Reference:
<https://github.com/apache/paimon/blob/release-0.8.2/paimon-core/src/main/java/org/apache/paimon/manifest/FileKind.java>
+#[derive(PartialEq, Eq, Debug, Clone, Serialize_repr, Deserialize_repr)]
+#[repr(u8)]
+pub enum FileKind {
+ Add = 0,
+ Delete = 1,
+}
+
+/// Entry of a manifest file, representing an addition / deletion of a data
file.
+/// Impl Reference:
<https://github.com/apache/paimon/blob/release-0.8.2/paimon-core/src/main/java/org/apache/paimon/manifest/ManifestEntry.java>
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct ManifestEntry {
+ #[serde(rename = "_KIND")]
+ kind: FileKind,
+
+ #[serde(rename = "_PARTITION", with = "serde_bytes")]
+ partition: Vec<u8>,
+
+ #[serde(rename = "_BUCKET")]
+ bucket: i32,
+
+ #[serde(rename = "_TOTAL_BUCKETS")]
+ total_buckets: i32,
+
+ #[serde(rename = "_FILE")]
+ file: DataFileMeta,
+
+ #[serde(rename = "_VERSION")]
+ version: i32,
+}
+
+#[allow(dead_code)]
+impl ManifestEntry {
+ fn kind(&self) -> &FileKind {
+ &self.kind
+ }
+
+ fn partition(&self) -> &Vec<u8> {
+ &self.partition
+ }
+
+ fn bucket(&self) -> i32 {
+ self.bucket
+ }
+
+ fn level(&self) -> i32 {
+ self.file.level
+ }
+
+ fn file_name(&self) -> &str {
+ &self.file.file_name
+ }
+
+ fn min_key(&self) -> &Vec<u8> {
+ &self.file.min_key
+ }
+
+ fn max_key(&self) -> &Vec<u8> {
+ &self.file.max_key
+ }
+
+ fn identifier(&self) -> Identifier {
+ Identifier {
+ partition: self.partition.clone(),
+ bucket: self.bucket,
+ level: self.file.level,
+ file_name: self.file.file_name.clone(),
+ }
+ }
+
+ pub fn total_buckets(&self) -> i32 {
+ self.total_buckets
+ }
+
+ pub fn file(&self) -> &DataFileMeta {
+ &self.file
+ }
+
+ pub fn new(
+ kind: FileKind,
+ partition: Vec<u8>,
+ bucket: i32,
+ total_buckets: i32,
+ file: DataFileMeta,
+ version: i32,
+ ) -> Self {
+ ManifestEntry {
+ kind,
+ partition,
+ bucket,
+ total_buckets,
+ file,
+ version,
+ }
+ }
+}
diff --git a/crates/paimon/src/spec/manifest_file_meta.rs
b/crates/paimon/src/spec/manifest_file_meta.rs
index 382d579..36f92b9 100644
--- a/crates/paimon/src/spec/manifest_file_meta.rs
+++ b/crates/paimon/src/spec/manifest_file_meta.rs
@@ -15,6 +15,7 @@
// specific language governing permissions and limitations
// under the License.
+use crate::spec::stats::BinaryTableStats;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
@@ -128,61 +129,3 @@ impl Display for ManifestFileMeta {
)
}
}
-
-/// The statistics for columns, supports the following stats.
-///
-/// All statistics are stored in the form of a Binary, which can significantly
reduce its memory consumption, but the cost is that the column type needs to be
known when getting.
-///
-/// Impl Reference:
<https://github.com/apache/paimon/blob/release-0.8.2/paimon-core/src/main/java/org/apache/paimon/stats/FieldStatsArraySerializer.java#L111>
-#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
-pub struct BinaryTableStats {
- /// the minimum values of the columns
- #[serde(rename = "_MIN_VALUES", with = "serde_bytes")]
- min_values: Vec<u8>,
-
- /// the maximum values of the columns
- #[serde(rename = "_MAX_VALUES", with = "serde_bytes")]
- max_values: Vec<u8>,
-
- /// the number of nulls of the columns
- #[serde(rename = "_NULL_COUNTS")]
- null_counts: Vec<i64>,
-}
-
-impl BinaryTableStats {
- /// Get the minimum values of the columns
- #[inline]
- pub fn min_values(&self) -> &[u8] {
- &self.min_values
- }
-
- /// Get the maximum values of the columns
- #[inline]
- pub fn max_values(&self) -> &[u8] {
- &self.max_values
- }
-
- /// Get the number of nulls of the columns
- #[inline]
- pub fn null_counts(&self) -> &Vec<i64> {
- &self.null_counts
- }
-
- pub fn new(
- min_values: Vec<u8>,
- max_values: Vec<u8>,
- null_counts: Vec<i64>,
- ) -> BinaryTableStats {
- Self {
- min_values,
- max_values,
- null_counts,
- }
- }
-}
-
-impl Display for BinaryTableStats {
- fn fmt(&self, _: &mut Formatter<'_>) -> std::fmt::Result {
- todo!()
- }
-}
diff --git a/crates/paimon/src/spec/manifest_list.rs
b/crates/paimon/src/spec/manifest_list.rs
deleted file mode 100644
index 2cffd5c..0000000
--- a/crates/paimon/src/spec/manifest_list.rs
+++ /dev/null
@@ -1,111 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-use super::manifest_file_meta::ManifestFileMeta;
-use crate::io::FileIO;
-use crate::{Error, Result};
-use apache_avro::types::Value;
-use apache_avro::{from_value, Reader};
-use serde::{Deserialize, Serialize};
-
-#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(transparent)]
-/// This file includes several [`ManifestFileMeta`], representing all data of
the whole table at the corresponding snapshot.
-pub struct ManifestList {
- entries: Vec<ManifestFileMeta>,
-}
-
-impl ManifestList {
- pub fn entries(&self) -> &Vec<ManifestFileMeta> {
- &self.entries
- }
-
- pub fn from_avro_bytes(bytes: &[u8]) -> Result<ManifestList> {
- let reader = Reader::new(bytes).map_err(Error::from)?;
- let records = reader
- .collect::<std::result::Result<Vec<Value>, _>>()
- .map_err(Error::from)?;
- let values = Value::Array(records);
- from_value::<ManifestList>(&values).map_err(Error::from)
- }
-}
-
-pub struct ManifestListFactory {
- file_io: FileIO,
-}
-
-/// The factory to read and write [`ManifestList`]
-impl ManifestListFactory {
- pub fn new(file_io: FileIO) -> ManifestListFactory {
- Self { file_io }
- }
-
- /// Write several [`ManifestFileMeta`]s into a manifest list.
- ///
- /// NOTE: This method is atomic.
- pub fn write(&mut self, _metas: Vec<ManifestFileMeta>) -> &str {
- todo!()
- }
-
- /// Read [`ManifestList`] from the manifest file.
- pub async fn read(&self, path: &str) -> Result<ManifestList> {
- let bs = self.file_io.new_input(path)?.read().await?;
- // todo support other formats
- ManifestList::from_avro_bytes(bs.as_ref())
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::spec::{BinaryTableStats, ManifestFileMeta, ManifestList};
-
- #[tokio::test]
- async fn test_read_manifest_list() {
- let workdir =
- std::env::current_dir().unwrap_or_else(|err| panic!("current_dir
must exist: {err}"));
- let path = workdir
-
.join("tests/fixtures/manifest/manifest-list-5c7399a0-46ae-4a5e-9c13-3ab07212cdb6-0");
- let v = std::fs::read(path.to_str().unwrap()).unwrap();
- let res = ManifestList::from_avro_bytes(&v).unwrap();
- let value_bytes = vec![
- 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 49, 0,
0, 0, 0, 0, 0, 129,
- ];
- assert_eq!(
- res,
- ManifestList {
- entries: vec![
- ManifestFileMeta::new(
-
"manifest-19d138df-233f-46f7-beb6-fadaf4741c0e".to_string(),
- 10,
- 10,
- 10,
- BinaryTableStats::new(value_bytes.clone(),
value_bytes.clone(), vec![1, 2]),
- 1
- ),
- ManifestFileMeta::new(
-
"manifest-a703ee48-c411-413e-b84e-c03bdb179631".to_string(),
- 11,
- 0,
- 10,
- BinaryTableStats::new(value_bytes.clone(),
value_bytes.clone(), vec![1, 2]),
- 2
- )
- ],
- }
- );
- }
-}
diff --git a/crates/paimon/src/spec/mod.rs b/crates/paimon/src/spec/mod.rs
index 80fb47d..59c7d00 100644
--- a/crates/paimon/src/spec/mod.rs
+++ b/crates/paimon/src/spec/mod.rs
@@ -34,8 +34,9 @@ pub use snapshot::*;
mod manifest_file_meta;
pub use manifest_file_meta::*;
-mod manifest_list;
-pub use manifest_list::*;
-
+mod manifest_entry;
+mod objects_file;
+mod stats;
mod types;
+
pub use types::*;
diff --git a/crates/paimon/src/spec/objects_file.rs
b/crates/paimon/src/spec/objects_file.rs
new file mode 100644
index 0000000..5135c32
--- /dev/null
+++ b/crates/paimon/src/spec/objects_file.rs
@@ -0,0 +1,162 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use crate::Error;
+use apache_avro::types::Value;
+use apache_avro::{from_value, Reader};
+use serde::de::DeserializeOwned;
+
+#[allow(dead_code)]
+pub fn from_avro_bytes<T: DeserializeOwned>(bytes: &[u8]) ->
crate::Result<Vec<T>> {
+ let reader = Reader::new(bytes).map_err(Error::from)?;
+ let records = reader
+ .collect::<Result<Vec<Value>, _>>()
+ .map_err(Error::from)?;
+ let values = Value::Array(records);
+ from_value::<Vec<T>>(&values).map_err(Error::from)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::spec::manifest_entry::{FileKind, ManifestEntry};
+ use crate::spec::objects_file::from_avro_bytes;
+ use crate::spec::stats::BinaryTableStats;
+ use crate::spec::{DataFileMeta, ManifestFileMeta};
+ use chrono::{DateTime, Utc};
+
+ #[tokio::test]
+ async fn test_read_manifest_list() {
+ let workdir =
+ std::env::current_dir().unwrap_or_else(|err| panic!("current_dir
must exist: {err}"));
+ let path = workdir
+
.join("tests/fixtures/manifest/manifest-list-5c7399a0-46ae-4a5e-9c13-3ab07212cdb6-0");
+ let v = std::fs::read(path.to_str().unwrap()).unwrap();
+ let res = from_avro_bytes::<ManifestFileMeta>(&v).unwrap();
+ let value_bytes = vec![
+ 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 49, 0,
0, 0, 0, 0, 0, 129,
+ ];
+ assert_eq!(
+ res,
+ vec![
+ ManifestFileMeta::new(
+
"manifest-19d138df-233f-46f7-beb6-fadaf4741c0e".to_string(),
+ 10,
+ 10,
+ 10,
+ BinaryTableStats::new(value_bytes.clone(),
value_bytes.clone(), vec![1, 2]),
+ 1
+ ),
+ ManifestFileMeta::new(
+
"manifest-a703ee48-c411-413e-b84e-c03bdb179631".to_string(),
+ 11,
+ 0,
+ 10,
+ BinaryTableStats::new(value_bytes.clone(),
value_bytes.clone(), vec![1, 2]),
+ 2
+ )
+ ],
+ );
+ }
+
+ #[tokio::test]
+ async fn test_read_manifest_entry() {
+ let workdir =
+ std::env::current_dir().unwrap_or_else(|err| panic!("current_dir
must exist: {err}"));
+ let path =
+
workdir.join("tests/fixtures/manifest/manifest-8ded1f09-fcda-489e-9167-582ac0f9f846-0");
+ let v = std::fs::read(path.to_str().unwrap()).unwrap();
+ let res = from_avro_bytes::<ManifestEntry>(&v).unwrap();
+ let value_bytes = vec![
+ 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0, 129, 1,
0, 0, 0, 0, 0, 0, 0,
+ ];
+ let single_value = vec![0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0];
+ assert_eq!(
+ res,
+ vec![
+ ManifestEntry::new(
+ FileKind::Delete,
+ single_value.clone(),
+ 1,
+ 10,
+ DataFileMeta {
+ file_name: "f1.parquet".to_string(),
+
+ file_size: 10,
+ row_count: 100,
+ min_key: single_value.clone(),
+ max_key: single_value.clone(),
+ key_stats: BinaryTableStats::new(
+ value_bytes.clone(),
+ value_bytes.clone(),
+ vec![1, 2]
+ ),
+ value_stats: BinaryTableStats::new(
+ value_bytes.clone(),
+ value_bytes.clone(),
+ vec![1, 2]
+ ),
+ min_sequence_number: 1,
+ max_sequence_number: 100,
+ schema_id: 0,
+ level: 1,
+ extra_files: vec![],
+ creation_time: "2024-09-06T07:45:55.039+00:00"
+ .parse::<DateTime<Utc>>()
+ .unwrap(),
+ delete_row_count: Some(0),
+ embedded_index: None,
+ },
+ 2
+ ),
+ ManifestEntry::new(
+ FileKind::Add,
+ single_value.clone(),
+ 2,
+ 10,
+ DataFileMeta {
+ file_name: "f2.parquet".to_string(),
+ file_size: 10,
+ row_count: 100,
+ min_key: single_value.clone(),
+ max_key: single_value.clone(),
+ key_stats: BinaryTableStats::new(
+ value_bytes.clone(),
+ value_bytes.clone(),
+ vec![1, 2]
+ ),
+ value_stats: BinaryTableStats::new(
+ value_bytes.clone(),
+ value_bytes.clone(),
+ vec![1, 2]
+ ),
+ min_sequence_number: 1,
+ max_sequence_number: 100,
+ schema_id: 0,
+ level: 1,
+ extra_files: vec![],
+ creation_time: "2024-09-06T07:45:55.039+00:00"
+ .parse::<DateTime<Utc>>()
+ .unwrap(),
+ delete_row_count: Some(1),
+ embedded_index: None,
+ },
+ 2
+ ),
+ ]
+ )
+ }
+}
diff --git a/crates/paimon/src/spec/stats.rs b/crates/paimon/src/spec/stats.rs
new file mode 100644
index 0000000..98923ce
--- /dev/null
+++ b/crates/paimon/src/spec/stats.rs
@@ -0,0 +1,77 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use serde::{Deserialize, Serialize};
+use std::fmt::{Display, Formatter};
+
+/// The statistics for columns, supports the following stats.
+///
+/// All statistics are stored in the form of a Binary, which can significantly
reduce its memory consumption, but the cost is that the column type needs to be
known when getting.
+///
+/// Impl Reference:
<https://github.com/apache/paimon/blob/release-0.8.2/paimon-core/src/main/java/org/apache/paimon/stats/FieldStatsArraySerializer.java#L111>
+#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
+pub struct BinaryTableStats {
+ /// the minimum values of the columns
+ #[serde(rename = "_MIN_VALUES", with = "serde_bytes")]
+ min_values: Vec<u8>,
+
+ /// the maximum values of the columns
+ #[serde(rename = "_MAX_VALUES", with = "serde_bytes")]
+ max_values: Vec<u8>,
+
+ /// the number of nulls of the columns
+ #[serde(rename = "_NULL_COUNTS")]
+ null_counts: Vec<i64>,
+}
+
+impl BinaryTableStats {
+ /// Get the minimum values of the columns
+ #[inline]
+ pub fn min_values(&self) -> &[u8] {
+ &self.min_values
+ }
+
+ /// Get the maximum values of the columns
+ #[inline]
+ pub fn max_values(&self) -> &[u8] {
+ &self.max_values
+ }
+
+ /// Get the number of nulls of the columns
+ #[inline]
+ pub fn null_counts(&self) -> &Vec<i64> {
+ &self.null_counts
+ }
+
+ pub fn new(
+ min_values: Vec<u8>,
+ max_values: Vec<u8>,
+ null_counts: Vec<i64>,
+ ) -> BinaryTableStats {
+ Self {
+ min_values,
+ max_values,
+ null_counts,
+ }
+ }
+}
+
+impl Display for BinaryTableStats {
+ fn fmt(&self, _: &mut Formatter<'_>) -> std::fmt::Result {
+ todo!()
+ }
+}
diff --git
a/crates/paimon/tests/fixtures/manifest/manifest-8ded1f09-fcda-489e-9167-582ac0f9f846-0
b/crates/paimon/tests/fixtures/manifest/manifest-8ded1f09-fcda-489e-9167-582ac0f9f846-0
new file mode 100644
index 0000000..57a7ef4
Binary files /dev/null and
b/crates/paimon/tests/fixtures/manifest/manifest-8ded1f09-fcda-489e-9167-582ac0f9f846-0
differ