use std::fmt;
use std::sync::Arc;
use arrow::datatypes::SchemaRef;
use futures::future::{BoxFuture, Shared};
use iceberg::arrow::schema_to_arrow_schema;
use iceberg::spec::{DataFile, ManifestFile, ManifestStatus, SnapshotRef, TableMetadata};
use crate::contract::Contract;
use crate::error::{Error, Result};
use crate::lake::Lake;
use crate::name::TableName;
use crate::summary;
#[derive(Clone)]
pub struct Table {
lake: Lake,
name: TableName,
location: String,
version: u64,
metadata: Arc<TableMetadata>,
contract: Contract,
schema: SchemaRef,
files: Option<Arc<Vec<LiveEntry>>>,
check: Option<FreshnessCheck>,
}
pub(crate) type FreshnessCheck = Shared<BoxFuture<'static, Result<bool, Arc<Error>>>>;
#[derive(Clone, Debug)]
pub struct LiveEntry {
pub status: ManifestStatus,
pub data_file: Arc<DataFile>,
pub snapshot_id: i64,
pub sequence_number: i64,
pub file_sequence_number: i64,
}
impl Table {
pub(crate) fn new(
lake: Lake,
name: TableName,
location: String,
version: u64,
metadata: TableMetadata,
) -> Result<Self> {
let contract = Contract::from_properties(metadata.properties())?;
contract.check_readable().map_err(|e| Error::invalid(format!("{name}: {e}")))?;
let schema = Arc::new(schema_to_arrow_schema(metadata.current_schema())?);
Ok(Self {
lake,
name,
location,
version,
metadata: Arc::new(metadata),
contract,
schema,
files: None,
check: None,
})
}
pub(crate) fn with_files(mut self, files: Arc<Vec<LiveEntry>>) -> Self {
self.files = Some(files);
self
}
pub(crate) fn known_files(&self) -> Option<&Arc<Vec<LiveEntry>>> {
self.files.as_ref()
}
pub(crate) fn with_check(mut self, check: FreshnessCheck) -> Self {
self.check = Some(check);
self
}
pub(crate) async fn is_stale(&self) -> Result<bool, Arc<Error>> {
match &self.check {
Some(check) => check.clone().await,
None => Ok(false),
}
}
pub fn lake(&self) -> &Lake {
&self.lake
}
pub fn name(&self) -> &TableName {
&self.name
}
pub fn location(&self) -> &str {
&self.location
}
pub fn version(&self) -> u64 {
self.version
}
pub fn metadata(&self) -> &TableMetadata {
&self.metadata
}
pub fn contract(&self) -> &Contract {
&self.contract
}
pub fn schema(&self) -> SchemaRef {
self.schema.clone()
}
pub async fn refresh(&self) -> Result<Table> {
self.lake.load(&self.name).await
}
pub fn current_snapshot(&self) -> Option<&SnapshotRef> {
self.metadata.current_snapshot()
}
pub(crate) fn summary_value(&self, key: &str) -> Option<&str> {
self.current_snapshot()?.summary().additional_properties.get(key).map(String::as_str)
}
pub fn last_value_index(&self) -> Option<String> {
self.summary_value(summary::LAST_VALUE_INDEX).map(str::to_string)
}
pub async fn manifests(&self) -> Result<Arc<Vec<ManifestFile>>> {
match self.current_snapshot() {
None => Ok(Arc::default()),
Some(s) => self.lake.manifest_list(s.manifest_list(), self.metadata.format_version()).await,
}
}
pub async fn files(&self) -> Result<Vec<LiveEntry>> {
if let Some(files) = &self.files {
return Ok(files.as_ref().clone());
}
let manifests = self.manifests().await?;
let loaded = futures::future::try_join_all(manifests.iter().map(|m| self.lake.manifest(m))).await?;
Ok(loaded.iter().flat_map(|es| es.iter().filter(|e| e.status != ManifestStatus::Deleted).cloned()).collect())
}
}
impl fmt::Debug for Table {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Table").field("name", &self.name).field("version", &self.version).finish_non_exhaustive()
}
}