Skip to content

Commit ab68b26

Browse files
authored
feat: add referenced_files_size and physical_files_size table functions (#323)
Introduce two DataFusion table-valued functions for storage analysis: - `referenced_files_size('table')`: Aggregates manifest/data/index file counts and sizes across all snapshots from main branch, tags, and other branches. Output rows: total, branch:main, branch:<name>. Uses a shared manifest cache to avoid redundant reads and processes snapshots concurrently. - `physical_files_size('table')`: Scans the table directory recursively and reports actual physical file sizes categorized by file type (manifest, data, index). Concurrently lists subdirectories for high throughput on object stores. Both functions gracefully handle NotFound errors from concurrently deleted files during cleanup.
1 parent 2d0781e commit ab68b26

9 files changed

Lines changed: 1456 additions & 1 deletion

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@
2020
.idea
2121
.vscode
2222
**/.DS_Store
23-
dist/*
23+
**/dist/
24+
docs/site/
2425
.qoder

crates/integrations/datafusion/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ mod filter_pushdown;
4343
#[cfg(feature = "fulltext")]
4444
mod full_text_search;
4545
mod merge_into;
46+
mod physical_files_size;
4647
mod physical_plan;
4748
mod procedures;
49+
mod referenced_files_size;
4850
mod relation_planner;
4951
pub mod runtime;
5052
mod sql_context;
@@ -67,7 +69,9 @@ pub use catalog::{PaimonCatalogProvider, PaimonSchemaProvider};
6769
pub use error::to_datafusion_error;
6870
#[cfg(feature = "fulltext")]
6971
pub use full_text_search::{register_full_text_search, FullTextSearchFunction};
72+
pub use physical_files_size::{register_physical_files_size, PhysicalFilesSizeFunction};
7073
pub use physical_plan::PaimonTableScan;
74+
pub use referenced_files_size::{register_referenced_files_size, ReferencedFilesSizeFunction};
7175
pub use relation_planner::PaimonRelationPlanner;
7276
pub use sql_context::SQLContext;
7377
pub use table::PaimonTableProvider;
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//! Table function that computes the total physical file sizes in the table directory.
19+
//!
20+
//! Usage: `SELECT * FROM physical_files_size('db.table_name')`
21+
22+
use std::any::Any;
23+
use std::fmt::Debug;
24+
use std::sync::{Arc, OnceLock};
25+
26+
use async_trait::async_trait;
27+
use datafusion::arrow::array::{Int64Array, RecordBatch};
28+
use datafusion::arrow::datatypes::{DataType, Field, Schema, SchemaRef};
29+
use datafusion::catalog::Session;
30+
use datafusion::catalog::TableFunctionImpl;
31+
use datafusion::datasource::memory::MemorySourceConfig;
32+
use datafusion::datasource::{TableProvider, TableType};
33+
use datafusion::error::Result as DFResult;
34+
use datafusion::logical_expr::Expr;
35+
use datafusion::physical_plan::ExecutionPlan;
36+
use datafusion::prelude::SessionContext;
37+
use paimon::catalog::Catalog;
38+
use paimon::table::referenced_files::{collect_physical_files_summary, PhysicalFilesSummary};
39+
use paimon::table::Table;
40+
41+
use crate::error::to_datafusion_error;
42+
use crate::runtime::{await_with_runtime, block_on_with_runtime};
43+
use crate::table_function_args::{extract_string_literal, parse_table_identifier};
44+
45+
const FUNCTION_NAME: &str = "physical_files_size";
46+
47+
pub fn register_physical_files_size(
48+
ctx: &SessionContext,
49+
catalog: Arc<dyn Catalog>,
50+
default_database: &str,
51+
) {
52+
ctx.register_udtf(
53+
FUNCTION_NAME,
54+
Arc::new(PhysicalFilesSizeFunction::new(catalog, default_database)),
55+
);
56+
}
57+
58+
pub struct PhysicalFilesSizeFunction {
59+
catalog: Arc<dyn Catalog>,
60+
default_database: String,
61+
}
62+
63+
impl Debug for PhysicalFilesSizeFunction {
64+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65+
f.debug_struct("PhysicalFilesSizeFunction")
66+
.field("default_database", &self.default_database)
67+
.finish()
68+
}
69+
}
70+
71+
impl PhysicalFilesSizeFunction {
72+
pub fn new(catalog: Arc<dyn Catalog>, default_database: &str) -> Self {
73+
Self {
74+
catalog,
75+
default_database: default_database.to_string(),
76+
}
77+
}
78+
}
79+
80+
impl TableFunctionImpl for PhysicalFilesSizeFunction {
81+
fn call(&self, args: &[Expr]) -> DFResult<Arc<dyn TableProvider>> {
82+
if args.len() != 1 {
83+
return Err(datafusion::error::DataFusionError::Plan(
84+
"physical_files_size requires 1 argument: (table_name)".to_string(),
85+
));
86+
}
87+
88+
let table_name = extract_string_literal(FUNCTION_NAME, &args[0], "table_name")?;
89+
let identifier =
90+
parse_table_identifier(FUNCTION_NAME, &table_name, &self.default_database)?;
91+
92+
let catalog = Arc::clone(&self.catalog);
93+
let table = block_on_with_runtime(
94+
async move { catalog.get_table(&identifier).await },
95+
"physical_files_size: catalog access thread panicked",
96+
)
97+
.map_err(to_datafusion_error)?;
98+
99+
Ok(Arc::new(PhysicalFilesSizeTableProvider { table }))
100+
}
101+
}
102+
103+
fn output_schema() -> SchemaRef {
104+
static SCHEMA: OnceLock<SchemaRef> = OnceLock::new();
105+
SCHEMA
106+
.get_or_init(|| {
107+
Arc::new(Schema::new(vec![
108+
Field::new("manifest_file_count", DataType::Int64, false),
109+
Field::new("manifest_file_size", DataType::Int64, false),
110+
Field::new("data_file_count", DataType::Int64, false),
111+
Field::new("data_file_size", DataType::Int64, false),
112+
Field::new("index_file_count", DataType::Int64, false),
113+
Field::new("index_file_size", DataType::Int64, false),
114+
]))
115+
})
116+
.clone()
117+
}
118+
119+
#[derive(Debug)]
120+
struct PhysicalFilesSizeTableProvider {
121+
table: Table,
122+
}
123+
124+
#[async_trait]
125+
impl TableProvider for PhysicalFilesSizeTableProvider {
126+
fn as_any(&self) -> &dyn Any {
127+
self
128+
}
129+
130+
fn schema(&self) -> SchemaRef {
131+
output_schema()
132+
}
133+
134+
fn table_type(&self) -> TableType {
135+
TableType::View
136+
}
137+
138+
async fn scan(
139+
&self,
140+
_state: &dyn Session,
141+
projection: Option<&Vec<usize>>,
142+
_filters: &[Expr],
143+
_limit: Option<usize>,
144+
) -> DFResult<Arc<dyn ExecutionPlan>> {
145+
let table = self.table.clone();
146+
let summary = await_with_runtime(async move {
147+
collect_physical_files_summary(table.file_io(), table.location()).await
148+
})
149+
.await
150+
.map_err(to_datafusion_error)?;
151+
152+
let batch = summary_to_record_batch(&summary)?;
153+
let schema = output_schema();
154+
155+
Ok(MemorySourceConfig::try_new_exec(
156+
&[vec![batch]],
157+
schema,
158+
projection.cloned(),
159+
)?)
160+
}
161+
}
162+
163+
fn summary_to_record_batch(s: &PhysicalFilesSummary) -> DFResult<RecordBatch> {
164+
Ok(RecordBatch::try_new(
165+
output_schema(),
166+
vec![
167+
Arc::new(Int64Array::from(vec![s.manifest_file_count])),
168+
Arc::new(Int64Array::from(vec![s.manifest_file_size])),
169+
Arc::new(Int64Array::from(vec![s.data_file_count])),
170+
Arc::new(Int64Array::from(vec![s.data_file_size])),
171+
Arc::new(Int64Array::from(vec![s.index_file_count])),
172+
Arc::new(Int64Array::from(vec![s.index_file_size])),
173+
],
174+
)?)
175+
}

0 commit comments

Comments
 (0)