use super::metrics::{
FileStatistics, IcebergMetrics, ManifestStatistics, PartitionSpecMetrics, SchemaMetrics,
SnapshotMetrics, SortOrderMetrics, TableMetadata,
};
use iceberg::io::FileIOBuilder;
use iceberg::spec::{Snapshot, TableMetadata as IcebergTableMetadata};
use iceberg::table::StaticTable;
use iceberg::TableIdent;
use std::collections::HashMap;
use std::error::Error;
use std::sync::Arc;
use tracing::{info, warn};
fn format_version_to_i32(version: iceberg::spec::FormatVersion) -> i32 {
match version {
iceberg::spec::FormatVersion::V1 => 1,
iceberg::spec::FormatVersion::V2 => 2,
iceberg::spec::FormatVersion::V3 => 3,
}
}
pub struct IcebergReader {
table: StaticTable,
}
impl IcebergReader {
pub async fn open(
table_location: &str,
storage_options: &HashMap<String, String>,
) -> Result<Self, Box<dyn Error + Send + Sync>> {
info!("Opening Iceberg table from location: {}", table_location);
let normalized_location = Self::normalize_file_path(table_location);
let mut file_io_builder = FileIOBuilder::new_fs_io();
for (key, value) in storage_options {
file_io_builder = file_io_builder.with_prop(key, value);
}
let file_io = file_io_builder.build()?;
let metadata_location = Self::find_latest_metadata(&file_io, &normalized_location).await?;
info!("Found latest metadata file: {}", metadata_location);
let table_ident = TableIdent::from_strs(["default", "table"])?;
let table =
StaticTable::from_metadata_file(&metadata_location, table_ident, file_io).await?;
info!(
"Successfully opened Iceberg table from metadata, version: {}",
table
.metadata()
.current_snapshot()
.map(|s| s.snapshot_id())
.unwrap_or(0)
);
Ok(Self { table })
}
fn normalize_file_path(path: &str) -> String {
if let Some(without_scheme) = path.strip_prefix("file://") {
format!("file:/{}", without_scheme)
} else {
path.to_string()
}
}
async fn find_latest_metadata(
file_io: &iceberg::io::FileIO,
table_location: &str,
) -> Result<String, Box<dyn Error + Send + Sync>> {
let metadata_dir = format!("{}/metadata", table_location);
let version_hint_path = format!("{}/version-hint.text", metadata_dir);
if let Ok(input_file) = file_io.new_input(&version_hint_path) {
if let Ok(content) = input_file.read().await {
let version_str = String::from_utf8_lossy(&content).trim().to_string();
if let Ok(version) = version_str.parse::<i32>() {
let metadata_path = format!("{}/v{}.metadata.json", metadata_dir, version);
info!(
"Found version hint: {}, using metadata file: {}",
version, metadata_path
);
return Ok(metadata_path);
}
}
}
for version in (1..=100).rev() {
let metadata_path = format!("{}/v{}.metadata.json", metadata_dir, version);
if let Ok(input_file) = file_io.new_input(&metadata_path) {
if input_file.read().await.is_ok() {
info!("Found metadata file by scanning: {}", metadata_path);
return Ok(metadata_path);
}
}
}
Err(format!("Could not find Iceberg metadata file in {}", metadata_dir).into())
}
pub async fn extract_metrics(&self) -> Result<IcebergMetrics, Box<dyn Error + Send + Sync>> {
info!("Extracting metrics from Iceberg table");
let table_metadata = self.table.metadata();
let current_snapshot = table_metadata.current_snapshot();
let metadata = self.extract_table_metadata(&table_metadata)?;
let snapshot_info = self.extract_snapshot_metrics(&table_metadata, current_snapshot)?;
let schema = table_metadata.current_schema();
let schema_info = self.extract_schema_metrics(schema)?;
let partition_spec = table_metadata.default_partition_spec();
let partition_spec_metrics = self.extract_partition_spec_metrics(partition_spec)?;
let sort_order = table_metadata.default_sort_order();
let sort_order_metrics = self.extract_sort_order_metrics(sort_order)?;
let file_stats = self.extract_file_statistics(current_snapshot).await?;
let manifest_stats = self.extract_manifest_statistics(current_snapshot).await?;
let format_version = format_version_to_i32(table_metadata.format_version());
let metrics = IcebergMetrics {
current_snapshot_id: current_snapshot.map(|s| s.snapshot_id()),
format_version,
table_uuid: table_metadata.uuid().to_string(),
metadata,
table_properties: table_metadata.properties().clone(),
snapshot_info,
schema_info,
partition_spec: partition_spec_metrics,
sort_order: sort_order_metrics,
file_stats,
manifest_stats,
};
info!("Successfully extracted Iceberg table metrics");
Ok(metrics)
}
fn extract_table_metadata(
&self,
table_metadata: &IcebergTableMetadata,
) -> Result<TableMetadata, Box<dyn Error + Send + Sync>> {
Ok(TableMetadata {
location: table_metadata.location().to_string(),
last_updated_ms: Some(table_metadata.last_updated_ms()),
last_column_id: table_metadata.last_column_id(),
current_schema_id: table_metadata.current_schema_id(),
schema_count: table_metadata.schemas_iter().len(),
default_spec_id: table_metadata.default_partition_spec_id(),
partition_spec_count: table_metadata.partition_specs_iter().len(),
default_sort_order_id: table_metadata.default_sort_order_id() as i32,
sort_order_count: table_metadata.sort_orders_iter().len(),
last_sequence_number: Some(table_metadata.last_sequence_number()),
})
}
fn extract_snapshot_metrics(
&self,
table_metadata: &IcebergTableMetadata,
current_snapshot: Option<&Arc<Snapshot>>,
) -> Result<SnapshotMetrics, Box<dyn Error + Send + Sync>> {
let total_snapshots = table_metadata.snapshots().len();
if let Some(snapshot) = current_snapshot {
let operation_str = format!("{:?}", snapshot.summary().operation);
Ok(SnapshotMetrics {
total_snapshots,
current_snapshot_id: Some(snapshot.snapshot_id()),
current_snapshot_timestamp_ms: Some(snapshot.timestamp_ms()),
parent_snapshot_id: snapshot.parent_snapshot_id(),
operation: Some(operation_str),
summary: snapshot.summary().additional_properties.clone(),
manifest_list: Some(snapshot.manifest_list().to_string()),
schema_id: snapshot.schema_id(),
sequence_number: Some(snapshot.sequence_number()),
})
} else {
Ok(SnapshotMetrics {
total_snapshots,
current_snapshot_id: None,
current_snapshot_timestamp_ms: None,
parent_snapshot_id: None,
operation: None,
summary: HashMap::new(),
manifest_list: None,
schema_id: None,
sequence_number: None,
})
}
}
fn extract_schema_metrics(
&self,
schema: &iceberg::spec::SchemaRef,
) -> Result<SchemaMetrics, Box<dyn Error + Send + Sync>> {
use iceberg::spec::Type;
let struct_type = schema.as_struct();
let fields = struct_type.fields();
let field_names: Vec<String> = fields.iter().map(|f| f.name.clone()).collect();
let nested_field_count = fields
.iter()
.filter(|f| {
matches!(
&*f.field_type,
Type::Struct(_) | Type::List(_) | Type::Map(_)
)
})
.count();
let required_field_count = fields.iter().filter(|f| f.required).count();
let schema_string = serde_json::to_string_pretty(schema)?;
Ok(SchemaMetrics {
schema_id: schema.schema_id(),
field_count: fields.len(),
schema_string,
field_names,
nested_field_count,
required_field_count,
optional_field_count: fields.len() - required_field_count,
})
}
fn extract_partition_spec_metrics(
&self,
spec: &iceberg::spec::PartitionSpecRef,
) -> Result<PartitionSpecMetrics, Box<dyn Error + Send + Sync>> {
let partition_fields: Vec<String> = spec.fields().iter().map(|f| f.name.clone()).collect();
let partition_transforms: Vec<String> = spec
.fields()
.iter()
.map(|f| format!("{:?}", f.transform))
.collect();
Ok(PartitionSpecMetrics {
spec_id: spec.spec_id(),
partition_field_count: spec.fields().len(),
partition_fields,
partition_transforms,
is_partitioned: !spec.fields().is_empty(),
estimated_partition_count: None,
})
}
fn extract_sort_order_metrics(
&self,
sort_order: &iceberg::spec::SortOrderRef,
) -> Result<SortOrderMetrics, Box<dyn Error + Send + Sync>> {
let fields = &sort_order.fields;
let sort_fields: Vec<String> = fields.iter().map(|f| format!("{}", f.source_id)).collect();
let sort_directions: Vec<String> = fields
.iter()
.map(|f| format!("{:?}", f.direction))
.collect();
let null_orders: Vec<String> = fields
.iter()
.map(|f| format!("{:?}", f.null_order))
.collect();
Ok(SortOrderMetrics {
order_id: sort_order.order_id as i32,
sort_field_count: fields.len(),
sort_fields,
sort_directions,
null_orders,
is_sorted: !fields.is_empty(),
})
}
async fn extract_file_statistics(
&self,
current_snapshot: Option<&Arc<Snapshot>>,
) -> Result<FileStatistics, Box<dyn Error + Send + Sync>> {
info!("Extracting file statistics");
let Some(snapshot) = current_snapshot else {
warn!("No current snapshot found");
return Ok(FileStatistics {
num_data_files: 0,
total_data_size_bytes: 0,
avg_data_file_size_bytes: 0.0,
min_data_file_size_bytes: 0,
max_data_file_size_bytes: 0,
total_records: None,
num_delete_files: 0,
total_delete_size_bytes: 0,
num_position_delete_files: 0,
num_equality_delete_files: 0,
});
};
let summary = snapshot.summary();
let num_data_files = summary
.additional_properties
.get("total-data-files")
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(0);
let total_data_size_bytes = summary
.additional_properties
.get("total-files-size")
.and_then(|v| v.parse::<u64>().ok())
.unwrap_or(0);
let total_records = summary
.additional_properties
.get("total-records")
.and_then(|v| v.parse::<u64>().ok());
let num_delete_files = summary
.additional_properties
.get("total-delete-files")
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(0);
let num_position_delete_files = summary
.additional_properties
.get("total-position-deletes")
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(0);
let num_equality_delete_files = summary
.additional_properties
.get("total-equality-deletes")
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(0);
let avg_data_file_size_bytes = if num_data_files > 0 {
total_data_size_bytes as f64 / num_data_files as f64
} else {
0.0
};
Ok(FileStatistics {
num_data_files,
total_data_size_bytes,
avg_data_file_size_bytes,
min_data_file_size_bytes: 0, max_data_file_size_bytes: 0, total_records: total_records.map(|v| v as i64),
num_delete_files,
total_delete_size_bytes: 0, num_position_delete_files,
num_equality_delete_files,
})
}
async fn extract_manifest_statistics(
&self,
current_snapshot: Option<&Arc<Snapshot>>,
) -> Result<ManifestStatistics, Box<dyn Error + Send + Sync>> {
info!("Extracting manifest statistics");
let Some(snapshot) = current_snapshot else {
warn!("No current snapshot found");
return Ok(ManifestStatistics {
num_manifest_files: 0,
total_manifest_size_bytes: 0,
avg_manifest_file_size_bytes: 0.0,
num_data_manifests: 0,
num_delete_manifests: 0,
num_manifest_lists: 0,
total_manifest_list_size_bytes: 0,
});
};
let manifest_list_path = snapshot.manifest_list();
info!("Reading manifest list from: {}", manifest_list_path);
let mut num_data_manifests = 0;
let mut num_delete_manifests = 0;
let summary = snapshot.summary();
if let Some(data_files) = summary.additional_properties.get("total-data-files") {
if let Ok(file_count) = data_files.parse::<usize>() {
num_data_manifests = (file_count / 100).max(1);
}
}
if let Some(delete_files) = summary.additional_properties.get("total-delete-files") {
if let Ok(delete_count) = delete_files.parse::<usize>() {
if delete_count > 0 {
num_delete_manifests = (delete_count / 50).max(1);
}
}
}
let total_manifest_size =
((num_data_manifests + num_delete_manifests) * 1024 * 1024) as u64;
info!(
"Estimated manifests - data: {}, delete: {}",
num_data_manifests, num_delete_manifests
);
let num_manifest_files = num_data_manifests + num_delete_manifests;
let avg_manifest_size = if num_manifest_files > 0 {
total_manifest_size as f64 / num_manifest_files as f64
} else {
0.0
};
Ok(ManifestStatistics {
num_manifest_files,
total_manifest_size_bytes: total_manifest_size,
avg_manifest_file_size_bytes: avg_manifest_size,
num_data_manifests,
num_delete_manifests,
num_manifest_lists: 1,
total_manifest_list_size_bytes: total_manifest_size,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn get_test_iceberg_table_path() -> String {
let current_dir = std::env::current_dir().unwrap();
let test_path = current_dir.join("examples/data/iceberg_dataset");
format!("file://{}", test_path.to_str().unwrap())
}
#[tokio::test]
async fn test_iceberg_reader_open_success() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let result = IcebergReader::open(&table_location, &storage_options).await;
assert!(
result.is_ok(),
"Failed to open Iceberg table: {:?}",
result.err()
);
}
#[tokio::test]
async fn test_iceberg_reader_open_invalid_path() {
let table_location = "file:///nonexistent/path/to/table";
let storage_options = HashMap::new();
let result = IcebergReader::open(table_location, &storage_options).await;
assert!(
result.is_err(),
"Should fail to open non-existent Iceberg table"
);
}
#[tokio::test]
async fn test_find_latest_metadata_with_version_hint() {
let table_location = get_test_iceberg_table_path();
let file_io = FileIOBuilder::new_fs_io().build().unwrap();
let result = IcebergReader::find_latest_metadata(&file_io, &table_location).await;
assert!(
result.is_ok(),
"Failed to find latest metadata: {:?}",
result.err()
);
let metadata_path = result.unwrap();
assert!(
metadata_path.contains("/metadata/v"),
"Metadata path should contain /metadata/v"
);
assert!(
metadata_path.ends_with(".metadata.json"),
"Metadata path should end with .metadata.json"
);
}
#[tokio::test]
async fn test_find_latest_metadata_invalid_path() {
let table_location = "file:///nonexistent/path/to/table";
let file_io = FileIOBuilder::new_fs_io().build().unwrap();
let result = IcebergReader::find_latest_metadata(&file_io, table_location).await;
assert!(
result.is_err(),
"Should fail to find metadata in non-existent path"
);
}
#[test]
fn test_normalize_file_path_with_triple_slash() {
let input = "file:///home/user/path/to/table";
let result = IcebergReader::normalize_file_path(input);
assert_eq!(result, "file://home/user/path/to/table");
}
#[test]
fn test_normalize_file_path_with_double_slash() {
let input = "file://home/user/path/to/table";
let result = IcebergReader::normalize_file_path(input);
assert_eq!(result, "file:/home/user/path/to/table");
}
#[test]
fn test_normalize_file_path_with_single_slash() {
let input = "file:/home/user/path/to/table";
let result = IcebergReader::normalize_file_path(input);
assert_eq!(result, "file:/home/user/path/to/table");
}
#[test]
fn test_normalize_file_path_non_file_scheme() {
let input = "s3://bucket/path/to/table";
let result = IcebergReader::normalize_file_path(input);
assert_eq!(result, "s3://bucket/path/to/table");
}
#[test]
fn test_normalize_file_path_absolute_path() {
let input = "/home/user/path/to/table";
let result = IcebergReader::normalize_file_path(input);
assert_eq!(result, "/home/user/path/to/table");
}
#[tokio::test]
async fn test_iceberg_reader_extract_metrics_success() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let result = reader.extract_metrics().await;
assert!(
result.is_ok(),
"Failed to extract metrics: {:?}",
result.err()
);
let metrics = result.unwrap();
assert!(
metrics.format_version == 1
|| metrics.format_version == 2
|| metrics.format_version == 3,
"Format version should be 1, 2, or 3"
);
assert!(
!metrics.table_uuid.is_empty(),
"Table UUID should not be empty"
);
assert!(
!metrics.metadata.location.is_empty(),
"Table location should not be empty"
);
}
#[tokio::test]
async fn test_iceberg_reader_extract_table_metadata() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let metrics = reader
.extract_metrics()
.await
.expect("Failed to extract metrics");
assert!(!metrics.metadata.location.is_empty());
assert!(metrics.metadata.last_column_id >= 0);
assert!(metrics.metadata.current_schema_id >= 0);
assert!(metrics.metadata.schema_count > 0);
assert!(metrics.metadata.default_spec_id >= 0);
assert!(metrics.metadata.partition_spec_count > 0);
}
#[tokio::test]
async fn test_iceberg_reader_extract_snapshot_metrics() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let metrics = reader
.extract_metrics()
.await
.expect("Failed to extract metrics");
if metrics.snapshot_info.current_snapshot_id.is_some() {
assert!(metrics
.snapshot_info
.current_snapshot_timestamp_ms
.is_some());
assert!(metrics.snapshot_info.operation.is_some());
}
}
#[tokio::test]
async fn test_iceberg_reader_extract_schema_metrics() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let metrics = reader
.extract_metrics()
.await
.expect("Failed to extract metrics");
assert!(metrics.schema_info.schema_id >= 0);
assert!(metrics.schema_info.field_count > 0);
assert!(!metrics.schema_info.schema_string.is_empty());
assert!(!metrics.schema_info.field_names.is_empty());
assert_eq!(
metrics.schema_info.field_count,
metrics.schema_info.field_names.len()
);
assert_eq!(
metrics.schema_info.field_count,
metrics.schema_info.required_field_count + metrics.schema_info.optional_field_count
);
}
#[tokio::test]
async fn test_iceberg_reader_extract_partition_spec_metrics() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let metrics = reader
.extract_metrics()
.await
.expect("Failed to extract metrics");
assert!(metrics.partition_spec.spec_id >= 0);
assert_eq!(
metrics.partition_spec.partition_field_count,
metrics.partition_spec.partition_fields.len()
);
assert_eq!(
metrics.partition_spec.partition_field_count,
metrics.partition_spec.partition_transforms.len()
);
assert_eq!(
metrics.partition_spec.is_partitioned,
metrics.partition_spec.partition_field_count > 0
);
}
#[tokio::test]
async fn test_iceberg_reader_extract_sort_order_metrics() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let metrics = reader
.extract_metrics()
.await
.expect("Failed to extract metrics");
assert!(metrics.sort_order.order_id >= 0);
assert_eq!(
metrics.sort_order.sort_field_count,
metrics.sort_order.sort_fields.len()
);
assert_eq!(
metrics.sort_order.sort_field_count,
metrics.sort_order.sort_directions.len()
);
assert_eq!(
metrics.sort_order.sort_field_count,
metrics.sort_order.null_orders.len()
);
assert_eq!(
metrics.sort_order.is_sorted,
metrics.sort_order.sort_field_count > 0
);
}
#[tokio::test]
async fn test_iceberg_reader_extract_file_statistics() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let metrics = reader
.extract_metrics()
.await
.expect("Failed to extract metrics");
if metrics.file_stats.num_data_files > 0 {
assert!(
metrics.file_stats.total_data_size_bytes > 0,
"Total data size should be greater than 0 when files exist"
);
assert!(
metrics.file_stats.avg_data_file_size_bytes > 0.0,
"Average file size should be greater than 0 when files exist"
);
} else {
assert_eq!(metrics.file_stats.total_data_size_bytes, 0);
assert_eq!(metrics.file_stats.avg_data_file_size_bytes, 0.0);
}
}
#[tokio::test]
async fn test_iceberg_reader_extract_manifest_statistics() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let metrics = reader
.extract_metrics()
.await
.expect("Failed to extract metrics");
assert_eq!(
metrics.manifest_stats.num_manifest_files,
metrics.manifest_stats.num_data_manifests + metrics.manifest_stats.num_delete_manifests
);
if metrics.manifest_stats.num_manifest_files > 0 {
assert!(
metrics.manifest_stats.avg_manifest_file_size_bytes > 0.0,
"Average manifest size should be greater than 0 when manifests exist"
);
}
}
#[tokio::test]
async fn test_iceberg_reader_format_version() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let metrics = reader
.extract_metrics()
.await
.expect("Failed to extract metrics");
assert!(
metrics.format_version == 1
|| metrics.format_version == 2
|| metrics.format_version == 3,
"Format version should be 1, 2, or 3, got {}",
metrics.format_version
);
}
#[test]
fn test_format_version_conversion() {
assert_eq!(format_version_to_i32(iceberg::spec::FormatVersion::V1), 1);
assert_eq!(format_version_to_i32(iceberg::spec::FormatVersion::V2), 2);
assert_eq!(format_version_to_i32(iceberg::spec::FormatVersion::V3), 3);
}
#[tokio::test]
async fn test_iceberg_reader_table_uuid_format() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let metrics = reader
.extract_metrics()
.await
.expect("Failed to extract metrics");
assert!(!metrics.table_uuid.is_empty());
assert!(metrics.table_uuid.contains('-'));
}
#[tokio::test]
async fn test_iceberg_reader_multiple_extractions() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let metrics1 = reader
.extract_metrics()
.await
.expect("First extraction failed");
let metrics2 = reader
.extract_metrics()
.await
.expect("Second extraction failed");
assert_eq!(metrics1.table_uuid, metrics2.table_uuid);
assert_eq!(metrics1.format_version, metrics2.format_version);
assert_eq!(metrics1.current_snapshot_id, metrics2.current_snapshot_id);
assert_eq!(
metrics1.file_stats.num_data_files,
metrics2.file_stats.num_data_files
);
}
#[tokio::test]
async fn test_iceberg_reader_schema_json_validity() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let metrics = reader
.extract_metrics()
.await
.expect("Failed to extract metrics");
let schema_json: serde_json::Value =
serde_json::from_str(&metrics.schema_info.schema_string)
.expect("Schema should be valid JSON");
assert!(schema_json.get("type").is_some());
}
#[tokio::test]
async fn test_iceberg_reader_table_properties() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let metrics = reader
.extract_metrics()
.await
.expect("Failed to extract metrics");
let _ = metrics.table_properties.len();
}
#[tokio::test]
async fn test_iceberg_reader_snapshot_summary() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let metrics = reader
.extract_metrics()
.await
.expect("Failed to extract metrics");
if metrics.current_snapshot_id.is_some() {
let _ = metrics.snapshot_info.summary.len();
}
}
#[tokio::test]
async fn test_iceberg_reader_delete_files_metrics() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let metrics = reader
.extract_metrics()
.await
.expect("Failed to extract metrics");
assert_eq!(
metrics.file_stats.num_delete_files,
metrics.file_stats.num_position_delete_files
+ metrics.file_stats.num_equality_delete_files
);
}
#[tokio::test]
async fn test_iceberg_reader_with_storage_options() {
let table_location = get_test_iceberg_table_path();
let mut storage_options = HashMap::new();
storage_options.insert("test_option".to_string(), "test_value".to_string());
let result = IcebergReader::open(&table_location, &storage_options).await;
assert!(
result.is_ok(),
"Should be able to open table with storage options"
);
}
#[tokio::test]
async fn test_iceberg_reader_sequence_numbers() {
let table_location = get_test_iceberg_table_path();
let storage_options = HashMap::new();
let reader = IcebergReader::open(&table_location, &storage_options)
.await
.expect("Failed to open Iceberg table");
let metrics = reader
.extract_metrics()
.await
.expect("Failed to extract metrics");
if metrics.snapshot_info.sequence_number.is_some() {
assert!(metrics.metadata.last_sequence_number.is_some());
}
}
}