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

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


The following commit(s) were added to refs/heads/main by this push:
     new d89d8495 feat(file-index): support additional types and validate 
decimal scale (#271)
d89d8495 is described below

commit d89d8495883e80a44e0e7b200d7bb6e6fba3a153
Author: Zhang Jiawei <[email protected]>
AuthorDate: Wed Sep 9 19:01:18 2026 +0800

    feat(file-index): support additional types and validate decimal scale (#271)
---
 include/paimon/file_index/file_index_reader.h      |   2 +
 include/paimon/global_index/global_index_reader.h  |   2 +
 src/paimon/CMakeLists.txt                          |   1 +
 src/paimon/common/file_index/CMakeLists.txt        |   4 +-
 .../file_index/bitmap/bitmap_file_index_meta.cpp   |  32 +++
 .../file_index/bitmap/bitmap_file_index_test.cpp   | 245 +++++++++++++++++++++
 .../bsi/bit_slice_index_bitmap_file_index.cpp      |  21 +-
 .../bsi/bit_slice_index_bitmap_file_index_test.cpp |  26 +++
 .../rangebitmap/dictionary/chunked_dictionary.cpp  |   6 +-
 .../rangebitmap/dictionary/chunked_dictionary.h    |   1 -
 .../dictionary/chunked_dictionary_test.cpp         |  22 +-
 .../rangebitmap/dictionary/key_factory.cpp         |  26 ++-
 .../dictionary/variable_length_chunk.cpp           | 171 ++++++++++++++
 .../rangebitmap/dictionary/variable_length_chunk.h | 103 +++++++++
 .../rangebitmap/range_bitmap_file_index.cpp        |  89 +++++---
 .../rangebitmap/range_bitmap_file_index.h          |  20 +-
 .../rangebitmap/range_bitmap_file_index_test.cpp   | 187 +++++++++++++++-
 .../rangebitmap/range_bitmap_type_adapter.cpp      | 113 ++++++++++
 .../rangebitmap/range_bitmap_type_adapter.h        |  56 +++++
 .../rangebitmap/range_bitmap_type_adapter_test.cpp | 109 +++++++++
 .../btree/btree_global_index_integration_test.cpp  |   8 +
 src/paimon/common/predicate/predicate_validator.h  |  50 ++++-
 .../common/predicate/predicate_validator_test.cpp  | 104 +++++++--
 .../core/operation/internal_read_context.cpp       |   2 -
 src/paimon/core/table/format/format_table_read.cpp |   1 -
 src/paimon/core/table/source/table_scan.cpp        |   2 -
 test/inte/blob_table_inte_test.cpp                 |   5 +-
 test/inte/global_index_test.cpp                    |   8 +
 test/inte/read_inte_test.cpp                       |   2 +-
 test/inte/scan_inte_test.cpp                       |   2 +-
 30 files changed, 1332 insertions(+), 88 deletions(-)

diff --git a/include/paimon/file_index/file_index_reader.h 
b/include/paimon/file_index/file_index_reader.h
index ff1d31d1..2da36fd2 100644
--- a/include/paimon/file_index/file_index_reader.h
+++ b/include/paimon/file_index/file_index_reader.h
@@ -34,6 +34,8 @@ namespace paimon {
 /// `std::shared_ptr<FileIndexResult>` objects. It reads pre-built file-level 
index data
 /// (e.g., bitmap, bsi or bloom filters) from index file and evaluates
 /// whether a given data file may contain rows matching a specific predicate.
+/// @note Callers of `Visit*` for DECIMAL fields must ensure each literal's 
scale matches the scale
+/// of the indexed data; otherwise, index filtering results may be incorrect.
 class PAIMON_EXPORT FileIndexReader : public 
FunctionVisitor<std::shared_ptr<FileIndexResult>> {
  public:
     Result<std::shared_ptr<FileIndexResult>> VisitIsNotNull() override;
diff --git a/include/paimon/global_index/global_index_reader.h 
b/include/paimon/global_index/global_index_reader.h
index ca3f4c9b..25792465 100644
--- a/include/paimon/global_index/global_index_reader.h
+++ b/include/paimon/global_index/global_index_reader.h
@@ -35,6 +35,8 @@ namespace paimon {
 /// Derived classes are expected to implement the visitor methods (e.g., 
`VisitEqual`,
 /// `VisitIsNull`, etc.) to return index-based results that indicate which
 /// rows satisfy the given predicate.
+/// @note Callers of `Visit*` for DECIMAL fields must ensure each literal's 
scale matches the scale
+/// of the indexed data; otherwise, index filtering results may be incorrect.
 class PAIMON_EXPORT GlobalIndexReader : public 
FunctionVisitor<std::shared_ptr<GlobalIndexResult>> {
  public:
     /// VisitVectorSearch performs approximate vector similarity search.
diff --git a/src/paimon/CMakeLists.txt b/src/paimon/CMakeLists.txt
index 0e7433a0..6fc1deaa 100644
--- a/src/paimon/CMakeLists.txt
+++ b/src/paimon/CMakeLists.txt
@@ -591,6 +591,7 @@ if(PAIMON_BUILD_TESTS)
                     
common/file_index/rangebitmap/dictionary/chunked_dictionary_test.cpp
                     
common/file_index/rangebitmap/range_bitmap_file_index_test.cpp
                     common/file_index/rangebitmap/range_bitmap_io_test.cpp
+                    
common/file_index/rangebitmap/range_bitmap_type_adapter_test.cpp
                     
common/file_index/bloomfilter/bloom_filter_file_index_test.cpp
                     common/file_index/bloomfilter/fast_hash_test.cpp
                     
common/global_index/complete_index_score_batch_reader_test.cpp
diff --git a/src/paimon/common/file_index/CMakeLists.txt 
b/src/paimon/common/file_index/CMakeLists.txt
index 7ab5a069..1e083c51 100644
--- a/src/paimon/common/file_index/CMakeLists.txt
+++ b/src/paimon/common/file_index/CMakeLists.txt
@@ -29,11 +29,13 @@ set(PAIMON_FILE_INDEX_SRC
     rangebitmap/dictionary/chunked_dictionary.cpp
     rangebitmap/dictionary/fixed_length_chunk.cpp
     rangebitmap/dictionary/key_factory.cpp
+    rangebitmap/dictionary/variable_length_chunk.cpp
     rangebitmap/utils/literal_serialization_utils.cpp
     rangebitmap/bit_slice_index_bitmap.cpp
     rangebitmap/range_bitmap.cpp
     rangebitmap/range_bitmap_file_index.cpp
-    rangebitmap/range_bitmap_file_index_factory.cpp)
+    rangebitmap/range_bitmap_file_index_factory.cpp
+    rangebitmap/range_bitmap_type_adapter.cpp)
 
 add_paimon_lib(paimon_file_index
                SOURCES
diff --git a/src/paimon/common/file_index/bitmap/bitmap_file_index_meta.cpp 
b/src/paimon/common/file_index/bitmap/bitmap_file_index_meta.cpp
index 64e05c65..50b88af3 100644
--- a/src/paimon/common/file_index/bitmap/bitmap_file_index_meta.cpp
+++ b/src/paimon/common/file_index/bitmap/bitmap_file_index_meta.cpp
@@ -18,11 +18,13 @@
 
 #include "paimon/common/file_index/bitmap/bitmap_file_index_meta.h"
 
+#include <cstdint>
 #include <string>
 #include <utility>
 
 #include "fmt/format.h"
 #include "paimon/common/utils/field_type_utils.h"
+#include "paimon/common/utils/math.h"
 #include "paimon/defs.h"
 #include "paimon/io/data_input_stream.h"
 #include "paimon/memory/bytes.h"
@@ -80,6 +82,18 @@ Result<std::function<void(const Literal&)>> 
BitmapFileIndexMeta::GetValueWriter(
                 [output_stream](const Literal& literal) -> void {
                     
output_stream->WriteValue<int64_t>(literal.GetValue<int64_t>());
                 });
+        case FieldType::FLOAT:
+            return std::function<void(const Literal&)>(
+                [output_stream](const Literal& literal) -> void {
+                    const float value = 
CanonicalizeFloatingPoint(literal.GetValue<float>());
+                    output_stream->WriteValue<float>(value);
+                });
+        case FieldType::DOUBLE:
+            return std::function<void(const Literal&)>(
+                [output_stream](const Literal& literal) -> void {
+                    const double value = 
CanonicalizeFloatingPoint(literal.GetValue<double>());
+                    output_stream->WriteValue<double>(value);
+                });
         case FieldType::STRING:
             return std::function<void(const Literal&)>(
                 [output_stream](const Literal& literal) -> void {
@@ -155,6 +169,24 @@ Result<std::function<Result<Literal>()>> 
BitmapFileIndexMeta::GetValueReader(
             };
             return func;
         }
+        case FieldType::FLOAT: {
+            std::function<Result<Literal>()> func = [&in, move_body_start,
+                                                     this]() -> 
Result<Literal> {
+                PAIMON_ASSIGN_OR_RAISE(float value,
+                                       ReadAndMoveBodyStart<float>(in, 
move_body_start));
+                return Literal(value);
+            };
+            return func;
+        }
+        case FieldType::DOUBLE: {
+            std::function<Result<Literal>()> func = [&in, move_body_start,
+                                                     this]() -> 
Result<Literal> {
+                PAIMON_ASSIGN_OR_RAISE(double value,
+                                       ReadAndMoveBodyStart<double>(in, 
move_body_start));
+                return Literal(value);
+            };
+            return func;
+        }
         case FieldType::DATE: {
             std::function<Result<Literal>()> func = [&in, move_body_start,
                                                      this]() -> 
Result<Literal> {
diff --git a/src/paimon/common/file_index/bitmap/bitmap_file_index_test.cpp 
b/src/paimon/common/file_index/bitmap/bitmap_file_index_test.cpp
index 42a888a8..e8f74c83 100644
--- a/src/paimon/common/file_index/bitmap/bitmap_file_index_test.cpp
+++ b/src/paimon/common/file_index/bitmap/bitmap_file_index_test.cpp
@@ -27,6 +27,7 @@
 #include "paimon/common/utils/arrow/status_utils.h"
 #include "paimon/common/utils/checked_cast.h"
 #include "paimon/common/utils/date_time_utils.h"
+#include "paimon/common/utils/math.h"
 #include "paimon/data/timestamp.h"
 #include "paimon/defs.h"
 #include "paimon/file_index/bitmap_index_result.h"
@@ -36,6 +37,15 @@
 #include "paimon/memory/memory_pool.h"
 #include "paimon/testing/utils/testharness.h"
 namespace paimon::test {
+namespace {
+
+template <size_t N>
+std::vector<char> JavaBytes(const char (&bytes)[N]) {
+    return std::vector<char>(bytes, bytes + N - 1);
+}
+
+}  // namespace
+
 class BitmapIndexTest : public ::testing::Test {
  public:
     void SetUp() override {
@@ -78,6 +88,21 @@ class BitmapIndexTest : public ::testing::Test {
         return writer->SerializedBytes();
     }
 
+    template <typename ArrowBuilder, typename ValueType>
+    Result<std::shared_ptr<arrow::Array>> CreateArray(const 
std::shared_ptr<arrow::DataType>& type,
+                                                      const 
std::vector<ValueType>& values) const {
+        auto value_builder = std::make_shared<ArrowBuilder>();
+        for (ValueType value : values) {
+            PAIMON_RETURN_NOT_OK_FROM_ARROW(value_builder->Append(value));
+        }
+        PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Array> 
value_array,
+                                          value_builder->Finish());
+        PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
+            std::shared_ptr<arrow::StructArray> struct_array,
+            arrow::StructArray::Make({value_array}, {arrow::field("f0", 
type)}));
+        return struct_array;
+    }
+
  private:
     std::shared_ptr<MemoryPool> pool_;
 };
@@ -620,6 +645,226 @@ TEST_F(BitmapIndexTest, TestTimestampType) {
     }
 }
 
+TEST_F(BitmapIndexTest, TestFloatAndDoubleTypes) {
+    const auto check_float = [&](int32_t version) {
+        const auto type = arrow::float32();
+        auto array =
+            
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_({arrow::field("f0", 
type)}),
+                                                      R"([[1.25], [null], 
[-2.5], [1.25], [3.75]])")
+                .ValueOrDie();
+        ASSERT_OK_AND_ASSIGN(PAIMON_UNIQUE_PTR<Bytes> index_bytes,
+                             WriteIndex(type, version, array));
+        auto input_stream =
+            std::make_shared<ByteArrayInputStream>(index_bytes->data(), 
index_bytes->size());
+        BitmapFileIndex file_index({});
+        ASSERT_OK_AND_ASSIGN(auto reader,
+                             
file_index.CreateReader(CreateArrowSchema(type).get(), 0,
+                                                     index_bytes->size(), 
input_stream, pool_));
+        CheckResult(reader->VisitEqual(Literal(1.25f)).value(), {0, 3});
+        CheckResult(reader->VisitNotEqual(Literal(1.25f)).value(), {2, 4});
+        CheckResult(reader->VisitIsNull().value(), {1});
+    };
+
+    const auto check_double = [&](int32_t version) {
+        const auto type = arrow::float64();
+        auto array =
+            
arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_({arrow::field("f0", 
type)}),
+                                                      R"([[1.25], [null], 
[-2.5], [1.25], [3.75]])")
+                .ValueOrDie();
+        ASSERT_OK_AND_ASSIGN(PAIMON_UNIQUE_PTR<Bytes> index_bytes,
+                             WriteIndex(type, version, array));
+        auto input_stream =
+            std::make_shared<ByteArrayInputStream>(index_bytes->data(), 
index_bytes->size());
+        BitmapFileIndex file_index({});
+        ASSERT_OK_AND_ASSIGN(auto reader,
+                             
file_index.CreateReader(CreateArrowSchema(type).get(), 0,
+                                                     index_bytes->size(), 
input_stream, pool_));
+        CheckResult(reader->VisitEqual(Literal(1.25)).value(), {0, 3});
+        CheckResult(reader->VisitNotEqual(Literal(1.25)).value(), {2, 4});
+        CheckResult(reader->VisitIsNull().value(), {1});
+    };
+
+    for (int32_t version : {1, 2}) {
+        check_float(version);
+        check_double(version);
+    }
+}
+
+TEST_F(BitmapIndexTest, TestFloatingPointJavaCompatibility) {
+    const std::vector<char> java_float_v1 = JavaBytes(
+        "\x01\x00\x00\x00\x08\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00"
+        "\x00\x00\x80\x00\x00\x00\x00\x00\x00\x14\x7f\xc0\x00\x00\x00\x00"
+        "\x00\x28\x3a\x30\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x10\x00"
+        "\x00\x00\x04\x00\x07\x00\x3a\x30\x00\x00\x01\x00\x00\x00\x00\x00"
+        "\x01\x00\x10\x00\x00\x00\x03\x00\x06\x00\x3a\x30\x00\x00\x01\x00"
+        "\x00\x00\x00\x00\x03\x00\x10\x00\x00\x00\x00\x00\x01\x00\x02\x00"
+        "\x05\x00");
+    const std::vector<char> java_float_v2 = JavaBytes(
+        "\x02\x00\x00\x00\x08\x00\x00\x00\x03\x00\x00\x00\x00\x01\x80\x00"
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x28\x00\x00\x00\x03\x80\x00"
+        "\x00\x00\x00\x00\x00\x14\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00"
+        "\x00\x00\x00\x00\x00\x14\x7f\xc0\x00\x00\x00\x00\x00\x28\x00\x00"
+        "\x00\x18\x3a\x30\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x10\x00"
+        "\x00\x00\x04\x00\x07\x00\x3a\x30\x00\x00\x01\x00\x00\x00\x00\x00"
+        "\x01\x00\x10\x00\x00\x00\x03\x00\x06\x00\x3a\x30\x00\x00\x01\x00"
+        "\x00\x00\x00\x00\x03\x00\x10\x00\x00\x00\x00\x00\x01\x00\x02\x00"
+        "\x05\x00");
+    const std::vector<char> java_double_v1 = JavaBytes(
+        "\x01\x00\x00\x00\x08\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00"
+        "\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+        "\x00\x14\x7f\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x28\x3a\x30"
+        "\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x10\x00\x00\x00\x04\x00"
+        "\x07\x00\x3a\x30\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x10\x00"
+        "\x00\x00\x03\x00\x06\x00\x3a\x30\x00\x00\x01\x00\x00\x00\x00\x00"
+        "\x03\x00\x10\x00\x00\x00\x00\x00\x01\x00\x02\x00\x05\x00");
+    const std::vector<char> java_double_v2 = JavaBytes(
+        "\x02\x00\x00\x00\x08\x00\x00\x00\x03\x00\x00\x00\x00\x01\x80\x00"
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x34\x00\x00"
+        "\x00\x03\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00"
+        "\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+        "\x00\x14\x7f\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x28\x00\x00"
+        "\x00\x18\x3a\x30\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x10\x00"
+        "\x00\x00\x04\x00\x07\x00\x3a\x30\x00\x00\x01\x00\x00\x00\x00\x00"
+        "\x01\x00\x10\x00\x00\x00\x03\x00\x06\x00\x3a\x30\x00\x00\x01\x00"
+        "\x00\x00\x00\x00\x03\x00\x10\x00\x00\x00\x00\x00\x01\x00\x02\x00"
+        "\x05\x00");
+    const std::vector<char> java_float_nan_v1 = JavaBytes(
+        "\x01\x00\x00\x00\x03\x00\x00\x00\x01\x00\x7f\xc0\x00\x00\x00\x00"
+        "\x00\x00\x3a\x30\x00\x00\x01\x00\x00\x00\x00\x00\x02\x00\x10\x00"
+        "\x00\x00\x00\x00\x01\x00\x02\x00");
+    const std::vector<char> java_float_nan_v2 = JavaBytes(
+        "\x02\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x00\x01\x7f\xc0"
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x01\x7f\xc0"
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16\x3a\x30\x00\x00\x01\x00"
+        "\x00\x00\x00\x00\x02\x00\x10\x00\x00\x00\x00\x00\x01\x00\x02\x00");
+    const std::vector<char> java_double_nan_v1 = JavaBytes(
+        "\x01\x00\x00\x00\x03\x00\x00\x00\x01\x00\x7f\xf8\x00\x00\x00\x00"
+        "\x00\x00\x00\x00\x00\x00\x3a\x30\x00\x00\x01\x00\x00\x00\x00\x00"
+        "\x02\x00\x10\x00\x00\x00\x00\x00\x01\x00\x02\x00");
+    const std::vector<char> java_double_nan_v2 = JavaBytes(
+        "\x02\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\x00\x01\x7f\xf8"
+        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00"
+        "\x00\x01\x7f\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+        "\x00\x16\x3a\x30\x00\x00\x01\x00\x00\x00\x00\x00\x02\x00\x10\x00"
+        "\x00\x00\x00\x00\x01\x00\x02\x00");
+
+    const auto float_nan = 
FloatingPointFromBits<float>(kCanonicalFloatNaNBits);
+    const auto float_positive_payload_nan = 
FloatingPointFromBits<float>(uint32_t{0x7fc12345});
+    const auto float_negative_payload_nan = 
FloatingPointFromBits<float>(uint32_t{0xffc54321});
+    const std::vector<float> float_values = {float_nan,
+                                             float_positive_payload_nan,
+                                             float_negative_payload_nan,
+                                             -0.0f,
+                                             +0.0f,
+                                             float_negative_payload_nan,
+                                             -0.0f,
+                                             +0.0f};
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Array> float_array,
+                         (CreateArray<arrow::FloatBuilder>(arrow::float32(), 
float_values)));
+
+    const auto double_nan = 
FloatingPointFromBits<double>(kCanonicalDoubleNaNBits);
+    const auto double_positive_payload_nan =
+        FloatingPointFromBits<double>(uint64_t{0x7ff8123456789abc});
+    const auto double_negative_payload_nan =
+        FloatingPointFromBits<double>(uint64_t{0xfff8abcdef012345});
+    const std::vector<double> double_values = {double_nan,
+                                               double_positive_payload_nan,
+                                               double_negative_payload_nan,
+                                               -0.0,
+                                               +0.0,
+                                               double_negative_payload_nan,
+                                               -0.0,
+                                               +0.0};
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Array> double_array,
+                         (CreateArray<arrow::DoubleBuilder>(arrow::float64(), 
double_values)));
+
+    const auto check_reader = [&](const std::shared_ptr<FileIndexReader>& 
reader,
+                                  const std::vector<Literal>& nan_literals,
+                                  const Literal& negative_zero, const Literal& 
positive_zero) {
+        for (const Literal& nan_literal : nan_literals) {
+            CheckResult(reader->VisitEqual(nan_literal).value(), {0, 1, 2, 5});
+        }
+        CheckResult(reader->VisitEqual(negative_zero).value(), {3, 6});
+        CheckResult(reader->VisitEqual(positive_zero).value(), {4, 7});
+    };
+
+    const auto check = [&](const std::shared_ptr<arrow::DataType>& type,
+                           const std::shared_ptr<arrow::Array>& array,
+                           const std::vector<Literal>& nan_literals, const 
Literal& negative_zero,
+                           const Literal& positive_zero, int32_t version,
+                           const std::vector<char>& java_bytes) {
+        auto input_stream =
+            std::make_shared<ByteArrayInputStream>(java_bytes.data(), 
java_bytes.size());
+        BitmapFileIndex file_index({});
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileIndexReader> reader,
+                             
file_index.CreateReader(CreateArrowSchema(type).get(), 0,
+                                                     java_bytes.size(), 
input_stream, pool_));
+        check_reader(reader, nan_literals, negative_zero, positive_zero);
+
+        ASSERT_OK_AND_ASSIGN(PAIMON_UNIQUE_PTR<Bytes> cpp_bytes, 
WriteIndex(type, version, array));
+        auto cpp_input_stream =
+            std::make_shared<ByteArrayInputStream>(cpp_bytes->data(), 
cpp_bytes->size());
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileIndexReader> cpp_reader,
+                             
file_index.CreateReader(CreateArrowSchema(type).get(), 0,
+                                                     cpp_bytes->size(), 
cpp_input_stream, pool_));
+        check_reader(cpp_reader, nan_literals, negative_zero, positive_zero);
+    };
+
+    const auto check_nan_meta = [&](const std::shared_ptr<arrow::DataType>& 
type,
+                                    const std::shared_ptr<arrow::Array>& 
array, int32_t version,
+                                    size_t key_size, const std::vector<char>& 
java_bytes) {
+        ASSERT_OK_AND_ASSIGN(PAIMON_UNIQUE_PTR<Bytes> cpp_bytes, 
WriteIndex(type, version, array));
+        // RoaringBitmap may choose a different, semantically equivalent body 
encoding. Compare
+        // only the prefix which is independent of that encoding. For V1 it 
contains version,
+        // row count, entry count, has-null flag, key and offset: 1 + 4 + 4 + 
1 + key_size + 4.
+        // For V2 it additionally contains one secondary-index entry, 
bitmap-body offset, and one
+        // index-block entry through entry.offset, but excludes entry.length 
and the bitmap body:
+        // 1 + 4 + 4 + 1 + 4 + (key_size + 4) + 4 + 4 + (key_size + 4).
+        const size_t comparable_prefix_size =
+            version == BitmapFileIndex::VERSION_1 ? 14 + key_size : 30 + 2 * 
key_size;
+        ASSERT_GE(java_bytes.size(), comparable_prefix_size);
+        ASSERT_GE(cpp_bytes->size(), comparable_prefix_size);
+        ASSERT_EQ(
+            std::vector<char>(java_bytes.begin(), java_bytes.begin() + 
comparable_prefix_size),
+            std::vector<char>(cpp_bytes->data(), cpp_bytes->data() + 
comparable_prefix_size));
+    };
+
+    check(arrow::float32(), float_array,
+          {Literal(float_nan), Literal(float_positive_payload_nan),
+           Literal(float_negative_payload_nan)},
+          Literal(-0.0f), Literal(+0.0f), /*version=*/1, java_float_v1);
+    check(arrow::float32(), float_array,
+          {Literal(float_nan), Literal(float_positive_payload_nan),
+           Literal(float_negative_payload_nan)},
+          Literal(-0.0f), Literal(+0.0f), /*version=*/2, java_float_v2);
+    check(arrow::float64(), double_array,
+          {Literal(double_nan), Literal(double_positive_payload_nan),
+           Literal(double_negative_payload_nan)},
+          Literal(-0.0), Literal(+0.0), /*version=*/1, java_double_v1);
+    check(arrow::float64(), double_array,
+          {Literal(double_nan), Literal(double_positive_payload_nan),
+           Literal(double_negative_payload_nan)},
+          Literal(-0.0), Literal(+0.0), /*version=*/2, java_double_v2);
+
+    const std::vector<float> float_nan_values = {float_negative_payload_nan,
+                                                 float_positive_payload_nan, 
float_nan};
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Array> float_nan_array,
+                         (CreateArray<arrow::FloatBuilder>(arrow::float32(), 
float_nan_values)));
+    check_nan_meta(arrow::float32(), float_nan_array, /*version=*/1, 
sizeof(float),
+                   java_float_nan_v1);
+    check_nan_meta(arrow::float32(), float_nan_array, /*version=*/2, 
sizeof(float),
+                   java_float_nan_v2);
+
+    const std::vector<double> double_nan_values = {double_negative_payload_nan,
+                                                   
double_positive_payload_nan, double_nan};
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Array> double_nan_array,
+                         (CreateArray<arrow::DoubleBuilder>(arrow::float64(), 
double_nan_values)));
+    check_nan_meta(arrow::float64(), double_nan_array, /*version=*/1, 
sizeof(double),
+                   java_double_nan_v1);
+    check_nan_meta(arrow::float64(), double_nan_array, /*version=*/2, 
sizeof(double),
+                   java_double_nan_v2);
+}
+
 TEST_F(BitmapIndexTest, TestHighCardinalityForCompatibility) {
     auto type = arrow::utf8();
     auto check_result = [&](const std::string& index_file_name) {
diff --git 
a/src/paimon/common/file_index/bsi/bit_slice_index_bitmap_file_index.cpp 
b/src/paimon/common/file_index/bsi/bit_slice_index_bitmap_file_index.cpp
index 4cb98cdc..558dec50 100644
--- a/src/paimon/common/file_index/bsi/bit_slice_index_bitmap_file_index.cpp
+++ b/src/paimon/common/file_index/bsi/bit_slice_index_bitmap_file_index.cpp
@@ -38,6 +38,7 @@
 #include "paimon/common/utils/checked_cast.h"
 #include "paimon/common/utils/date_time_utils.h"
 #include "paimon/common/utils/field_type_utils.h"
+#include "paimon/data/decimal.h"
 #include "paimon/data/timestamp.h"
 #include "paimon/defs.h"
 #include "paimon/file_index/bitmap_index_result.h"
@@ -252,10 +253,26 @@ Result<BitSliceIndexBitmapFileIndex::ValueMapperType> 
BitSliceIndexBitmapFileInd
                     return literal.GetValue<Timestamp>().ToMicrosecond();
                 });
         }
