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

SteNicholas 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 81608c7  fix: support nullable map key schemas and reject null key 
data (#176)
81608c7 is described below

commit 81608c7579e908a3b694beeab6dfe95234d39fa0
Author: lxy <[email protected]>
AuthorDate: Tue Aug 4 16:49:12 2026 +0800

    fix: support nullable map key schemas and reject null key data (#176)
    
    * fix: support nullable map key schemas and reject null key data
    
    * add validate for avro
---
 docs/source/user_guide/data_types.rst              |   7 ++--
 docs/source/user_guide/schema.rst                  |  32 ++-------------
 src/paimon/common/types/data_type_json_parser.cpp  |  14 +++----
 .../common/types/data_type_json_parser_test.cpp    |   4 +-
 src/paimon/core/schema/arrow_schema_validator.cpp  |   4 --
 src/paimon/core/schema/schema_validation_test.cpp  |  10 +++--
 src/paimon/core/schema/table_schema_test.cpp       |  17 +++++---
 src/paimon/format/avro/avro_file_batch_reader.cpp  |   1 +
 src/paimon/format/orc/orc_adapter.cpp              |   1 +
 .../format/parquet/parquet_file_batch_reader.cpp   |   1 +
 test/inte/scan_and_read_inte_test.cpp              |  44 +++++++++++++++++++++
 .../append_multiple/schema/schema-0                |   2 +-
 .../append_simple.db/append_simple/schema/schema-0 |   2 +-
 .../append_with_multiple_map/schema/schema-0       |  18 ++++-----
 .../pk_with_multiple_type/schema/schema-0          |   2 +-
 .../schema/schema-0                                |   2 +-
 .../append_table_with_nested_type/schema/schema-0  |   2 +-
 .../nullable_map_key.db/nullable_map_key/README    |  13 ++++++
 ...data-7e6899e9-f3cd-4d2e-86bc-7a5f383d5922-0.orc | Bin 0 -> 562 bytes
 ...manifest-a956b9da-04c5-41b6-b6f0-20efa3974bd4-0 | Bin 0 -> 2089 bytes
 ...est-list-67c61c9b-ab3c-4564-a5f2-612aa684a8f2-0 | Bin 0 -> 1006 bytes
 ...est-list-67c61c9b-ab3c-4564-a5f2-612aa684a8f2-1 | Bin 0 -> 1110 bytes
 .../nullable_map_key/schema/schema-0               |  25 ++++++++++++
 .../nullable_map_key/snapshot/EARLIEST             |   1 +
 .../nullable_map_key/snapshot/LATEST               |   1 +
 .../nullable_map_key/snapshot/snapshot-1           |  17 ++++++++
 .../pk_table_nested_type/schema/schema-0           |   2 +-
 .../schema/schema-0                                |   2 +-
 .../parquet_append_table/schema/schema-0           |   2 +-
 .../pk_table_nested_type/schema/schema-0           |   2 +-
 30 files changed, 154 insertions(+), 74 deletions(-)

diff --git a/docs/source/user_guide/data_types.rst 
b/docs/source/user_guide/data_types.rst
index f532642..3d52933 100644
--- a/docs/source/user_guide/data_types.rst
+++ b/docs/source/user_guide/data_types.rst
@@ -188,15 +188,14 @@ and `Arrow DataTypes 
<https://arrow.apache.org/docs/format/Columnar.html#data-ty
 
    * - ``MAP<kt, vt>``
      - Map
-     - Data type of an associative array that maps keys to values (including 
NULL). A map cannot contain duplicate keys; each key can map to at most one 
value.
+     - Data type of an associative array that maps keys (including NULL) to 
values (including NULL). A map cannot contain duplicate keys; each key can map 
to at most one value.
 
        There is no restriction of element types; it is the responsibility of 
the user to ensure uniqueness.
 
        The type can be declared using ``MAP<kt, vt>`` where kt is the data 
type of the key elements and vt is the data type of the value elements.
 
-       **Note:** In Paimon C++, map keys must be explicitly marked as ``NOT 
NULL``.
-       Apache Arrow does not support nullable map keys. If the key type is not
-       marked as ``NOT NULL`` in the schema, parsing will fail with an error.
+       **Note:** Paimon C++ accepts nullable map key declarations for schema 
compatibility.
+       A query fails only if the data actually contains a NULL map key.
 
    * - ``MULTISET<t>``
      - Not Supported
diff --git a/docs/source/user_guide/schema.rst 
b/docs/source/user_guide/schema.rst
index 0fe8e72..5db69ef 100644
--- a/docs/source/user_guide/schema.rst
+++ b/docs/source/user_guide/schema.rst
@@ -84,35 +84,11 @@ DataField represents a column of the table.
 3. ``type``: data type, very similar to SQL type string.
 4. ``description``: string.
 
-Limitations
------------
+Nullable MAP Keys
+-----------------
 
-MAP Key Must Be NOT NULL
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-Apache Arrow does not support nullable map keys. When defining a ``MAP`` type 
in the schema,
-the key must be explicitly marked as ``NOT NULL``. If the key is not marked as 
``NOT NULL``,
-schema parsing will fail with an error.
-
-For example, the following is **valid**:
-
-.. code-block:: json
-
-   {
-     "type": "MAP",
-     "key": "TINYINT NOT NULL",
-     "value": "SMALLINT"
-   }
-
-The following is **invalid** and will be rejected:
-
-.. code-block:: json
-
-   {
-     "type": "MAP",
-     "key": "TINYINT",
-     "value": "SMALLINT"
-   }
+Paimon C++ accepts nullable ``MAP`` key declarations for compatibility with 
existing schemas.
+A query fails only when the data actually contains a NULL map key.
 
 Update Schema
 -------------
diff --git a/src/paimon/common/types/data_type_json_parser.cpp 
b/src/paimon/common/types/data_type_json_parser.cpp
index 950308a..33308ab 100644
--- a/src/paimon/common/types/data_type_json_parser.cpp
+++ b/src/paimon/common/types/data_type_json_parser.cpp
@@ -689,17 +689,13 @@ Result<std::shared_ptr<arrow::Field>> 
DataTypeJsonParser::ParseMapType(
 
     PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Field> key,
                            ParseType("key", type_json_value["key"]));
+
     // NOTE: Unlike Java Paimon, this C++ implementation does not support 
nullable keys in
     // MapType. This is a limitation of Apache Arrow, which does not allow 
null keys in its
-    // MapType. As a result, we validate `nullable = false` for the map key.
-    if (key->nullable()) {
-        return Status::Invalid(fmt::format(
-            "Map field '{}' has a nullable key."
-            "Map keys must be explicitly marked as NOT NULL in the schema for 
paimon-cpp "
-            "because Apache Arrow does not support nullable map keys. "
-            "Please add 'NOT NULL' to the key type definition.",
-            name));
-    }
+    // MapType. As a result, we explicitly set `nullable = false` for the map 
key, regardless of
+    // the original schema. Please be aware of this behavioral difference when 
migrating or
+    // interoperating with Java Paimon.
+    key = key->WithNullable(false);
     PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Field> value,
                            ParseType("value", type_json_value["value"]));
     return arrow::field(name, std::make_shared<arrow::MapType>(key, value), 
nullable);
diff --git a/src/paimon/common/types/data_type_json_parser_test.cpp 
b/src/paimon/common/types/data_type_json_parser_test.cpp
index bbb4fb5..bfa69c3 100644
--- a/src/paimon/common/types/data_type_json_parser_test.cpp
+++ b/src/paimon/common/types/data_type_json_parser_test.cpp
@@ -51,7 +51,7 @@ TEST(DataTypeJsonParserTest, ParseTypeMapTypeSuccess) {
     const std::string name = "map_field";
     const char* json = R"({
         "type": "MAP",
-        "key": "STRING NOT NULL",
+        "key": "STRING",
         "value": "INT"
     })";
     rapidjson::Document doc;
@@ -60,6 +60,8 @@ TEST(DataTypeJsonParserTest, ParseTypeMapTypeSuccess) {
     ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Field> field,
                          DataTypeJsonParser::ParseType(name, doc));
     ASSERT_NE(field, nullptr);
+    auto map_type = std::static_pointer_cast<arrow::MapType>(field->type());
+    ASSERT_FALSE(map_type->key_field()->nullable());
 }
 
 TEST(DataTypeJsonParserTest, ParseTypeRowTypeSuccess) {
diff --git a/src/paimon/core/schema/arrow_schema_validator.cpp 
b/src/paimon/core/schema/arrow_schema_validator.cpp
index b9ea5da..e01afe0 100644
--- a/src/paimon/core/schema/arrow_schema_validator.cpp
+++ b/src/paimon/core/schema/arrow_schema_validator.cpp
@@ -231,10 +231,6 @@ Status ArrowSchemaValidator::ValidateField(const 
std::shared_ptr<arrow::Field>&
                 arrow::internal::checked_cast<const 
arrow::MapType&>(*field->type()).key_field();
             const auto& item_field =
                 arrow::internal::checked_cast<const 
arrow::MapType&>(*field->type()).item_field();
-            if (key_field->nullable()) {
-                return Status::Invalid(
-                    fmt::format("Map field '{}' has a nullable key.", 
field->name()));
-            }
             PAIMON_RETURN_NOT_OK(ValidateField(key_field, 
/*allow_blob=*/false));
             PAIMON_RETURN_NOT_OK(ValidateField(item_field, 
/*allow_blob=*/false));
             break;
diff --git a/src/paimon/core/schema/schema_validation_test.cpp 
b/src/paimon/core/schema/schema_validation_test.cpp
index 57179e8..3c3054a 100644
--- a/src/paimon/core/schema/schema_validation_test.cpp
+++ b/src/paimon/core/schema/schema_validation_test.cpp
@@ -938,7 +938,7 @@ TEST(SchemaValidationTest, TestMapStorageLayout) {
     }
 }
 
-TEST(SchemaValidationTest, TestMapRequiresNonNullableKey) {
+TEST(SchemaValidationTest, TestMapSharedShreddingRequiresNonNullableKey) {
     auto nullable_key_map =
         std::make_shared<arrow::MapType>(arrow::field("key", arrow::utf8(), 
/*nullable=*/true),
                                          arrow::field("value", 
arrow::int64()));
@@ -951,9 +951,11 @@ TEST(SchemaValidationTest, TestMapRequiresNonNullableKey) {
         {Options::BUCKET_KEY, "f0"},
         {"fields.f1.map.storage-layout", "shared-shredding"},
     };
-    ASSERT_NOK_WITH_MSG(TableSchema::Create(/*schema_id=*/0, schema, 
/*partition_keys=*/{},
-                                            /*primary_keys=*/{}, options),
-                        "Map field 'f1' has a nullable key.");
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
+                         TableSchema::Create(/*schema_id=*/0, schema, 
/*partition_keys=*/{},
+                                             /*primary_keys=*/{}, options));
+    ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
+                        "map key type is nullable");
 }
 
 TEST(SchemaValidationTest, TestMapSharedShreddingRejectsBlobValue) {
diff --git a/src/paimon/core/schema/table_schema_test.cpp 
b/src/paimon/core/schema/table_schema_test.cpp
index fd0ac42..1a46eaa 100644
--- a/src/paimon/core/schema/table_schema_test.cpp
+++ b/src/paimon/core/schema/table_schema_test.cpp
@@ -1297,7 +1297,7 @@ TEST_F(TableSchemaTest, SetFieldIdNestedListInStruct) {
     }
 }
 
-TEST_F(TableSchemaTest, MapKeyMustBeNotNull) {
+TEST_F(TableSchemaTest, NullableMapKeySchemaIsSupported) {
     std::string table_schema_str = R"({
         "version" : 3,
         "id" : 0,
@@ -1316,16 +1316,21 @@ TEST_F(TableSchemaTest, MapKeyMustBeNotNull) {
         "options" : {},
         "timeMillis" : 1721614341162
     })";
-    ASSERT_NOK_WITH_MSG(TableSchema::CreateFromJson(table_schema_str),
-                        "Map field 'f0' has a nullable key.");
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<TableSchema> table_schema,
+                         TableSchema::CreateFromJson(table_schema_str));
+    auto json_map_type = 
std::static_pointer_cast<arrow::MapType>(table_schema->Fields()[0].Type());
+    ASSERT_FALSE(json_map_type->key_field()->nullable());
 
     auto nullable_key_map =
         std::make_shared<arrow::MapType>(arrow::field("key", arrow::int8(), 
/*nullable=*/true),
                                          arrow::field("value", 
arrow::int16()));
-    ASSERT_NOK_WITH_MSG(
+    ASSERT_OK_AND_ASSIGN(
+        std::shared_ptr<TableSchema> direct_table_schema,
         TableSchema::Create(/*schema_id=*/0, arrow::schema({arrow::field("f0", 
nullable_key_map)}),
-                            /*partition_keys=*/{}, /*primary_keys=*/{}, 
/*options=*/{}),
-        "Map field 'f0' has a nullable key.");
+                            /*partition_keys=*/{}, /*primary_keys=*/{}, 
/*options=*/{}));
+    auto direct_map_type =
+        
std::static_pointer_cast<arrow::MapType>(direct_table_schema->Fields()[0].Type());
+    ASSERT_TRUE(direct_map_type->key_field()->nullable());
 }
 
 TEST_F(TableSchemaTest, MapKeysSortedIsNormalized) {
diff --git a/src/paimon/format/avro/avro_file_batch_reader.cpp 
b/src/paimon/format/avro/avro_file_batch_reader.cpp
index 92ac769..f48ec4c 100644
--- a/src/paimon/format/avro/avro_file_batch_reader.cpp
+++ b/src/paimon/format/avro/avro_file_batch_reader.cpp
@@ -123,6 +123,7 @@ Result<BatchReader::ReadBatch> 
AvroFileBatchReader::NextBatch() {
         }
         PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Array> array,
                                           array_builder_->Finish());
+        PAIMON_RETURN_NOT_OK_FROM_ARROW(array->Validate());
         std::unique_ptr<ArrowArray> c_array = std::make_unique<ArrowArray>();
         std::unique_ptr<ArrowSchema> c_schema = 
std::make_unique<ArrowSchema>();
         PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow::ExportArray(*array, 
c_array.get(), c_schema.get()));
diff --git a/src/paimon/format/orc/orc_adapter.cpp 
b/src/paimon/format/orc/orc_adapter.cpp
index 992387d..4773f2e 100644
--- a/src/paimon/format/orc/orc_adapter.cpp
+++ b/src/paimon/format/orc/orc_adapter.cpp
@@ -945,6 +945,7 @@ Result<std::shared_ptr<arrow::Array>> 
OrcAdapter::AppendBatch(
                            MakeArrowBuilder(type, batch, pool));
     std::shared_ptr<arrow::Array> array;
     PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Finish(&array));
+    PAIMON_RETURN_NOT_OK_FROM_ARROW(array->Validate());
     return array;
 }
 
diff --git a/src/paimon/format/parquet/parquet_file_batch_reader.cpp 
b/src/paimon/format/parquet/parquet_file_batch_reader.cpp
index 4f1d013..fcc47c1 100644
--- a/src/paimon/format/parquet/parquet_file_batch_reader.cpp
+++ b/src/paimon/format/parquet/parquet_file_batch_reader.cpp
@@ -537,6 +537,7 @@ Result<BatchReader::ReadBatch> 
ParquetFileBatchReader::NextBatch() {
         }
         PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Array> array,
                                           batch->ToStructArray());
+        PAIMON_RETURN_NOT_OK_FROM_ARROW(array->Validate());
         PAIMON_ASSIGN_OR_RAISE(bool need_cast, 
ParquetTimestampConverter::NeedCastArrayForTimestamp(
                                                    array->type(), 
read_data_type_));
         if (need_cast) {
diff --git a/test/inte/scan_and_read_inte_test.cpp 
b/test/inte/scan_and_read_inte_test.cpp
index a9294ef..2338463 100644
--- a/test/inte/scan_and_read_inte_test.cpp
+++ b/test/inte/scan_and_read_inte_test.cpp
@@ -2819,6 +2819,50 @@ TEST_P(ScanAndReadInteTest, 
TestWithPKBucketSelectByPredicate) {
     ASSERT_TRUE(expected->Equals(read_result)) << read_result->ToString();
 }
 
+TEST_P(ScanAndReadInteTest, TestReadNullableMapKey) {
+    auto file_format = FileFormat();
+    if (file_format != "orc") {
+        return;
+    }
+    // Java Parquet does not support writing null MAP keys; see
+    // ParquetRowDataWriter.MapWriter#writeMapData in Apache Paimon. 
Therefore, this test uses an
+    // ORC table. The table contains three rows: the first two rows have 
non-null MAP keys, while
+    // the third row has a null MAP key and is expected to fail during reading.
+    const std::string table_path =
+        paimon::test::GetDataDir() + 
"orc/nullable_map_key.db/nullable_map_key";
+
+    ScanContextBuilder scan_context_builder(table_path);
+    ASSERT_OK_AND_ASSIGN(auto scan_context, scan_context_builder.Finish());
+    ASSERT_OK_AND_ASSIGN(auto table_scan, 
TableScan::Create(std::move(scan_context)));
+    ASSERT_OK_AND_ASSIGN(auto result_plan, table_scan->CreatePlan());
+    ASSERT_TRUE(result_plan->SnapshotId());
+    ASSERT_EQ(result_plan->SnapshotId().value(), 1);
+    ASSERT_EQ(result_plan->Splits().size(), 1);
+
+    ReadContextBuilder read_context_builder(table_path);
+    read_context_builder.AddOption(Options::READ_BATCH_SIZE, "1");
+    ASSERT_OK_AND_ASSIGN(auto read_context, read_context_builder.Finish());
+    ASSERT_OK_AND_ASSIGN(auto table_read, 
TableRead::Create(std::move(read_context)));
+    ASSERT_OK_AND_ASSIGN(auto batch_reader, 
table_read->CreateReader(result_plan->Splits()));
+
+    const std::vector<std::string> expected_rows = {R"([[0, 1, [["one", 
10]]]])",
+                                                    R"([[0, 2, [["two", 
20]]]])"};
+    for (int32_t i = 0; i < 2; ++i) {
+        ASSERT_OK_AND_ASSIGN(BatchReader::ReadBatch batch, 
batch_reader->NextBatch());
+        ASSERT_FALSE(BatchReader::IsEofBatch(batch));
+        ASSERT_OK_AND_ASSIGN(std::shared_ptr<arrow::Array> array,
+                             ReadResultCollector::GetArray(std::move(batch)));
+        ASSERT_EQ(array->length(), 1);
+
+        std::shared_ptr<arrow::Array> expected_array =
+            arrow::ipc::internal::json::ArrayFromJSON(array->type(), 
expected_rows[i]).ValueOrDie();
+        ASSERT_TRUE(array->Equals(expected_array))
+            << "actual: " << array->ToString() << ", expected: " << 
expected_array->ToString();
+    }
+    ASSERT_NOK_WITH_MSG(batch_reader->NextBatch(), "Map array keys array 
should have no nulls");
+    batch_reader->Close();
+}
+
 TEST_P(ScanAndReadInteTest, TestCountRowsEmptySplits) {
     auto file_format = FileFormat();
     std::string table_path = paimon::test::GetDataDir() + file_format +
diff --git 
a/test/test_data/avro/append_multiple.db/append_multiple/schema/schema-0 
b/test/test_data/avro/append_multiple.db/append_multiple/schema/schema-0
index e3b8f71..c0d6d75 100644
--- a/test/test_data/avro/append_multiple.db/append_multiple/schema/schema-0
+++ b/test/test_data/avro/append_multiple.db/append_multiple/schema/schema-0
@@ -93,7 +93,7 @@
             "name": "f0",
             "type": {
               "type": "MAP",
-              "key": "STRING NOT NULL",
+              "key": "STRING",
               "value": "INT"
             }
           },
diff --git a/test/test_data/avro/append_simple.db/append_simple/schema/schema-0 
b/test/test_data/avro/append_simple.db/append_simple/schema/schema-0
index c8b4be2..be12e26 100644
--- a/test/test_data/avro/append_simple.db/append_simple/schema/schema-0
+++ b/test/test_data/avro/append_simple.db/append_simple/schema/schema-0
@@ -23,7 +23,7 @@
         "name" : "f0",
         "type" : {
           "type" : "MAP",
-          "key" : "STRING NOT NULL",
+          "key" : "STRING",
           "value" : "INT"
         }
       }, {
diff --git 
a/test/test_data/avro/append_with_multiple_map.db/append_with_multiple_map/schema/schema-0
 
b/test/test_data/avro/append_with_multiple_map.db/append_with_multiple_map/schema/schema-0
index 7237632..b118e22 100644
--- 
a/test/test_data/avro/append_with_multiple_map.db/append_with_multiple_map/schema/schema-0
+++ 
b/test/test_data/avro/append_with_multiple_map.db/append_with_multiple_map/schema/schema-0
@@ -6,7 +6,7 @@
     "name" : "f0",
     "type" : {
       "type" : "MAP",
-      "key" : "INT NOT NULL",
+      "key" : "INT",
       "value" : "INT"
     }
   }, {
@@ -14,7 +14,7 @@
     "name" : "f1",
     "type" : {
       "type" : "MAP",
-      "key" : "DOUBLE NOT NULL",
+      "key" : "DOUBLE",
       "value" : "DOUBLE"
     }
   }, {
@@ -22,7 +22,7 @@
     "name" : "f2",
     "type" : {
       "type" : "MAP",
-      "key" : "STRING NOT NULL",
+      "key" : "STRING",
       "value" : "STRING"
     }
   }, {
@@ -30,7 +30,7 @@
     "name" : "f3",
     "type" : {
       "type" : "MAP",
-      "key" : "STRING NOT NULL",
+      "key" : "STRING",
       "value" : "BINARY(6)"
     }
   }, {
@@ -38,7 +38,7 @@
     "name" : "f4",
     "type" : {
       "type" : "MAP",
-      "key" : "TIMESTAMP(6) NOT NULL",
+      "key" : "TIMESTAMP(6)",
       "value" : "TIMESTAMP(6)"
     }
   }, {
@@ -46,7 +46,7 @@
     "name" : "f5",
     "type" : {
       "type" : "MAP",
-      "key" : "STRING NOT NULL",
+      "key" : "STRING",
       "value" : {
         "type" : "ARRAY",
         "element" : "DOUBLE"
@@ -57,10 +57,10 @@
     "name" : "f6",
     "type" : {
       "type" : "MAP",
-      "key" : "STRING NOT NULL",
+      "key" : "STRING",
       "value" : {
         "type" : "MAP",
-        "key" : "DOUBLE NOT NULL",
+        "key" : "DOUBLE",
         "value" : "STRING"
       }
     }
@@ -69,7 +69,7 @@
     "name" : "f7",
     "type" : {
       "type" : "MAP",
-      "key" : "BIGINT NOT NULL",
+      "key" : "BIGINT",
       "value" : {
         "type" : "ROW",
         "fields" : [ {
diff --git 
a/test/test_data/avro/pk_with_multiple_type.db/pk_with_multiple_type/schema/schema-0
 
b/test/test_data/avro/pk_with_multiple_type.db/pk_with_multiple_type/schema/schema-0
index 5533e14..608d0df 100644
--- 
a/test/test_data/avro/pk_with_multiple_type.db/pk_with_multiple_type/schema/schema-0
+++ 
b/test/test_data/avro/pk_with_multiple_type.db/pk_with_multiple_type/schema/schema-0
@@ -55,7 +55,7 @@
         "name" : "f0",
         "type" : {
           "type" : "MAP",
-          "key" : "STRING NOT NULL",
+          "key" : "STRING",
           "value" : "INT"
         }
       }, {
diff --git 
a/test/test_data/orc/append_complex_build_in_fieldid.db/append_complex_build_in_fieldid/schema/schema-0
 
b/test/test_data/orc/append_complex_build_in_fieldid.db/append_complex_build_in_fieldid/schema/schema-0
index 699682f..f973a64 100644
--- 
a/test/test_data/orc/append_complex_build_in_fieldid.db/append_complex_build_in_fieldid/schema/schema-0
+++ 
b/test/test_data/orc/append_complex_build_in_fieldid.db/append_complex_build_in_fieldid/schema/schema-0
@@ -6,7 +6,7 @@
     "name" : "f1",
     "type" : {
       "type" : "MAP",
-      "key" : "TINYINT NOT NULL",
+      "key" : "TINYINT",
       "value" : "SMALLINT"
     }
   }, {
diff --git 
a/test/test_data/orc/append_table_with_nested_type.db/append_table_with_nested_type/schema/schema-0
 
b/test/test_data/orc/append_table_with_nested_type.db/append_table_with_nested_type/schema/schema-0
index c754fec..57cd38a 100644
--- 
a/test/test_data/orc/append_table_with_nested_type.db/append_table_with_nested_type/schema/schema-0
+++ 
b/test/test_data/orc/append_table_with_nested_type.db/append_table_with_nested_type/schema/schema-0
@@ -64,7 +64,7 @@
     "type" : {
       "type" : "MAP",
       "key" : {
-        "type" : "ROW NOT NULL",
+        "type" : "ROW",
         "fields" : [ {
           "id" : 13,
           "name" : "sub1",
diff --git a/test/test_data/orc/nullable_map_key.db/nullable_map_key/README 
b/test/test_data/orc/nullable_map_key.db/nullable_map_key/README
new file mode 100644
index 0000000..02b0d4e
--- /dev/null
+++ b/test/test_data/orc/nullable_map_key.db/nullable_map_key/README
@@ -0,0 +1,13 @@
+id:int attrs:map(string, int) (all can be null)
+no partition key
+no primary key
+bucket count: -1
+file format: orc
+
+Rows (snapshot-1):
+Add: [1, {"one": 10}]
+Add: [2, {"two": 20}]
+Add: [3, {null: 30}]
+NoCompact
+
+The third row contains a null MAP key and is expected to fail during reading.
diff --git 
a/test/test_data/orc/nullable_map_key.db/nullable_map_key/bucket-0/data-7e6899e9-f3cd-4d2e-86bc-7a5f383d5922-0.orc
 
b/test/test_data/orc/nullable_map_key.db/nullable_map_key/bucket-0/data-7e6899e9-f3cd-4d2e-86bc-7a5f383d5922-0.orc
new file mode 100644
index 0000000..89c6f60
Binary files /dev/null and 
b/test/test_data/orc/nullable_map_key.db/nullable_map_key/bucket-0/data-7e6899e9-f3cd-4d2e-86bc-7a5f383d5922-0.orc
 differ
diff --git 
a/test/test_data/orc/nullable_map_key.db/nullable_map_key/manifest/manifest-a956b9da-04c5-41b6-b6f0-20efa3974bd4-0
 
b/test/test_data/orc/nullable_map_key.db/nullable_map_key/manifest/manifest-a956b9da-04c5-41b6-b6f0-20efa3974bd4-0
new file mode 100644
index 0000000..e96a4bb
Binary files /dev/null and 
b/test/test_data/orc/nullable_map_key.db/nullable_map_key/manifest/manifest-a956b9da-04c5-41b6-b6f0-20efa3974bd4-0
 differ
diff --git 
a/test/test_data/orc/nullable_map_key.db/nullable_map_key/manifest/manifest-list-67c61c9b-ab3c-4564-a5f2-612aa684a8f2-0
 
b/test/test_data/orc/nullable_map_key.db/nullable_map_key/manifest/manifest-list-67c61c9b-ab3c-4564-a5f2-612aa684a8f2-0
new file mode 100644
index 0000000..0b80517
Binary files /dev/null and 
b/test/test_data/orc/nullable_map_key.db/nullable_map_key/manifest/manifest-list-67c61c9b-ab3c-4564-a5f2-612aa684a8f2-0
 differ
diff --git 
a/test/test_data/orc/nullable_map_key.db/nullable_map_key/manifest/manifest-list-67c61c9b-ab3c-4564-a5f2-612aa684a8f2-1
 
b/test/test_data/orc/nullable_map_key.db/nullable_map_key/manifest/manifest-list-67c61c9b-ab3c-4564-a5f2-612aa684a8f2-1
new file mode 100644
index 0000000..47fc5e9
Binary files /dev/null and 
b/test/test_data/orc/nullable_map_key.db/nullable_map_key/manifest/manifest-list-67c61c9b-ab3c-4564-a5f2-612aa684a8f2-1
 differ
diff --git 
a/test/test_data/orc/nullable_map_key.db/nullable_map_key/schema/schema-0 
b/test/test_data/orc/nullable_map_key.db/nullable_map_key/schema/schema-0
new file mode 100644
index 0000000..e1c711c
--- /dev/null
+++ b/test/test_data/orc/nullable_map_key.db/nullable_map_key/schema/schema-0
@@ -0,0 +1,25 @@
+{
+  "version" : 3,
+  "id" : 0,
+  "fields" : [ {
+    "id" : 0,
+    "name" : "id",
+    "type" : "INT"
+  }, {
+    "id" : 1,
+    "name" : "attrs",
+    "type" : {
+      "type" : "MAP",
+      "key" : "STRING",
+      "value" : "INT"
+    }
+  } ],
+  "highestFieldId" : 1,
+  "partitionKeys" : [ ],
+  "primaryKeys" : [ ],
+  "options" : {
+    "bucket" : "-1",
+    "file.format" : "orc"
+  },
+  "timeMillis" : 1785815466343
+}
\ No newline at end of file
diff --git 
a/test/test_data/orc/nullable_map_key.db/nullable_map_key/snapshot/EARLIEST 
b/test/test_data/orc/nullable_map_key.db/nullable_map_key/snapshot/EARLIEST
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/test/test_data/orc/nullable_map_key.db/nullable_map_key/snapshot/EARLIEST
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git 
a/test/test_data/orc/nullable_map_key.db/nullable_map_key/snapshot/LATEST 
b/test/test_data/orc/nullable_map_key.db/nullable_map_key/snapshot/LATEST
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/test/test_data/orc/nullable_map_key.db/nullable_map_key/snapshot/LATEST
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git 
a/test/test_data/orc/nullable_map_key.db/nullable_map_key/snapshot/snapshot-1 
b/test/test_data/orc/nullable_map_key.db/nullable_map_key/snapshot/snapshot-1
new file mode 100644
index 0000000..78c3467
--- /dev/null
+++ 
b/test/test_data/orc/nullable_map_key.db/nullable_map_key/snapshot/snapshot-1
@@ -0,0 +1,17 @@
+{
+  "version" : 3,
+  "uuid" : "67b9daa5-c055-45f9-ac6a-d87d017490b1",
+  "id" : 1,
+  "schemaId" : 0,
+  "baseManifestList" : "manifest-list-67c61c9b-ab3c-4564-a5f2-612aa684a8f2-0",
+  "baseManifestListSize" : 1006,
+  "deltaManifestList" : "manifest-list-67c61c9b-ab3c-4564-a5f2-612aa684a8f2-1",
+  "deltaManifestListSize" : 1110,
+  "commitUser" : "8bd3a648-0532-4a3e-8815-9e1da2e6d7f9",
+  "commitIdentifier" : 0,
+  "commitKind" : "APPEND",
+  "timeMillis" : 1785815467254,
+  "totalRecordCount" : 3,
+  "deltaRecordCount" : 3,
+  "nextRowId" : 0
+}
\ No newline at end of file
diff --git 
a/test/test_data/orc/pk_table_nested_type.db/pk_table_nested_type/schema/schema-0
 
b/test/test_data/orc/pk_table_nested_type.db/pk_table_nested_type/schema/schema-0
index 7a3fdc5..7f38243 100644
--- 
a/test/test_data/orc/pk_table_nested_type.db/pk_table_nested_type/schema/schema-0
+++ 
b/test/test_data/orc/pk_table_nested_type.db/pk_table_nested_type/schema/schema-0
@@ -44,7 +44,7 @@
     "name" : "col2",
     "type" : {
       "type" : "MAP",
-      "key" : "INT NOT NULL",
+      "key" : "INT",
       "value" : "INT"
     }
   } ],
diff --git 
a/test/test_data/parquet/append_complex_build_in_fieldid.db/append_complex_build_in_fieldid/schema/schema-0
 
b/test/test_data/parquet/append_complex_build_in_fieldid.db/append_complex_build_in_fieldid/schema/schema-0
index 4bf9bca..296bbca 100644
--- 
a/test/test_data/parquet/append_complex_build_in_fieldid.db/append_complex_build_in_fieldid/schema/schema-0
+++ 
b/test/test_data/parquet/append_complex_build_in_fieldid.db/append_complex_build_in_fieldid/schema/schema-0
@@ -6,7 +6,7 @@
     "name" : "f1",
     "type" : {
       "type" : "MAP",
-      "key" : "TINYINT NOT NULL",
+      "key" : "TINYINT",
       "value" : "SMALLINT"
     }
   }, {
diff --git 
a/test/test_data/parquet/parquet_append_table.db/parquet_append_table/schema/schema-0
 
b/test/test_data/parquet/parquet_append_table.db/parquet_append_table/schema/schema-0
index 524117c..ae19f8a 100644
--- 
a/test/test_data/parquet/parquet_append_table.db/parquet_append_table/schema/schema-0
+++ 
b/test/test_data/parquet/parquet_append_table.db/parquet_append_table/schema/schema-0
@@ -43,7 +43,7 @@
     "type" : {
       "type" : "MAP",
       "key" : {
-        "type" : "ARRAY NOT NULL",
+        "type" : "ARRAY",
         "element" : "FLOAT"
       },
       "value" : {
diff --git 
a/test/test_data/parquet/pk_table_nested_type.db/pk_table_nested_type/schema/schema-0
 
b/test/test_data/parquet/pk_table_nested_type.db/pk_table_nested_type/schema/schema-0
index 22968d4..db05092 100644
--- 
a/test/test_data/parquet/pk_table_nested_type.db/pk_table_nested_type/schema/schema-0
+++ 
b/test/test_data/parquet/pk_table_nested_type.db/pk_table_nested_type/schema/schema-0
@@ -44,7 +44,7 @@
     "name" : "col2",
     "type" : {
       "type" : "MAP",
-      "key" : "INT NOT NULL",
+      "key" : "INT",
       "value" : "INT"
     }
   } ],

Reply via email to