This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new b2e73f9  test: cover Mosaic read compatibility (#403)
b2e73f9 is described below

commit b2e73f9388dee39909265de5373c8bac9c21d0b0
Author: QuakeWang <[email protected]>
AuthorDate: Tue Jun 23 15:25:57 2026 +0800

    test: cover Mosaic read compatibility (#403)
---
 .../datafusion/testdata/test_mosaic_read.tar.gz    | Bin 0 -> 2420 bytes
 .../integrations/datafusion/tests/mosaic_tables.rs | 187 +++++++++++++++++++++
 docs/src/getting-started.md                        |  11 ++
 docs/src/sql.md                                    |  18 ++
 4 files changed, 216 insertions(+)

diff --git a/crates/integrations/datafusion/testdata/test_mosaic_read.tar.gz 
b/crates/integrations/datafusion/testdata/test_mosaic_read.tar.gz
new file mode 100644
index 0000000..05234cf
Binary files /dev/null and 
b/crates/integrations/datafusion/testdata/test_mosaic_read.tar.gz differ
diff --git a/crates/integrations/datafusion/tests/mosaic_tables.rs 
b/crates/integrations/datafusion/tests/mosaic_tables.rs
new file mode 100644
index 0000000..4a6f679
--- /dev/null
+++ b/crates/integrations/datafusion/tests/mosaic_tables.rs
@@ -0,0 +1,187 @@
+// 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.
+
+#![cfg(feature = "mosaic")]
+
+//! Mosaic file format read compatibility tests.
+
+use std::path::Path;
+use std::sync::Arc;
+
+use datafusion::arrow::array::{Int32Array, Int64Array, StringArray};
+use datafusion::arrow::record_batch::RecordBatch;
+use paimon::{Catalog, CatalogOptions, FileSystemCatalog, Options};
+use paimon_datafusion::SQLContext;
+
+const FIXTURE_TABLE: &str = "test_mosaic_read";
+
+fn extract_test_warehouse() -> (tempfile::TempDir, String) {
+    let archive_path =
+        
Path::new(env!("CARGO_MANIFEST_DIR")).join("testdata/test_mosaic_read.tar.gz");
+    let file = std::fs::File::open(&archive_path)
+        .unwrap_or_else(|e| panic!("Failed to open {}: {e}", 
archive_path.display()));
+    let decoder = flate2::read::GzDecoder::new(file);
+    let mut archive = tar::Archive::new(decoder);
+
+    let tmp = tempfile::tempdir().expect("Failed to create temp dir");
+    let db_dir = tmp.path().join("default.db");
+    std::fs::create_dir_all(&db_dir).expect("Failed to create default database 
dir");
+    archive.unpack(&db_dir).expect("Failed to extract fixture");
+
+    let warehouse = format!("file://{}", tmp.path().display());
+    (tmp, warehouse)
+}
+
+async fn create_context() -> (tempfile::TempDir, SQLContext) {
+    let (tmp, warehouse) = extract_test_warehouse();
+    let mut options = Options::new();
+    options.set(CatalogOptions::WAREHOUSE, warehouse);
+    let catalog = FileSystemCatalog::new(options).expect("Failed to create 
catalog");
+    let catalog: Arc<dyn Catalog> = Arc::new(catalog);
+
+    let mut ctx = SQLContext::new();
+    ctx.register_catalog("paimon", catalog)
+        .await
+        .expect("Failed to register catalog");
+    (tmp, ctx)
+}
+
+async fn run_sql(ctx: &SQLContext, sql: &str) -> Vec<RecordBatch> {
+    ctx.sql(sql)
+        .await
+        .unwrap_or_else(|e| panic!("Failed to plan `{sql}`: {e}"))
+        .collect()
+        .await
+        .unwrap_or_else(|e| panic!("Failed to execute `{sql}`: {e}"))
+}
+
+fn collect_id_name_score(batches: &[RecordBatch]) -> Vec<(i32, String, i64)> {
+    let mut rows = Vec::new();
+    for batch in batches {
+        let ids = batch
+            .column_by_name("id")
+            .and_then(|column| column.as_any().downcast_ref::<Int32Array>())
+            .expect("id column");
+        let names = batch
+            .column_by_name("name")
+            .and_then(|column| column.as_any().downcast_ref::<StringArray>())
+            .expect("name column");
+        let scores = batch
+            .column_by_name("score")
+            .and_then(|column| column.as_any().downcast_ref::<Int64Array>())
+            .expect("score column");
+
+        for row in 0..batch.num_rows() {
+            rows.push((
+                ids.value(row),
+                names.value(row).to_string(),
+                scores.value(row),
+            ));
+        }
+    }
+    rows
+}
+
+fn collect_name_id(batches: &[RecordBatch]) -> Vec<(String, i32)> {
+    let mut rows = Vec::new();
+    for batch in batches {
+        let names = batch
+            .column(0)
+            .as_any()
+            .downcast_ref::<StringArray>()
+            .expect("first column should be name");
+        let ids = batch
+            .column(1)
+            .as_any()
+            .downcast_ref::<Int32Array>()
+            .expect("second column should be id");
+
+        for row in 0..batch.num_rows() {
+            rows.push((names.value(row).to_string(), ids.value(row)));
+        }
+    }
+    rows
+}
+
+fn contains_mosaic_file(path: &Path) -> bool {
+    let entries = std::fs::read_dir(path)
+        .unwrap_or_else(|e| panic!("Failed to read {}: {e}", path.display()));
+    for entry in entries {
+        let path = entry.expect("Failed to read dir entry").path();
+        if path.is_dir() {
+            if contains_mosaic_file(&path) {
+                return true;
+            }
+        } else if path
+            .extension()
+            .is_some_and(|extension| extension == "mosaic")
+        {
+            return true;
+        }
+    }
+    false
+}
+
+#[tokio::test]
+async fn test_read_pypaimon_mosaic_fixture() {
+    let (tmp, ctx) = create_context().await;
+    assert!(
+        contains_mosaic_file(tmp.path()),
+        "expected fixture to contain a Mosaic data file"
+    );
+
+    let rows = collect_id_name_score(
+        &run_sql(
+            &ctx,
+            &format!("SELECT id, name, score FROM 
paimon.default.{FIXTURE_TABLE} ORDER BY id"),
+        )
+        .await,
+    );
+    assert_eq!(
+        rows,
+        vec![
+            (1, "Alice".to_string(), 10),
+            (2, "Bob".to_string(), 20),
+            (3, "Carol".to_string(), 30),
+        ]
+    );
+
+    let projection_rows = collect_name_id(
+        &run_sql(
+            &ctx,
+            &format!("SELECT name, id FROM paimon.default.{FIXTURE_TABLE} 
ORDER BY id"),
+        )
+        .await,
+    );
+    assert_eq!(
+        projection_rows,
+        vec![
+            ("Alice".to_string(), 1),
+            ("Bob".to_string(), 2),
+            ("Carol".to_string(), 3),
+        ]
+    );
+
+    let filtered_rows = collect_id_name_score(
+        &run_sql(
+            &ctx,
+            &format!("SELECT id, name, score FROM 
paimon.default.{FIXTURE_TABLE} WHERE id = 2"),
+        )
+        .await,
+    );
+    assert_eq!(filtered_rows, vec![(2, "Bob".to_string(), 20)]);
+}
diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md
index 90ec311..74b5038 100644
--- a/docs/src/getting-started.md
+++ b/docs/src/getting-started.md
@@ -51,6 +51,17 @@ Available storage features:
 | `storage-hdfs`   | HDFS             |
 | `storage-all`    | All of the above |
 
+## Optional File Formats
+
+Mosaic data files can be read by enabling the `mosaic` feature:
+
+```toml
+[dependencies]
+paimon = { version = "0.1.0", features = ["mosaic"] }
+```
+
+The current Mosaic support is read-only. Paimon Rust can read existing 
`.mosaic` data files in a Paimon table, but it does not write Mosaic data files 
yet.
+
 ## Catalog Management
 
 Paimon supports multiple catalog types. The `CatalogFactory` provides a 
unified way to create catalogs based on configuration options.
diff --git a/docs/src/sql.md b/docs/src/sql.md
index a4dc0fa..e6312ca 100644
--- a/docs/src/sql.md
+++ b/docs/src/sql.md
@@ -31,6 +31,18 @@ datafusion = "53"
 tokio = { version = "1", features = ["full"] }
 ```
 
+To query tables with Mosaic data files, enable the `mosaic` feature on both 
crates:
+
+```toml
+[dependencies]
+paimon = { version = "0.1.0", features = ["mosaic"] }
+paimon-datafusion = { version = "0.1.0", features = ["mosaic"] }
+datafusion = "53"
+tokio = { version = "1", features = ["full"] }
+```
+
+Mosaic support is currently read-only. SQL queries can read existing `.mosaic` 
files, but Paimon Rust does not write Mosaic data files yet.
+
 ## Registering Catalog
 
 Register an entire Paimon catalog so all databases and tables are accessible 
via `paimon.database.table` syntax:
@@ -256,6 +268,12 @@ INSERT INTO paimon.my_db.users SELECT * FROM source_table;
 
 For primary-key tables, records with duplicate keys are deduplicated according 
to the merge engine (default: Deduplicate engine, where the last written value 
wins).
 
+### Mosaic Read Scope
+
+The Mosaic reader uses row-group statistics for conservative pruning when they 
are present. This pruning is not row-level filter enforcement; DataFusion still 
applies SQL filters above the reader to produce exact query results.
+
+Unsupported or limited Mosaic areas include writing `.mosaic` files, emitting 
manifest `value_stats` for Mosaic writes, Mosaic bloom filters, and 
Mosaic-specific performance tuning.
+
 ### INSERT OVERWRITE
 
 For partitioned tables, `INSERT OVERWRITE` replaces only the affected 
partitions. For unpartitioned tables, it replaces the entire table:

Reply via email to