+        case FieldType::DECIMAL:
+            return BitSliceIndexBitmapFileIndex::ValueMapperType(
+                [](const Literal& literal) -> Result<int64_t> {
+                    if (literal.IsNull()) {
+                        return Status::Invalid(
+                            "literal cannot be null when GetValue in 
BitSliceIndexBitmapFileIndex");
+                    }
+                    const auto value = literal.GetValue<Decimal>();
+                    if (value.Value() < std::numeric_limits<int64_t>::min() ||
+                        value.Value() > std::numeric_limits<int64_t>::max()) {
+                        return Status::Invalid(fmt::format(
+                            "decimal unscaled value {} does not fit in int64 
for bsi index",
+                            value.ToString()));
+                    }
+                    return value.ToUnscaledLong();
+                });
         default:
-            // TODO(xinyu.lxy): support decimal
             return Status::Invalid(
-                "BitSliceIndexBitmapFileIndex only support 
TINYINT/SMALLINT/INT/BIGINT/DATE");
+                "BitSliceIndexBitmapFileIndex only support "
+                "TINYINT/SMALLINT/INT/BIGINT/DATE/TIMESTAMP/DECIMAL");
     }
 }
 
diff --git 
a/src/paimon/common/file_index/bsi/bit_slice_index_bitmap_file_index_test.cpp 
b/src/paimon/common/file_index/bsi/bit_slice_index_bitmap_file_index_test.cpp
index 760434e1..8da7ace2 100644
--- 
a/src/paimon/common/file_index/bsi/bit_slice_index_bitmap_file_index_test.cpp
+++ 
b/src/paimon/common/file_index/bsi/bit_slice_index_bitmap_file_index_test.cpp
@@ -27,6 +27,7 @@
 #include "gtest/gtest.h"
 #include "paimon/common/utils/arrow/status_utils.h"
 #include "paimon/common/utils/field_type_utils.h"
+#include "paimon/data/decimal.h"
 #include "paimon/data/timestamp.h"
 #include "paimon/defs.h"
 #include "paimon/file_index/bitmap_index_result.h"
