Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions crates/iceberg/src/spec/manifest/_serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use serde_derive::{Deserialize, Serialize};
use serde_with::serde_as;

use super::{Datum, ManifestEntry, Schema, Struct};
use crate::spec::{Literal, RawLiteral, StructType, Type};
use crate::spec::{FormatVersion, Literal, RawLiteral, StructType, Type};
use crate::{Error, ErrorKind};

#[derive(Serialize, Deserialize)]
Expand All @@ -40,7 +40,7 @@ impl ManifestEntryV2 {
snapshot_id: value.snapshot_id,
sequence_number: value.sequence_number,
file_sequence_number: value.file_sequence_number,
data_file: DataFileSerde::try_from(value.data_file, partition_type, false)?,
data_file: DataFileSerde::try_from(value.data_file, partition_type, FormatVersion::V2)?,
})
}

Expand Down Expand Up @@ -74,7 +74,7 @@ impl ManifestEntryV1 {
Ok(Self {
status: value.status as i32,
snapshot_id: value.snapshot_id.unwrap_or_default(),
data_file: DataFileSerde::try_from(value.data_file, partition_type, true)?,
data_file: DataFileSerde::try_from(value.data_file, partition_type, FormatVersion::V1)?,
})
}

Expand Down Expand Up @@ -129,9 +129,13 @@ impl DataFileSerde {
pub fn try_from(
value: super::DataFile,
partition_type: &StructType,
is_version_1: bool,
format_version: FormatVersion,
) -> Result<Self, Error> {
let block_size_in_bytes = if is_version_1 { Some(0) } else { None };
let block_size_in_bytes = if format_version == FormatVersion::V1 {
Some(0)
} else {
None
};
Ok(Self {
content: value.content as i32,
file_path: value.file_path,
Expand Down
8 changes: 6 additions & 2 deletions crates/iceberg/src/spec/manifest/data_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,12 @@ pub fn write_data_files_to_avro<W: Write>(
let mut writer = AvroWriter::new(&avro_schema, writer);

for data_file in data_files {
let value = to_value(DataFileSerde::try_from(data_file, partition_type, true)?)?
.resolve(&avro_schema)?;
let value = to_value(DataFileSerde::try_from(
data_file,
partition_type,
FormatVersion::V1,
)?)?
.resolve(&avro_schema)?;
writer.append(value)?;
}

Expand Down
153 changes: 152 additions & 1 deletion crates/iceberg/src/spec/manifest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use super::{
UNASSIGNED_SEQUENCE_NUMBER,
};
use crate::error::Result;
use crate::{Error, ErrorKind};

/// A manifest contains metadata and a list of entries.
#[derive(Debug, PartialEq, Eq, Clone)]
Expand Down Expand Up @@ -119,12 +120,46 @@ impl Manifest {
}
}

/// Serialize a DataFile to a JSON string.
pub fn serialize_data_file_to_json(
data_file: DataFile,
partition_type: &super::StructType,
format_version: FormatVersion,
) -> Result<String> {
let serde = _serde::DataFileSerde::try_from(data_file, partition_type, format_version)?;
serde_json::to_string(&serde).map_err(|e| {
Error::new(
ErrorKind::DataInvalid,
"Failed to serialize DataFile to JSON!".to_string(),
)
.with_source(e)
})
}

/// Deserialize a DataFile from a JSON string.
pub fn deserialize_data_file_from_json(
json: &str,
partition_spec_id: i32,
partition_type: &super::StructType,
schema: &Schema,
) -> Result<DataFile> {
let serde = serde_json::from_str::<_serde::DataFileSerde>(json).map_err(|e| {
Error::new(
ErrorKind::DataInvalid,
"Failed to deserialize JSON to DataFile!".to_string(),
)
.with_source(e)
})?;

serde.try_into(partition_spec_id, partition_type, schema)
}

#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::fs;
use std::sync::Arc;

use arrow_array::StringArray;
use tempfile::TempDir;

use super::*;
Expand Down Expand Up @@ -1056,4 +1091,120 @@ mod tests {
assert!(!partitions[2].clone().contains_null);
assert_eq!(partitions[2].clone().contains_nan, Some(false));
}

Comment thread
CTTY marked this conversation as resolved.
#[test]
fn test_data_file_serialization() {
// Create a simple schema
let schema = Schema::builder()
.with_schema_id(1)
.with_identifier_field_ids(vec![1])
.with_fields(vec![
NestedField::required(1, "id", Type::Primitive(PrimitiveType::Long))
.into(),
NestedField::required(
2,
"name",
Type::Primitive(PrimitiveType::String),
)
.into(),
])
.build()
.unwrap();

// Create a partition spec
let partition_spec = PartitionSpec::builder(schema.clone())
.with_spec_id(1)
.add_partition_field("id", "id_partition", Transform::Identity)
.unwrap()
.build()
.unwrap();

// Get partition type from the partition spec
let partition_type = partition_spec.partition_type(&schema).unwrap();

// Create a vector of DataFile objects
let data_files = vec![
DataFileBuilder::default()
.content(DataContentType::Data)
.file_format(DataFileFormat::Parquet)
.file_path("path/to/file1.parquet".to_string())
.file_size_in_bytes(1024)
.record_count(100)
.partition_spec_id(1)
.partition(Struct::empty())
.column_sizes(HashMap::from([(1, 512), (2, 512)]))
.value_counts(HashMap::from([(1, 100), (2, 100)]))
.null_value_counts(HashMap::from([(1, 0), (2, 0)]))
.build()
.unwrap(),
DataFileBuilder::default()
.content(crate::spec::DataContentType::Data)
.file_format(DataFileFormat::Parquet)
.file_path("path/to/file2.parquet".to_string())
.file_size_in_bytes(2048)
.record_count(200)
.partition_spec_id(1)
.partition(Struct::empty())
.column_sizes(HashMap::from([(1, 1024), (2, 1024)]))
.value_counts(HashMap::from([(1, 200), (2, 200)]))
.null_value_counts(HashMap::from([(1, 10), (2, 5)]))
.build()
.unwrap(),
];

// Serialize the DataFile objects
let serialized_files = data_files
.into_iter()
.map(|f| {
let json =
serialize_data_file_to_json(f, &partition_type, FormatVersion::V2).unwrap();
println!("Test serialized data file: {}", json);
json
})
.collect::<Vec<String>>();

// Verify we have the expected number of serialized files
assert_eq!(serialized_files.len(), 2);

// Verify each serialized file contains expected data
for json in &serialized_files {
assert!(json.contains("path/to/file"));
assert!(json.contains("parquet"));
assert!(json.contains("record_count"));
assert!(json.contains("file_size_in_bytes"));
}

// Convert Vec<String> to StringArray and print it
let string_array = StringArray::from(serialized_files.clone());
println!("StringArray: {:?}", string_array);

// Now deserialize the JSON strings back into DataFile objects
println!("\nDeserializing back to DataFile objects:");
let deserialized_files: Vec<DataFile> = serialized_files
.into_iter()
.map(|json| {
let data_file = deserialize_data_file_from_json(
&json,
partition_spec.spec_id(),
&partition_type,
&schema,
)
.unwrap();

println!("Deserialized DataFile: {:?}", data_file);
data_file
})
.collect();

// Verify we have the expected number of deserialized files
assert_eq!(deserialized_files.len(), 2);

// Verify the deserialized files have the expected properties
for file in &deserialized_files {
assert_eq!(file.content_type(), DataContentType::Data);
assert_eq!(file.file_format(), DataFileFormat::Parquet);
assert!(file.file_path().contains("path/to/file"));
assert!(file.record_count() == 100 || file.record_count() == 200);
}
}
}
Loading