This is an automated email from the ASF dual-hosted git repository.
alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-iceberg.git
The following commit(s) were added to refs/heads/main by this push:
new 05e277e Add CI (#6)
05e277e is described below
commit 05e277e8a8bf342db53cbf2d53ed6f2098b44a88
Author: Gabriel <[email protected]>
AuthorDate: Tue Sep 22 13:09:11 2026 +0200
Add CI (#6)
* Add basic CI for lint, test and format
* Format the codebase
* Fix rust toolchain version to 1.98.1
* Better concurrency control for pipelines
* Commit rust-toolchain.toml
* Commit rustfmt.toml
* Format code
* Improve CARGO_INCREMENTAL comment
* Add checks as required to asf.yaml
---
.asf.yaml | 7 +-
.github/actions/setup-rust/action.yml | 44 +++++
.github/dependabot.yml | 55 ++++++
.github/workflows/asf-allowlist-check.yml | 43 +++++
.github/workflows/ci.yml | 84 +++++++++
crates/datafusion/src/physical_plan/commit.rs | 103 +++++++----
.../src/physical_plan/expr_to_predicate.rs | 136 ++++++++------
.../datafusion/src/physical_plan/metadata_scan.rs | 4 +-
crates/datafusion/src/physical_plan/project.rs | 111 ++++++++----
crates/datafusion/src/physical_plan/repartition.rs | 42 +++--
crates/datafusion/src/physical_plan/sort.rs | 35 ++--
crates/datafusion/src/physical_plan/write.rs | 135 ++++++++------
crates/datafusion/src/schema.rs | 70 +++++---
crates/datafusion/src/table/mod.rs | 195 +++++++++++++--------
.../datafusion/src/table/table_provider_factory.rs | 4 +-
crates/datafusion/src/task_writer.rs | 104 +++++++----
.../tests/integration_datafusion_test.rs | 62 +++++--
crates/playground/src/catalog.rs | 10 +-
crates/playground/src/main.rs | 3 +-
crates/sqllogictest/src/engine/datafusion.rs | 30 +++-
crates/sqllogictest/src/engine/mod.rs | 7 +-
crates/sqllogictest/src/schedule.rs | 7 +-
rust-toolchain.toml | 21 +++
rustfmt.toml | 19 ++
24 files changed, 970 insertions(+), 361 deletions(-)
diff --git a/.asf.yaml b/.asf.yaml
index 2200ca8..426ac8d 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -46,5 +46,8 @@ github:
required_status_checks:
# Require branches to be up-to-date before merging.
strict: true
- # Do not require any jobs to pass.
- contexts: []
+ contexts:
+ - "Format"
+ - "Clippy"
+ - "Test"
+ - "asf-allowlist-check"
diff --git a/.github/actions/setup-rust/action.yml
b/.github/actions/setup-rust/action.yml
new file mode 100644
index 0000000..84a54e2
--- /dev/null
+++ b/.github/actions/setup-rust/action.yml
@@ -0,0 +1,44 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: Setup Rust
+description: Install the Rust toolchain and optional components used by CI.
+
+inputs:
+ cache:
+ description: Restore Rust dependencies and save them on main-branch pushes.
+ required: false
+ default: "false"
+ components:
+ description: Comma-separated Rust components to install.
+ required: false
+
+runs:
+ using: composite
+ steps:
+ - name: Install Rust toolchain
+ uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
+ with:
+ toolchain: 1.98.1
+ components: ${{ inputs.components }}
+ - name: Cache Rust artifacts
+ if: inputs.cache == 'true'
+ uses: swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 #
v2.9.2
+ with:
+ # Pull requests may restore the main-branch cache, but only trusted
+ # main-branch runs may create or update cache entries.
+ save-if: ${{ github.event_name == 'push' && github.ref ==
'refs/heads/main' }}
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..3216549
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,55 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+version: 2
+updates:
+ # Keep routine Cargo updates current while reserving ecosystem-coupled major
+ # upgrades for an intentional compatibility review, as DataFusion does.
+ - package-ecosystem: cargo
+ directory: "/"
+ schedule:
+ interval: weekly
+ target-branch: main
+ open-pull-requests-limit: 15
+ ignore:
+ - dependency-name: "datafusion*"
+ update-types: ["version-update:semver-major"]
+ - dependency-name: "parquet"
+ update-types: ["version-update:semver-major"]
+ groups:
+ all-other-cargo-deps:
+ applies-to: version-updates
+ patterns:
+ - "*"
+ exclude-patterns:
+ - "datafusion*"
+ - "parquet"
+ update-types:
+ - minor
+ - patch
+
+ # The glob covers local composite actions such as setup-rust as well as
+ # workflow files in the repository root.
+ - package-ecosystem: github-actions
+ directories: ["/", "/.github/actions/*"]
+ schedule:
+ interval: weekly
+ open-pull-requests-limit: 10
+ groups:
+ github-actions:
+ patterns:
+ - "*"
diff --git a/.github/workflows/asf-allowlist-check.yml
b/.github/workflows/asf-allowlist-check.yml
new file mode 100644
index 0000000..9a75b1b
--- /dev/null
+++ b/.github/workflows/asf-allowlist-check.yml
@@ -0,0 +1,43 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Taken from
https://github.com/apache/infrastructure-actions/tree/main/allowlist-check
+
+name: "ASF Allowlist Check"
+
+on:
+ workflow_dispatch:
+ pull_request:
+ paths:
+ - ".github/**"
+ push:
+ branches:
+ - main
+ paths:
+ - ".github/**"
+
+permissions:
+ contents: read
+
+jobs:
+ asf-allowlist-check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+ - uses: apache/infrastructure-actions/allowlist-check@main
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..89c0545
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,84 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: CI
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+ merge_group:
+
+# Cancel obsolete runs for the same pull request or branch. The three jobs
below
+# keep this workflow well below ASF's 15-job recommended concurrency limit.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number ||
github.sha }}
+ cancel-in-progress: true
+
+# CI needs no credentials beyond reading the source tree.
+permissions:
+ contents: read
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ format:
+ name: Format
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #
v7.0.1
+ with:
+ persist-credentials: false
+ - name: Setup Rust
+ uses: ./.github/actions/setup-rust
+ with:
+ components: rustfmt
+ - run: cargo fmt --all -- --check
+
+ clippy:
+ name: Clippy
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #
v7.0.1
+ with:
+ persist-credentials: false
+ - name: Setup Rust
+ uses: ./.github/actions/setup-rust
+ with:
+ components: clippy
+ cache: "true"
+ - run: cargo clippy --workspace --locked --all-targets -- -D warnings
+
+ test:
+ name: Test
+ runs-on: ubuntu-latest
+ env:
+ # Incremental artifacts and debug info go unused in CI and only
+ # add cache size and build time.
+ CARGO_INCREMENTAL: "0"
+ CARGO_PROFILE_DEV_DEBUG: "0"
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #
v7.0.1
+ with:
+ persist-credentials: false
+ - name: Setup Rust
+ uses: ./.github/actions/setup-rust
+ with:
+ cache: "true"
+ - run: cargo test --workspace --locked
diff --git a/crates/datafusion/src/physical_plan/commit.rs
b/crates/datafusion/src/physical_plan/commit.rs
index bb50721..d080f61 100644
--- a/crates/datafusion/src/physical_plan/commit.rs
+++ b/crates/datafusion/src/physical_plan/commit.rs
@@ -28,7 +28,9 @@ use datafusion::execution::{SendableRecordBatchStream,
TaskContext};
use datafusion::physical_expr::{EquivalenceProperties, Partitioning,
PhysicalExpr};
use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
-use datafusion::physical_plan::{DisplayAs, DisplayFormatType, ExecutionPlan,
PlanProperties};
+use datafusion::physical_plan::{
+ DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties,
+};
use futures::StreamExt;
use iceberg::Catalog;
use iceberg::spec::{DataFile, deserialize_data_file_from_json};
@@ -85,12 +87,13 @@ impl IcebergCommitExec {
fn make_count_batch(count: u64) -> DFResult<RecordBatch> {
let count_array = Arc::new(UInt64Array::from(vec![count])) as ArrayRef;
- RecordBatch::try_from_iter_with_nullable(vec![("count", count_array,
false)]).map_err(|e| {
- DataFusionError::ArrowError(
- Box::new(e),
- Some("Failed to make count batch!".to_string()),
- )
- })
+ RecordBatch::try_from_iter_with_nullable(vec![("count", count_array,
false)])
+ .map_err(|e| {
+ DataFusionError::ArrowError(
+ Box::new(e),
+ Some("Failed to make count batch!".to_string()),
+ )
+ })
}
fn make_count_schema() -> ArrowSchemaRef {
@@ -144,8 +147,13 @@ impl ExecutionPlan for IcebergCommitExec {
Ok(TreeNodeRecursion::Continue)
}
- fn required_input_distribution(&self) ->
Vec<datafusion::physical_plan::Distribution> {
- vec![datafusion::physical_plan::Distribution::SinglePartition;
self.children().len()]
+ fn required_input_distribution(
+ &self,
+ ) -> Vec<datafusion::physical_plan::Distribution> {
+ vec![
+ datafusion::physical_plan::Distribution::SinglePartition;
+ self.children().len()
+ ]
}
fn benefits_from_input_partitioning(&self) -> Vec<bool> {
@@ -236,7 +244,8 @@ impl ExecutionPlan for IcebergCommitExec {
.collect::<datafusion::common::Result<_>>()?;
// add record_counts from the current batch to total record
count
- total_record_count += batch_files.iter().map(|f|
f.record_count()).sum::<u64>();
+ total_record_count +=
+ batch_files.iter().map(|f| f.record_count()).sum::<u64>();
// Add all deserialized files to our collection
data_files.extend(batch_files);
@@ -276,7 +285,9 @@ mod tests {
use std::fmt;
use std::sync::Arc;
- use datafusion::arrow::array::{ArrayRef, Int32Array, RecordBatch,
StringArray, UInt64Array};
+ use datafusion::arrow::array::{
+ ArrayRef, Int32Array, RecordBatch, StringArray, UInt64Array,
+ };
use datafusion::arrow::datatypes::{DataType, Field, Schema as ArrowSchema};
use datafusion::datasource::MemTable;
use datafusion::execution::context::TaskContext;
@@ -284,13 +295,15 @@ mod tests {
use datafusion::physical_plan::common::collect;
use datafusion::physical_plan::execution_plan::Boundedness;
use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
- use datafusion::physical_plan::{DisplayAs, DisplayFormatType,
ExecutionPlan, PlanProperties};
+ use datafusion::physical_plan::{
+ DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties,
+ };
use datafusion::prelude::*;
use futures::StreamExt;
use iceberg::memory::{MEMORY_CATALOG_WAREHOUSE, MemoryCatalogBuilder};
use iceberg::spec::{
- DataContentType, DataFileBuilder, DataFileFormat, NestedField,
PrimitiveType, Schema,
- Struct, Type,
+ DataContentType, DataFileBuilder, DataFileFormat, NestedField,
PrimitiveType,
+ Schema, Struct, Type,
};
use iceberg::{Catalog, CatalogBuilder, NamespaceIdent, TableCreation,
TableIdent};
@@ -366,7 +379,8 @@ mod tests {
_context: Arc<TaskContext>,
) -> datafusion::common::Result<SendableRecordBatchStream> {
// Create a record batch with the serialized data files
- let array =
Arc::new(StringArray::from(self.data_files_json.clone())) as ArrayRef;
+ let array =
+ Arc::new(StringArray::from(self.data_files_json.clone())) as
ArrayRef;
let batch = RecordBatch::try_new(self.schema.clone(),
vec![array])?;
// Create a stream that returns this batch
@@ -415,8 +429,10 @@ mod tests {
let schema = Schema::builder()
.with_schema_id(1)
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
+ NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String))
+ .into(),
])
.build()?;
@@ -466,7 +482,8 @@ mod tests {
)?;
// Create a mock execution plan that returns the serialized data files
- let input_exec = Arc::new(MockWriteExec::new(vec![data_file1_json,
data_file2_json]));
+ let input_exec =
+ Arc::new(MockWriteExec::new(vec![data_file1_json,
data_file2_json]));
// Create the IcebergCommitExec
let arrow_schema = Arc::new(ArrowSchema::new(vec![Field::new(
@@ -475,8 +492,12 @@ mod tests {
false,
)]));
- let commit_exec =
- IcebergCommitExec::new(table.clone(), catalog.clone(), input_exec,
arrow_schema);
+ let commit_exec = IcebergCommitExec::new(
+ table.clone(),
+ catalog.clone(),
+ input_exec,
+ arrow_schema,
+ );
// Verify Execution Plan schema matches the count schema
assert_eq!(commit_exec.schema(),
IcebergCommitExec::make_count_schema());
@@ -537,7 +558,8 @@ mod tests {
}
#[tokio::test]
- async fn test_iceberg_commit_exec_empty_insert() -> Result<(), Box<dyn
std::error::Error>> {
+ async fn test_iceberg_commit_exec_empty_insert()
+ -> Result<(), Box<dyn std::error::Error>> {
let catalog = Arc::new(
MemoryCatalogBuilder::default()
.load(
@@ -557,7 +579,8 @@ mod tests {
let schema = Schema::builder()
.with_schema_id(1)
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
])
.build()?;
@@ -578,8 +601,12 @@ mod tests {
DataType::Utf8,
false,
)]));
- let commit_exec =
- IcebergCommitExec::new(table.clone(), catalog.clone(), input_exec,
arrow_schema);
+ let commit_exec = IcebergCommitExec::new(
+ table.clone(),
+ catalog.clone(),
+ input_exec,
+ arrow_schema,
+ );
let task_ctx = Arc::new(TaskContext::default());
let stream = commit_exec.execute(0, task_ctx)?;
@@ -600,7 +627,8 @@ mod tests {
// No new snapshot should be created for an empty insert
let updated_table = catalog
.load_table(
- &TableIdent::from_strs(["test_empty_insert",
"empty_insert_table"]).unwrap(),
+ &TableIdent::from_strs(["test_empty_insert",
"empty_insert_table"])
+ .unwrap(),
)
.await?;
let snapshot_count_after = updated_table.metadata().snapshots().len();
@@ -614,8 +642,8 @@ mod tests {
}
#[tokio::test]
- async fn test_datafusion_execution_partitioned_source() -> Result<(),
Box<dyn std::error::Error>>
- {
+ async fn test_datafusion_execution_partitioned_source()
+ -> Result<(), Box<dyn std::error::Error>> {
let catalog = Arc::new(
MemoryCatalogBuilder::default()
.load(
@@ -634,8 +662,10 @@ mod tests {
let schema = Schema::builder()
.with_schema_id(1)
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
+ NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String))
+ .into(),
])
.build()?;
@@ -655,10 +685,14 @@ mod tests {
let batches: Vec<RecordBatch> = (1..4)
.map(|idx| {
- RecordBatch::try_new(arrow_schema.clone(), vec![
- Arc::new(Int32Array::from(vec![idx])) as ArrayRef,
- Arc::new(StringArray::from(vec![format!("Name{idx}")])) as
ArrayRef,
- ])
+ RecordBatch::try_new(
+ arrow_schema.clone(),
+ vec![
+ Arc::new(Int32Array::from(vec![idx])) as ArrayRef,
+ Arc::new(StringArray::from(vec![format!("Name{idx}")]))
+ as ArrayRef,
+ ],
+ )
})
.collect::<Result<_, _>>()?;
@@ -670,7 +704,8 @@ mod tests {
// Create multiple partitions - each batch becomes a separate partition
let partitions: Vec<Vec<RecordBatch>> =
batches.into_iter().map(|batch| vec![batch]).collect();
- let source_table =
Arc::new(MemTable::try_new(Arc::clone(&arrow_schema), partitions)?);
+ let source_table =
+ Arc::new(MemTable::try_new(Arc::clone(&arrow_schema),
partitions)?);
ctx.register_table("source_table", source_table)?;
let iceberg_table_provider = IcebergTableProvider::try_new(
diff --git a/crates/datafusion/src/physical_plan/expr_to_predicate.rs
b/crates/datafusion/src/physical_plan/expr_to_predicate.rs
index fb5440a..58b3ee2 100644
--- a/crates/datafusion/src/physical_plan/expr_to_predicate.rs
+++ b/crates/datafusion/src/physical_plan/expr_to_predicate.rs
@@ -21,7 +21,9 @@ use datafusion::arrow::datatypes::DataType;
use datafusion::logical_expr::expr::ScalarFunction;
use datafusion::logical_expr::{BinaryExpr, Expr, Like, Operator};
use datafusion::scalar::ScalarValue;
-use iceberg::expr::{BinaryExpression, Predicate, PredicateOperator, Reference,
UnaryExpression};
+use iceberg::expr::{
+ BinaryExpression, Predicate, PredicateOperator, Reference, UnaryExpression,
+};
use iceberg::spec::{Datum, PrimitiveLiteral};
// A datafusion expression could be an Iceberg predicate, column, or literal.
@@ -76,7 +78,9 @@ fn to_iceberg_predicate(expr: &Expr) -> TransformedResult {
let right = to_iceberg_predicate(&binary.right);
let op = to_iceberg_operation(binary.op);
match op {
- OpTransformedResult::Operator(op) =>
to_iceberg_binary_predicate(left, right, op),
+ OpTransformedResult::Operator(op) => {
+ to_iceberg_binary_predicate(left, right, op)
+ }
OpTransformedResult::And => to_iceberg_and_predicate(left,
right),
OpTransformedResult::Or => to_iceberg_or_predicate(left,
right),
OpTransformedResult::NotTransformed =>
TransformedResult::NotTransformed,
@@ -88,11 +92,13 @@ fn to_iceberg_predicate(expr: &Expr) -> TransformedResult {
TransformedResult::Predicate(p) =>
TransformedResult::Predicate(!p),
TransformedResult::Column(column) => {
// NOT of a bare boolean column: NOT col => col = false
-
TransformedResult::Predicate(Predicate::Binary(BinaryExpression::new(
- PredicateOperator::Eq,
- column,
- Datum::bool(false),
- )))
+ TransformedResult::Predicate(Predicate::Binary(
+ BinaryExpression::new(
+ PredicateOperator::Eq,
+ column,
+ Datum::bool(false),
+ ),
+ ))
}
_ => TransformedResult::NotTransformed,
}
@@ -124,23 +130,24 @@ fn to_iceberg_predicate(expr: &Expr) -> TransformedResult
{
Expr::IsNull(expr) => {
let p = to_iceberg_predicate(expr);
match p {
- TransformedResult::Column(r) =>
TransformedResult::Predicate(Predicate::Unary(
- UnaryExpression::new(PredicateOperator::IsNull, r),
- )),
+ TransformedResult::Column(r) => TransformedResult::Predicate(
+
Predicate::Unary(UnaryExpression::new(PredicateOperator::IsNull, r)),
+ ),
_ => TransformedResult::NotTransformed,
}
}
Expr::IsNotNull(expr) => {
let p = to_iceberg_predicate(expr);
match p {
- TransformedResult::Column(r) =>
TransformedResult::Predicate(Predicate::Unary(
- UnaryExpression::new(PredicateOperator::NotNull, r),
- )),
+ TransformedResult::Column(r) => TransformedResult::Predicate(
+
Predicate::Unary(UnaryExpression::new(PredicateOperator::NotNull, r)),
+ ),
_ => TransformedResult::NotTransformed,
}
}
Expr::Cast(c) => {
- if *c.field.data_type() == DataType::Date32 ||
*c.field.data_type() == DataType::Date64
+ if *c.field.data_type() == DataType::Date32
+ || *c.field.data_type() == DataType::Date64
{
// Casts to date truncate the expression, we cannot simply
extract it as it
// can create erroneous predicates.
@@ -212,7 +219,9 @@ fn to_iceberg_operation(op: Operator) ->
OpTransformedResult {
Operator::Lt =>
OpTransformedResult::Operator(PredicateOperator::LessThan),
Operator::LtEq =>
OpTransformedResult::Operator(PredicateOperator::LessThanOrEq),
Operator::Gt =>
OpTransformedResult::Operator(PredicateOperator::GreaterThan),
- Operator::GtEq =>
OpTransformedResult::Operator(PredicateOperator::GreaterThanOrEq),
+ Operator::GtEq => {
+ OpTransformedResult::Operator(PredicateOperator::GreaterThanOrEq)
+ }
// AND OR
Operator::And => OpTransformedResult::And,
Operator::Or => OpTransformedResult::Or,
@@ -224,7 +233,10 @@ fn to_iceberg_operation(op: Operator) ->
OpTransformedResult {
/// Translates a DataFusion scalar function into an Iceberg predicate.
/// Unlike dedicated Expr variants (e.g. `Expr::IsNull`), scalar functions are
/// identified by name at runtime, so we need to handle them here.
-fn scalar_function_to_iceberg_predicate(func_name: &str, args: &[Expr]) ->
TransformedResult {
+fn scalar_function_to_iceberg_predicate(
+ func_name: &str,
+ args: &[Expr],
+) -> TransformedResult {
match func_name {
"isnan" if args.len() == 1 => match
resolve_nan_preserving_reference(&args[0]) {
Some(r) => TransformedResult::Predicate(r.is_nan()),
@@ -369,7 +381,10 @@ fn to_iceberg_and_predicate(
}
}
-fn to_iceberg_or_predicate(left: TransformedResult, right: TransformedResult)
-> TransformedResult {
+fn to_iceberg_or_predicate(
+ left: TransformedResult,
+ right: TransformedResult,
+) -> TransformedResult {
match (left, right) {
(TransformedResult::Predicate(left),
TransformedResult::Predicate(right)) => {
TransformedResult::Predicate(left.or(right))
@@ -384,8 +399,12 @@ fn to_iceberg_binary_predicate(
op: PredicateOperator,
) -> TransformedResult {
let (r, d, op) = match (left, right) {
- (TransformedResult::NotTransformed, _) => return
TransformedResult::NotTransformed,
- (_, TransformedResult::NotTransformed) => return
TransformedResult::NotTransformed,
+ (TransformedResult::NotTransformed, _) => {
+ return TransformedResult::NotTransformed;
+ }
+ (_, TransformedResult::NotTransformed) => {
+ return TransformedResult::NotTransformed;
+ }
(TransformedResult::Column(r), TransformedResult::Literal(d)) => (r,
d, op),
(TransformedResult::Literal(d), TransformedResult::Column(r)) => {
(r, d, reverse_predicate_operator(op))
@@ -430,7 +449,9 @@ fn scalar_value_to_datum(value: &ScalarValue) ->
Option<Datum> {
// DataFusion's type coercion always converts them to match the column
type
// (either TimestampMicrosecond or TimestampNanosecond) before
predicate pushdown.
// See unit tests for how those conversions would work if needed.
- ScalarValue::TimestampMicrosecond(Some(v), _) =>
Some(Datum::timestamp_micros(*v)),
+ ScalarValue::TimestampMicrosecond(Some(v), _) => {
+ Some(Datum::timestamp_micros(*v))
+ }
ScalarValue::TimestampNanosecond(Some(v), _) =>
Some(Datum::timestamp_nanos(*v)),
_ => None,
}
@@ -451,23 +472,24 @@ mod tests {
use super::convert_filters_to_predicate;
fn create_test_schema() -> DFSchema {
- let arrow_schema = Schema::new(vec![
- Field::new("foo", DataType::Int32,
true).with_metadata(HashMap::from([(
- PARQUET_FIELD_ID_META_KEY.to_string(),
- "1".to_string(),
- )])),
- Field::new("bar", DataType::Utf8,
true).with_metadata(HashMap::from([(
- PARQUET_FIELD_ID_META_KEY.to_string(),
- "2".to_string(),
- )])),
- Field::new("ts", DataType::Timestamp(TimeUnit::Second, None),
true).with_metadata(
- HashMap::from([(PARQUET_FIELD_ID_META_KEY.to_string(),
"3".to_string())]),
- ),
- Field::new("qux", DataType::Float64,
true).with_metadata(HashMap::from([(
- PARQUET_FIELD_ID_META_KEY.to_string(),
- "4".to_string(),
- )])),
- ]);
+ let arrow_schema =
+ Schema::new(vec![
+ Field::new("foo", DataType::Int32,
true).with_metadata(HashMap::from([
+ (PARQUET_FIELD_ID_META_KEY.to_string(), "1".to_string()),
+ ])),
+ Field::new("bar", DataType::Utf8,
true).with_metadata(HashMap::from([(
+ PARQUET_FIELD_ID_META_KEY.to_string(),
+ "2".to_string(),
+ )])),
+ Field::new("ts", DataType::Timestamp(TimeUnit::Second, None),
true)
+ .with_metadata(HashMap::from([(
+ PARQUET_FIELD_ID_META_KEY.to_string(),
+ "3".to_string(),
+ )])),
+ Field::new("qux", DataType::Float64,
true).with_metadata(HashMap::from(
+ [(PARQUET_FIELD_ID_META_KEY.to_string(), "4".to_string())],
+ )),
+ ]);
DFSchema::try_from_qualified_schema("my_table", &arrow_schema).unwrap()
}
@@ -640,8 +662,8 @@ mod tests {
fn test_predicate_conversion_with_cast() {
let sql = "ts >= timestamp '2023-01-05T00:00:00'";
let predicate = convert_to_iceberg_predicate(sql).unwrap();
- let expected_predicate =
-
Reference::new("ts").greater_than_or_equal_to(Datum::string("2023-01-05T00:00:00"));
+ let expected_predicate = Reference::new("ts")
+ .greater_than_or_equal_to(Datum::string("2023-01-05T00:00:00"));
assert_eq!(predicate, expected_predicate);
}
@@ -658,18 +680,23 @@ mod tests {
// Test TimestampMicrosecond - maps directly to Datum::timestamp_micros
let ts_micros = 1672876800000000i64; // 2023-01-05 00:00:00 UTC in
microseconds
- let datum =
-
super::scalar_value_to_datum(&ScalarValue::TimestampMicrosecond(Some(ts_micros),
None));
+ let datum =
super::scalar_value_to_datum(&ScalarValue::TimestampMicrosecond(
+ Some(ts_micros),
+ None,
+ ));
assert_eq!(datum, Some(Datum::timestamp_micros(ts_micros)));
// Test TimestampNanosecond - maps to Datum::timestamp_nanos to
preserve precision
let ts_nanos = 1672876800000000500i64; // 2023-01-05
00:00:00.000000500 UTC in nanoseconds
- let datum =
-
super::scalar_value_to_datum(&ScalarValue::TimestampNanosecond(Some(ts_nanos),
None));
+ let datum =
super::scalar_value_to_datum(&ScalarValue::TimestampNanosecond(
+ Some(ts_nanos),
+ None,
+ ));
assert_eq!(datum, Some(Datum::timestamp_nanos(ts_nanos)));
// Test None timestamp
- let datum =
super::scalar_value_to_datum(&ScalarValue::TimestampMicrosecond(None, None));
+ let datum =
+
super::scalar_value_to_datum(&ScalarValue::TimestampMicrosecond(None, None));
assert_eq!(datum, None);
// Note: TimestampSecond and TimestampMillisecond are not supported
because
@@ -678,13 +705,17 @@ mod tests {
//
// These return None (not pushed down):
let ts_seconds = 1672876800i64; // 2023-01-05 00:00:00 UTC in seconds
- let datum =
-
super::scalar_value_to_datum(&ScalarValue::TimestampSecond(Some(ts_seconds),
None));
+ let datum = super::scalar_value_to_datum(&ScalarValue::TimestampSecond(
+ Some(ts_seconds),
+ None,
+ ));
assert_eq!(datum, None);
let ts_millis = 1672876800000i64; // 2023-01-05 00:00:00 UTC in
milliseconds
- let datum =
-
super::scalar_value_to_datum(&ScalarValue::TimestampMillisecond(Some(ts_millis),
None));
+ let datum =
super::scalar_value_to_datum(&ScalarValue::TimestampMillisecond(
+ Some(ts_millis),
+ None,
+ ));
assert_eq!(datum, None);
}
@@ -693,10 +724,12 @@ mod tests {
use datafusion::common::ScalarValue;
let bytes = vec![1u8, 2u8, 3u8];
- let datum =
super::scalar_value_to_datum(&ScalarValue::Binary(Some(bytes.clone())));
+ let datum =
+
super::scalar_value_to_datum(&ScalarValue::Binary(Some(bytes.clone())));
assert_eq!(datum, Some(Datum::binary(bytes.clone())));
- let datum =
super::scalar_value_to_datum(&ScalarValue::LargeBinary(Some(bytes.clone())));
+ let datum =
+
super::scalar_value_to_datum(&ScalarValue::LargeBinary(Some(bytes.clone())));
assert_eq!(datum, Some(Datum::binary(bytes)));
let datum = super::scalar_value_to_datum(&ScalarValue::Binary(None));
@@ -892,7 +925,8 @@ mod tests {
#[test]
fn test_predicate_conversion_with_isnan_nested_expr() {
// Nested NaN-preserving transformations resolve to the inner column
- let predicate = convert_to_iceberg_predicate("isnan(-(abs(qux) + 1) *
3)").unwrap();
+ let predicate =
+ convert_to_iceberg_predicate("isnan(-(abs(qux) + 1) *
3)").unwrap();
assert_eq!(predicate, Reference::new("qux").is_nan());
}
diff --git a/crates/datafusion/src/physical_plan/metadata_scan.rs
b/crates/datafusion/src/physical_plan/metadata_scan.rs
index 33c1143..92e0671 100644
--- a/crates/datafusion/src/physical_plan/metadata_scan.rs
+++ b/crates/datafusion/src/physical_plan/metadata_scan.rs
@@ -73,7 +73,9 @@ impl ExecutionPlan for IcebergMetadataScan {
fn apply_expressions(
&self,
- _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) ->
datafusion::error::Result<TreeNodeRecursion>,
+ _f: &mut dyn FnMut(
+ &Arc<dyn PhysicalExpr>,
+ ) -> datafusion::error::Result<TreeNodeRecursion>,
) -> datafusion::error::Result<TreeNodeRecursion> {
Ok(TreeNodeRecursion::Continue)
}
diff --git a/crates/datafusion/src/physical_plan/project.rs
b/crates/datafusion/src/physical_plan/project.rs
index a77abd0..d8ab9b3 100644
--- a/crates/datafusion/src/physical_plan/project.rs
+++ b/crates/datafusion/src/physical_plan/project.rs
@@ -68,8 +68,8 @@ pub fn project_with_partition(
schema_to_arrow_schema(table_schema.as_ref()).map_err(to_datafusion_error)?;
let input_schema_cleaned =
strip_metadata_from_schema(&input_schema).map_err(to_datafusion_error)?;
- let expected_schema_cleaned =
-
strip_metadata_from_schema(&expected_arrow_schema).map_err(to_datafusion_error)?;
+ let expected_schema_cleaned =
strip_metadata_from_schema(&expected_arrow_schema)
+ .map_err(to_datafusion_error)?;
if input_schema_cleaned != expected_schema_cleaned {
return Err(DataFusionError::Plan(format!(
@@ -106,7 +106,10 @@ struct PartitionExpr {
}
impl PartitionExpr {
- fn new(calculator: PartitionValueCalculator, partition_spec:
Arc<PartitionSpec>) -> Self {
+ fn new(
+ calculator: PartitionValueCalculator,
+ partition_spec: Arc<PartitionSpec>,
+ ) -> Self {
Self {
calculator: Arc::new(calculator),
partition_spec,
@@ -189,7 +192,9 @@ mod tests {
use datafusion::arrow::array::{ArrayRef, Int32Array, StructArray};
use datafusion::arrow::datatypes::{DataType, Field, Fields};
use datafusion::physical_plan::empty::EmptyExec;
- use iceberg::spec::{NestedField, PrimitiveType, Schema, StructType,
Transform, Type};
+ use iceberg::spec::{
+ NestedField, PrimitiveType, Schema, StructType, Transform, Type,
+ };
use iceberg::test_utils::test_runtime;
use super::*;
@@ -199,8 +204,10 @@ mod tests {
let table_schema = Schema::builder()
.with_schema_id(0)
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
+ NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String))
+ .into(),
])
.build()
.unwrap();
@@ -211,7 +218,8 @@ mod tests {
.build()
.unwrap();
- let calculator = PartitionValueCalculator::try_new(&partition_spec,
&table_schema).unwrap();
+ let calculator =
+ PartitionValueCalculator::try_new(&partition_spec,
&table_schema).unwrap();
// Verify partition type
assert_eq!(calculator.partition_type().fields().len(), 1);
@@ -223,8 +231,10 @@ mod tests {
let table_schema = Schema::builder()
.with_schema_id(0)
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
+ NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String))
+ .into(),
])
.build()
.unwrap();
@@ -244,7 +254,8 @@ mod tests {
let input = Arc::new(EmptyExec::new(arrow_schema.clone()));
- let calculator = PartitionValueCalculator::try_new(&partition_spec,
&table_schema).unwrap();
+ let calculator =
+ PartitionValueCalculator::try_new(&partition_spec,
&table_schema).unwrap();
let mut projection_exprs: Vec<(Arc<dyn PhysicalExpr>, String)> =
Vec::with_capacity(arrow_schema.fields().len() + 1);
@@ -254,7 +265,8 @@ mod tests {
}
let partition_expr = Arc::new(PartitionExpr::new(calculator,
partition_spec));
- projection_exprs.push((partition_expr,
PROJECTED_PARTITION_VALUE_COLUMN.to_string()));
+ projection_exprs
+ .push((partition_expr,
PROJECTED_PARTITION_VALUE_COLUMN.to_string()));
let projection = ProjectionExec::try_new(projection_exprs,
input).unwrap();
let result = Arc::new(projection);
@@ -270,8 +282,10 @@ mod tests {
let table_schema = Schema::builder()
.with_schema_id(0)
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "data",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
+ NestedField::required(2, "data",
Type::Primitive(PrimitiveType::String))
+ .into(),
])
.build()
.unwrap();
@@ -287,16 +301,20 @@ mod tests {
Field::new("data", DataType::Utf8, false),
]));
- let batch = RecordBatch::try_new(arrow_schema.clone(), vec![
- Arc::new(Int32Array::from(vec![10, 20, 30])),
- Arc::new(datafusion::arrow::array::StringArray::from(vec![
- "a", "b", "c",
- ])),
- ])
+ let batch = RecordBatch::try_new(
+ arrow_schema.clone(),
+ vec![
+ Arc::new(Int32Array::from(vec![10, 20, 30])),
+ Arc::new(datafusion::arrow::array::StringArray::from(vec![
+ "a", "b", "c",
+ ])),
+ ],
+ )
.unwrap();
let partition_spec = Arc::new(partition_spec);
- let calculator = PartitionValueCalculator::try_new(&partition_spec,
&table_schema).unwrap();
+ let calculator =
+ PartitionValueCalculator::try_new(&partition_spec,
&table_schema).unwrap();
let partition_type = calculator.partition_arrow_type().clone();
let expr = PartitionExpr::new(calculator, partition_spec);
@@ -324,14 +342,17 @@ mod tests {
#[test]
fn test_nested_partition() {
let address_struct = StructType::new(vec![
- NestedField::required(3, "street",
Type::Primitive(PrimitiveType::String)).into(),
- NestedField::required(4, "city",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(3, "street",
Type::Primitive(PrimitiveType::String))
+ .into(),
+ NestedField::required(4, "city",
Type::Primitive(PrimitiveType::String))
+ .into(),
]);
let table_schema = Schema::builder()
.with_schema_id(0)
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
NestedField::required(2, "address",
Type::Struct(address_struct)).into(),
])
.build()
@@ -373,13 +394,17 @@ mod tests {
),
]);
- let batch = RecordBatch::try_new(arrow_schema.clone(), vec![
- Arc::new(Int32Array::from(vec![1, 2])),
- Arc::new(struct_array),
- ])
+ let batch = RecordBatch::try_new(
+ arrow_schema.clone(),
+ vec![
+ Arc::new(Int32Array::from(vec![1, 2])),
+ Arc::new(struct_array),
+ ],
+ )
.unwrap();
- let calculator = PartitionValueCalculator::try_new(&partition_spec,
&table_schema).unwrap();
+ let calculator =
+ PartitionValueCalculator::try_new(&partition_spec,
&table_schema).unwrap();
let array = calculator.calculate(&batch).unwrap();
let struct_array =
array.as_any().downcast_ref::<StructArray>().unwrap();
@@ -403,8 +428,14 @@ mod tests {
let table_schema = Arc::new(
Schema::builder()
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
+ NestedField::required(
+ 2,
+ "name",
+ Type::Primitive(PrimitiveType::String),
+ )
+ .into(),
])
.build()
.unwrap(),
@@ -462,8 +493,14 @@ mod tests {
let table_schema = Arc::new(
Schema::builder()
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
+ NestedField::required(
+ 2,
+ "name",
+ Type::Primitive(PrimitiveType::String),
+ )
+ .into(),
])
.build()
.unwrap(),
@@ -532,8 +569,14 @@ mod tests {
let table_schema = Arc::new(
Schema::builder()
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
+ NestedField::required(
+ 2,
+ "name",
+ Type::Primitive(PrimitiveType::String),
+ )
+ .into(),
])
.build()
.unwrap(),
diff --git a/crates/datafusion/src/physical_plan/repartition.rs
b/crates/datafusion/src/physical_plan/repartition.rs
index efa0f3c..e6cbc13 100644
--- a/crates/datafusion/src/physical_plan/repartition.rs
+++ b/crates/datafusion/src/physical_plan/repartition.rs
@@ -171,15 +171,16 @@ fn determine_partitioning_strategy(
#[cfg(test)]
mod tests {
use datafusion::arrow::datatypes::{
- DataType as ArrowDataType, Field as ArrowField, Fields, Schema as
ArrowSchema, TimeUnit,
+ DataType as ArrowDataType, Field as ArrowField, Fields, Schema as
ArrowSchema,
+ TimeUnit,
};
use datafusion::execution::TaskContext;
use datafusion::physical_plan::empty::EmptyExec;
use iceberg::TableIdent;
use iceberg::io::FileIO;
use iceberg::spec::{
- NestedField, NullOrder, PrimitiveType, Schema, SortDirection,
SortField, SortOrder,
- Transform, Type,
+ NestedField, NullOrder, PrimitiveType, Schema, SortDirection,
SortField,
+ SortOrder, Transform, Type,
};
use iceberg::table::Table;
use iceberg::test_utils::test_runtime;
@@ -258,7 +259,8 @@ mod tests {
let input = Arc::new(EmptyExec::new(create_test_arrow_schema()));
let repartitioned_plan =
- repartition(input, table.metadata_ref(),
NonZeroUsize::new(8).unwrap()).unwrap();
+ repartition(input, table.metadata_ref(),
NonZeroUsize::new(8).unwrap())
+ .unwrap();
let partitioning =
repartitioned_plan.properties().output_partitioning();
match partitioning {
@@ -310,7 +312,8 @@ mod tests {
let input = Arc::new(EmptyExec::new(create_test_arrow_schema()));
let repartitioned_plan =
- repartition(input, table.metadata_ref(),
NonZeroUsize::new(3).unwrap()).unwrap();
+ repartition(input, table.metadata_ref(),
NonZeroUsize::new(3).unwrap())
+ .unwrap();
let partitioning =
repartitioned_plan.properties().output_partitioning();
match partitioning {
@@ -384,7 +387,8 @@ mod tests {
]));
let input = Arc::new(EmptyExec::new(arrow_schema));
let repartitioned_plan =
- repartition(input, table.metadata_ref(),
NonZeroUsize::new(4).unwrap()).unwrap();
+ repartition(input, table.metadata_ref(),
NonZeroUsize::new(4).unwrap())
+ .unwrap();
let partitioning =
repartitioned_plan.properties().output_partitioning();
// For bucketed tables without _partition column, should use
round-robin
@@ -453,7 +457,9 @@ mod tests {
let table_metadata = table_metadata_builder.build().unwrap();
let table = Table::builder()
.metadata(table_metadata.metadata)
- .identifier(TableIdent::from_strs(["test",
"partitioned_bucketed_table"]).unwrap())
+ .identifier(
+ TableIdent::from_strs(["test",
"partitioned_bucketed_table"]).unwrap(),
+ )
.file_io(FileIO::new_with_fs())
.metadata_location("/test/partitioned_bucketed_metadata.json")
.runtime(test_runtime())
@@ -472,7 +478,8 @@ mod tests {
]));
let input = Arc::new(EmptyExec::new(arrow_schema));
let repartitioned_plan =
- repartition(input, table.metadata_ref(),
NonZeroUsize::new(4).unwrap()).unwrap();
+ repartition(input, table.metadata_ref(),
NonZeroUsize::new(4).unwrap())
+ .unwrap();
let partitioning =
repartitioned_plan.properties().output_partitioning();
match partitioning {
@@ -542,7 +549,8 @@ mod tests {
let input = Arc::new(EmptyExec::new(create_test_arrow_schema()));
let repartitioned_plan =
- repartition(input, table.metadata_ref(),
NonZeroUsize::new(4).unwrap()).unwrap();
+ repartition(input, table.metadata_ref(),
NonZeroUsize::new(4).unwrap())
+ .unwrap();
let partitioning =
repartitioned_plan.properties().output_partitioning();
assert!(
@@ -620,7 +628,8 @@ mod tests {
]));
let input = Arc::new(EmptyExec::new(arrow_schema));
let repartitioned_plan =
- repartition(input, table.metadata_ref(),
NonZeroUsize::new(4).unwrap()).unwrap();
+ repartition(input, table.metadata_ref(),
NonZeroUsize::new(4).unwrap())
+ .unwrap();
let partitioning =
repartitioned_plan.properties().output_partitioning();
assert!(
@@ -674,7 +683,9 @@ mod tests {
let table_metadata = table_metadata_builder.build().unwrap();
let table = Table::builder()
.metadata(table_metadata.metadata)
- .identifier(TableIdent::from_strs(["test",
"mixed_transforms_table"]).unwrap())
+ .identifier(
+ TableIdent::from_strs(["test",
"mixed_transforms_table"]).unwrap(),
+ )
.file_io(FileIO::new_with_fs())
.metadata_location("/test/mixed_transforms_metadata.json")
.runtime(test_runtime())
@@ -693,7 +704,8 @@ mod tests {
]));
let input = Arc::new(EmptyExec::new(arrow_schema));
let repartitioned_plan =
- repartition(input, table.metadata_ref(),
NonZeroUsize::new(4).unwrap()).unwrap();
+ repartition(input, table.metadata_ref(),
NonZeroUsize::new(4).unwrap())
+ .unwrap();
let partitioning =
repartitioned_plan.properties().output_partitioning();
match partitioning {
@@ -776,7 +788,8 @@ mod tests {
let input = Arc::new(EmptyExec::new(arrow_schema));
let repartitioned_plan =
- repartition(input, table.metadata_ref(),
NonZeroUsize::new(4).unwrap()).unwrap();
+ repartition(input, table.metadata_ref(),
NonZeroUsize::new(4).unwrap())
+ .unwrap();
let partitioning =
repartitioned_plan.properties().output_partitioning();
assert!(
@@ -842,7 +855,8 @@ mod tests {
let input = Arc::new(EmptyExec::new(arrow_schema));
let repartitioned_plan =
- repartition(input, table.metadata_ref(),
NonZeroUsize::new(4).unwrap()).unwrap();
+ repartition(input, table.metadata_ref(),
NonZeroUsize::new(4).unwrap())
+ .unwrap();
let partitioning =
repartitioned_plan.properties().output_partitioning();
assert!(
diff --git a/crates/datafusion/src/physical_plan/sort.rs
b/crates/datafusion/src/physical_plan/sort.rs
index 587ab12..fce9210 100644
--- a/crates/datafusion/src/physical_plan/sort.rs
+++ b/crates/datafusion/src/physical_plan/sort.rs
@@ -42,7 +42,9 @@ use iceberg::arrow::PROJECTED_PARTITION_VALUE_COLUMN;
/// # Returns
/// * `Ok(Arc<dyn ExecutionPlan>)` - A SortExec that sorts by partition values
/// * `Err` - If the partition column is not found
-pub(crate) fn sort_by_partition(input: Arc<dyn ExecutionPlan>) ->
DFResult<Arc<dyn ExecutionPlan>> {
+pub(crate) fn sort_by_partition(
+ input: Arc<dyn ExecutionPlan>,
+) -> DFResult<Arc<dyn ExecutionPlan>> {
let schema = input.schema();
// Find the partition column in the schema
@@ -68,7 +70,9 @@ pub(crate) fn sort_by_partition(input: Arc<dyn
ExecutionPlan>) -> DFResult<Arc<d
// Create a SortExec with preserve_partitioning=true to ensure the output
partitioning
// is the same as the input partitioning, and the data is sorted within
each partition
let lex_ordering = LexOrdering::new(vec![sort_expr]).ok_or_else(|| {
- DataFusionError::Plan("Failed to create LexOrdering from sort
expression".to_string())
+ DataFusionError::Plan(
+ "Failed to create LexOrdering from sort expression".to_string(),
+ )
})?;
let sort_exec = SortExec::new(lex_ordering,
input).with_preserve_partitioning(true);
@@ -109,9 +113,11 @@ mod tests {
Arc::new(Int32Array::from(vec![3, 1, 2])) as _,
)]));
- let batch =
- RecordBatch::try_new(schema.clone(), vec![id_array, name_array,
partition_array])
- .unwrap();
+ let batch = RecordBatch::try_new(
+ schema.clone(),
+ vec![id_array, name_array, partition_array],
+ )
+ .unwrap();
let ctx = SessionContext::new();
let mem_table = MemTable::try_new(schema.clone(),
vec![vec![batch]]).unwrap();
@@ -147,10 +153,13 @@ mod tests {
Field::new("name", DataType::Utf8, false),
]));
- let batch = RecordBatch::try_new(schema.clone(), vec![
- Arc::new(Int32Array::from(vec![1, 2, 3])),
- Arc::new(StringArray::from(vec!["a", "b", "c"])),
- ])
+ let batch = RecordBatch::try_new(
+ schema.clone(),
+ vec![
+ Arc::new(Int32Array::from(vec![1, 2, 3])),
+ Arc::new(StringArray::from(vec!["a", "b", "c"])),
+ ],
+ )
.unwrap();
let ctx = SessionContext::new();
@@ -204,9 +213,11 @@ mod tests {
),
]));
- let batch =
- RecordBatch::try_new(schema.clone(), vec![id_array, data_array,
partition_array])
- .unwrap();
+ let batch = RecordBatch::try_new(
+ schema.clone(),
+ vec![id_array, data_array, partition_array],
+ )
+ .unwrap();
let ctx = SessionContext::new();
let mem_table = MemTable::try_new(schema.clone(),
vec![vec![batch]]).unwrap();
diff --git a/crates/datafusion/src/physical_plan/write.rs
b/crates/datafusion/src/physical_plan/write.rs
index 40ad9fa..c5a4206 100644
--- a/crates/datafusion/src/physical_plan/write.rs
+++ b/crates/datafusion/src/physical_plan/write.rs
@@ -68,7 +68,8 @@ pub(crate) struct IcebergWriteExec {
impl IcebergWriteExec {
pub fn new(table: Table, input: Arc<dyn ExecutionPlan>) -> Self {
- let plan_properties = Self::compute_properties(&input,
Self::make_result_schema());
+ let plan_properties =
+ Self::compute_properties(&input, Self::make_result_schema());
Self {
table,
@@ -84,7 +85,9 @@ impl IcebergWriteExec {
) -> Arc<PlanProperties> {
Arc::new(PlanProperties::new(
EquivalenceProperties::new(schema),
-
Partitioning::UnknownPartitioning(input.output_partitioning().partition_count()),
+ Partitioning::UnknownPartitioning(
+ input.output_partitioning().partition_count(),
+ ),
EmissionType::Final,
Boundedness::Bounded,
))
@@ -216,27 +219,30 @@ impl ExecutionPlan for IcebergWriteExec {
let write_format_default = table_props
.write_format_default()
.map_err(to_datafusion_error)?;
- let file_format =
-
DataFileFormat::from_str(&write_format_default).map_err(to_datafusion_error)?;
+ let file_format = DataFileFormat::from_str(&write_format_default)
+ .map_err(to_datafusion_error)?;
if file_format != DataFileFormat::Parquet {
return Err(to_datafusion_error(Error::new(
ErrorKind::FeatureUnsupported,
- format!("File format {file_format} is not supported for
insert_into yet!"),
+ format!(
+ "File format {file_format} is not supported for
insert_into yet!"
+ ),
)));
}
// Build the writer from the already-parsed table properties so it
honors
// `write.parquet.*` settings (e.g. CDC). Arrow batches flowing through
// DataFusion carry no field-id metadata, so match fields by name.
- let mut parquet_file_writer_builder =
ParquetWriterBuilder::from_table_properties(
- &table_props,
- self.table.metadata().current_schema().clone(),
- )
- .map_err(to_datafusion_error)?
- .with_match_mode(FieldMatchMode::Name);
+ let mut parquet_file_writer_builder =
+ ParquetWriterBuilder::from_table_properties(
+ &table_props,
+ self.table.metadata().current_schema().clone(),
+ )
+ .map_err(to_datafusion_error)?
+ .with_match_mode(FieldMatchMode::Name);
if let Some(encryption_manager) = self.table.encryption_manager() {
- parquet_file_writer_builder =
-
parquet_file_writer_builder.with_encryption_manager(encryption_manager.clone());
+ parquet_file_writer_builder = parquet_file_writer_builder
+ .with_encryption_manager(encryption_manager.clone());
}
let target_file_size = table_props
.write_target_file_size_bytes()
@@ -244,8 +250,8 @@ impl ExecutionPlan for IcebergWriteExec {
let file_io = self.table.file_io().clone();
// todo location_gen and file_name_gen should be configurable
- let location_generator =
-
DefaultLocationGenerator::new(self.table.metadata()).map_err(to_datafusion_error)?;
+ let location_generator =
DefaultLocationGenerator::new(self.table.metadata())
+ .map_err(to_datafusion_error)?;
// todo filename prefix/suffix should be configurable
let file_name_generator =
DefaultFileNameGenerator::new(Uuid::now_v7().to_string(), None,
file_format);
@@ -299,8 +305,12 @@ impl ExecutionPlan for IcebergWriteExec {
let data_files_strs: Vec<String> = data_files
.into_iter()
.map(|data_file| {
- serialize_data_file_to_json(data_file, &partition_type,
format_version)
- .map_err(to_datafusion_error)
+ serialize_data_file_to_json(
+ data_file,
+ &partition_type,
+ format_version,
+ )
+ .map_err(to_datafusion_error)
})
.collect::<DFResult<Vec<String>>>()?;
@@ -330,13 +340,18 @@ mod tests {
use datafusion::physical_expr::{EquivalenceProperties, Partitioning};
use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
- use datafusion::physical_plan::{DisplayAs, DisplayFormatType,
ExecutionPlan, PlanProperties};
+ use datafusion::physical_plan::{
+ DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties,
+ };
use futures::{StreamExt, stream};
use iceberg::memory::{MEMORY_CATALOG_WAREHOUSE, MemoryCatalogBuilder};
use iceberg::spec::{
- DataFileFormat, NestedField, PrimitiveType, Schema, Type,
deserialize_data_file_from_json,
+ DataFileFormat, NestedField, PrimitiveType, Schema, Type,
+ deserialize_data_file_from_json,
+ };
+ use iceberg::{
+ Catalog, CatalogBuilder, MemoryCatalog, NamespaceIdent, Result,
TableCreation,
};
- use iceberg::{Catalog, CatalogBuilder, MemoryCatalog, NamespaceIdent,
Result, TableCreation};
use parquet::arrow::PARQUET_FIELD_ID_META_KEY;
use tempfile::TempDir;
@@ -447,8 +462,10 @@ mod tests {
Schema::builder()
.with_schema_id(0)
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
+ NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String))
+ .into(),
])
.build()
}
@@ -488,32 +505,34 @@ mod tests {
let table = iceberg_catalog.create_table(&namespace, creation).await?;
// 2. Create test data
- let arrow_schema = Arc::new(ArrowSchema::new(vec![
- Field::new("id", DataType::Int32,
false).with_metadata(HashMap::from([(
- PARQUET_FIELD_ID_META_KEY.to_string(),
- "1".to_string(),
- )])),
- Field::new("name", DataType::Utf8,
false).with_metadata(HashMap::from([(
- PARQUET_FIELD_ID_META_KEY.to_string(),
- "2".to_string(),
- )])),
- ]));
+ let arrow_schema =
+ Arc::new(ArrowSchema::new(vec![
+ Field::new("id", DataType::Int32,
false).with_metadata(HashMap::from([
+ (PARQUET_FIELD_ID_META_KEY.to_string(), "1".to_string()),
+ ])),
+ Field::new("name", DataType::Utf8,
false).with_metadata(HashMap::from([
+ (PARQUET_FIELD_ID_META_KEY.to_string(), "2".to_string()),
+ ])),
+ ]));
let id_array = Arc::new(Int32Array::from(vec![1, 2, 3])) as ArrayRef;
- let name_array = Arc::new(StringArray::from(vec!["Alice", "Bob",
"Charlie"])) as ArrayRef;
-
- let batch = RecordBatch::try_new(arrow_schema.clone(), vec![id_array,
name_array])
- .map_err(|e| {
- Error::new(
- ErrorKind::Unexpected,
- format!("Failed to create record batch: {e}"),
- )
- })?;
+ let name_array =
+ Arc::new(StringArray::from(vec!["Alice", "Bob", "Charlie"])) as
ArrayRef;
+
+ let batch =
+ RecordBatch::try_new(arrow_schema.clone(), vec![id_array,
name_array])
+ .map_err(|e| {
+ Error::new(
+ ErrorKind::Unexpected,
+ format!("Failed to create record batch: {e}"),
+ )
+ })?;
// 3. Create mock input execution plan
- let input_plan = Arc::new(MockExecutionPlan::new(arrow_schema.clone(),
vec![
- batch.clone(),
- ]));
+ let input_plan = Arc::new(MockExecutionPlan::new(
+ arrow_schema.clone(),
+ vec![batch.clone()],
+ ));
// 4. Create IcebergWriteExec
let write_exec = IcebergWriteExec::new(table.clone(), input_plan);
@@ -543,7 +562,11 @@ mod tests {
// Check schema
assert_eq!(
result_batch.schema().as_ref(),
- &ArrowSchema::new(vec![Field::new(DATA_FILES_COL_NAME,
DataType::Utf8, false)])
+ &ArrowSchema::new(vec![Field::new(
+ DATA_FILES_COL_NAME,
+ DataType::Utf8,
+ false
+ )])
);
// Check data
@@ -562,9 +585,13 @@ mod tests {
let spec_id = table.metadata().default_partition_spec_id();
let schema = table.metadata().current_schema();
- let data_file =
- deserialize_data_file_from_json(data_file_json, spec_id,
partition_type, schema)
- .expect("Failed to deserialize data file JSON");
+ let data_file = deserialize_data_file_from_json(
+ data_file_json,
+ spec_id,
+ partition_type,
+ schema,
+ )
+ .expect("Failed to deserialize data file JSON");
// Verify data file properties
assert_eq!(
@@ -605,11 +632,13 @@ mod tests {
// Verify lower and upper bounds
assert!(
- data_file.lower_bounds().contains_key(&1) ||
data_file.lower_bounds().contains_key(&2),
+ data_file.lower_bounds().contains_key(&1)
+ || data_file.lower_bounds().contains_key(&2),
"Expected lower bounds to contain at least one column"
);
assert!(
- data_file.upper_bounds().contains_key(&1) ||
data_file.upper_bounds().contains_key(&2),
+ data_file.upper_bounds().contains_key(&1)
+ || data_file.upper_bounds().contains_key(&2),
"Expected upper bounds to contain at least one column"
);
@@ -644,7 +673,11 @@ mod tests {
assert_eq!(
write_exec.schema().as_ref(),
- &ArrowSchema::new(vec![Field::new(DATA_FILES_COL_NAME,
DataType::Utf8, false)]),
+ &ArrowSchema::new(vec![Field::new(
+ DATA_FILES_COL_NAME,
+ DataType::Utf8,
+ false
+ )]),
"IcebergWriteExec should advertise the data_files schema, not the
table schema"
);
diff --git a/crates/datafusion/src/schema.rs b/crates/datafusion/src/schema.rs
index 545863f..04bea0b 100644
--- a/crates/datafusion/src/schema.rs
+++ b/crates/datafusion/src/schema.rs
@@ -29,7 +29,9 @@ use futures::future::try_join_all;
use iceberg::arrow::arrow_schema_to_schema_auto_assign_ids;
use iceberg::inspect::MetadataTableType;
use iceberg::spec::FormatVersion;
-use iceberg::{Catalog, Error, ErrorKind, NamespaceIdent, Result,
TableCreation, TableIdent};
+use iceberg::{
+ Catalog, Error, ErrorKind, NamespaceIdent, Result, TableCreation,
TableIdent,
+};
use crate::table::IcebergTableProvider;
use crate::to_datafusion_error;
@@ -75,7 +77,9 @@ impl IcebergSchemaProvider {
let providers = try_join_all(
table_names
.iter()
- .map(|name| IcebergTableProvider::try_new(client.clone(),
namespace.clone(), name))
+ .map(|name| {
+ IcebergTableProvider::try_new(client.clone(),
namespace.clone(), name)
+ })
.collect::<Vec<_>>(),
)
.await?;
@@ -100,13 +104,11 @@ impl SchemaProvider for IcebergSchemaProvider {
.iter()
.flat_map(|entry| {
let table_name = entry.key().clone();
- [table_name.clone()]
- .into_iter()
- .chain(
- MetadataTableType::all_types().map(move
|metadata_table_name| {
- format!("{}${}", table_name,
metadata_table_name.as_str())
- }),
- )
+ [table_name.clone()].into_iter().chain(
+ MetadataTableType::all_types().map(move
|metadata_table_name| {
+ format!("{}${}", table_name,
metadata_table_name.as_str())
+ }),
+ )
})
.collect()
}
@@ -122,8 +124,8 @@ impl SchemaProvider for IcebergSchemaProvider {
async fn table(&self, name: &str) -> DFResult<Option<Arc<dyn
TableProvider>>> {
if let Some((table_name, metadata_table_name)) = name.split_once('$') {
- let metadata_table_type =
-
MetadataTableType::try_from(metadata_table_name).map_err(DataFusionError::Plan)?;
+ let metadata_table_type =
MetadataTableType::try_from(metadata_table_name)
+ .map_err(DataFusionError::Plan)?;
if let Some(table) = self.tables.get(table_name) {
let metadata_table = table
.metadata_table(metadata_table_type)
@@ -246,8 +248,9 @@ impl SchemaProvider for IcebergSchemaProvider {
})
});
- futures::executor::block_on(result)
- .map_err(|e| DataFusionError::Execution(format!("Failed to drop
Iceberg table: {e}")))?
+ futures::executor::block_on(result).map_err(|e| {
+ DataFusionError::Execution(format!("Failed to drop Iceberg table:
{e}"))
+ })?
}
}
@@ -258,7 +261,9 @@ async fn ensure_table_is_empty(table: &Arc<dyn
TableProvider>) -> Result<()> {
let exec_plan = table
.scan(&session_ctx.state(), None, &[], Some(1))
.await
- .map_err(|e| Error::new(ErrorKind::Unexpected, format!("Failed to scan
table: {e}")))?;
+ .map_err(|e| {
+ Error::new(ErrorKind::Unexpected, format!("Failed to scan table:
{e}"))
+ })?;
let task_ctx = Arc::new(TaskContext::default());
let stream = exec_plan.execute(0, task_ctx).map_err(|e| {
@@ -306,7 +311,10 @@ mod tests {
let catalog = MemoryCatalogBuilder::default()
.load(
"memory",
- HashMap::from([(MEMORY_CATALOG_WAREHOUSE.to_string(),
warehouse_path.clone())]),
+ HashMap::from([(
+ MEMORY_CATALOG_WAREHOUSE.to_string(),
+ warehouse_path.clone(),
+ )]),
)
.await
.unwrap();
@@ -334,16 +342,20 @@ mod tests {
Field::new("name", DataType::Utf8, true),
]));
- let batch = RecordBatch::try_new(arrow_schema.clone(), vec![
- Arc::new(Int32Array::from(vec![1, 2, 3])),
- Arc::new(StringArray::from(vec!["Alice", "Bob", "Charlie"])),
- ])
+ let batch = RecordBatch::try_new(
+ arrow_schema.clone(),
+ vec![
+ Arc::new(Int32Array::from(vec![1, 2, 3])),
+ Arc::new(StringArray::from(vec!["Alice", "Bob", "Charlie"])),
+ ],
+ )
.unwrap();
let mem_table = MemTable::try_new(arrow_schema,
vec![vec![batch]]).unwrap();
// Attempt to register the table with data - should fail
- let result = schema_provider.register_table("test_table".to_string(),
Arc::new(mem_table));
+ let result =
+ schema_provider.register_table("test_table".to_string(),
Arc::new(mem_table));
assert!(result.is_err());
let err = result.unwrap_err();
@@ -369,7 +381,8 @@ mod tests {
let mem_table = MemTable::try_new(arrow_schema,
vec![vec![empty_batch]]).unwrap();
// Attempt to register the empty table - should succeed
- let result = schema_provider.register_table("empty_table".to_string(),
Arc::new(mem_table));
+ let result = schema_provider
+ .register_table("empty_table".to_string(), Arc::new(mem_table));
assert!(result.is_ok(), "Expected success, got: {result:?}");
@@ -390,15 +403,19 @@ mod tests {
let empty_batch1 = RecordBatch::new_empty(arrow_schema.clone());
let empty_batch2 = RecordBatch::new_empty(arrow_schema.clone());
- let mem_table1 = MemTable::try_new(arrow_schema.clone(),
vec![vec![empty_batch1]]).unwrap();
- let mem_table2 = MemTable::try_new(arrow_schema,
vec![vec![empty_batch2]]).unwrap();
+ let mem_table1 =
+ MemTable::try_new(arrow_schema.clone(),
vec![vec![empty_batch1]]).unwrap();
+ let mem_table2 =
+ MemTable::try_new(arrow_schema, vec![vec![empty_batch2]]).unwrap();
// Register first table - should succeed
- let result1 = schema_provider.register_table("dup_table".to_string(),
Arc::new(mem_table1));
+ let result1 =
+ schema_provider.register_table("dup_table".to_string(),
Arc::new(mem_table1));
assert!(result1.is_ok());
// Register second table with same name - should fail
- let result2 = schema_provider.register_table("dup_table".to_string(),
Arc::new(mem_table2));
+ let result2 =
+ schema_provider.register_table("dup_table".to_string(),
Arc::new(mem_table2));
assert!(result2.is_err());
let err = result2.unwrap_err();
assert!(
@@ -422,7 +439,8 @@ mod tests {
let mem_table = MemTable::try_new(arrow_schema,
vec![vec![empty_batch]]).unwrap();
// Register the table
- let result = schema_provider.register_table("drop_me".to_string(),
Arc::new(mem_table));
+ let result =
+ schema_provider.register_table("drop_me".to_string(),
Arc::new(mem_table));
assert!(result.is_ok());
assert!(schema_provider.table_exist("drop_me"));
diff --git a/crates/datafusion/src/table/mod.rs
b/crates/datafusion/src/table/mod.rs
index 9de7bcb..a1db3c8 100644
--- a/crates/datafusion/src/table/mod.rs
+++ b/crates/datafusion/src/table/mod.rs
@@ -179,8 +179,8 @@ impl TableProvider for IcebergTableProvider {
};
// Step 2: Repartition for parallel processing
- let target_partitions =
-
NonZeroUsize::new(state.config().target_partitions()).ok_or_else(|| {
+ let target_partitions =
NonZeroUsize::new(state.config().target_partitions())
+ .ok_or_else(|| {
DataFusionError::Configuration(
"target_partitions must be greater than 0".to_string(),
)
@@ -267,7 +267,10 @@ impl IcebergStaticTableProvider {
///
/// Queries the specified snapshot for all operations. Useful for
time-travel queries.
/// Does not support write operations.
- pub async fn try_new_from_table_snapshot(table: Table, snapshot_id: i64)
-> Result<Self> {
+ pub async fn try_new_from_table_snapshot(
+ table: Table,
+ snapshot_id: i64,
+ ) -> Result<Self> {
let snapshot = table
.metadata()
.snapshot_by_id(snapshot_id)
@@ -366,22 +369,30 @@ mod tests {
metadata_file_name
);
let file_io = FileIO::new_with_fs();
- let static_identifier = TableIdent::from_strs(["static_ns",
"static_table"]).unwrap();
- let static_table =
- StaticTable::from_metadata_file(&metadata_file_path,
static_identifier, file_io)
- .await
- .unwrap();
+ let static_identifier =
+ TableIdent::from_strs(["static_ns", "static_table"]).unwrap();
+ let static_table = StaticTable::from_metadata_file(
+ &metadata_file_path,
+ static_identifier,
+ file_io,
+ )
+ .await
+ .unwrap();
static_table.into_table()
}
- async fn get_test_catalog_and_table() -> (Arc<dyn Catalog>,
NamespaceIdent, String, TempDir) {
+ async fn get_test_catalog_and_table()
+ -> (Arc<dyn Catalog>, NamespaceIdent, String, TempDir) {
let temp_dir = TempDir::new().unwrap();
let warehouse_path = temp_dir.path().to_str().unwrap().to_string();
let catalog = MemoryCatalogBuilder::default()
.load(
"memory",
- HashMap::from([(MEMORY_CATALOG_WAREHOUSE.to_string(),
warehouse_path.clone())]),
+ HashMap::from([(
+ MEMORY_CATALOG_WAREHOUSE.to_string(),
+ warehouse_path.clone(),
+ )]),
)
.await
.unwrap();
@@ -395,8 +406,10 @@ mod tests {
let schema = Schema::builder()
.with_schema_id(0)
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
+ NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String))
+ .into(),
])
.build()
.unwrap();
@@ -426,9 +439,10 @@ mod tests {
#[tokio::test]
async fn test_static_provider_from_table() {
let table = get_test_table_from_metadata_file().await;
- let table_provider =
IcebergStaticTableProvider::try_new_from_table(table.clone())
- .await
- .unwrap();
+ let table_provider =
+ IcebergStaticTableProvider::try_new_from_table(table.clone())
+ .await
+ .unwrap();
let ctx = SessionContext::new();
ctx.register_table("mytable", Arc::new(table_provider))
.unwrap();
@@ -451,10 +465,12 @@ mod tests {
async fn test_static_provider_from_snapshot() {
let table = get_test_table_from_metadata_file().await;
let snapshot_id =
table.metadata().snapshots().next().unwrap().snapshot_id();
- let table_provider =
-
IcebergStaticTableProvider::try_new_from_table_snapshot(table.clone(),
snapshot_id)
- .await
- .unwrap();
+ let table_provider =
IcebergStaticTableProvider::try_new_from_table_snapshot(
+ table.clone(),
+ snapshot_id,
+ )
+ .await
+ .unwrap();
let ctx = SessionContext::new();
ctx.register_table("mytable", Arc::new(table_provider))
.unwrap();
@@ -476,9 +492,10 @@ mod tests {
#[tokio::test]
async fn test_static_provider_rejects_writes() {
let table = get_test_table_from_metadata_file().await;
- let table_provider =
IcebergStaticTableProvider::try_new_from_table(table.clone())
- .await
- .unwrap();
+ let table_provider =
+ IcebergStaticTableProvider::try_new_from_table(table.clone())
+ .await
+ .unwrap();
let ctx = SessionContext::new();
ctx.register_table("mytable", Arc::new(table_provider))
.unwrap();
@@ -499,9 +516,10 @@ mod tests {
#[tokio::test]
async fn test_static_provider_scan() {
let table = get_test_table_from_metadata_file().await;
- let table_provider =
IcebergStaticTableProvider::try_new_from_table(table.clone())
- .await
- .unwrap();
+ let table_provider =
+ IcebergStaticTableProvider::try_new_from_table(table.clone())
+ .await
+ .unwrap();
let ctx = SessionContext::new();
ctx.register_table("mytable", Arc::new(table_provider))
.unwrap();
@@ -516,13 +534,17 @@ mod tests {
#[tokio::test]
async fn test_catalog_backed_provider_creation() {
- let (catalog, namespace, table_name, _temp_dir) =
get_test_catalog_and_table().await;
+ let (catalog, namespace, table_name, _temp_dir) =
+ get_test_catalog_and_table().await;
// Test creating a catalog-backed provider
- let provider =
- IcebergTableProvider::try_new(catalog.clone(), namespace.clone(),
table_name.clone())
- .await
- .unwrap();
+ let provider = IcebergTableProvider::try_new(
+ catalog.clone(),
+ namespace.clone(),
+ table_name.clone(),
+ )
+ .await
+ .unwrap();
// Verify the schema is loaded correctly
let schema = provider.schema();
@@ -533,12 +555,16 @@ mod tests {
#[tokio::test]
async fn test_catalog_backed_provider_scan() {
- let (catalog, namespace, table_name, _temp_dir) =
get_test_catalog_and_table().await;
+ let (catalog, namespace, table_name, _temp_dir) =
+ get_test_catalog_and_table().await;
- let provider =
- IcebergTableProvider::try_new(catalog.clone(), namespace.clone(),
table_name.clone())
- .await
- .unwrap();
+ let provider = IcebergTableProvider::try_new(
+ catalog.clone(),
+ namespace.clone(),
+ table_name.clone(),
+ )
+ .await
+ .unwrap();
let ctx = SessionContext::new();
ctx.register_table("test_table", Arc::new(provider))
@@ -559,12 +585,16 @@ mod tests {
#[tokio::test]
async fn test_catalog_backed_provider_insert() {
- let (catalog, namespace, table_name, _temp_dir) =
get_test_catalog_and_table().await;
+ let (catalog, namespace, table_name, _temp_dir) =
+ get_test_catalog_and_table().await;
- let provider =
- IcebergTableProvider::try_new(catalog.clone(), namespace.clone(),
table_name.clone())
- .await
- .unwrap();
+ let provider = IcebergTableProvider::try_new(
+ catalog.clone(),
+ namespace.clone(),
+ table_name.clone(),
+ )
+ .await
+ .unwrap();
let ctx = SessionContext::new();
ctx.register_table("test_table", Arc::new(provider))
@@ -586,12 +616,16 @@ mod tests {
#[tokio::test]
async fn test_physical_input_schema_consistent_with_logical_input_schema()
{
- let (catalog, namespace, table_name, _temp_dir) =
get_test_catalog_and_table().await;
+ let (catalog, namespace, table_name, _temp_dir) =
+ get_test_catalog_and_table().await;
- let provider =
- IcebergTableProvider::try_new(catalog.clone(), namespace.clone(),
table_name.clone())
- .await
- .unwrap();
+ let provider = IcebergTableProvider::try_new(
+ catalog.clone(),
+ namespace.clone(),
+ table_name.clone(),
+ )
+ .await
+ .unwrap();
let ctx = SessionContext::new();
ctx.register_table("test_table", Arc::new(provider))
@@ -634,7 +668,10 @@ mod tests {
let catalog = MemoryCatalogBuilder::default()
.load(
"memory",
- HashMap::from([(MEMORY_CATALOG_WAREHOUSE.to_string(),
warehouse_path.clone())]),
+ HashMap::from([(
+ MEMORY_CATALOG_WAREHOUSE.to_string(),
+ warehouse_path.clone(),
+ )]),
)
.await
.unwrap();
@@ -648,8 +685,14 @@ mod tests {
let schema = Schema::builder()
.with_schema_id(0)
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "category",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
+ NestedField::required(
+ 2,
+ "category",
+ Type::Primitive(PrimitiveType::String),
+ )
+ .into(),
])
.build()
.unwrap();
@@ -706,7 +749,8 @@ mod tests {
async fn test_catalog_backed_provider_rejects_non_append_op() {
use datafusion::physical_plan::empty::EmptyExec;
- let (catalog, namespace, table_name, _temp_dir) =
get_test_catalog_and_table().await;
+ let (catalog, namespace, table_name, _temp_dir) =
+ get_test_catalog_and_table().await;
let provider = IcebergTableProvider::try_new(catalog, namespace,
table_name)
.await
.unwrap();
@@ -722,7 +766,8 @@ mod tests {
"IcebergTableProvider supports only append inserts, got
Replace Into",
),
] {
- let input = Arc::new(EmptyExec::new(provider.schema())) as Arc<dyn
ExecutionPlan>;
+ let input =
+ Arc::new(EmptyExec::new(provider.schema())) as Arc<dyn
ExecutionPlan>;
let error = provider
.insert_into(&ctx.state(), input, insert_op)
.await
@@ -748,10 +793,13 @@ mod tests {
let (catalog, namespace, table_name, _temp_dir) =
get_partitioned_test_catalog_and_table(Some(true)).await;
- let provider =
- IcebergTableProvider::try_new(catalog.clone(), namespace.clone(),
table_name.clone())
- .await
- .unwrap();
+ let provider = IcebergTableProvider::try_new(
+ catalog.clone(),
+ namespace.clone(),
+ table_name.clone(),
+ )
+ .await
+ .unwrap();
let ctx = SessionContext::new();
let input_schema = provider.schema();
@@ -780,10 +828,13 @@ mod tests {
let (catalog, namespace, table_name, _temp_dir) =
get_partitioned_test_catalog_and_table(Some(false)).await;
- let provider =
- IcebergTableProvider::try_new(catalog.clone(), namespace.clone(),
table_name.clone())
- .await
- .unwrap();
+ let provider = IcebergTableProvider::try_new(
+ catalog.clone(),
+ namespace.clone(),
+ table_name.clone(),
+ )
+ .await
+ .unwrap();
let ctx = SessionContext::new();
let input_schema = provider.schema();
@@ -807,9 +858,10 @@ mod tests {
use datafusion::datasource::TableProvider;
let table = get_test_table_from_metadata_file().await;
- let table_provider =
IcebergStaticTableProvider::try_new_from_table(table.clone())
- .await
- .unwrap();
+ let table_provider =
+ IcebergStaticTableProvider::try_new_from_table(table.clone())
+ .await
+ .unwrap();
let ctx = SessionContext::new();
let state = ctx.state();
@@ -837,12 +889,16 @@ mod tests {
async fn test_limit_pushdown_catalog_backed_provider() {
use datafusion::datasource::TableProvider;
- let (catalog, namespace, table_name, _temp_dir) =
get_test_catalog_and_table().await;
+ let (catalog, namespace, table_name, _temp_dir) =
+ get_test_catalog_and_table().await;
- let provider =
- IcebergTableProvider::try_new(catalog.clone(), namespace.clone(),
table_name.clone())
- .await
- .unwrap();
+ let provider = IcebergTableProvider::try_new(
+ catalog.clone(),
+ namespace.clone(),
+ table_name.clone(),
+ )
+ .await
+ .unwrap();
let ctx = SessionContext::new();
let state = ctx.state();
@@ -868,9 +924,10 @@ mod tests {
use datafusion::datasource::TableProvider;
let table = get_test_table_from_metadata_file().await;
- let table_provider =
IcebergStaticTableProvider::try_new_from_table(table.clone())
- .await
- .unwrap();
+ let table_provider =
+ IcebergStaticTableProvider::try_new_from_table(table.clone())
+ .await
+ .unwrap();
let ctx = SessionContext::new();
let state = ctx.state();
diff --git a/crates/datafusion/src/table/table_provider_factory.rs
b/crates/datafusion/src/table/table_provider_factory.rs
index 6d81700..656854d 100644
--- a/crates/datafusion/src/table/table_provider_factory.rs
+++ b/crates/datafusion/src/table/table_provider_factory.rs
@@ -196,7 +196,9 @@ fn check_cmd(cmd: &CreateExternalTable) -> Result<&str> {
/// # See also
/// - [`iceberg::NamespaceIdent`]
/// - [`datafusion::sql::planner::SqlToRel::external_table_to_plan`]
-fn complement_namespace_if_necessary(table_name: &TableReference) -> Cow<'_,
TableReference> {
+fn complement_namespace_if_necessary(
+ table_name: &TableReference,
+) -> Cow<'_, TableReference> {
match table_name {
TableReference::Bare { table } => {
Cow::Owned(TableReference::partial("default", table.as_ref()))
diff --git a/crates/datafusion/src/task_writer.rs
b/crates/datafusion/src/task_writer.rs
index 99ba5c0..3e7c713 100644
--- a/crates/datafusion/src/task_writer.rs
+++ b/crates/datafusion/src/task_writer.rs
@@ -186,10 +186,12 @@ impl<B: IcebergWriterBuilder> TaskWriter<B> {
writer.write(batch).await
}
SupportedWriter::Fanout(writer) => {
- Self::write_partitioned_batches(writer,
&self.partition_splitter, &batch).await
+ Self::write_partitioned_batches(writer,
&self.partition_splitter, &batch)
+ .await
}
SupportedWriter::Clustered(writer) => {
- Self::write_partitioned_batches(writer,
&self.partition_splitter, &batch).await
+ Self::write_partitioned_batches(writer,
&self.partition_splitter, &batch)
+ .await
}
}
}
@@ -264,11 +266,15 @@ mod tests {
use std::collections::HashMap;
use std::sync::Arc;
- use datafusion::arrow::array::{ArrayRef, Int32Array, RecordBatch,
StringArray, StructArray};
+ use datafusion::arrow::array::{
+ ArrayRef, Int32Array, RecordBatch, StringArray, StructArray,
+ };
use datafusion::arrow::datatypes::{DataType, Field, Schema};
use iceberg::arrow::PROJECTED_PARTITION_VALUE_COLUMN;
use iceberg::io::FileIO;
- use iceberg::spec::{DataFileFormat, NestedField, PartitionSpec,
PrimitiveType, Type};
+ use iceberg::spec::{
+ DataFileFormat, NestedField, PartitionSpec, PrimitiveType, Type,
+ };
use iceberg::writer::base_writer::data_file_writer::DataFileWriterBuilder;
use iceberg::writer::file_writer::ParquetWriterBuilder;
use iceberg::writer::file_writer::location_generator::{
@@ -286,10 +292,20 @@ mod tests {
iceberg::spec::Schema::builder()
.with_schema_id(1)
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String)).into(),
- NestedField::required(3, "region",
Type::Primitive(PrimitiveType::String))
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
.into(),
+ NestedField::required(
+ 2,
+ "name",
+ Type::Primitive(PrimitiveType::String),
+ )
+ .into(),
+ NestedField::required(
+ 3,
+ "region",
+ Type::Primitive(PrimitiveType::String),
+ )
+ .into(),
])
.build()?,
))
@@ -353,8 +369,11 @@ mod tests {
let location_gen = DefaultLocationGenerator::with_data_location(
temp_dir.path().to_str().unwrap().to_string(),
);
- let file_name_gen =
- DefaultFileNameGenerator::new("test".to_string(), None,
DataFileFormat::Parquet);
+ let file_name_gen = DefaultFileNameGenerator::new(
+ "test".to_string(),
+ None,
+ DataFileFormat::Parquet,
+ );
let parquet_writer_builder =
ParquetWriterBuilder::new(WriterProperties::builder().build(),
schema);
let rolling_writer_builder =
RollingFileWriterBuilder::new_with_default_file_size(
@@ -376,14 +395,18 @@ mod tests {
let partition_spec =
Arc::new(PartitionSpec::builder(schema.clone()).build()?);
let writer_builder = create_writer_builder(&temp_dir, schema.clone())?;
- let mut task_writer = TaskWriter::try_new(writer_builder, false,
schema, partition_spec)?;
+ let mut task_writer =
+ TaskWriter::try_new(writer_builder, false, schema,
partition_spec)?;
// Write data
- let batch = RecordBatch::try_new(arrow_schema, vec![
- Arc::new(Int32Array::from(vec![1, 2, 3])),
- Arc::new(StringArray::from(vec!["Alice", "Bob", "Charlie"])),
- Arc::new(StringArray::from(vec!["US", "EU", "US"])),
- ])?;
+ let batch = RecordBatch::try_new(
+ arrow_schema,
+ vec![
+ Arc::new(Int32Array::from(vec![1, 2, 3])),
+ Arc::new(StringArray::from(vec!["Alice", "Bob", "Charlie"])),
+ Arc::new(StringArray::from(vec!["US", "EU", "US"])),
+ ],
+ )?;
task_writer.write(batch).await?;
let data_files = task_writer.close().await?;
@@ -417,7 +440,8 @@ mod tests {
_ => panic!("Expected string partition value"),
};
- *partition_counts.entry(region.clone()).or_insert(0) +=
data_file.record_count();
+ *partition_counts.entry(region.clone()).or_insert(0) +=
+ data_file.record_count();
// Verify file path contains partition information
assert!(
@@ -437,12 +461,17 @@ mod tests {
let partition_spec = Arc::new(
PartitionSpec::builder(schema.clone())
.with_spec_id(1)
- .add_partition_field("region", "region",
iceberg::spec::Transform::Identity)?
+ .add_partition_field(
+ "region",
+ "region",
+ iceberg::spec::Transform::Identity,
+ )?
.build()?,
);
let writer_builder = create_writer_builder(&temp_dir, schema.clone())?;
- let mut task_writer = TaskWriter::try_new(writer_builder, true,
schema, partition_spec)?;
+ let mut task_writer =
+ TaskWriter::try_new(writer_builder, true, schema, partition_spec)?;
// Create partition column
let partition_field = Field::new("region", DataType::Utf8,
false).with_metadata(
@@ -454,12 +483,15 @@ mod tests {
Arc::new(partition_values) as ArrayRef,
)]);
- let batch = RecordBatch::try_new(arrow_schema, vec![
- Arc::new(Int32Array::from(vec![1, 2, 3, 4])),
- Arc::new(StringArray::from(vec!["Alice", "Bob", "Charlie",
"Dave"])),
- Arc::new(StringArray::from(vec!["US", "EU", "US", "EU"])),
- Arc::new(partition_struct),
- ])?;
+ let batch = RecordBatch::try_new(
+ arrow_schema,
+ vec![
+ Arc::new(Int32Array::from(vec![1, 2, 3, 4])),
+ Arc::new(StringArray::from(vec!["Alice", "Bob", "Charlie",
"Dave"])),
+ Arc::new(StringArray::from(vec!["US", "EU", "US", "EU"])),
+ Arc::new(partition_struct),
+ ],
+ )?;
task_writer.write(batch).await?;
let data_files = task_writer.close().await?;
@@ -480,12 +512,17 @@ mod tests {
let partition_spec = Arc::new(
PartitionSpec::builder(schema.clone())
.with_spec_id(1)
- .add_partition_field("region", "region",
iceberg::spec::Transform::Identity)?
+ .add_partition_field(
+ "region",
+ "region",
+ iceberg::spec::Transform::Identity,
+ )?
.build()?,
);
let writer_builder = create_writer_builder(&temp_dir, schema.clone())?;
- let mut task_writer = TaskWriter::try_new(writer_builder, false,
schema, partition_spec)?;
+ let mut task_writer =
+ TaskWriter::try_new(writer_builder, false, schema,
partition_spec)?;
// Create partition column
let partition_field = Field::new("region", DataType::Utf8,
false).with_metadata(
@@ -498,12 +535,15 @@ mod tests {
)]);
// ClusteredWriter expects data to be pre-sorted by partition
- let batch = RecordBatch::try_new(arrow_schema, vec![
- Arc::new(Int32Array::from(vec![1, 2, 3, 4])),
- Arc::new(StringArray::from(vec!["Alice", "Bob", "Charlie",
"Dave"])),
- Arc::new(StringArray::from(vec!["ASIA", "ASIA", "EU", "EU"])),
- Arc::new(partition_struct),
- ])?;
+ let batch = RecordBatch::try_new(
+ arrow_schema,
+ vec![
+ Arc::new(Int32Array::from(vec![1, 2, 3, 4])),
+ Arc::new(StringArray::from(vec!["Alice", "Bob", "Charlie",
"Dave"])),
+ Arc::new(StringArray::from(vec!["ASIA", "ASIA", "EU", "EU"])),
+ Arc::new(partition_struct),
+ ],
+ )?;
task_writer.write(batch).await?;
let data_files = task_writer.close().await?;
diff --git a/crates/datafusion/tests/integration_datafusion_test.rs
b/crates/datafusion/tests/integration_datafusion_test.rs
index 83f780f..41753ab 100644
--- a/crates/datafusion/tests/integration_datafusion_test.rs
+++ b/crates/datafusion/tests/integration_datafusion_test.rs
@@ -34,7 +34,8 @@ use iceberg::spec::{
};
use iceberg::test_utils::check_record_batches;
use iceberg::{
- Catalog, CatalogBuilder, MemoryCatalog, NamespaceIdent, Result,
TableCreation, TableIdent,
+ Catalog, CatalogBuilder, MemoryCatalog, NamespaceIdent, Result,
TableCreation,
+ TableIdent,
};
use tempfile::TempDir;
@@ -61,7 +62,10 @@ fn get_struct_type() -> StructType {
])
}
-async fn set_test_namespace(catalog: &MemoryCatalog, namespace:
&NamespaceIdent) -> Result<()> {
+async fn set_test_namespace(
+ catalog: &MemoryCatalog,
+ namespace: &NamespaceIdent,
+) -> Result<()> {
let properties = HashMap::new();
catalog.create_namespace(namespace, properties).await?;
@@ -78,8 +82,10 @@ fn get_table_creation(
None => Schema::builder()
.with_schema_id(0)
.with_fields(vec![
- NestedField::required(1, "foo1",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "foo2",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(1, "foo1",
Type::Primitive(PrimitiveType::Int))
+ .into(),
+ NestedField::required(2, "foo2",
Type::Primitive(PrimitiveType::String))
+ .into(),
])
.build()?,
Some(schema) => schema,
@@ -216,7 +222,8 @@ async fn test_table_projection() -> Result<()> {
.with_schema_id(0)
.with_fields(vec![
NestedField::required(1, "foo1",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "foo2",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(2, "foo2",
Type::Primitive(PrimitiveType::String))
+ .into(),
NestedField::optional(3, "foo3",
Type::Struct(get_struct_type())).into(),
])
.build()?;
@@ -284,7 +291,8 @@ async fn test_table_predict_pushdown() -> Result<()> {
.with_schema_id(0)
.with_fields(vec![
NestedField::required(1, "foo",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::optional(2, "bar",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::optional(2, "bar",
Type::Primitive(PrimitiveType::String))
+ .into(),
])
.build()?;
let creation = get_table_creation(temp_path(), "t1", Some(schema))?;
@@ -329,7 +337,8 @@ async fn test_metadata_table() -> Result<()> {
.with_schema_id(0)
.with_fields(vec![
NestedField::required(1, "foo",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::optional(2, "bar",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::optional(2, "bar",
Type::Primitive(PrimitiveType::String))
+ .into(),
])
.build()?;
let creation = get_table_creation(temp_path(), "t1", Some(schema))?;
@@ -536,9 +545,16 @@ fn get_nested_struct_type() -> StructType {
10,
"address",
Type::Struct(StructType::new(vec![
- NestedField::optional(11, "street",
Type::Primitive(PrimitiveType::String)).into(),
- NestedField::optional(12, "city",
Type::Primitive(PrimitiveType::String)).into(),
- NestedField::optional(13, "zip",
Type::Primitive(PrimitiveType::Int)).into(),
+ NestedField::optional(
+ 11,
+ "street",
+ Type::Primitive(PrimitiveType::String),
+ )
+ .into(),
+ NestedField::optional(12, "city",
Type::Primitive(PrimitiveType::String))
+ .into(),
+ NestedField::optional(13, "zip",
Type::Primitive(PrimitiveType::Int))
+ .into(),
])),
)
.into(),
@@ -546,8 +562,18 @@ fn get_nested_struct_type() -> StructType {
20,
"contact",
Type::Struct(StructType::new(vec![
- NestedField::optional(21, "email",
Type::Primitive(PrimitiveType::String)).into(),
- NestedField::optional(22, "phone",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::optional(
+ 21,
+ "email",
+ Type::Primitive(PrimitiveType::String),
+ )
+ .into(),
+ NestedField::optional(
+ 22,
+ "phone",
+ Type::Primitive(PrimitiveType::String),
+ )
+ .into(),
])),
)
.into(),
@@ -566,8 +592,10 @@ async fn test_insert_into_nested() -> Result<()> {
.with_schema_id(0)
.with_fields(vec![
NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String)).into(),
- NestedField::optional(3, "profile",
Type::Struct(get_nested_struct_type())).into(),
+ NestedField::required(2, "name",
Type::Primitive(PrimitiveType::String))
+ .into(),
+ NestedField::optional(3, "profile",
Type::Struct(get_nested_struct_type()))
+ .into(),
])
.build()?;
@@ -821,8 +849,10 @@ async fn test_insert_into_partitioned() -> Result<()> {
.with_schema_id(0)
.with_fields(vec![
NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "category",
Type::Primitive(PrimitiveType::String)).into(),
- NestedField::required(3, "value",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(2, "category",
Type::Primitive(PrimitiveType::String))
+ .into(),
+ NestedField::required(3, "value",
Type::Primitive(PrimitiveType::String))
+ .into(),
])
.build()?;
diff --git a/crates/playground/src/catalog.rs b/crates/playground/src/catalog.rs
index 10a7ac3..3aa19e8 100644
--- a/crates/playground/src/catalog.rs
+++ b/crates/playground/src/catalog.rs
@@ -21,11 +21,11 @@ use std::sync::Arc;
use anyhow::anyhow;
use datafusion::catalog::{CatalogProvider, CatalogProviderList};
+use datafusion_iceberg::IcebergCatalogProvider;
use fs_err::read_to_string;
use iceberg::CatalogBuilder;
use iceberg::memory::MemoryCatalogBuilder;
use iceberg_catalog_rest::RestCatalogBuilder;
-use datafusion_iceberg::IcebergCatalogProvider;
use toml::{Table as TomlTable, Value};
const CONFIG_NAME_CATALOGS: &str = "catalogs";
@@ -44,7 +44,9 @@ impl IcebergCatalogList {
pub async fn parse_table(configs: &TomlTable) -> anyhow::Result<Self> {
if let Value::Array(catalogs_config) =
configs.get(CONFIG_NAME_CATALOGS).ok_or_else(|| {
- anyhow::Error::msg(format!("{CONFIG_NAME_CATALOGS} entry not
found in config"))
+ anyhow::Error::msg(format!(
+ "{CONFIG_NAME_CATALOGS} entry not found in config"
+ ))
})?
{
let mut catalogs = HashMap::with_capacity(catalogs_config.len());
@@ -96,7 +98,9 @@ impl IcebergCatalogList {
// Create catalog based on type using the appropriate builder
let catalog: Arc<dyn iceberg::Catalog> = match r#type {
"rest" => Arc::new(RestCatalogBuilder::default().load(name,
props).await?),
- "memory" => Arc::new(MemoryCatalogBuilder::default().load(name,
props).await?),
+ "memory" => {
+ Arc::new(MemoryCatalogBuilder::default().load(name,
props).await?)
+ }
_ => {
return Err(anyhow::anyhow!(
"Unsupported catalog type: '{type}'. Supported types:
rest, memory"
diff --git a/crates/playground/src/main.rs b/crates/playground/src/main.rs
index 94068bb..853494c 100644
--- a/crates/playground/src/main.rs
+++ b/crates/playground/src/main.rs
@@ -87,7 +87,8 @@ async fn main_inner() -> anyhow::Result<()> {
let runtime_env = rt_builder.build_arc()?;
// enable dynamic file query
- let ctx = SessionContext::new_with_config_rt(session_config,
runtime_env).enable_url_table();
+ let ctx = SessionContext::new_with_config_rt(session_config, runtime_env)
+ .enable_url_table();
ctx.refresh_catalogs().await?;
let mut print_options = PrintOptions {
diff --git a/crates/sqllogictest/src/engine/datafusion.rs
b/crates/sqllogictest/src/engine/datafusion.rs
index 56008a0..e5fc045 100644
--- a/crates/sqllogictest/src/engine/datafusion.rs
+++ b/crates/sqllogictest/src/engine/datafusion.rs
@@ -21,14 +21,15 @@ use std::sync::Arc;
use datafusion::catalog::CatalogProvider;
use datafusion::prelude::{SessionConfig, SessionContext};
+use datafusion_iceberg::IcebergCatalogProvider;
use datafusion_sqllogictest::DataFusion;
use iceberg::encryption::kms::MemoryKmsClientFactory;
use iceberg::memory::{MEMORY_CATALOG_WAREHOUSE, MemoryCatalogBuilder};
use iceberg::spec::{
- NestedField, PrimitiveType, Schema, TableProperties, Transform, Type,
UnboundPartitionSpec,
+ NestedField, PrimitiveType, Schema, TableProperties, Transform, Type,
+ UnboundPartitionSpec,
};
use iceberg::{Catalog, CatalogBuilder, NamespaceIdent, TableCreation};
-use datafusion_iceberg::IcebergCatalogProvider;
use indicatif::ProgressBar;
use crate::engine::{DatafusionCatalogConfig, EngineRunner,
run_slt_with_runner};
@@ -121,9 +122,16 @@ impl DataFusionEngine {
) -> anyhow::Result<()> {
let schema = Schema::builder()
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::required(2, "category",
Type::Primitive(PrimitiveType::String)).into(),
- NestedField::optional(3, "value",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
+ NestedField::required(
+ 2,
+ "category",
+ Type::Primitive(PrimitiveType::String),
+ )
+ .into(),
+ NestedField::optional(3, "value",
Type::Primitive(PrimitiveType::String))
+ .into(),
])
.build()?;
@@ -155,8 +163,10 @@ impl DataFusionEngine {
) -> anyhow::Result<()> {
let schema = Schema::builder()
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::optional(2, "data",
Type::Primitive(PrimitiveType::Binary)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
+ NestedField::optional(2, "data",
Type::Primitive(PrimitiveType::Binary))
+ .into(),
])
.build()?;
@@ -179,8 +189,10 @@ impl DataFusionEngine {
) -> anyhow::Result<()> {
let schema = Schema::builder()
.with_fields(vec![
- NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int)).into(),
- NestedField::optional(2, "name",
Type::Primitive(PrimitiveType::String)).into(),
+ NestedField::required(1, "id",
Type::Primitive(PrimitiveType::Int))
+ .into(),
+ NestedField::optional(2, "name",
Type::Primitive(PrimitiveType::String))
+ .into(),
])
.build()?;
diff --git a/crates/sqllogictest/src/engine/mod.rs
b/crates/sqllogictest/src/engine/mod.rs
index a276671..aa9292b 100644
--- a/crates/sqllogictest/src/engine/mod.rs
+++ b/crates/sqllogictest/src/engine/mod.rs
@@ -55,7 +55,9 @@ pub trait EngineRunner: Send {
pub async fn load_engine_runner(config: EngineConfig) -> Result<Box<dyn
EngineRunner>> {
match config {
- EngineConfig::Datafusion { catalog } =>
Ok(Box::new(DataFusionEngine::new(catalog).await?)),
+ EngineConfig::Datafusion { catalog } => {
+ Ok(Box::new(DataFusionEngine::new(catalog).await?))
+ }
}
}
@@ -68,7 +70,8 @@ where
M: MakeConnection<Conn = D> + Send + 'static,
{
let path = step_slt_file.as_ref().canonicalize()?;
- let records = parse_file(&path).map_err(|e| Error(anyhow!("parsing slt
file failed: {e}")))?;
+ let records =
+ parse_file(&path).map_err(|e| Error(anyhow!("parsing slt file failed:
{e}")))?;
for record in records {
if let Err(err) = runner.run_async(record).await {
diff --git a/crates/sqllogictest/src/schedule.rs
b/crates/sqllogictest/src/schedule.rs
index 29e37c5..4dc3da3 100644
--- a/crates/sqllogictest/src/schedule.rs
+++ b/crates/sqllogictest/src/schedule.rs
@@ -147,9 +147,10 @@ mod tests {
assert_eq!(config.engines.len(), 1);
assert!(config.engines.contains_key("df"));
- assert!(matches!(config.engines["df"], EngineConfig::Datafusion {
- catalog: None
- }));
+ assert!(matches!(
+ config.engines["df"],
+ EngineConfig::Datafusion { catalog: None }
+ ));
assert_eq!(config.steps.len(), 1);
assert_eq!(config.steps[0].engine, "df");
assert_eq!(config.steps[0].slt, "test.slt");
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
new file mode 100644
index 0000000..52d0bc7
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Keep local development and CI on the same reviewed toolchain.
+[toolchain]
+channel = "1.98.1"
+components = ["rustfmt", "clippy"]
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..5b864b0
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+edition = "2024"
+max_width = 90
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]