@@ -420,6 +421,31 @@ TEST_F(BitSliceIndexBitmapIndexReaderTest, 
TestTimestampType) {
                         "literal cannot be null when GetValue in 
BitSliceIndexBitmapFileIndex");
 }
 
+TEST_F(BitSliceIndexBitmapIndexReaderTest, TestDecimalType) {
+    const auto type = arrow::decimal128(10, 2);
+    ASSERT_OK_AND_ASSIGN(PAIMON_UNIQUE_PTR<Bytes> index_bytes,
+                         WriteIndex(type, R"([["1.00"], ["2.50"], [null], 
["-1.25"], ["2.50"]])"));
+    auto input_stream =
+        std::make_shared<ByteArrayInputStream>(index_bytes->data(), 
index_bytes->size());
+    BitSliceIndexBitmapFileIndex file_index({});
+    ASSERT_OK_AND_ASSIGN(
+        auto reader, file_index.CreateReader(CreateArrowSchema(type).get(), 0, 
index_bytes->size(),
+                                             input_stream, pool_));
+
+    CheckResult(reader->VisitEqual(Literal(Decimal(10, 2, 250))).value(), {1, 
4});
+    CheckResult(reader->VisitGreaterThan(Literal(Decimal(10, 2, 
100))).value(), {1, 4});
+    CheckResult(reader->VisitLessThan(Literal(Decimal(10, 2, 0))).value(), 
{3});
+    CheckResult(reader->VisitIsNull().value(), {2});
+
+    // BSI does not rescale Decimal literals. A mathematically equivalent 
literal with a
+    // different scale produces an incorrect empty result, so callers must use 
the field's scale.
+    CheckResult(reader->VisitEqual(Literal(Decimal(10, 3, 2500))).value(), {});
+
+    // test invalid case for decimal128(20, 0) which exceeds int64 range
+    ASSERT_NOK_WITH_MSG(WriteIndex(arrow::decimal128(20, 0), 
R"([["9223372036854775808"]])"),
+                        "does not fit in int64 for bsi index");
+}
+
 TEST_F(BitSliceIndexBitmapIndexReaderTest, TestUnInvalidType) {
     std::vector<char> index_bytes = {
         1,  0, 0, 0,  5,  1,  1,  0, 0, 0, 0, 0,  0, 0, 0, 0,  0, 0,  0,  0, 
0, 0, 2, 58,
diff --git 
a/src/paimon/common/file_index/rangebitmap/dictionary/chunked_dictionary.cpp 
b/src/paimon/common/file_index/rangebitmap/dictionary/chunked_dictionary.cpp
index 3011afea..cc96e48f 100644
--- a/src/paimon/common/file_index/rangebitmap/dictionary/chunked_dictionary.cpp
+++ b/src/paimon/common/file_index/rangebitmap/dictionary/chunked_dictionary.cpp
@@ -89,14 +89,11 @@ Result<std::shared_ptr<Chunk>> 
ChunkedDictionary::GetChunk(int32_t index) {
     if (index < 0 || index >= size_) {
         return Status::Invalid(fmt::format("Invalid chunk index: {}", index));
     }
-    if (offsets_bytes_ == nullptr || chunks_bytes_ == nullptr) {
+    if (offsets_bytes_ == nullptr) {
         PAIMON_RETURN_NOT_OK(input_stream_->Seek(body_offset_, FS_SEEK_SET));
         auto offsets = Bytes::AllocateBytes(offsets_length_, pool_.get());
         PAIMON_RETURN_NOT_OK(input_stream_->Read(offsets->data(), 
offsets_length_));
         offsets_bytes_ = std::move(offsets);
-        auto chunks = Bytes::AllocateBytes(chunks_length_, pool_.get());
-        PAIMON_RETURN_NOT_OK(input_stream_->Read(chunks->data(), 
chunks_length_));
-        chunks_bytes_ = std::move(chunks);
     }
     if (chunks_cache_[index]) {
         return chunks_cache_[index];
@@ -244,6 +241,5 @@ ChunkedDictionary::ChunkedDictionary(const 
std::shared_ptr<InputStream>& input_s
       chunks_length_(chunks_length),
       body_offset_(body_offset),
       offsets_bytes_(nullptr),
-      chunks_bytes_(nullptr),
       chunks_cache_(std::vector<std::shared_ptr<Chunk>>(size)) {}
 }  // namespace paimon
diff --git 
a/src/paimon/common/file_index/rangebitmap/dictionary/chunked_dictionary.h 
b/src/paimon/common/file_index/rangebitmap/dictionary/chunked_dictionary.h
index 7cb4ba4f..a1a6edcf 100644
--- a/src/paimon/common/file_index/rangebitmap/dictionary/chunked_dictionary.h
+++ b/src/paimon/common/file_index/rangebitmap/dictionary/chunked_dictionary.h
@@ -96,7 +96,6 @@ class ChunkedDictionary final : public Dictionary {
 
     // for lazy loading
     PAIMON_UNIQUE_PTR<Bytes> offsets_bytes_;
-    PAIMON_UNIQUE_PTR<Bytes> chunks_bytes_;
 
     // mmap chunks cache
     std::vector<std::shared_ptr<Chunk>> chunks_cache_;
diff --git 
a/src/paimon/common/file_index/rangebitmap/dictionary/chunked_dictionary_test.cpp
 
b/src/paimon/common/file_index/rangebitmap/dictionary/chunked_dictionary_test.cpp
index 93c19854..671d891c 100644
--- 
a/src/paimon/common/file_index/rangebitmap/dictionary/chunked_dictionary_test.cpp
+++ 
b/src/paimon/common/file_index/rangebitmap/dictionary/chunked_dictionary_test.cpp
@@ -456,9 +456,25 @@ TEST_F(ChunkedDictionaryTest, 
TestKeyFactoryUnsupportedType) {
                         "Unsupported field type for KeyFactory: BINARY");
 }
 
-TEST_F(ChunkedDictionaryTest, TestStringKeyFactoryNotImplemented) {
-    ASSERT_NOK_WITH_MSG(KeyFactory::Create(FieldType::STRING),
-                        "Unsupported field type for KeyFactory: STRING");
+TEST_F(ChunkedDictionaryTest, TestStringKeyFactory) {
+    ASSERT_OK_AND_ASSIGN(auto key_factory, 
KeyFactory::Create(FieldType::STRING));
+    ASSERT_OK_AND_ASSIGN(auto appender,
+                         ChunkedDictionary::Appender::Create(key_factory, 12, 
pool_));
+    ASSERT_OK(appender->AppendSorted(Literal(FieldType::STRING, "apple", 5), 
0));
+    ASSERT_OK(appender->AppendSorted(Literal(FieldType::STRING, "banana", 6), 
1));
+    ASSERT_OK(appender->AppendSorted(Literal(FieldType::STRING, "pear", 4), 
2));
+    ASSERT_OK_AND_ASSIGN(auto bytes, appender->Serialize());
+    auto input_stream = std::make_shared<ByteArrayInputStream>(bytes->data(), 
bytes->size());
+    ASSERT_OK_AND_ASSIGN(auto dict,
+                         ChunkedDictionary::Create(FieldType::STRING, 
input_stream, 0, pool_));
+
+    ASSERT_OK_AND_ASSIGN(int32_t banana_code, 
dict->Find(Literal(FieldType::STRING, "banana", 6)));
+    ASSERT_EQ(banana_code, 1);
+    ASSERT_OK_AND_ASSIGN(Literal pear, dict->Find(2));
+    ASSERT_EQ(pear.GetValue<std::string>(), "pear");
+    ASSERT_OK_AND_ASSIGN(int32_t between_code,
+                         dict->Find(Literal(FieldType::STRING, "blueberry", 
9)));
+    ASSERT_EQ(between_code, -3);
 }
 
 TEST_F(ChunkedDictionaryTest, TestFindByCodeInvalidNegative) {
diff --git 
a/src/paimon/common/file_index/rangebitmap/dictionary/key_factory.cpp 
b/src/paimon/common/file_index/rangebitmap/dictionary/key_factory.cpp
index 37f80c31..a3fafd59 100644
--- a/src/paimon/common/file_index/rangebitmap/dictionary/key_factory.cpp
+++ b/src/paimon/common/file_index/rangebitmap/dictionary/key_factory.cpp
@@ -23,6 +23,7 @@
 #include "fmt/format.h"
 #include "paimon/common/file_index/rangebitmap/dictionary/chunked_dictionary.h"
 #include "paimon/common/file_index/rangebitmap/dictionary/fixed_length_chunk.h"
+#include 
"paimon/common/file_index/rangebitmap/dictionary/variable_length_chunk.h"
 #include 
"paimon/common/file_index/rangebitmap/utils/literal_serialization_utils.h"
 #include "paimon/common/utils/field_type_utils.h"
 #include "paimon/common/utils/fields_comparator.h"
@@ -48,6 +49,8 @@ Result<std::shared_ptr<KeyFactory>> 
KeyFactory::Create(FieldType field_type) {
             return std::make_shared<FloatKeyFactory>();
         case FieldType::DOUBLE:
             return std::make_shared<DoubleKeyFactory>();
+        case FieldType::STRING:
+            return std::make_shared<StringKeyFactory>();
         default:
             return Status::Invalid(fmt::format("Unsupported field type for 
KeyFactory: {}",
                                                
FieldTypeUtils::FieldTypeToString(field_type)));
@@ -91,12 +94,31 @@ Result<std::unique_ptr<Chunk>> 
FixedLengthKeyFactory::MmapChunk(
 Result<std::unique_ptr<Chunk>> VariableLengthKeyFactory::CreateChunk(
     const Literal& key, int32_t code, int32_t keys_length_limit,
     const std::shared_ptr<MemoryPool>& pool) {
-    return Status::NotImplemented("VariableLengthKeyFactory::CreateChunk not 
implemented");
+    PAIMON_ASSIGN_OR_RAISE(LiteralSerDeUtils::Serializer serializer,
+                           
LiteralSerDeUtils::CreateValueWriter(GetFieldType()));
+    return std::make_unique<VariableLengthChunk>(key, code, keys_length_limit, 
shared_from_this(),
+                                                 serializer, pool);
 }
 Result<std::unique_ptr<Chunk>> VariableLengthKeyFactory::MmapChunk(
     const std::shared_ptr<InputStream>& input_stream, int32_t chunk_offset,
     int32_t keys_base_offset, const std::shared_ptr<MemoryPool>& pool) {
-    return Status::NotImplemented("VariableLengthKeyFactory::MmapChunk not 
implemented");
+    PAIMON_RETURN_NOT_OK(input_stream->Seek(chunk_offset, FS_SEEK_SET));
+    const auto data_in = std::make_shared<DataInputStream>(input_stream);
+    PAIMON_ASSIGN_OR_RAISE(int8_t version, data_in->ReadValue<int8_t>());
+    if (version != VariableLengthChunk::kCurrentVersion) {
+        return Status::Invalid(fmt::format("Unsupported version for 
KeyFactory: {}", version));
+    }
+    PAIMON_ASSIGN_OR_RAISE(LiteralSerDeUtils::Deserializer deserializer,
+                           
LiteralSerDeUtils::CreateValueReader(GetFieldType()));
+    PAIMON_ASSIGN_OR_RAISE(Literal key_literal, deserializer(data_in, 
pool.get()));
+    PAIMON_ASSIGN_OR_RAISE(int32_t code, data_in->ReadValue<int32_t>());
+    PAIMON_ASSIGN_OR_RAISE(int32_t offset, data_in->ReadValue<int32_t>());
+    PAIMON_ASSIGN_OR_RAISE(int32_t size, data_in->ReadValue<int32_t>());
+    PAIMON_ASSIGN_OR_RAISE(int32_t offsets_length, 
data_in->ReadValue<int32_t>());
+    PAIMON_ASSIGN_OR_RAISE(int32_t keys_length, data_in->ReadValue<int32_t>());
+    return std::make_unique<VariableLengthChunk>(key_literal, code, offset, 
size,
+                                                 shared_from_this(), 
input_stream, keys_base_offset,
+                                                 offsets_length, keys_length, 
pool);
 }
 
 /// Java-compatible ordering for floats
diff --git 
a/src/paimon/common/file_index/rangebitmap/dictionary/variable_length_chunk.cpp 
b/src/paimon/common/file_index/rangebitmap/dictionary/variable_length_chunk.cpp
new file mode 100644
index 00000000..5bb6dda4
--- /dev/null
+++ 
b/src/paimon/common/file_index/rangebitmap/dictionary/variable_length_chunk.cpp
@@ -0,0 +1,171 @@
+/*
+ * 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.
+ */
+
+#include 
"paimon/common/file_index/rangebitmap/dictionary/variable_length_chunk.h"
+
+#include <limits>
+#include <utility>
+
+#include "fmt/format.h"
+#include "paimon/common/file_index/rangebitmap/dictionary/key_factory.h"
+#include "paimon/common/io/memory_segment_output_stream.h"
+#include "paimon/common/memory/memory_segment_utils.h"
+#include "paimon/io/byte_array_input_stream.h"
+#include "paimon/io/data_input_stream.h"
+#include "paimon/memory/bytes.h"
+
+namespace paimon {
+
+Result<bool> VariableLengthChunk::TryAdd(const Literal& key) {
+    PAIMON_ASSIGN_OR_RAISE(int32_t key_length, 
LiteralSerDeUtils::GetSerializedSizeInBytes(key));
+    if (key_length > remaining_keys_size_ ||
+        static_cast<int32_t>(sizeof(int32_t)) > remaining_offsets_size_) {
+        return false;
+    }
+    
offsets_stream_out_->WriteValue<int32_t>(static_cast<int32_t>(keys_stream_out_->CurrentSize()));
+    PAIMON_RETURN_NOT_OK(serializer_(keys_stream_out_, key));
+    remaining_offsets_size_ -= sizeof(int32_t);
+    remaining_keys_size_ -= key_length;
+    ++size_;
+    return true;
+}
+
+Result<int32_t> VariableLengthChunk::CompareKey(const Literal& lhs, const 
Literal& rhs) {
+    return factory_->CompareLiteral(lhs, rhs);
+}
+
+Status VariableLengthChunk::LoadKeys() {
+    if (offsets_ != nullptr && keys_ != nullptr) {
+        return Status::OK();
+    }
+    if (offsets_length_ < 0 || keys_length_ < 0 ||
+        offsets_length_ > std::numeric_limits<int32_t>::max() - keys_length_) {
+        return Status::Invalid("Invalid variable length chunk payload length");
+    }
+    PAIMON_RETURN_NOT_OK(input_stream_->Seek(keys_base_offset_ + offset_, 
FS_SEEK_SET));
+    offsets_ = Bytes::AllocateBytes(offsets_length_, pool_.get());
+    PAIMON_ASSIGN_OR_RAISE(int64_t offsets_read,
+                           input_stream_->Read(offsets_->data(), 
offsets_length_));
+    if (offsets_read != offsets_length_) {
+        return Status::Invalid(fmt::format(
+            "Failed to read variable length chunk offsets, expected {} bytes 
but got {}",
+            offsets_length_, offsets_read));
+    }
+    keys_ = Bytes::AllocateBytes(keys_length_, pool_.get());
+    PAIMON_ASSIGN_OR_RAISE(int64_t keys_read, 
input_stream_->Read(keys_->data(), keys_length_));
+    if (keys_read != keys_length_) {
+        return Status::Invalid(
+            fmt::format("Failed to read variable length chunk keys, expected 
{} bytes but got {}",
+                        keys_length_, keys_read));
+    }
+    PAIMON_ASSIGN_OR_RAISE(deserializer_,
+                           
LiteralSerDeUtils::CreateValueReader(factory_->GetFieldType()));
+    return Status::OK();
+}
+
+Result<Literal> VariableLengthChunk::GetKey(int32_t index) {
+    if (index < 0 || index >= size_) {
+        return Status::Invalid("Index out of bounds");
+    }
+    PAIMON_RETURN_NOT_OK(LoadKeys());
+    auto offsets_in = std::make_shared<DataInputStream>(
+        std::make_shared<ByteArrayInputStream>(offsets_->data(), 
offsets_->size()));
+    PAIMON_RETURN_NOT_OK(offsets_in->Seek(static_cast<int64_t>(index) * 
sizeof(int32_t)));
+    PAIMON_ASSIGN_OR_RAISE(int32_t key_offset, 
offsets_in->ReadValue<int32_t>());
+    if (key_offset < 0 || key_offset >= keys_length_) {
+        return Status::Invalid("Invalid key offset in variable length chunk");
+    }
+    auto keys_in = std::make_shared<DataInputStream>(
+        std::make_shared<ByteArrayInputStream>(keys_->data(), keys_->size()));
+    PAIMON_RETURN_NOT_OK(keys_in->Seek(key_offset));
+    return deserializer_(keys_in, pool_.get());
+}
+
+Result<PAIMON_UNIQUE_PTR<Bytes>> VariableLengthChunk::SerializeChunk() const {
+    const auto data_out = std::make_shared<MemorySegmentOutputStream>(
+        MemorySegmentOutputStream::DEFAULT_SEGMENT_SIZE, pool_);
+    data_out->WriteValue<int8_t>(kCurrentVersion);
+    PAIMON_RETURN_NOT_OK(serializer_(data_out, key_));
+    data_out->WriteValue<int32_t>(code_);
+    data_out->WriteValue<int32_t>(offset_);
+    data_out->WriteValue<int32_t>(size_);
+    
data_out->WriteValue<int32_t>(static_cast<int32_t>(offsets_stream_out_->CurrentSize()));
+    
data_out->WriteValue<int32_t>(static_cast<int32_t>(keys_stream_out_->CurrentSize()));
+    return MemorySegmentUtils::CopyToBytes(
+        data_out->Segments(), 0, 
static_cast<int32_t>(data_out->CurrentSize()), pool_.get());
+}
+
+Result<PAIMON_UNIQUE_PTR<Bytes>> VariableLengthChunk::SerializeKeys() const {
+    const auto data_out = std::make_shared<MemorySegmentOutputStream>(
+        MemorySegmentOutputStream::DEFAULT_SEGMENT_SIZE, pool_);
+    PAIMON_RETURN_NOT_OK(MemorySegmentUtils::CopyToStream(
+        offsets_stream_out_->Segments(), 0,
+        static_cast<int32_t>(offsets_stream_out_->CurrentSize()), 
data_out.get()));
+    PAIMON_RETURN_NOT_OK(MemorySegmentUtils::CopyToStream(
+        keys_stream_out_->Segments(), 0, 
static_cast<int32_t>(keys_stream_out_->CurrentSize()),
+        data_out.get()));
+    return MemorySegmentUtils::CopyToBytes(
+        data_out->Segments(), 0, 
static_cast<int32_t>(data_out->CurrentSize()), pool_.get());
+}
+
+/// Read path
+VariableLengthChunk::VariableLengthChunk(Literal key, int32_t code, int32_t 
offset, int32_t size,
+                                         const std::shared_ptr<KeyFactory>& 
factory,
+                                         const std::shared_ptr<InputStream>& 
input_stream,
+                                         int32_t keys_base_offset, int32_t 
offsets_length,
+                                         int32_t keys_length,
+                                         const std::shared_ptr<MemoryPool>& 
pool)
+    : pool_(pool),
+      key_(std::move(key)),
+      code_(code),
+      offset_(offset),
+      size_(size),
+      factory_(factory),
+      input_stream_(input_stream),
+      keys_base_offset_(keys_base_offset),
+      offsets_length_(offsets_length),
+      keys_length_(keys_length),
+      deserializer_({}),
+      serializer_({}),
+      remaining_offsets_size_(0),
+      remaining_keys_size_(0) {}
+
+/// Write path
+VariableLengthChunk::VariableLengthChunk(Literal key, int32_t code, int32_t 
keys_length_limit,
+                                         const std::shared_ptr<KeyFactory>& 
factory,
+                                         const LiteralSerDeUtils::Serializer& 
serializer,
+                                         const std::shared_ptr<MemoryPool>& 
pool)
+    : pool_(pool),
+      key_(std::move(key)),
+      code_(code),
+      offset_(0),
+      size_(0),
+      factory_(factory),
+      keys_base_offset_(0),
+      offsets_length_(0),
+      keys_length_(0),
+      deserializer_({}),
+      serializer_(serializer),
+      offsets_stream_out_(std::make_shared<MemorySegmentOutputStream>(
+          MemorySegmentOutputStream::DEFAULT_SEGMENT_SIZE, pool)),
+      keys_stream_out_(std::make_shared<MemorySegmentOutputStream>(
+          MemorySegmentOutputStream::DEFAULT_SEGMENT_SIZE, pool)),
+      remaining_offsets_size_(keys_length_limit),
+      remaining_keys_size_(keys_length_limit) {}
+
+}  // namespace paimon
diff --git 
a/src/paimon/common/file_index/rangebitmap/dictionary/variable_length_chunk.h 
b/src/paimon/common/file_index/rangebitmap/dictionary/variable_length_chunk.h
new file mode 100644
index 00000000..0c173327
--- /dev/null
+++ 
b/src/paimon/common/file_index/rangebitmap/dictionary/variable_length_chunk.h
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include "paimon/common/file_index/rangebitmap/dictionary/chunk.h"
+#include 
"paimon/common/file_index/rangebitmap/utils/literal_serialization_utils.h"
+#include "paimon/fs/file_system.h"
+
+namespace paimon {
+
+class DataInputStream;
+class InputStream;
+class KeyFactory;
+class MemoryPool;
+class MemorySegmentOutputStream;
+
+class VariableLengthChunk final : public Chunk {
+ public:
+    Result<bool> TryAdd(const Literal& key) override;
+    Result<int32_t> CompareKey(const Literal& lhs, const Literal& rhs) 
override;
+    Result<Literal> GetKey(int32_t index) override;
+
+    const Literal& Key() const override {
+        return key_;
+    }
+    int32_t Code() const override {
+        return code_;
+    }
+    int32_t Offset() const override {
+        return offset_;
+    }
+    void SetOffset(int32_t offset) override {
+        offset_ = offset;
+    }
+    int32_t Size() const override {
+        return size_;
+    }
+
+    Result<PAIMON_UNIQUE_PTR<Bytes>> SerializeChunk() const override;
+    Result<PAIMON_UNIQUE_PTR<Bytes>> SerializeKeys() const override;
+
+    // For Read Path
+    VariableLengthChunk(Literal key, int32_t code, int32_t offset, int32_t 
size,
+                        const std::shared_ptr<KeyFactory>& factory,
+                        const std::shared_ptr<InputStream>& input_stream, 
int32_t keys_base_offset,
+                        int32_t offsets_length, int32_t keys_length,
+                        const std::shared_ptr<MemoryPool>& pool);
+
+    // For Write Path
+    VariableLengthChunk(Literal key, int32_t code, int32_t keys_length_limit,
+                        const std::shared_ptr<KeyFactory>& factory,
+                        const LiteralSerDeUtils::Serializer& serializer,
+                        const std::shared_ptr<MemoryPool>& pool);
+
+ public:
+    static constexpr int8_t kCurrentVersion = 1;
+
+ private:
+    Status LoadKeys();
+
+    std::shared_ptr<MemoryPool> pool_;
+    Literal key_;     // representative key for binary search
+    int32_t code_;    // first code in this chunk
+    int32_t offset_;  // offset of this chunk
+    int32_t size_;    // number of keys in this chunk
+    std::shared_ptr<KeyFactory> factory_;
+
+    // For read path lazy keys loading
+    std::shared_ptr<InputStream> input_stream_;
+    int32_t keys_base_offset_;
+    int32_t offsets_length_;
+    int32_t keys_length_;
+    PAIMON_UNIQUE_PTR<Bytes> offsets_;
+    PAIMON_UNIQUE_PTR<Bytes> keys_;
+    LiteralSerDeUtils::Deserializer deserializer_;
+
+    // For write path
+    LiteralSerDeUtils::Serializer serializer_;
+    std::shared_ptr<MemorySegmentOutputStream> offsets_stream_out_;
+    std::shared_ptr<MemorySegmentOutputStream> keys_stream_out_;
+    int32_t remaining_offsets_size_;
+    int32_t remaining_keys_size_;
+};
+
+}  // namespace paimon
diff --git 
a/src/paimon/common/file_index/rangebitmap/range_bitmap_file_index.cpp 
b/src/paimon/common/file_index/rangebitmap/range_bitmap_file_index.cpp
index 88e081bd..8384e7df 100644
--- a/src/paimon/common/file_index/rangebitmap/range_bitmap_file_index.cpp
+++ b/src/paimon/common/file_index/rangebitmap/range_bitmap_file_index.cpp
@@ -22,12 +22,12 @@
 #include <arrow/type.h>
 
 #include "paimon/common/file_index/rangebitmap/range_bitmap.h"
+#include "paimon/common/file_index/rangebitmap/range_bitmap_type_adapter.h"
 #include "paimon/common/io/offset_input_stream.h"
 #include "paimon/common/options/memory_size.h"
 #include "paimon/common/predicate/literal_converter.h"
 #include "paimon/common/utils/arrow/status_utils.h"
 #include "paimon/common/utils/checked_cast.h"
-#include "paimon/common/utils/field_type_utils.h"
 #include "paimon/file_index/bitmap_index_result.h"
 #include "paimon/predicate/literal.h"
 #include "paimon/result.h"
@@ -66,10 +66,10 @@ Result<std::shared_ptr<FileIndexWriter>> 
RangeBitmapFileIndex::CreateWriter(
 Result<std::shared_ptr<RangeBitmapFileIndexWriter>> 
RangeBitmapFileIndexWriter::Create(
     const std::shared_ptr<arrow::Field>& field, const std::map<std::string, 
std::string>& options,
     const std::shared_ptr<MemoryPool>& pool) {
-    PAIMON_ASSIGN_OR_RAISE(FieldType field_type,
-                           
FieldTypeUtils::ConvertToFieldType(field->type()->id()));
+    PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<RangeBitmapTypeAdapter> 
type_adapter,
+                           RangeBitmapTypeAdapter::Create(field->type()));
     PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<KeyFactory> shared_key_factory,
-                           KeyFactory::Create(field_type));
+                           KeyFactory::Create(type_adapter->GetStorageType()));
     PAIMON_ASSIGN_OR_RAISE(int64_t parsed_chunk_size,
                            
MemorySize::ParseBytes(KeyFactory::kDefaultChunkSize));
     if (const auto chunk_size_it = 
options.find(RangeBitmapFileIndex::kChunkSize);
@@ -80,10 +80,12 @@ Result<std::shared_ptr<RangeBitmapFileIndexWriter>> 
RangeBitmapFileIndexWriter::
     PAIMON_ASSIGN_OR_RAISE(
         std::unique_ptr<RangeBitmap::Appender> appender_ptr,
         RangeBitmap::Appender::Create(shared_key_factory, parsed_chunk_size, 
pool));
-    return std::make_shared<RangeBitmapFileIndexWriter>(struct_type, pool, 
shared_key_factory,
-                                                        
std::move(appender_ptr));
+    return std::shared_ptr<RangeBitmapFileIndexWriter>(new 
RangeBitmapFileIndexWriter(
+        struct_type, std::move(type_adapter), std::move(appender_ptr)));
 }
 
+RangeBitmapFileIndexWriter::~RangeBitmapFileIndexWriter() = default;
+
 Status RangeBitmapFileIndexWriter::AddBatch(::ArrowArray* batch) {
     PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Array> array,
                                       arrow::ImportArray(batch, struct_type_));
@@ -91,8 +93,9 @@ Status RangeBitmapFileIndexWriter::AddBatch(::ArrowArray* 
batch) {
     PAIMON_ASSIGN_OR_RAISE(std::vector<Literal> array_values,
                            
LiteralConverter::ConvertLiteralsFromArray(*(struct_array->field(0)),
                                                                       
/*own_data=*/true));
-    for (const auto& literal : array_values) {
-        appender_->Append(literal);
+    for (const Literal& literal : array_values) {
+        PAIMON_ASSIGN_OR_RAISE(Literal converted_literal, 
type_adapter_->ToStorageLiteral(literal));
+        appender_->Append(converted_literal);
     }
     return Status::OK();
 }
@@ -102,58 +105,70 @@ Result<PAIMON_UNIQUE_PTR<Bytes>> 
RangeBitmapFileIndexWriter::SerializedBytes() c
 }
 
 RangeBitmapFileIndexWriter::RangeBitmapFileIndexWriter(
-    const std::shared_ptr<arrow::DataType>& struct_type, const 
std::shared_ptr<MemoryPool>& pool,
-    const std::shared_ptr<KeyFactory>& key_factory, 
std::unique_ptr<RangeBitmap::Appender> appender)
+    const std::shared_ptr<arrow::DataType>& struct_type,
+    std::unique_ptr<RangeBitmapTypeAdapter> type_adapter,
+    std::unique_ptr<RangeBitmap::Appender> appender)
     : struct_type_(struct_type),
-      pool_(pool),
-      key_factory_(key_factory),
+      type_adapter_(std::move(type_adapter)),
       appender_(std::move(appender)) {}
 
 Result<std::shared_ptr<RangeBitmapFileIndexReader>> 
RangeBitmapFileIndexReader::Create(
     const std::shared_ptr<arrow::DataType>& arrow_type, const int32_t start, 
const int32_t length,
     const std::shared_ptr<InputStream>& input_stream, const 
std::shared_ptr<MemoryPool>& pool) {
-    PAIMON_ASSIGN_OR_RAISE(FieldType field_type,
-                           
FieldTypeUtils::ConvertToFieldType(arrow_type->id()));
+    PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<RangeBitmapTypeAdapter> 
type_adapter,
+                           RangeBitmapTypeAdapter::Create(arrow_type));
     PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<OffsetInputStream> bounded_stream,
                            OffsetInputStream::Create(input_stream, length, 
start));
-    PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<RangeBitmap> range_bitmap,
-                           RangeBitmap::Create(bounded_stream, 0, field_type, 
pool));
+    PAIMON_ASSIGN_OR_RAISE(
+        std::unique_ptr<RangeBitmap> range_bitmap,
+        RangeBitmap::Create(bounded_stream, 0, type_adapter->GetStorageType(), 
pool));
     return std::shared_ptr<RangeBitmapFileIndexReader>(
-        new RangeBitmapFileIndexReader(std::move(range_bitmap)));
+        new RangeBitmapFileIndexReader(std::move(type_adapter), 
std::move(range_bitmap)));
 }
 
-RangeBitmapFileIndexReader::RangeBitmapFileIndexReader(std::unique_ptr<RangeBitmap>
 range_bitmap)
-    : range_bitmap_(std::move(range_bitmap)) {}
+RangeBitmapFileIndexReader::~RangeBitmapFileIndexReader() = default;
+
+RangeBitmapFileIndexReader::RangeBitmapFileIndexReader(
+    std::unique_ptr<RangeBitmapTypeAdapter> type_adapter, 
std::unique_ptr<RangeBitmap> range_bitmap)
+    : type_adapter_(std::move(type_adapter)), 
range_bitmap_(std::move(range_bitmap)) {}
 
 Result<std::shared_ptr<FileIndexResult>> 
RangeBitmapFileIndexReader::VisitEqual(
     const Literal& literal) {
+    PAIMON_ASSIGN_OR_RAISE(Literal converted_literal, 
type_adapter_->ToStorageLiteral(literal));
     return std::make_shared<BitmapIndexResult>(
-        [self = shared_from_this(), literal]() -> Result<RoaringBitmap32> {
-            return self->range_bitmap_->Eq(literal);
+        [self = shared_from_this(), converted_literal]() -> 
Result<RoaringBitmap32> {
+            return self->range_bitmap_->Eq(converted_literal);
         });
 }
 
 Result<std::shared_ptr<FileIndexResult>> 
RangeBitmapFileIndexReader::VisitNotEqual(
     const Literal& literal) {
+    PAIMON_ASSIGN_OR_RAISE(Literal converted_literal, 
type_adapter_->ToStorageLiteral(literal));
     return std::make_shared<BitmapIndexResult>(
-        [self = shared_from_this(), literal]() -> Result<RoaringBitmap32> {
-            return self->range_bitmap_->Neq(literal);
+        [self = shared_from_this(), converted_literal]() -> 
Result<RoaringBitmap32> {
+            return self->range_bitmap_->Neq(converted_literal);
         });
 }
 
 Result<std::shared_ptr<FileIndexResult>> RangeBitmapFileIndexReader::VisitIn(
     const std::vector<Literal>& literals) {
+    PAIMON_ASSIGN_OR_RAISE(std::vector<Literal> converted_literals,
+                           type_adapter_->ToStorageLiterals(literals));
     return std::make_shared<BitmapIndexResult>(
-        [self = shared_from_this(), literals]() -> Result<RoaringBitmap32> {
-            return self->range_bitmap_->In(literals);
+        [self = shared_from_this(),
+         converted_literals = std::move(converted_literals)]() -> 
Result<RoaringBitmap32> {
+            return self->range_bitmap_->In(converted_literals);
         });
 }
 
 Result<std::shared_ptr<FileIndexResult>> 
RangeBitmapFileIndexReader::VisitNotIn(
     const std::vector<Literal>& literals) {
+    PAIMON_ASSIGN_OR_RAISE(std::vector<Literal> converted_literals,
+                           type_adapter_->ToStorageLiterals(literals));
     return std::make_shared<BitmapIndexResult>(
-        [self = shared_from_this(), literals]() -> Result<RoaringBitmap32> {
-            return self->range_bitmap_->NotIn(literals);
+        [self = shared_from_this(),
+         converted_literals = std::move(converted_literals)]() -> 
Result<RoaringBitmap32> {
+            return self->range_bitmap_->NotIn(converted_literals);
         });
 }
 
@@ -173,33 +188,37 @@ Result<std::shared_ptr<FileIndexResult>> 
RangeBitmapFileIndexReader::VisitIsNotN
 
 Result<std::shared_ptr<FileIndexResult>> 
RangeBitmapFileIndexReader::VisitGreaterThan(
     const Literal& literal) {
+    PAIMON_ASSIGN_OR_RAISE(Literal converted_literal, 
type_adapter_->ToStorageLiteral(literal));
     return std::make_shared<BitmapIndexResult>(
-        [self = shared_from_this(), literal]() -> Result<RoaringBitmap32> {
-            return self->range_bitmap_->Gt(literal);
+        [self = shared_from_this(), converted_literal]() -> 
Result<RoaringBitmap32> {
+            return self->range_bitmap_->Gt(converted_literal);
         });
 }
 
 Result<std::shared_ptr<FileIndexResult>> 
RangeBitmapFileIndexReader::VisitLessThan(
     const Literal& literal) {
+    PAIMON_ASSIGN_OR_RAISE(Literal converted_literal, 
type_adapter_->ToStorageLiteral(literal));
     return std::make_shared<BitmapIndexResult>(
-        [self = shared_from_this(), literal]() -> Result<RoaringBitmap32> {
-            return self->range_bitmap_->Lt(literal);
+        [self = shared_from_this(), converted_literal]() -> 
Result<RoaringBitmap32> {
+            return self->range_bitmap_->Lt(converted_literal);
         });
 }
 
 Result<std::shared_ptr<FileIndexResult>> 
RangeBitmapFileIndexReader::VisitGreaterOrEqual(
     const Literal& literal) {
+    PAIMON_ASSIGN_OR_RAISE(Literal converted_literal, 
type_adapter_->ToStorageLiteral(literal));
     return std::make_shared<BitmapIndexResult>(
-        [self = shared_from_this(), literal]() -> Result<RoaringBitmap32> {
-            return self->range_bitmap_->Gte(literal);
+        [self = shared_from_this(), converted_literal]() -> 
Result<RoaringBitmap32> {
+            return self->range_bitmap_->Gte(converted_literal);
         });
 }
 
 Result<std::shared_ptr<FileIndexResult>> 
RangeBitmapFileIndexReader::VisitLessOrEqual(
     const Literal& literal) {
+    PAIMON_ASSIGN_OR_RAISE(Literal converted_literal, 
type_adapter_->ToStorageLiteral(literal));
     return std::make_shared<BitmapIndexResult>(
-        [self = shared_from_this(), literal]() -> Result<RoaringBitmap32> {
-            return self->range_bitmap_->Lte(literal);
+        [self = shared_from_this(), converted_literal]() -> 
Result<RoaringBitmap32> {
+            return self->range_bitmap_->Lte(converted_literal);
         });
 }
 
diff --git a/src/paimon/common/file_index/rangebitmap/range_bitmap_file_index.h 
b/src/paimon/common/file_index/rangebitmap/range_bitmap_file_index.h
index 64289a53..3e7d511e 100644
--- a/src/paimon/common/file_index/rangebitmap/range_bitmap_file_index.h
+++ b/src/paimon/common/file_index/rangebitmap/range_bitmap_file_index.h
@@ -36,6 +36,7 @@ namespace paimon {
 
 class RangeBitmapFileIndexWriter;
 class RangeBitmapFileIndexReader;
+class RangeBitmapTypeAdapter;
 
 class PAIMON_EXPORT RangeBitmapFileIndex final : public FileIndexer {
  public:
@@ -64,20 +65,19 @@ class RangeBitmapFileIndexWriter final : public 
FileIndexWriter {
         const std::shared_ptr<arrow::Field>& field,
         const std::map<std::string, std::string>& options, const 
std::shared_ptr<MemoryPool>& pool);
 
+    ~RangeBitmapFileIndexWriter() override;
+
     Status AddBatch(::ArrowArray* batch) override;
     Result<PAIMON_UNIQUE_PTR<Bytes>> SerializedBytes() const override;
 
+ private:
     RangeBitmapFileIndexWriter(const std::shared_ptr<arrow::DataType>& 
struct_type,
-                               const std::shared_ptr<MemoryPool>& pool,
-                               const std::shared_ptr<KeyFactory>& key_factory,
+                               std::unique_ptr<RangeBitmapTypeAdapter> 
type_adapter,
                                std::unique_ptr<RangeBitmap::Appender> 
appender);
 
- private:
-    /// @note struct_type_ contains only one field with arrow_type_, used for 
import from C
-    /// interface.
+    /// @note struct_type_ contains only the indexed field and is used to 
import Arrow C data.
     std::shared_ptr<arrow::DataType> struct_type_;
-    std::shared_ptr<MemoryPool> pool_;
-    std::shared_ptr<KeyFactory> key_factory_;
+    std::unique_ptr<RangeBitmapTypeAdapter> type_adapter_;
     std::unique_ptr<RangeBitmap::Appender> appender_;
 };
 
@@ -89,8 +89,11 @@ class RangeBitmapFileIndexReader final
         const std::shared_ptr<arrow::DataType>& arrow_type, int32_t start, 
int32_t length,
         const std::shared_ptr<InputStream>& input_stream, const 
std::shared_ptr<MemoryPool>& pool);
 
+    ~RangeBitmapFileIndexReader() override;
+
  private:
-    explicit RangeBitmapFileIndexReader(std::unique_ptr<RangeBitmap> 
range_bitmap);
+    RangeBitmapFileIndexReader(std::unique_ptr<RangeBitmapTypeAdapter> 
type_adapter,
+                               std::unique_ptr<RangeBitmap> range_bitmap);
 
     Result<std::shared_ptr<FileIndexResult>> VisitEqual(const Literal& 
literal) override;
     Result<std::shared_ptr<FileIndexResult>> VisitNotEqual(const Literal& 
literal) override;
@@ -104,6 +107,7 @@ class RangeBitmapFileIndexReader final
     Result<std::shared_ptr<FileIndexResult>> VisitGreaterOrEqual(const 
Literal& literal) override;
     Result<std::shared_ptr<FileIndexResult>> VisitLessOrEqual(const Literal& 
literal) override;
 
+    std::unique_ptr<RangeBitmapTypeAdapter> type_adapter_;
     std::unique_ptr<RangeBitmap> range_bitmap_;
 };
 
diff --git 
a/src/paimon/common/file_index/rangebitmap/range_bitmap_file_index_test.cpp 
b/src/paimon/common/file_index/rangebitmap/range_bitmap_file_index_test.cpp
index 6157ee02..794f25c5 100644
--- a/src/paimon/common/file_index/rangebitmap/range_bitmap_file_index_test.cpp
+++ b/src/paimon/common/file_index/rangebitmap/range_bitmap_file_index_test.cpp
@@ -20,13 +20,18 @@
 
 #include <gtest/gtest.h>
 
+#include <limits>
 #include <memory>
 #include <numeric>
 #include <set>
 
 #include "arrow/api.h"
 #include "arrow/c/bridge.h"
+#include "arrow/ipc/json_simple.h"
 #include "paimon/common/utils/arrow/status_utils.h"
+#include "paimon/common/utils/math.h"
+#include "paimon/data/decimal.h"
+#include "paimon/data/timestamp.h"
 #include "paimon/file_index/bitmap_index_result.h"
 #include "paimon/file_index/file_index_format.h"
 #include "paimon/file_index/file_indexer_factory.h"
@@ -98,6 +103,17 @@ class RangeBitmapFileIndexTest : public ::testing::Test {
         const std::set<int32_t>& null_indices, const std::map<std::string, 
std::string>& options,
         PAIMON_UNIQUE_PTR<Bytes>* serialized_bytes_out);
 
+    Result<std::shared_ptr<RangeBitmapFileIndexReader>> CreateReaderFromJson(
+        const std::shared_ptr<arrow::DataType>& arrow_type, const std::string& 
json,
+        const std::map<std::string, std::string>& options,
+        PAIMON_UNIQUE_PTR<Bytes>* serialized_bytes_out);
+
+    Result<std::shared_ptr<RangeBitmapFileIndexReader>> CreateReaderFromArray(
+        const std::shared_ptr<arrow::DataType>& arrow_type,
+        const std::shared_ptr<arrow::Array>& array,
+        const std::map<std::string, std::string>& options,
+        PAIMON_UNIQUE_PTR<Bytes>* serialized_bytes_out);
+
  protected:
     std::shared_ptr<MemoryPool> pool_;
 
@@ -122,11 +138,18 @@ Result<std::shared_ptr<RangeBitmapFileIndexReader>> 
RangeBitmapFileIndexTest::Cr
     }
     std::shared_ptr<arrow::Array> arrow_array;
     PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Finish(&arrow_array));
+    return CreateReaderFromArray(arrow_type, arrow_array, options, 
serialized_bytes_out);
+}
+
+Result<std::shared_ptr<RangeBitmapFileIndexReader>> 
RangeBitmapFileIndexTest::CreateReaderFromArray(
+    const std::shared_ptr<arrow::DataType>& arrow_type, const 
std::shared_ptr<arrow::Array>& array,
+    const std::map<std::string, std::string>& options,
+    PAIMON_UNIQUE_PTR<Bytes>* serialized_bytes_out) {
     // Wrap in StructArray (single field) as required by 
RangeBitmapFileIndexWriter
     auto field = arrow::field("test_field", arrow_type);
     arrow::FieldVector fields = {field};
     PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::StructArray> 
struct_array,
-                                      arrow::StructArray::Make({arrow_array}, 
fields));
+                                      arrow::StructArray::Make({array}, 
fields));
     auto c_array = std::make_unique<::ArrowArray>();
     PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportArray(*struct_array, 
c_array.get()));
     // Create writer
@@ -149,6 +172,18 @@ Result<std::shared_ptr<RangeBitmapFileIndexReader>> 
RangeBitmapFileIndexTest::Cr
     return reader;
 }
 
+Result<std::shared_ptr<RangeBitmapFileIndexReader>> 
RangeBitmapFileIndexTest::CreateReaderFromJson(
+    const std::shared_ptr<arrow::DataType>& arrow_type, const std::string& 
json,
+    const std::map<std::string, std::string>& options,
+    PAIMON_UNIQUE_PTR<Bytes>* serialized_bytes_out) {
+    const auto field = arrow::field("test_field", arrow_type);
+    PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
+        std::shared_ptr<arrow::Array> array,
+        arrow::ipc::internal::json::ArrayFromJSON(arrow::struct_({field}), 
json));
+    const auto struct_array = checked_pointer_cast<arrow::StructArray>(array);
+    return CreateReaderFromArray(arrow_type, struct_array->field(0), options, 
serialized_bytes_out);
+}
+
 // Test with all NULL values
 TEST_F(RangeBitmapFileIndexTest, TestAllNullValues) {
     constexpr int32_t num_rows = 10;
@@ -532,6 +567,106 @@ TEST_F(RangeBitmapFileIndexTest, 
TestWriteAndReadRangeBitmapIndexDouble) {
     CheckResult(is_not_null_result, all_positions);
 }
 
+TEST_F(RangeBitmapFileIndexTest, TestFloatingPointSpecialValues) {
+    const std::vector<int32_t> nan_positions = {0, 1, 2, 5};
+    const std::vector<int32_t> negative_zero_positions = {3, 6};
+    const std::vector<int32_t> positive_zero_positions = {4, 7};
+    const std::vector<int32_t> non_nan_positions = {3, 4, 6, 7, 8, 9, 10, 11};
+    const std::vector<int32_t> less_than_positive_zero_positions = {3, 6, 8, 
10};
+    const std::vector<int32_t> nan_and_negative_zero_positions = {0, 1, 2, 3, 
5, 6};
+    const std::vector<int32_t> non_nan_and_non_negative_zero_positions = {4, 
7, 8, 9, 10, 11};
+
+    const auto check_reader = [&](const 
std::shared_ptr<RangeBitmapFileIndexReader>& reader,
+                                  const std::vector<Literal>& nan_literals,
+                                  const Literal& negative_zero, const Literal& 
positive_zero,
+                                  const Literal& positive_infinity) {
+        for (const Literal& nan_literal : nan_literals) {
+            ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileIndexResult> result,
+                                 reader->VisitEqual(nan_literal));
+            CheckResult(result, nan_positions);
+        }
+
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileIndexResult> 
negative_zero_result,
+                             reader->VisitEqual(negative_zero));
+        CheckResult(negative_zero_result, negative_zero_positions);
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileIndexResult> 
positive_zero_result,
+                             reader->VisitEqual(positive_zero));
+        CheckResult(positive_zero_result, positive_zero_positions);
+
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileIndexResult> 
less_than_nan_result,
+                             reader->VisitLessThan(nan_literals.front()));
+        CheckResult(less_than_nan_result, non_nan_positions);
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileIndexResult> 
greater_than_infinity_result,
+                             reader->VisitGreaterThan(positive_infinity));
+        CheckResult(greater_than_infinity_result, nan_positions);
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileIndexResult> 
less_than_positive_zero_result,
+                             reader->VisitLessThan(positive_zero));
+        CheckResult(less_than_positive_zero_result, 
less_than_positive_zero_positions);
+
+        const std::vector<Literal> nan_and_negative_zero = {nan_literals[1], 
negative_zero};
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileIndexResult> in_result,
+                             reader->VisitIn(nan_and_negative_zero));
+        CheckResult(in_result, nan_and_negative_zero_positions);
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileIndexResult> not_in_result,
+                             reader->VisitNotIn(nan_and_negative_zero));
+        CheckResult(not_in_result, non_nan_and_non_negative_zero_positions);
+    };
+
+    const auto float_nan = 
FloatingPointFromBits<float>(kCanonicalFloatNaNBits);
+    const auto float_positive_payload_nan = 
FloatingPointFromBits<float>(uint32_t{0x7fc12345});
+    const auto float_negative_payload_nan = 
FloatingPointFromBits<float>(uint32_t{0xffc54321});
+    const std::vector<float> float_values = {
+        float_nan,
+        float_positive_payload_nan,
+        float_negative_payload_nan,
+        -0.0f,
+        +0.0f,
+        float_negative_payload_nan,
+        -0.0f,
+        +0.0f,
+        -std::numeric_limits<float>::infinity(),
+        std::numeric_limits<float>::infinity(),
+        -1.0f,
+        +1.0f,
+    };
+    PAIMON_UNIQUE_PTR<Bytes> float_serialized_bytes;
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<RangeBitmapFileIndexReader> 
float_reader,
+                         (CreateReaderForTest<arrow::FloatBuilder, float>(
+                             arrow::float32(), float_values, 
&float_serialized_bytes)));
+    check_reader(float_reader,
+                 {Literal(float_nan), Literal(float_positive_payload_nan),
+                  Literal(float_negative_payload_nan)},
+                 Literal(-0.0f), Literal(+0.0f), 
Literal(std::numeric_limits<float>::infinity()));
+
+    const auto double_nan = 
FloatingPointFromBits<double>(kCanonicalDoubleNaNBits);
+    const auto double_positive_payload_nan =
+        FloatingPointFromBits<double>(uint64_t{0x7ff8123456789abc});
+    const auto double_negative_payload_nan =
+        FloatingPointFromBits<double>(uint64_t{0xfff8abcdef012345});
+    const std::vector<double> double_values = {
+        double_nan,
+        double_positive_payload_nan,
+        double_negative_payload_nan,
+        -0.0,
+        +0.0,
+        double_negative_payload_nan,
+        -0.0,
+        +0.0,
+        -std::numeric_limits<double>::infinity(),
+        std::numeric_limits<double>::infinity(),
+        -1.0,
+        +1.0,
+    };
+    PAIMON_UNIQUE_PTR<Bytes> double_serialized_bytes;
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<RangeBitmapFileIndexReader> 
double_reader,
+                         (CreateReaderForTest<arrow::DoubleBuilder, double>(
+                             arrow::float64(), double_values, 
&double_serialized_bytes)));
+    check_reader(double_reader,
+                 {Literal(double_nan), Literal(double_positive_payload_nan),
+                  Literal(double_negative_payload_nan)},
+                 Literal(-0.0), Literal(+0.0), 
Literal(std::numeric_limits<double>::infinity()));
+}
+
 TEST_F(RangeBitmapFileIndexTest, TestWriteAndReadRangeBitmapIndexDate) {
     std::vector<int32_t> test_data = {42432, 24649, 42432, 38001, 24649, 
50000, 12000};
     const auto& arrow_type = arrow::date32();
@@ -580,6 +715,56 @@ TEST_F(RangeBitmapFileIndexTest, 
TestWriteAndReadRangeBitmapIndexDate) {
     CheckResult(is_not_null_result, all_positions);
 }
 
+TEST_F(RangeBitmapFileIndexTest, TestWriteAndReadStringDecimalAndTimestamp) {
+    {
+        const auto type = arrow::utf8();
+        PAIMON_UNIQUE_PTR<Bytes> serialized_bytes;
+        ASSERT_OK_AND_ASSIGN(
+            auto reader,
+            CreateReaderFromJson(type, R"([["pear"], ["apple"], [null], 
["banana"], ["apple"]])",
+                                 {{"chunk-size", "12b"}}, &serialized_bytes));
+        const Literal apple(FieldType::STRING, "apple", 5);
+        const Literal banana(FieldType::STRING, "banana", 6);
+        CheckResult(reader->VisitEqual(apple).value(), {1, 4});
+        CheckResult(reader->VisitGreaterOrEqual(banana).value(), {0, 3});
+        CheckResult(reader->VisitIsNull().value(), {2});
+    }
+    {
+        const auto type = arrow::decimal128(10, 2);
+        PAIMON_UNIQUE_PTR<Bytes> serialized_bytes;
+        ASSERT_OK_AND_ASSIGN(
+            auto reader,
+            CreateReaderFromJson(type, R"([["1.00"], ["2.50"], [null], 
["-1.25"], ["2.50"]])", {},
+                                 &serialized_bytes));
+        CheckResult(reader->VisitEqual(Literal(Decimal(10, 2, 250))).value(), 
{1, 4});
+        CheckResult(reader->VisitLessThan(Literal(Decimal(10, 2, 0))).value(), 
{3});
+        CheckResult(reader->VisitIsNull().value(), {2});
+
+        // Range Bitmap does not rescale Decimal literals. A mathematically 
equivalent literal
+        // with a different scale produces an incorrect empty result, so 
callers must use the
+        // field's scale.
+        CheckResult(reader->VisitEqual(Literal(Decimal(10, 3, 2500))).value(), 
{});
+    }
+    {
+        const auto type = arrow::timestamp(arrow::TimeUnit::MICRO);
+        PAIMON_UNIQUE_PTR<Bytes> serialized_bytes;
+        ASSERT_OK_AND_ASSIGN(
+            auto reader,
+            CreateReaderFromJson(type, R"([[1000001], [2000002], [null], 
[-1000001], [2000002]])",
+                                 {}, &serialized_bytes));
+        CheckResult(reader->VisitEqual(Literal(Timestamp(2000, 
2000))).value(), {1, 4});
+        CheckResult(reader->VisitLessThan(Literal(Timestamp(0, 0))).value(), 
{3});
+        CheckResult(reader->VisitIsNull().value(), {2});
+    }
+
+    ASSERT_NOK_WITH_MSG(
+        RangeBitmapFileIndexWriter::Create(arrow::field("f0", 
arrow::decimal128(19, 2)), {}, pool_),
+        "DECIMAL with precision in [1, 18]");
+    ASSERT_NOK_WITH_MSG(RangeBitmapFileIndexWriter::Create(
+                            arrow::field("f0", 
arrow::timestamp(arrow::TimeUnit::NANO)), {}, pool_),
+                        "TIMESTAMP with precision in [0, 6]");
+}
+
 TEST_F(RangeBitmapFileIndexTest, TestRangeBitmapEdgeCases) {
     // Scope 1: All values identical
     {
diff --git 
a/src/paimon/common/file_index/rangebitmap/range_bitmap_type_adapter.cpp 
b/src/paimon/common/file_index/rangebitmap/range_bitmap_type_adapter.cpp
new file mode 100644
index 00000000..e9157ec1
--- /dev/null
+++ b/src/paimon/common/file_index/rangebitmap/range_bitmap_type_adapter.cpp
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+#include "paimon/common/file_index/rangebitmap/range_bitmap_type_adapter.h"
+
+#include "arrow/type.h"
+#include "fmt/format.h"
+#include "paimon/common/utils/checked_cast.h"
+#include "paimon/common/utils/date_time_utils.h"
+#include "paimon/common/utils/field_type_utils.h"
+#include "paimon/data/decimal.h"
+#include "paimon/data/timestamp.h"
+#include "paimon/status.h"
+
+namespace paimon {
+
+Result<std::unique_ptr<RangeBitmapTypeAdapter>> RangeBitmapTypeAdapter::Create(
+    const std::shared_ptr<arrow::DataType>& arrow_type) {
+    PAIMON_ASSIGN_OR_RAISE(FieldType field_type,
+                           
FieldTypeUtils::ConvertToFieldType(arrow_type->id()));
+    if (field_type == FieldType::DECIMAL) {
+        const auto decimal_type = 
checked_pointer_cast<arrow::Decimal128Type>(arrow_type);
+        if (decimal_type->precision() > 18) {
+            return Status::Invalid(fmt::format(
+                "range-bitmap index only supports DECIMAL with precision in 
[1, 18], got {}",
+                decimal_type->precision()));
+        }
+        return std::unique_ptr<RangeBitmapTypeAdapter>(
+            new RangeBitmapTypeAdapter(field_type, FieldType::BIGINT, 
std::nullopt));
+    }
+    if (field_type == FieldType::TIMESTAMP) {
+        const auto timestamp_type = 
checked_pointer_cast<arrow::TimestampType>(arrow_type);
+        const int32_t precision = 
DateTimeUtils::GetPrecisionFromType(timestamp_type);
+        if (precision > 6) {
+            return Status::Invalid(fmt::format(
+                "range-bitmap index only supports TIMESTAMP with precision in 
[0, 6], got {}",
+                precision));
+        }
+        return std::unique_ptr<RangeBitmapTypeAdapter>(
+            new RangeBitmapTypeAdapter(field_type, FieldType::BIGINT, 
precision));
+    }
+    return std::unique_ptr<RangeBitmapTypeAdapter>(
+        new RangeBitmapTypeAdapter(field_type, field_type, std::nullopt));
+}
+
+FieldType RangeBitmapTypeAdapter::GetStorageType() const {
+    return storage_type_;
+}
+
+Result<Literal> RangeBitmapTypeAdapter::ToStorageLiteral(const Literal& 
literal) const {
+    if (literal.IsNull()) {
+        return Literal(storage_type_);
+    }
+    if (logical_type_ == FieldType::DECIMAL) {
+        if (literal.GetType() != FieldType::DECIMAL) {
+            return Status::Invalid("range-bitmap DECIMAL field requires a 
DECIMAL literal");
+        }
+        return Literal(literal.GetValue<Decimal>().ToUnscaledLong());
+    }
+    if (logical_type_ == FieldType::TIMESTAMP) {
+        if (literal.GetType() != FieldType::TIMESTAMP) {
+            return Status::Invalid("range-bitmap TIMESTAMP field requires a 
TIMESTAMP literal");
+        }
+        if (!timestamp_precision_.has_value()) {
+            return Status::Invalid("range-bitmap TIMESTAMP adapter is missing 
precision");
+        }
+        const auto value = literal.GetValue<Timestamp>();
+        return Literal(*timestamp_precision_ <= Timestamp::MILLIS_PRECISION
+                           ? value.GetMillisecond()
+                           : value.ToMicrosecond());
+    }
+    if (literal.GetType() != storage_type_) {
+        return Status::Invalid(
+            fmt::format("range-bitmap literal type {} does not match field 
type {}",
+                        FieldTypeUtils::FieldTypeToString(literal.GetType()),
+                        FieldTypeUtils::FieldTypeToString(storage_type_)));
+    }
+    return literal;
+}
+
+Result<std::vector<Literal>> RangeBitmapTypeAdapter::ToStorageLiterals(
+    const std::vector<Literal>& literals) const {
+    std::vector<Literal> converted_literals;
+    converted_literals.reserve(literals.size());
+    for (const Literal& literal : literals) {
+        PAIMON_ASSIGN_OR_RAISE(Literal converted_literal, 
ToStorageLiteral(literal));
+        converted_literals.emplace_back(std::move(converted_literal));
+    }
+    return converted_literals;
+}
+
+RangeBitmapTypeAdapter::RangeBitmapTypeAdapter(FieldType logical_type, 
FieldType storage_type,
+                                               std::optional<int32_t> 
timestamp_precision)
+    : logical_type_(logical_type),
+      storage_type_(storage_type),
+      timestamp_precision_(timestamp_precision) {}
+
+}  // namespace paimon
diff --git 
a/src/paimon/common/file_index/rangebitmap/range_bitmap_type_adapter.h 
b/src/paimon/common/file_index/rangebitmap/range_bitmap_type_adapter.h
new file mode 100644
index 00000000..bd9cd5bb
--- /dev/null
+++ b/src/paimon/common/file_index/rangebitmap/range_bitmap_type_adapter.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+#include "paimon/defs.h"
+#include "paimon/predicate/literal.h"
+#include "paimon/result.h"
+
+namespace arrow {
+class DataType;
+}  // namespace arrow
+
+namespace paimon {
+
+/// Adapts logical field values to the physical key type stored by 
range-bitmap indexes.
+class RangeBitmapTypeAdapter {
+ public:
+    static Result<std::unique_ptr<RangeBitmapTypeAdapter>> Create(
+        const std::shared_ptr<arrow::DataType>& arrow_type);
+
+    FieldType GetStorageType() const;
+
+    Result<Literal> ToStorageLiteral(const Literal& literal) const;
+
+    Result<std::vector<Literal>> ToStorageLiterals(const std::vector<Literal>& 
literals) const;
+
+ private:
+    RangeBitmapTypeAdapter(FieldType logical_type, FieldType storage_type,
+                           std::optional<int32_t> timestamp_precision);
+
+    FieldType logical_type_;
+    FieldType storage_type_;
+    std::optional<int32_t> timestamp_precision_;
+};
+
+}  // namespace paimon
diff --git 
a/src/paimon/common/file_index/rangebitmap/range_bitmap_type_adapter_test.cpp 
b/src/paimon/common/file_index/rangebitmap/range_bitmap_type_adapter_test.cpp
new file mode 100644
index 00000000..90f7dfbb
--- /dev/null
+++ 
b/src/paimon/common/file_index/rangebitmap/range_bitmap_type_adapter_test.cpp
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+#include "paimon/common/file_index/rangebitmap/range_bitmap_type_adapter.h"
+
+#include <gtest/gtest.h>
+
+#include "arrow/api.h"
+#include "paimon/data/decimal.h"
+#include "paimon/data/timestamp.h"
+#include "paimon/testing/utils/testharness.h"
+
+namespace paimon::test {
+
+TEST(RangeBitmapTypeAdapterTest, TestStorageType) {
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<RangeBitmapTypeAdapter> int_adapter,
+                         RangeBitmapTypeAdapter::Create(arrow::int32()));
+    ASSERT_EQ(FieldType::INT, int_adapter->GetStorageType());
+
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<RangeBitmapTypeAdapter> 
string_adapter,
+                         RangeBitmapTypeAdapter::Create(arrow::utf8()));
+    ASSERT_EQ(FieldType::STRING, string_adapter->GetStorageType());
+
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<RangeBitmapTypeAdapter> 
decimal_adapter,
+                         RangeBitmapTypeAdapter::Create(arrow::decimal128(18, 
2)));
+    ASSERT_EQ(FieldType::BIGINT, decimal_adapter->GetStorageType());
+
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<RangeBitmapTypeAdapter> 
timestamp_adapter,
+                         
RangeBitmapTypeAdapter::Create(arrow::timestamp(arrow::TimeUnit::MICRO)));
+    ASSERT_EQ(FieldType::BIGINT, timestamp_adapter->GetStorageType());
+
+    ASSERT_NOK_WITH_MSG(RangeBitmapTypeAdapter::Create(arrow::decimal128(19, 
2)),
+                        "DECIMAL with precision in [1, 18]");
+    
ASSERT_NOK_WITH_MSG(RangeBitmapTypeAdapter::Create(arrow::timestamp(arrow::TimeUnit::NANO)),
+                        "TIMESTAMP with precision in [0, 6]");
+}
+
+TEST(RangeBitmapTypeAdapterTest, TestDecimalLiteralConversion) {
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<RangeBitmapTypeAdapter> adapter,
+                         RangeBitmapTypeAdapter::Create(arrow::decimal128(10, 
2)));
+
+    ASSERT_OK_AND_ASSIGN(Literal converted,
+                         adapter->ToStorageLiteral(Literal(Decimal(10, 2, 
12345))));
+    ASSERT_EQ(FieldType::BIGINT, converted.GetType());
+    ASSERT_EQ(12345, converted.GetValue<int64_t>());
+
+    ASSERT_OK_AND_ASSIGN(Literal converted_null,
+                         
adapter->ToStorageLiteral(Literal(FieldType::DECIMAL)));
+    ASSERT_EQ(FieldType::BIGINT, converted_null.GetType());
+    ASSERT_TRUE(converted_null.IsNull());
+
+    ASSERT_NOK_WITH_MSG(adapter->ToStorageLiteral(Literal(int64_t{12345})),
+                        "DECIMAL field requires a DECIMAL literal");
+}
+
+TEST(RangeBitmapTypeAdapterTest, TestTimestampLiteralConversion) {
+    const Timestamp timestamp(1234, 567000);
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<RangeBitmapTypeAdapter> 
millis_adapter,
+                         
RangeBitmapTypeAdapter::Create(arrow::timestamp(arrow::TimeUnit::MILLI)));
+    ASSERT_OK_AND_ASSIGN(Literal millis, 
millis_adapter->ToStorageLiteral(Literal(timestamp)));
+    ASSERT_EQ(FieldType::BIGINT, millis.GetType());
+    ASSERT_EQ(1234, millis.GetValue<int64_t>());
+
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<RangeBitmapTypeAdapter> 
micros_adapter,
+                         
RangeBitmapTypeAdapter::Create(arrow::timestamp(arrow::TimeUnit::MICRO)));
+    ASSERT_OK_AND_ASSIGN(Literal micros, 
micros_adapter->ToStorageLiteral(Literal(timestamp)));
+    ASSERT_EQ(FieldType::BIGINT, micros.GetType());
+    ASSERT_EQ(1234567, micros.GetValue<int64_t>());
+
+    ASSERT_OK_AND_ASSIGN(Literal converted_null,
+                         
micros_adapter->ToStorageLiteral(Literal(FieldType::TIMESTAMP)));
+    ASSERT_EQ(FieldType::BIGINT, converted_null.GetType());
+    ASSERT_TRUE(converted_null.IsNull());
+
+    
ASSERT_NOK_WITH_MSG(micros_adapter->ToStorageLiteral(Literal(int64_t{1234567})),
+                        "TIMESTAMP field requires a TIMESTAMP literal");
+}
+
+TEST(RangeBitmapTypeAdapterTest, TestLiteralBatchConversion) {
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<RangeBitmapTypeAdapter> adapter,
+                         RangeBitmapTypeAdapter::Create(arrow::int32()));
+    const std::vector<Literal> literals = {Literal(int32_t{1}), 
Literal(FieldType::INT),
+                                           Literal(int32_t{3})};
+    ASSERT_OK_AND_ASSIGN(std::vector<Literal> converted, 
adapter->ToStorageLiterals(literals));
+    ASSERT_EQ(3, converted.size());
+    ASSERT_EQ(1, converted[0].GetValue<int32_t>());
+    ASSERT_TRUE(converted[1].IsNull());
+    ASSERT_EQ(3, converted[2].GetValue<int32_t>());
+
+    ASSERT_NOK_WITH_MSG(adapter->ToStorageLiterals({Literal(int32_t{1}), 
Literal(int64_t{2})}),
+                        "literal type BIGINT does not match field type INT");
+}
+
+}  // namespace paimon::test
diff --git 
a/src/paimon/common/global_index/btree/btree_global_index_integration_test.cpp 
b/src/paimon/common/global_index/btree/btree_global_index_integration_test.cpp
index 653b617d..ce25b9b4 100644
--- 
a/src/paimon/common/global_index/btree/btree_global_index_integration_test.cpp
+++ 
b/src/paimon/common/global_index/btree/btree_global_index_integration_test.cpp
@@ -1330,6 +1330,14 @@ TEST_P(BTreeGlobalIndexIntegrationTest, 
WriteAndReadDecimalCompactData) {
         ASSERT_OK_AND_ASSIGN(auto result, reader->VisitEqual(lit_250));
         CheckResult(result, {2, 3});
     }
