mod _const_schema;
pub(super) mod _serde;
mod manifest_file;
mod reader;
mod writer;
use apache_avro::types::Value;
use apache_avro::{Reader, from_value};
pub use manifest_file::*;
pub use reader::*;
pub use serde_bytes::ByteBuf;
pub use writer::*;
use self::_const_schema::MANIFEST_LIST_AVRO_SCHEMA_V1;
use super::FormatVersion;
use crate::error::Result;
pub const UNASSIGNED_SEQUENCE_NUMBER: i64 = -1;
#[derive(Debug, Clone, PartialEq)]
pub struct ManifestList {
entries: Vec<ManifestFile>,
}
impl ManifestList {
pub fn parse_with_version(bs: &[u8], version: FormatVersion) -> Result<ManifestList> {
match version {
FormatVersion::V1 => {
let reader = Reader::with_schema(&MANIFEST_LIST_AVRO_SCHEMA_V1, bs)?;
let values = Value::Array(reader.collect::<std::result::Result<Vec<Value>, _>>()?);
from_value::<_serde::ManifestListV1>(&values)?.try_into()
}
FormatVersion::V2 => {
let reader = Reader::new(bs)?;
let values = Value::Array(reader.collect::<std::result::Result<Vec<Value>, _>>()?);
from_value::<_serde::ManifestListV2>(&values)?.try_into()
}
FormatVersion::V3 => {
let reader = Reader::new(bs)?;
let values = Value::Array(reader.collect::<std::result::Result<Vec<Value>, _>>()?);
from_value::<_serde::ManifestListV3>(&values)?.try_into()
}
}
}
pub fn entries(&self) -> &[ManifestFile] {
&self.entries
}
pub fn consume_entries(self) -> impl IntoIterator<Item = ManifestFile> {
Box::new(self.entries.into_iter())
}
}
#[cfg(test)]
mod test {
use std::fs;
use apache_avro::{Codec, Writer};
use tempfile::TempDir;
use super::_const_schema::MANIFEST_LIST_AVRO_SCHEMA_V2;
use super::_serde::ManifestFileV2;
use super::*;
use crate::io::FileIO;
use crate::spec::{Datum, FieldSummary, ManifestContentType, ManifestFile};
#[tokio::test]
async fn test_parse_manifest_list_v1() {
let manifest_list = ManifestList {
entries: vec![
ManifestFile {
manifest_path: "/opt/bitnami/spark/warehouse/db/table/metadata/10d28031-9739-484c-92db-cdf2975cead4-m0.avro".to_string(),
manifest_length: 5806,
partition_spec_id: 0,
content: ManifestContentType::Data,
sequence_number: 0,
min_sequence_number: 0,
added_snapshot_id: 1646658105718557341,
added_files_count: Some(3),
existing_files_count: Some(0),
deleted_files_count: Some(0),
added_rows_count: Some(3),
existing_rows_count: Some(0),
deleted_rows_count: Some(0),
partitions: Some(vec![]),
key_metadata: None,
first_row_id: None,
}
]
};
let file_io = FileIO::new_with_fs();
let tmp_dir = TempDir::new().unwrap();
let file_name = "simple_manifest_list_v1.avro";
let full_path = format!("{}/{}", tmp_dir.path().to_str().unwrap(), file_name);
let mut writer = ManifestListWriter::v1(
file_io
.new_output(full_path.clone())
.unwrap()
.writer()
.await
.unwrap(),
1646658105718557341,
Some(1646658105718557341),
);
writer
.add_manifests(manifest_list.entries.clone().into_iter())
.unwrap();
writer.close().await.unwrap();
let bs = fs::read(full_path).expect("read_file must succeed");
let parsed_manifest_list =
ManifestList::parse_with_version(&bs, crate::spec::FormatVersion::V1).unwrap();
assert_eq!(manifest_list, parsed_manifest_list);
}
#[tokio::test]
async fn test_parse_manifest_list_v2() {
let manifest_list = ManifestList {
entries: vec![
ManifestFile {
manifest_path: "s3a://icebergdata/demo/s1/t1/metadata/05ffe08b-810f-49b3-a8f4-e88fc99b254a-m0.avro".to_string(),
manifest_length: 6926,
partition_spec_id: 1,
content: ManifestContentType::Data,
sequence_number: 1,
min_sequence_number: 1,
added_snapshot_id: 377075049360453639,
added_files_count: Some(1),
existing_files_count: Some(0),
deleted_files_count: Some(0),
added_rows_count: Some(3),
existing_rows_count: Some(0),
deleted_rows_count: Some(0),
partitions: Some(
vec![FieldSummary { contains_null: false, contains_nan: Some(false), lower_bound: Some(Datum::long(1).to_bytes().unwrap()), upper_bound: Some(Datum::long(1).to_bytes().unwrap())}]
),
key_metadata: None,
first_row_id: None,
},
ManifestFile {
manifest_path: "s3a://icebergdata/demo/s1/t1/metadata/05ffe08b-810f-49b3-a8f4-e88fc99b254a-m1.avro".to_string(),
manifest_length: 6926,
partition_spec_id: 2,
content: ManifestContentType::Data,
sequence_number: 1,
min_sequence_number: 1,
added_snapshot_id: 377075049360453639,
added_files_count: Some(1),
existing_files_count: Some(0),
deleted_files_count: Some(0),
added_rows_count: Some(3),
existing_rows_count: Some(0),
deleted_rows_count: Some(0),
partitions: Some(
vec![FieldSummary { contains_null: false, contains_nan: Some(false), lower_bound: Some(Datum::float(1.1).to_bytes().unwrap()), upper_bound: Some(Datum::float(2.1).to_bytes().unwrap())}]
),
key_metadata: None,
first_row_id: None,
}
]
};
let file_io = FileIO::new_with_fs();
let tmp_dir = TempDir::new().unwrap();
let file_name = "simple_manifest_list_v1.avro";
let full_path = format!("{}/{}", tmp_dir.path().to_str().unwrap(), file_name);
let mut writer = ManifestListWriter::v2(
file_io
.new_output(full_path.clone())
.unwrap()
.writer()
.await
.unwrap(),
1646658105718557341,
Some(1646658105718557341),
1,
);
writer
.add_manifests(manifest_list.entries.clone().into_iter())
.unwrap();
writer.close().await.unwrap();
let bs = fs::read(full_path).expect("read_file must succeed");
let parsed_manifest_list =
ManifestList::parse_with_version(&bs, crate::spec::FormatVersion::V2).unwrap();
assert_eq!(manifest_list, parsed_manifest_list);
}
#[test]
fn test_parse_snappy_manifest_list_v2() {
let manifest_list = ManifestList {
entries: vec![ManifestFile {
manifest_path: "s3a://icebergdata/demo/s1/t1/metadata/snappy-m0.avro".to_string(),
manifest_length: 6926,
partition_spec_id: 1,
content: ManifestContentType::Data,
sequence_number: 1,
min_sequence_number: 1,
added_snapshot_id: 377075049360453639,
added_files_count: Some(1),
existing_files_count: Some(0),
deleted_files_count: Some(0),
added_rows_count: Some(3),
existing_rows_count: Some(0),
deleted_rows_count: Some(0),
partitions: Some(vec![FieldSummary {
contains_null: false,
contains_nan: Some(false),
lower_bound: Some(Datum::long(1).to_bytes().unwrap()),
upper_bound: Some(Datum::long(1).to_bytes().unwrap()),
}]),
key_metadata: None,
first_row_id: None,
}],
};
let manifest_entry: ManifestFileV2 = manifest_list.entries[0].clone().try_into().unwrap();
let mut writer =
Writer::with_codec(&MANIFEST_LIST_AVRO_SCHEMA_V2, Vec::new(), Codec::Snappy);
writer.append_ser(manifest_entry).unwrap();
let bs = writer.into_inner().unwrap();
let parsed_manifest_list =
ManifestList::parse_with_version(&bs, crate::spec::FormatVersion::V2).unwrap();
assert_eq!(manifest_list, parsed_manifest_list);
}
#[tokio::test]
async fn test_parse_manifest_list_v3() {
let manifest_list = ManifestList {
entries: vec![
ManifestFile {
manifest_path: "s3a://icebergdata/demo/s1/t1/metadata/05ffe08b-810f-49b3-a8f4-e88fc99b254a-m0.avro".to_string(),
manifest_length: 6926,
partition_spec_id: 1,
content: ManifestContentType::Data,
sequence_number: 1,
min_sequence_number: 1,
added_snapshot_id: 377075049360453639,
added_files_count: Some(1),
existing_files_count: Some(0),
deleted_files_count: Some(0),
added_rows_count: Some(3),
existing_rows_count: Some(0),
deleted_rows_count: Some(0),
partitions: Some(
vec![FieldSummary { contains_null: false, contains_nan: Some(false), lower_bound: Some(Datum::long(1).to_bytes().unwrap()), upper_bound: Some(Datum::long(1).to_bytes().unwrap())}]
),
key_metadata: None,
first_row_id: Some(10),
},
ManifestFile {
manifest_path: "s3a://icebergdata/demo/s1/t1/metadata/05ffe08b-810f-49b3-a8f4-e88fc99b254a-m1.avro".to_string(),
manifest_length: 6926,
partition_spec_id: 2,
content: ManifestContentType::Data,
sequence_number: 1,
min_sequence_number: 1,
added_snapshot_id: 377075049360453639,
added_files_count: Some(1),
existing_files_count: Some(0),
deleted_files_count: Some(0),
added_rows_count: Some(3),
existing_rows_count: Some(0),
deleted_rows_count: Some(0),
partitions: Some(
vec![FieldSummary { contains_null: false, contains_nan: Some(false), lower_bound: Some(Datum::float(1.1).to_bytes().unwrap()), upper_bound: Some(Datum::float(2.1).to_bytes().unwrap())}]
),
key_metadata: None,
first_row_id: Some(13),
}
]
};
let file_io = FileIO::new_with_fs();
let tmp_dir = TempDir::new().unwrap();
let file_name = "simple_manifest_list_v3.avro";
let full_path = format!("{}/{}", tmp_dir.path().to_str().unwrap(), file_name);
let mut writer = ManifestListWriter::v3(
file_io
.new_output(full_path.clone())
.unwrap()
.writer()
.await
.unwrap(),
377075049360453639,
Some(377075049360453639),
1,
Some(10),
);
writer
.add_manifests(manifest_list.entries.clone().into_iter())
.unwrap();
writer.close().await.unwrap();
let bs = fs::read(full_path).expect("read_file must succeed");
let parsed_manifest_list =
ManifestList::parse_with_version(&bs, crate::spec::FormatVersion::V3).unwrap();
assert_eq!(manifest_list, parsed_manifest_list);
}
}