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 de7734e  Enable Mosaic reads by default (#491)
de7734e is described below

commit de7734e2559eb21a7d2e5b38ee8fa53f923a5c74
Author: Jingsong Lee <[email protected]>
AuthorDate: Thu Jul 9 15:11:14 2026 +0800

    Enable Mosaic reads by default (#491)
---
 .github/workflows/ci.yml                           |   6 +-
 crates/integrations/datafusion/Cargo.toml          |   1 -
 .../integrations/datafusion/tests/mosaic_tables.rs |   2 -
 crates/paimon/Cargo.toml                           |   3 +-
 crates/paimon/src/arrow/format/mod.rs              |   3 -
 crates/paimon/src/arrow/format/mosaic.rs           | 186 ++++++++++++++++-----
 crates/paimon/src/table/data_file_reader.rs        |   2 +-
 docs/src/getting-started.md                        |  11 +-
 docs/src/sql.md                                    |  14 +-
 9 files changed, 149 insertions(+), 79 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 962e2a8..c32b5ff 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -58,7 +58,7 @@ jobs:
         run: cargo fmt --all -- --check
 
       - name: Clippy
-        run: cargo clippy --all-targets --workspace --features 
fulltext,vortex,mosaic -- -D warnings
+        run: cargo clippy --all-targets --workspace --features fulltext,vortex 
-- -D warnings
 
   build:
     runs-on: ${{ matrix.os }}
@@ -71,7 +71,7 @@ jobs:
     steps:
       - uses: actions/checkout@v7
       - name: Build
-        run: cargo build --features fulltext,vortex,mosaic
+        run: cargo build --features fulltext,vortex
 
   unit:
     runs-on: ${{ matrix.os }}
@@ -85,7 +85,7 @@ jobs:
       - uses: actions/checkout@v7
 
       - name: Test
-        run: cargo test -p paimon --all-targets --features 
fulltext,vortex,mosaic
+        run: cargo test -p paimon --all-targets --features fulltext,vortex
         env:
           RUST_LOG: DEBUG
           RUST_BACKTRACE: full
diff --git a/crates/integrations/datafusion/Cargo.toml 
b/crates/integrations/datafusion/Cargo.toml
index e78d10f..d7576d0 100644
--- a/crates/integrations/datafusion/Cargo.toml
+++ b/crates/integrations/datafusion/Cargo.toml
@@ -29,7 +29,6 @@ keywords = ["paimon", "datafusion", "integrations"]
 
 [features]
 fulltext = ["paimon/fulltext"]
-mosaic = ["paimon/mosaic"]
 vortex = ["paimon/vortex"]
 
 [dependencies]
diff --git a/crates/integrations/datafusion/tests/mosaic_tables.rs 
b/crates/integrations/datafusion/tests/mosaic_tables.rs
index 4a6f679..426515d 100644
--- a/crates/integrations/datafusion/tests/mosaic_tables.rs
+++ b/crates/integrations/datafusion/tests/mosaic_tables.rs
@@ -15,8 +15,6 @@
 // specific language governing permissions and limitations
 // under the License.
 
-#![cfg(feature = "mosaic")]
-
 //! Mosaic file format read compatibility tests.
 
 use std::path::Path;
diff --git a/crates/paimon/Cargo.toml b/crates/paimon/Cargo.toml
index 72fbfe9..d33debf 100644
--- a/crates/paimon/Cargo.toml
+++ b/crates/paimon/Cargo.toml
@@ -42,7 +42,6 @@ storage-all = [
     "storage-hdfs",
 ]
 fulltext = ["tantivy", "tempfile"]
-mosaic = ["dep:paimon-mosaic-core"]
 vortex = ["dep:vortex"]
 
 storage-memory = ["opendal/services-memory"]
@@ -104,7 +103,7 @@ uuid = { version = "1", features = ["v4"] }
 urlencoding = "2.1"
 tantivy = { version = "0.22", optional = true }
 tempfile = { version = "3", optional = true }
-paimon-mosaic-core = { version = "0.1.0", optional = true }
+paimon-mosaic-core = "0.2.0"
 paimon-vindex-core = "0.2.0"
 vortex = { version = "0.75.0", features = ["tokio"], optional = true }
 libloading = "0.9"
diff --git a/crates/paimon/src/arrow/format/mod.rs 
b/crates/paimon/src/arrow/format/mod.rs
index 87614bc..2a2fcbd 100644
--- a/crates/paimon/src/arrow/format/mod.rs
+++ b/crates/paimon/src/arrow/format/mod.rs
@@ -17,7 +17,6 @@
 
 mod avro;
 pub(crate) mod blob;
-#[cfg(feature = "mosaic")]
 mod mosaic;
 mod orc;
 mod parquet;
@@ -128,7 +127,6 @@ pub(crate) fn create_format_reader(
     } else if lower.ends_with(".row") {
         Box::new(row::RowFormatReader)
     } else {
-        #[cfg(feature = "mosaic")]
         if lower.ends_with(".mosaic") {
             return Ok(shredding::maybe_wrap_reader(
                 Box::new(mosaic::MosaicFormatReader),
@@ -159,7 +157,6 @@ fn supported_read_formats() -> Vec<&'static str> {
         ".orc",
         ".avro",
         ".row",
-        #[cfg(feature = "mosaic")]
         ".mosaic",
         #[cfg(feature = "vortex")]
         ".vortex",
diff --git a/crates/paimon/src/arrow/format/mosaic.rs 
b/crates/paimon/src/arrow/format/mosaic.rs
index 2ab9ae8..0242475 100644
--- a/crates/paimon/src/arrow/format/mosaic.rs
+++ b/crates/paimon/src/arrow/format/mosaic.rs
@@ -24,7 +24,7 @@ use crate::table::{ArrowRecordBatchStream, RowRange};
 use crate::Error;
 use arrow_array::RecordBatch;
 use arrow_array::RecordBatchOptions;
-use arrow_schema::{DataType as ArrowDataType, SchemaRef, TimeUnit};
+use arrow_schema::{DataType as ArrowDataType, SchemaRef};
 use async_stream::try_stream;
 use async_trait::async_trait;
 use bytes::Bytes;
@@ -449,42 +449,7 @@ fn validate_mosaic_schema(schema: &SchemaRef) -> 
crate::Result<()> {
 }
 
 fn validate_mosaic_arrow_type(data_type: &ArrowDataType) -> Result<(), String> 
{
-    match data_type {
-        ArrowDataType::Boolean
-        | ArrowDataType::Int8
-        | ArrowDataType::Int16
-        | ArrowDataType::Int32
-        | ArrowDataType::Int64
-        | ArrowDataType::Float32
-        | ArrowDataType::Float64
-        | ArrowDataType::Date32
-        | ArrowDataType::Utf8
-        | ArrowDataType::Binary => Ok(()),
-        ArrowDataType::Time32(TimeUnit::Millisecond) => Ok(()),
-        ArrowDataType::Decimal128(precision, _) => {
-            if *precision == 0 || *precision > 38 {
-                Err(format!(
-                    "Decimal precision must be in 1..=38, got {precision}"
-                ))
-            } else {
-                Ok(())
-            }
-        }
-        ArrowDataType::Timestamp(
-            TimeUnit::Millisecond | TimeUnit::Microsecond | 
TimeUnit::Nanosecond,
-            _,
-        ) => Ok(()),
-        ArrowDataType::Struct(fields) if is_timestamp_nanos_struct(fields) => 
Ok(()),
-        other => Err(format!("unsupported Arrow type {other:?}")),
-    }
-}
-
-fn is_timestamp_nanos_struct(fields: &arrow_schema::Fields) -> bool {
-    fields.len() == 2
-        && fields[0].name() == "millis"
-        && *fields[0].data_type() == ArrowDataType::Int64
-        && fields[1].name() == "nanos_of_milli"
-        && *fields[1].data_type() == ArrowDataType::Int32
+    paimon_mosaic_core::types::validate_data_type(data_type)
 }
 
 fn selected_slices_for_row_group(
@@ -625,15 +590,17 @@ mod tests {
     use crate::arrow::format::{FilePredicates, FormatFileReader};
     use crate::spec::{
         ArrayType, BigIntType, BooleanType, DataType, DateType, Datum, 
DecimalType, DoubleType,
-        FloatType, IntType, LocalZonedTimestampType, Predicate, 
PredicateBuilder, RowType,
+        FloatType, IntType, LocalZonedTimestampType, MapType, Predicate, 
PredicateBuilder, RowType,
         SmallIntType, TimeType, TimestampType, TinyIntType, VarBinaryType, 
VarCharType,
     };
     use arrow_array::{
         Array, BinaryArray, BooleanArray, Date32Array, Decimal128Array, 
Float32Array, Float64Array,
-        Int16Array, Int32Array, Int64Array, Int8Array, StringArray, 
Time32MillisecondArray,
-        TimestampMicrosecondArray, TimestampMillisecondArray, 
TimestampNanosecondArray,
+        Int16Array, Int32Array, Int64Array, Int8Array, ListArray, MapArray, 
StringArray,
+        StructArray, Time32MillisecondArray, TimestampMicrosecondArray, 
TimestampMillisecondArray,
+        TimestampNanosecondArray,
     };
-    use arrow_schema::{DataType as ArrowDataType, Field, Schema};
+    use arrow_buffer::{BooleanBuffer, NullBuffer, OffsetBuffer, ScalarBuffer};
+    use arrow_schema::{DataType as ArrowDataType, Field, Schema, TimeUnit};
     use bytes::Bytes;
     use futures::TryStreamExt;
     use paimon_mosaic_core::spec::COMPRESSION_NONE;
@@ -739,6 +706,41 @@ mod tests {
         DataField::new(id, name.to_string(), data_type)
     }
 
+    fn test_map_array(
+        offsets: Vec<i32>,
+        validities: Vec<bool>,
+        key_nullable: bool,
+        value_nullable: bool,
+        keys: Vec<Option<&str>>,
+        values: Vec<Option<i32>>,
+    ) -> MapArray {
+        let entries = StructArray::try_new(
+            vec![
+                Arc::new(Field::new("key", ArrowDataType::Utf8, key_nullable)),
+                Arc::new(Field::new("value", ArrowDataType::Int32, 
value_nullable)),
+            ]
+            .into(),
+            vec![
+                Arc::new(StringArray::from(keys)),
+                Arc::new(Int32Array::from(values)),
+            ],
+            None,
+        )
+        .unwrap();
+        MapArray::try_new(
+            Arc::new(Field::new(
+                "entries",
+                ArrowDataType::Struct(entries.fields().clone()),
+                false,
+            )),
+            OffsetBuffer::new(ScalarBuffer::from(offsets)),
+            entries,
+            Some(NullBuffer::new(BooleanBuffer::from(validities))),
+            false,
+        )
+        .unwrap()
+    }
+
     fn arrow_schema() -> SchemaRef {
         Arc::new(Schema::new(vec![
             Field::new("id", ArrowDataType::Int32, false),
@@ -1302,8 +1304,12 @@ mod tests {
             fields[0].clone(),
             field(
                 3,
-                "new_items",
-                DataType::Array(ArrayType::new(DataType::Int(IntType::new()))),
+                "new_nested",
+                DataType::Row(RowType::new(vec![field(
+                    4,
+                    "value",
+                    DataType::Int(IntType::new()),
+                )])),
             ),
         ];
         let data = write_mosaic(&sample_batch());
@@ -1320,7 +1326,11 @@ mod tests {
         let projected = vec![field(
             0,
             "id",
-            DataType::Array(ArrayType::new(DataType::Int(IntType::new()))),
+            DataType::Row(RowType::new(vec![field(
+                1,
+                "value",
+                DataType::Int(IntType::new()),
+            )])),
         )];
         let data = write_mosaic(&sample_batch());
         let err = read_batches(data, &projected, None).await.unwrap_err();
@@ -1498,8 +1508,7 @@ mod tests {
     }
 
     /// Round-trips every scalar/temporal type Mosaic supports through write + 
read,
-    /// asserting values survive the format. ARRAY/MAP are intentionally 
excluded:
-    /// `paimon-mosaic-core` 0.1.0 does not support them and the reader 
rejects them.
+    /// asserting values survive the format. Collection types are covered 
separately.
     #[tokio::test]
     async fn test_read_full_types() {
         let fields = full_type_fields();
@@ -1658,6 +1667,91 @@ mod tests {
         );
     }
 
+    #[tokio::test]
+    async fn test_read_array_and_map_types() {
+        let fields = vec![
+            field(
+                0,
+                "nums",
+                
DataType::Array(ArrayType::new(DataType::Int(IntType::with_nullable(true)))),
+            ),
+            field(
+                1,
+                "labels",
+                DataType::Map(MapType::new(
+                    DataType::VarChar(VarCharType::with_nullable(false, 
20).unwrap()),
+                    DataType::Int(IntType::with_nullable(true)),
+                )),
+            ),
+        ];
+        let schema = build_target_arrow_schema(&fields).unwrap();
+        let nums = ListArray::try_new(
+            Arc::new(Field::new("element", ArrowDataType::Int32, true)),
+            OffsetBuffer::new(ScalarBuffer::from(vec![0, 3, 3, 5])),
+            Arc::new(Int32Array::from(vec![
+                Some(1),
+                None,
+                Some(3),
+                Some(4),
+                Some(5),
+            ])),
+            Some(NullBuffer::new(BooleanBuffer::from(vec![
+                true, false, true,
+            ]))),
+        )
+        .unwrap();
+        let labels = test_map_array(
+            vec![0, 2, 2, 3],
+            vec![true, false, true],
+            false,
+            true,
+            vec![Some("a"), Some("b"), Some("c")],
+            vec![Some(10), None, Some(30)],
+        );
+        let batch = RecordBatch::try_new(schema, vec![Arc::new(nums), 
Arc::new(labels)]).unwrap();
+        let data = write_mosaic(&batch);
+        let batches = read_batches(data, &fields, None).await.unwrap();
+
+        assert_eq!(batches.len(), 1);
+        let result = &batches[0];
+        assert_eq!(result.num_rows(), 3);
+        assert_eq!(result.num_columns(), 2);
+
+        let nums = result
+            .column(0)
+            .as_any()
+            .downcast_ref::<ListArray>()
+            .unwrap();
+        assert_eq!(nums.value_offsets(), &[0, 3, 3, 5]);
+        assert!(nums.is_null(1));
+        let nums_values = 
nums.values().as_any().downcast_ref::<Int32Array>().unwrap();
+        assert_eq!(nums_values.value(0), 1);
+        assert!(nums_values.is_null(1));
+        assert_eq!(nums_values.value(4), 5);
+
+        let labels = result
+            .column(1)
+            .as_any()
+            .downcast_ref::<MapArray>()
+            .unwrap();
+        assert_eq!(labels.value_offsets(), &[0, 2, 2, 3]);
+        assert!(labels.is_null(1));
+        let label_keys = labels
+            .keys()
+            .as_any()
+            .downcast_ref::<StringArray>()
+            .unwrap();
+        let label_values = labels
+            .values()
+            .as_any()
+            .downcast_ref::<Int32Array>()
+            .unwrap();
+        assert_eq!(label_keys.value(0), "a");
+        assert_eq!(label_keys.value(2), "c");
+        assert!(label_values.is_null(1));
+        assert_eq!(label_values.value(2), 30);
+    }
+
     /// Null values in nullable columns must round-trip as nulls.
     #[tokio::test]
     async fn test_read_null_values() {
diff --git a/crates/paimon/src/table/data_file_reader.rs 
b/crates/paimon/src/table/data_file_reader.rs
index 5a7e0bf..8410992 100644
--- a/crates/paimon/src/table/data_file_reader.rs
+++ b/crates/paimon/src/table/data_file_reader.rs
@@ -976,7 +976,7 @@ mod row_tests {
     }
 }
 
-#[cfg(all(test, feature = "mosaic"))]
+#[cfg(test)]
 mod tests {
     use super::*;
     use crate::arrow::build_target_arrow_schema;
diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md
index b0317e5..2d43fad 100644
--- a/docs/src/getting-started.md
+++ b/docs/src/getting-started.md
@@ -58,16 +58,9 @@ Available storage features:
 paimon = { git = "https://github.com/apache/paimon-rust";, features = 
["storage-cos"] }
 ```
 
-## Optional File Formats
+## Mosaic File Format
 
-Mosaic data files can be read by enabling the `mosaic` feature. This feature 
is not in the latest release yet; it is available on the `main` branch:
-
-```toml
-[dependencies]
-paimon = { git = "https://github.com/apache/paimon-rust";, 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.
+Mosaic data file reads are always available. The current Mosaic support is 
read-only: Paimon Rust can read existing `.mosaic` data files, including array 
and map columns, in a Paimon table, but it does not write Mosaic data files yet.
 
 ## Catalog Management
 
diff --git a/docs/src/sql.md b/docs/src/sql.md
index 86cf612..c2b11cb 100644
--- a/docs/src/sql.md
+++ b/docs/src/sql.md
@@ -31,17 +31,7 @@ datafusion = "54.0.0"
 tokio = { version = "1", features = ["full"] }
 ```
 
-To query tables with Mosaic data files, enable the `mosaic` feature on both 
crates:
-
-```toml
-[dependencies]
-paimon = { version = "0.3.0", features = ["mosaic"] }
-paimon-datafusion = { version = "0.3.0", features = ["mosaic"] }
-datafusion = "54.0.0"
-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.
+Mosaic support is always available and currently read-only. SQL queries can 
read existing `.mosaic` files, but Paimon Rust does not write Mosaic data files 
yet.
 
 ## SQL Support Scope
 
@@ -534,7 +524,7 @@ For primary-key tables, records with duplicate keys are 
deduplicated according t
 
 ### 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.
+The Mosaic reader supports scalar, temporal, array, and map columns. It 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.
 

Reply via email to