+    {
+        // BTree does not rescale Decimal literals. A mathematically 
equivalent literal with a
+        // different scale produces an incorrect empty result, so callers must 
use the field's
+        // scale.
+        Literal lit_2500(Decimal::FromUnscaledLong(2500, 10, 3));
+        ASSERT_OK_AND_ASSIGN(auto result, reader->VisitEqual(lit_2500));
+        CheckResult(result, {});
+    }
     {
         Literal lit_250(Decimal::FromUnscaledLong(250, 10, 2));
         ASSERT_OK_AND_ASSIGN(auto result, reader->VisitNotEqual(lit_250));
diff --git a/src/paimon/common/predicate/predicate_validator.h 
b/src/paimon/common/predicate/predicate_validator.h
index 3242d4bc..7f199f50 100644
--- a/src/paimon/common/predicate/predicate_validator.h
+++ b/src/paimon/common/predicate/predicate_validator.h
@@ -24,8 +24,11 @@
 #include <vector>
 
 #include "arrow/type.h"
+#include "arrow/util/decimal.h"
 #include "fmt/format.h"
+#include "paimon/common/utils/checked_cast.h"
 #include "paimon/common/utils/field_type_utils.h"
+#include "paimon/data/decimal.h"
 #include "paimon/defs.h"
 #include "paimon/predicate/compound_predicate.h"
 #include "paimon/predicate/leaf_predicate.h"
@@ -39,6 +42,14 @@ class PredicateValidator {
     PredicateValidator() = delete;
     ~PredicateValidator() = delete;
 
+    static Status ValidatePredicateWithSchema(const arrow::Schema& schema,
+                                              const 
std::shared_ptr<Predicate>& predicate,
+                                              bool validate_field_idx) {
+        PAIMON_RETURN_NOT_OK(ValidatePredicateWithLiterals(predicate));
+        return ValidatePredicateWithSchemaImpl(schema, predicate, 
validate_field_idx);
+    }
+
+ private:
     static Status ValidatePredicateWithLiterals(const 
std::shared_ptr<Predicate>& predicate) {
         if (auto leaf_predicate = 
std::dynamic_pointer_cast<LeafPredicate>(predicate)) {
             const auto& field_name = leaf_predicate->FieldName();
@@ -68,9 +79,9 @@ class PredicateValidator {
         return Status::OK();
     }
 
-    static Status ValidatePredicateWithSchema(const arrow::Schema& schema,
-                                              const 
std::shared_ptr<Predicate>& predicate,
-                                              bool validate_field_idx) {
+    static Status ValidatePredicateWithSchemaImpl(const arrow::Schema& schema,
+                                                  const 
std::shared_ptr<Predicate>& predicate,
+                                                  bool validate_field_idx) {
         if (auto leaf_predicate = 
std::dynamic_pointer_cast<LeafPredicate>(predicate)) {
             const auto& field_name = leaf_predicate->FieldName();
             // check field index
@@ -86,20 +97,47 @@ class PredicateValidator {
                                 field_name, schema_field_idx, 
leaf_predicate->FieldIndex()));
             }
             // check field type (schema vs. predicate)
+            const std::shared_ptr<arrow::DataType>& schema_type =
+                schema.field(schema_field_idx)->type();
             PAIMON_RETURN_NOT_OK(ValidateDataTypeWithSchemaAndPredicate(
-                *schema.field(schema_field_idx)->type(), 
leaf_predicate->GetFieldType()));
+                *schema_type, leaf_predicate->GetFieldType()));
+            if (schema_type->id() == arrow::Type::DECIMAL128) {
+                PAIMON_RETURN_NOT_OK(ValidateDecimalLiterals(
+                    *checked_pointer_cast<arrow::Decimal128Type>(schema_type), 
*leaf_predicate));
+            }
         } else if (auto compound_predicate =
                        
std::dynamic_pointer_cast<CompoundPredicate>(predicate)) {
             const auto& children = compound_predicate->Children();
             for (const auto& child : children) {
                 PAIMON_RETURN_NOT_OK(
-                    ValidatePredicateWithSchema(schema, child, 
validate_field_idx));
+                    ValidatePredicateWithSchemaImpl(schema, child, 
validate_field_idx));
+            }
+        }
+        return Status::OK();
+    }
+
+    static Status ValidateDecimalLiterals(const arrow::Decimal128Type& 
field_type,
+                                          const LeafPredicate& predicate) {
+        const std::string& field_name = predicate.FieldName();
+        for (const Literal& literal : predicate.Literals()) {
+            const auto decimal = literal.GetValue<Decimal>();
+            if (decimal.Scale() != field_type.scale()) {
+                return Status::Invalid(fmt::format(
+                    "decimal literal for field {} has scale {}, expected {}; 
rescale the literal "
+                    "before building the predicate",
+                    field_name, decimal.Scale(), field_type.scale()));
+            }
+
+            arrow::Decimal128 unscaled_value(decimal.HighBits(), 
decimal.LowBits());
+            if (!unscaled_value.FitsInPrecision(field_type.precision())) {
+                return Status::Invalid(fmt::format(
+                    "decimal literal {} for field {} does not fit field type 
DECIMAL({}, {})",
+                    decimal.ToString(), field_name, field_type.precision(), 
field_type.scale()));
             }
         }
         return Status::OK();
     }
 
- private:
     static Status ValidateDataTypeWithSchemaAndPredicate(const 
arrow::DataType& schema_type,
                                                          const FieldType& 
field_type) {
         const auto kind = schema_type.id();
diff --git a/src/paimon/common/predicate/predicate_validator_test.cpp 
b/src/paimon/common/predicate/predicate_validator_test.cpp
index d8342075..75ae141c 100644
--- a/src/paimon/common/predicate/predicate_validator_test.cpp
+++ b/src/paimon/common/predicate/predicate_validator_test.cpp
@@ -33,6 +33,18 @@ class Schema;
 namespace paimon::test {
 TEST(PredicateValidatorTest, TestValidateLiterals) {
     std::string str("apple");
+    std::shared_ptr<arrow::Schema> schema = arrow::schema(arrow::FieldVector({
+        arrow::field("f0", arrow::int64()),
+        arrow::field("f1", arrow::float32()),
+        arrow::field("f2", arrow::utf8()),
+        arrow::field("f3", arrow::boolean()),
+        arrow::field("f4", arrow::float64()),
+        arrow::field("f5", arrow::int8()),
+        arrow::field("f6", arrow::date32()),
+        arrow::field("f7", arrow::timestamp(arrow::TimeUnit::NANO)),
+        arrow::field("f8", arrow::decimal128(23, 5)),
+        arrow::field("f9", arrow::binary()),
+    }));
     {
         ASSERT_OK_AND_ASSIGN(
             auto predicate,
@@ -59,7 +71,8 @@ TEST(PredicateValidatorTest, TestValidateLiterals) {
                 PredicateBuilder::Equal(/*field_index=*/9, 
/*field_name=*/"f9", FieldType::BINARY,
                                         Literal(FieldType::BINARY, str.data(), 
str.size())),
             }));
-        
ASSERT_OK(PredicateValidator::ValidatePredicateWithLiterals(predicate));
+        ASSERT_OK(PredicateValidator::ValidatePredicateWithSchema(*schema, 
predicate,
+                                                                  
/*validate_field_idx=*/true));
     }
     {
         // f1 field type is FLOAT, literal type is BIGINT
@@ -75,9 +88,10 @@ TEST(PredicateValidatorTest, TestValidateLiterals) {
                 PredicateBuilder::Equal(/*field_index=*/3, 
/*field_name=*/"f3", FieldType::BOOLEAN,
                                         Literal(true)),
             }));
-        
ASSERT_NOK_WITH_MSG(PredicateValidator::ValidatePredicateWithLiterals(predicate),
-                            "field f1 has field type BIGINT in literal, 
mismatch "
-                            "field type FLOAT in predicate");
+        ASSERT_NOK_WITH_MSG(
+            PredicateValidator::ValidatePredicateWithSchema(*schema, predicate,
+                                                            
/*validate_field_idx=*/true),
+            "field f1 has field type BIGINT in literal, mismatch field type 
FLOAT in predicate");
     }
     {
         // f2 field type is STRING, literal type is BINARY
@@ -93,9 +107,10 @@ TEST(PredicateValidatorTest, TestValidateLiterals) {
                 PredicateBuilder::Equal(/*field_index=*/3, 
/*field_name=*/"f3", FieldType::BOOLEAN,
                                         Literal(true)),
             }));
-        
ASSERT_NOK_WITH_MSG(PredicateValidator::ValidatePredicateWithLiterals(predicate),
-                            "field f2 has field type BINARY in literal, 
mismatch "
-                            "field type STRING in predicate");
+        ASSERT_NOK_WITH_MSG(
+            PredicateValidator::ValidatePredicateWithSchema(*schema, predicate,
+                                                            
/*validate_field_idx=*/true),
+            "field f2 has field type BINARY in literal, mismatch field type 
STRING in predicate");
     }
     {
         // f2 literal is null
@@ -111,8 +126,10 @@ TEST(PredicateValidatorTest, TestValidateLiterals) {
                 PredicateBuilder::Equal(/*field_index=*/3, 
/*field_name=*/"f3", FieldType::BOOLEAN,
                                         Literal(true)),
             }));
-        
ASSERT_NOK_WITH_MSG(PredicateValidator::ValidatePredicateWithLiterals(predicate),
-                            "literal cannot be null in predicate, field name 
f2");
+        ASSERT_NOK_WITH_MSG(
+            PredicateValidator::ValidatePredicateWithSchema(*schema, predicate,
+                                                            
/*validate_field_idx=*/true),
+            "literal cannot be null in predicate, field name f2");
     }
 }
 
@@ -158,8 +175,7 @@ TEST(PredicateValidatorTest, TestValidateSchema) {
                                                                   
/*validate_field_idx=*/true));
     }
     {
-        // f2 schema type is DECIMAL(23,5), predicate type can be different 
precision and scale,
-        // such as DECIMAL(22,4)
+        // f2 schema type is DECIMAL(23,5), but the literal scale is 4.
         std::shared_ptr<arrow::Schema> schema = 
arrow::schema(arrow::FieldVector({
             arrow::field("f0", arrow::int16()),
             arrow::field("f1", arrow::float32()),
@@ -179,9 +195,11 @@ TEST(PredicateValidatorTest, TestValidateSchema) {
                 PredicateBuilder::Equal(/*field_index=*/3, 
/*field_name=*/"f3", FieldType::BOOLEAN,
                                         Literal(true)),
             }));
-        
ASSERT_OK(PredicateValidator::ValidatePredicateWithLiterals(predicate));
-        ASSERT_OK(PredicateValidator::ValidatePredicateWithSchema(*schema, 
predicate,
-                                                                  
/*validate_field_idx=*/true));
+        ASSERT_NOK_WITH_MSG(
+            PredicateValidator::ValidatePredicateWithSchema(*schema, predicate,
+                                                            
/*validate_field_idx=*/true),
+            "decimal literal for field f2 has scale 4, expected 5; rescale the 
literal before "
+            "building the predicate");
     }
     {
         // predicate field idx mismatch
@@ -341,4 +359,62 @@ TEST(PredicateValidatorTest, TestValidateSchema) {
             "field f2 does not exist in schema");
     }
 }
+
+TEST(PredicateValidatorTest, TestValidateDecimalLiteral) {
+    std::shared_ptr<arrow::Schema> schema =
+        arrow::schema({arrow::field("amount", arrow::decimal128(10, 2))});
+
+    {
+        auto predicate =
+            PredicateBuilder::Equal(/*field_index=*/0, 
/*field_name=*/"amount", FieldType::DECIMAL,
+                                    Literal(Decimal(10, 2, 12345)));
+        ASSERT_OK(PredicateValidator::ValidatePredicateWithSchema(*schema, 
predicate,
+                                                                  
/*validate_field_idx=*/true));
+    }
+    {
+        // Literal precision metadata may be smaller than the field precision.
+        auto predicate = PredicateBuilder::Equal(/*field_index=*/0, 
/*field_name=*/"amount",
+                                                 FieldType::DECIMAL, 
Literal(Decimal(9, 2, 12345)));
+        ASSERT_OK(PredicateValidator::ValidatePredicateWithSchema(*schema, 
predicate,
+                                                                  
/*validate_field_idx=*/true));
+    }
+    {
+        // Literal precision metadata may be larger than the field precision 
if the value fits.
+        auto predicate =
+            PredicateBuilder::Equal(/*field_index=*/0, 
/*field_name=*/"amount", FieldType::DECIMAL,
+                                    Literal(Decimal(12, 2, 12345)));
+        ASSERT_OK(PredicateValidator::ValidatePredicateWithSchema(*schema, 
predicate,
+                                                                  
/*validate_field_idx=*/true));
+    }
+    {
+        auto predicate =
+            PredicateBuilder::Equal(/*field_index=*/0, 
/*field_name=*/"amount", FieldType::DECIMAL,
+                                    Literal(Decimal(10, 1, 12345)));
+        ASSERT_NOK_WITH_MSG(
+            PredicateValidator::ValidatePredicateWithSchema(*schema, predicate,
+                                                            
/*validate_field_idx=*/true),
+            "decimal literal for field amount has scale 1, expected 2; rescale 
the literal before "
+            "building the predicate");
+    }
+    {
+        auto predicate = PredicateBuilder::In(
+            /*field_index=*/0, /*field_name=*/"amount", FieldType::DECIMAL,
+            {Literal(Decimal(10, 2, 12345)), Literal(Decimal(10, 3, 123450))});
+        ASSERT_NOK_WITH_MSG(
+            PredicateValidator::ValidatePredicateWithSchema(*schema, predicate,
+                                                            
/*validate_field_idx=*/true),
+            "decimal literal for field amount has scale 3, expected 2; rescale 
the literal before "
+            "building the predicate");
+    }
+    {
+        auto predicate =
+            PredicateBuilder::Equal(/*field_index=*/0, 
/*field_name=*/"amount", FieldType::DECIMAL,
+                                    Literal(Decimal(10, 2, 10000000000LL)));
+        ASSERT_NOK_WITH_MSG(
+            PredicateValidator::ValidatePredicateWithSchema(*schema, predicate,
+                                                            
/*validate_field_idx=*/true),
+            "decimal literal 100000000.00 for field amount does not fit field 
type DECIMAL(10, "
+            "2)");
+    }
+}
 }  // namespace paimon::test
diff --git a/src/paimon/core/operation/internal_read_context.cpp 
b/src/paimon/core/operation/internal_read_context.cpp
index dd60ce40..c1a07eca 100644
--- a/src/paimon/core/operation/internal_read_context.cpp
+++ b/src/paimon/core/operation/internal_read_context.cpp
@@ -278,8 +278,6 @@ Result<std::unique_ptr<InternalReadContext>> 
InternalReadContext::Create(
     if (context->GetPredicate()) {
         PAIMON_RETURN_NOT_OK(PredicateValidator::ValidatePredicateWithSchema(
             *read_schema, context->GetPredicate(), 
/*validate_field_idx=*/true));
-        PAIMON_RETURN_NOT_OK(
-            
PredicateValidator::ValidatePredicateWithLiterals(context->GetPredicate()));
     }
 
     if (!context->GetMemoryPool()) {
diff --git a/src/paimon/core/table/format/format_table_read.cpp 
b/src/paimon/core/table/format/format_table_read.cpp
index 7bc14b59..86b1a880 100644
--- a/src/paimon/core/table/format/format_table_read.cpp
+++ b/src/paimon/core/table/format/format_table_read.cpp
@@ -425,7 +425,6 @@ Result<std::unique_ptr<FormatTableRead>> 
FormatTableRead::CreateInternal(
         // index is not among them: everything downstream resolves a field by 
name.
         PAIMON_RETURN_NOT_OK(PredicateValidator::ValidatePredicateWithSchema(
             *impl->read_schema, predicate, /*validate_field_idx=*/false));
-        
PAIMON_RETURN_NOT_OK(PredicateValidator::ValidatePredicateWithLiterals(predicate));
         if (enable_predicate_filter) {
             impl->filter_predicate = predicate;
         }
diff --git a/src/paimon/core/table/source/table_scan.cpp 
b/src/paimon/core/table/source/table_scan.cpp
index 20e13ce0..033a4e79 100644
--- a/src/paimon/core/table/source/table_scan.cpp
+++ b/src/paimon/core/table/source/table_scan.cpp
@@ -397,8 +397,6 @@ Result<std::unique_ptr<TableScan>> NewDataTableScan(
         PAIMON_RETURN_NOT_OK(PredicateValidator::ValidatePredicateWithSchema(
             *arrow_schema, context->GetScanFilters()->GetPredicate(),
             /*validate_field_idx=*/false));
-        PAIMON_RETURN_NOT_OK(PredicateValidator::ValidatePredicateWithLiterals(
-            context->GetScanFilters()->GetPredicate()));
     }
     PAIMON_ASSIGN_OR_RAISE(std::vector<std::string> external_paths,
                            core_options.CreateExternalPaths());
diff --git a/test/inte/blob_table_inte_test.cpp 
b/test/inte/blob_table_inte_test.cpp
index 4419b356..bfba944a 100644
--- a/test/inte/blob_table_inte_test.cpp
+++ b/test/inte/blob_table_inte_test.cpp
@@ -2225,9 +2225,8 @@ TEST_P(BlobTableInteTest, TestPartitionWithPredicate) {
     }
     {
         // set partition predicate and data field predicate, blob type not 
support predicate
-        auto equal =
-            PredicateBuilder::Equal(/*field_index=*/1, /*field_name=*/"f1", 
FieldType::STRING,
-                                    Literal(FieldType::BLOB, "2024", 4));
+        auto equal = PredicateBuilder::Equal(/*field_index=*/1, 
/*field_name=*/"f1",
+                                             FieldType::BLOB, 
Literal(FieldType::BLOB, "2024", 4));
         auto greater_than = PredicateBuilder::GreaterThan(/*field_index=*/0, 
/*field_name=*/"f0",
                                                           FieldType::INT, 
Literal(100));
         ASSERT_OK_AND_ASSIGN(auto predicate, PredicateBuilder::And({equal, 
greater_than}));
diff --git a/test/inte/global_index_test.cpp b/test/inte/global_index_test.cpp
index 517d55ac..355220c7 100644
--- a/test/inte/global_index_test.cpp
+++ b/test/inte/global_index_test.cpp
@@ -3394,6 +3394,14 @@ TEST_P(GlobalIndexTest, 
TestBTreeScanWithPartitionWithMultiMeta) {
             auto gt_mid,
             reader->VisitGreaterThan(Literal(Decimal::FromUnscaledLong(10 * 
123456L, 18, 6))));
         ASSERT_EQ(count_rows(gt_mid), 18);
+
+        // Global index readers do not rescale Decimal literals. A 
mathematically equivalent
+        // literal with a different scale produces an incorrect empty result, 
so callers must use
+        // the field's scale.
+        ASSERT_OK_AND_ASSIGN(auto eq_same_value_different_scale,
+                             
reader->VisitEqual(Literal(Decimal::FromUnscaledLong(
+                                 5 * 1234560L, /*precision=*/18, 
/*scale=*/7))));
+        ASSERT_EQ(count_rows(eq_same_value_different_scale), 0);
     }
 
     // ---- col_string (values are "str_00000" .. "str_00019") ----
diff --git a/test/inte/read_inte_test.cpp b/test/inte/read_inte_test.cpp
index de93a035..4b2f0f21 100644
--- a/test/inte/read_inte_test.cpp
+++ b/test/inte/read_inte_test.cpp
@@ -2078,7 +2078,7 @@ TEST_P(ReadInteTest, 
TestAppendReadWithComplexTypePredicate) {
         PredicateBuilder::And(
             {PredicateBuilder::Or(
                  {PredicateBuilder::GreaterThan(/*field_index=*/4, 
/*field_name=*/"f5",
-                                                FieldType::DECIMAL, 
Literal(Decimal(5, 2, 0))),
+                                                FieldType::DECIMAL, 
Literal(Decimal(23, 5, 0))),
                   PredicateBuilder::LessThan(/*field_index=*/2, 
/*field_name=*/"f4",
                                              FieldType::TIMESTAMP,
                                              
Literal(Timestamp(-2240521239999l, 1002))),
diff --git a/test/inte/scan_inte_test.cpp b/test/inte/scan_inte_test.cpp
index f626dba6..3f949484 100644
--- a/test/inte/scan_inte_test.cpp
+++ b/test/inte/scan_inte_test.cpp
@@ -1452,7 +1452,7 @@ TEST_P(ScanInteTest, 
TestScanAppendComplexDataWithSnapshot4WithPredicateFilter)
                                    Literal(paimon::Timestamp(1735344000, 0)));
     auto predicate2 = PredicateBuilder::GreaterThan(
         /*field_index=*/4, /*field_name=*/"f5", FieldType::DECIMAL,
-        Literal(paimon::Decimal(5, 2, 0)));
+        Literal(paimon::Decimal(23, 5, 0)));
     ASSERT_OK_AND_ASSIGN(auto predicate, PredicateBuilder::And({predicate1, 
predicate2}));
 
     ScanContextBuilder context_builder(table_path);

Reply via email to