/*
 * 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/data/shredding/map_shared_shredding_utils.h"

#include <set>

#include "arrow/type.h"
#include "arrow/util/key_value_metadata.h"
#include "gtest/gtest.h"
#include "paimon/core/core_options.h"
#include "paimon/testing/utils/testharness.h"

namespace paimon::test {

// ---- IsShreddingKeyMap ----

TEST(MapSharedShreddingUtilsTest, IsShreddingKeyMap) {
    ASSERT_TRUE(
        MapSharedShreddingUtils::IsShreddingKeyMap(arrow::map(arrow::utf8(), arrow::int32())));
    ASSERT_TRUE(
        MapSharedShreddingUtils::IsShreddingKeyMap(arrow::map(arrow::utf8(), arrow::float64())));
    // Nested value type (struct)
    auto nested_value =
        arrow::struct_({arrow::field("x", arrow::int32()), arrow::field("y", arrow::utf8())});
    ASSERT_TRUE(
        MapSharedShreddingUtils::IsShreddingKeyMap(arrow::map(arrow::utf8(), nested_value)));
    ASSERT_FALSE(
        MapSharedShreddingUtils::IsShreddingKeyMap(arrow::map(arrow::int32(), arrow::utf8())));
    ASSERT_FALSE(MapSharedShreddingUtils::IsShreddingKeyMap(arrow::int32()));
    ASSERT_FALSE(MapSharedShreddingUtils::IsShreddingKeyMap(arrow::list(arrow::utf8())));
}

// ---- DetectShreddingColumns ----

TEST(MapSharedShreddingUtilsTest, DetectShreddingColumnsBasic) {
    auto schema = arrow::schema({
        arrow::field("id", arrow::int32()),
        arrow::field("tags", arrow::map(arrow::utf8(), arrow::utf8())),
        arrow::field("metrics", arrow::map(arrow::utf8(), arrow::float64())),
        arrow::field("name", arrow::utf8()),
    });

    ASSERT_OK_AND_ASSIGN(
        CoreOptions options,
        CoreOptions::FromMap({{"fields.tags.map.storage-layout", "shared-shredding"},
                              {"fields.metrics.map.storage-layout", "shared-shredding"}}));

    ASSERT_OK_AND_ASSIGN(auto field_names,
                         MapSharedShreddingUtils::DetectShreddingColumns(schema, options));
    ASSERT_EQ(field_names.size(), 2);
    ASSERT_EQ(field_names[0], "tags");
    ASSERT_EQ(field_names[1], "metrics");
}

TEST(MapSharedShreddingUtilsTest, DetectShreddingColumnsNoShredding) {
    auto schema = arrow::schema({
        arrow::field("id", arrow::int32()),
        arrow::field("tags", arrow::map(arrow::utf8(), arrow::utf8())),
    });

    ASSERT_OK_AND_ASSIGN(CoreOptions options, CoreOptions::FromMap({}));
    ASSERT_OK_AND_ASSIGN(auto field_names,
                         MapSharedShreddingUtils::DetectShreddingColumns(schema, options));
    ASSERT_TRUE(field_names.empty());
}

// ---- LogicalToPhysicalSchema ----

TEST(MapSharedShreddingUtilsTest, LogicalToPhysicalSchemaBasic) {
    auto schema = arrow::schema({
        arrow::field("id", arrow::int32()),
        arrow::field("tags", arrow::map(arrow::utf8(), arrow::utf8())),
        arrow::field("name", arrow::utf8()),
    });

    std::map<std::string, int32_t> field_to_num_columns = {{"tags", 4}};
    ASSERT_OK_AND_ASSIGN(auto physical_schema, MapSharedShreddingUtils::LogicalToPhysicalSchema(
                                                   schema, field_to_num_columns));

    // Build expected schema for comparison
    auto expected_struct = arrow::struct_({
        arrow::field("__field_mapping", arrow::list(arrow::int32()), true),
        arrow::field("__col_0", arrow::utf8(), true),
        arrow::field("__col_1", arrow::utf8(), true),
        arrow::field("__col_2", arrow::utf8(), true),
        arrow::field("__col_3", arrow::utf8(), true),
        arrow::field("__overflow", arrow::map(arrow::int32(), arrow::utf8()), true),
    });
    auto expected_schema = arrow::schema({
        arrow::field("id", arrow::int32()),
        arrow::field("tags", expected_struct, true),
        arrow::field("name", arrow::utf8()),
    });
    ASSERT_TRUE(physical_schema->Equals(expected_schema));
}

TEST(MapSharedShreddingUtilsTest, LogicalToPhysicalSchemaNestedValue) {
    // MAP<STRING, STRUCT<a: int32, b: utf8>>
    auto nested_value =
        arrow::struct_({arrow::field("a", arrow::int32()), arrow::field("b", arrow::utf8())});
    auto map_type = arrow::map(arrow::utf8(), nested_value);
    auto schema = arrow::schema({arrow::field("data", map_type)});

    std::map<std::string, int32_t> field_to_num_columns = {{"data", 2}};
    ASSERT_OK_AND_ASSIGN(auto physical_schema, MapSharedShreddingUtils::LogicalToPhysicalSchema(
                                                   schema, field_to_num_columns));

    auto expected_struct = arrow::struct_({
        arrow::field("__field_mapping", arrow::list(arrow::int32()), true),
        arrow::field("__col_0", nested_value, true),
        arrow::field("__col_1", nested_value, true),
        arrow::field("__overflow", arrow::map(arrow::int32(), nested_value), true),
    });
    auto expected_schema = arrow::schema({arrow::field("data", expected_struct, true)});
    ASSERT_TRUE(physical_schema->Equals(expected_schema));
}

TEST(MapSharedShreddingUtilsTest, LogicalToPhysicalSchemaNullable) {
    // MAP value is nullable
    auto nullable_map = arrow::map(arrow::utf8(), arrow::field("item", arrow::int64(), true));
    auto schema_nullable = arrow::schema({arrow::field("m", nullable_map)});
    std::map<std::string, int32_t> col_map = {{"m", 2}};

    ASSERT_OK_AND_ASSIGN(
        auto physical, MapSharedShreddingUtils::LogicalToPhysicalSchema(schema_nullable, col_map));
    auto struct_type = physical->field(0)->type();
    ASSERT_TRUE(struct_type->field(0)->nullable());
    ASSERT_TRUE(struct_type->field(1)->nullable());
    ASSERT_TRUE(struct_type->field(2)->nullable());

    // MAP value is non-nullable
    auto non_nullable_map = arrow::map(arrow::utf8(), arrow::field("item", arrow::int64(), false));
    auto schema_non_nullable = arrow::schema({arrow::field("m", non_nullable_map)});

    ASSERT_OK_AND_ASSIGN(auto physical2, MapSharedShreddingUtils::LogicalToPhysicalSchema(
                                             schema_non_nullable, col_map));
    auto struct_type2 = physical2->field(0)->type();
    ASSERT_FALSE(struct_type2->field(1)->nullable());
    ASSERT_FALSE(struct_type2->field(2)->nullable());
}

TEST(MapSharedShreddingUtilsTest, LogicalToPhysicalSchemaPreservesFieldMetadata) {
    auto metadata = std::make_shared<arrow::KeyValueMetadata>();
    metadata->Append("paimon.field.id", "7");
    metadata->Append("description", "original map field");
    auto map_type = arrow::map(arrow::utf8(), arrow::int64());
    auto schema = arrow::schema({arrow::field("m", map_type, false, metadata)});
    std::map<std::string, int32_t> col_map = {{"m", 2}};

    ASSERT_OK_AND_ASSIGN(auto physical_schema,
                         MapSharedShreddingUtils::LogicalToPhysicalSchema(schema, col_map));

    ASSERT_FALSE(physical_schema->field(0)->nullable());
    ASSERT_TRUE(physical_schema->field(0)->metadata()->Equals(*metadata));
}

TEST(MapSharedShreddingUtilsTest, LogicalToPhysicalSchemaNoShreddingColumns) {
    auto schema = arrow::schema({
        arrow::field("id", arrow::int32()),
        arrow::field("name", arrow::utf8()),
    });

    std::map<std::string, int32_t> empty_map;
    ASSERT_OK_AND_ASSIGN(auto physical_schema,
                         MapSharedShreddingUtils::LogicalToPhysicalSchema(schema, empty_map));
    ASSERT_TRUE(physical_schema->Equals(schema));
}

TEST(MapSharedShreddingUtilsTest, BuildSpecificPhysicalStructTypeWithOverflow) {
    auto actual = MapSharedShreddingUtils::BuildSpecificPhysicalStructType(
        arrow::int64(), /*physical_col_ids=*/{3, 1}, /*value_nullable=*/false,
        /*include_overflow=*/true);

    auto expected = arrow::struct_({
        arrow::field("__field_mapping", arrow::list(arrow::int32()), true),
        arrow::field("__col_1", arrow::int64(), false),
        arrow::field("__col_3", arrow::int64(), false),
        arrow::field("__overflow",
                     arrow::map(arrow::int32(), arrow::field("value", arrow::int64(), false)),
                     true),
    });
    ASSERT_TRUE(actual->Equals(*expected)) << "Expected:\n"
                                           << expected->ToString() << "\nActual:\n"
                                           << actual->ToString();
}

TEST(MapSharedShreddingUtilsTest, BuildSpecificPhysicalStructTypeWithoutOverflow) {
    auto actual = MapSharedShreddingUtils::BuildSpecificPhysicalStructType(
        arrow::utf8(), /*physical_col_ids=*/{3}, /*value_nullable=*/true,
        /*include_overflow=*/false);

    auto expected = arrow::struct_({
        arrow::field("__field_mapping", arrow::list(arrow::int32()), true),
        arrow::field("__col_3", arrow::utf8(), true),
    });
    ASSERT_TRUE(actual->Equals(*expected)) << "Expected:\n"
                                           << expected->ToString() << "\nActual:\n"
                                           << actual->ToString();
}

// ---- BuildColumnToNumColumns ----

TEST(MapSharedShreddingUtilsTest, BuildColumnToNumColumns) {
    ASSERT_OK_AND_ASSIGN(
        CoreOptions options,
        CoreOptions::FromMap({{"fields.tags.map.shared-shredding.max-columns", "128"},
                              {"fields.metrics.map.shared-shredding.max-columns", "64"}}));

    std::vector<std::string> shredding_field_names = {"tags", "metrics"};
    ASSERT_OK_AND_ASSIGN(auto result, MapSharedShreddingUtils::BuildColumnToNumColumns(
                                          shredding_field_names, options));

    ASSERT_EQ(result.size(), 2);
    ASSERT_EQ(result.at("tags"), 128);
    ASSERT_EQ(result.at("metrics"), 64);
}

TEST(MapSharedShreddingUtilsTest, BuildColumnToNumColumnsDefault) {
    // No explicit max-columns config -> default 256
    ASSERT_OK_AND_ASSIGN(CoreOptions options, CoreOptions::FromMap({}));
    std::vector<std::string> shredding_field_names = {"tags"};
    ASSERT_OK_AND_ASSIGN(auto result, MapSharedShreddingUtils::BuildColumnToNumColumns(
                                          shredding_field_names, options));
    ASSERT_EQ(result.at("tags"), 256);
}

// ---- SerializeMetadata / DeserializeMetadata roundtrip ----

TEST(MapSharedShreddingUtilsTest, MetadataRoundtripNoneCompression) {
    MapSharedShreddingFieldMeta original;
    original.name_to_id = {{"age", 0}, {"name", 1}};
    original.field_to_columns = {{0, {0}}, {1, {1, 2}}};
    original.overflow_field_set = {1, 5};
    original.num_columns = 3;
    original.max_row_width = 2;

    auto metadata = std::make_shared<arrow::KeyValueMetadata>();
    ASSERT_OK(MapSharedShreddingUtils::SerializeMetadata(original, "none", metadata.get()));

    // Verify raw KV strings
    auto find_value = [&](const char* key) -> std::string {
        int32_t idx = metadata->FindKey(key);
        EXPECT_GE(idx, 0);
        return metadata->value(idx);
    };
    ASSERT_EQ(find_value(MapShreddingDefine::kStorageLayout), "shared-shredding");
    ASSERT_EQ(find_value(MapSharedShreddingDefine::kVersion), "1");
    ASSERT_EQ(find_value(MapSharedShreddingDefine::kNumColumns), "3");
    ASSERT_EQ(find_value(MapSharedShreddingDefine::kMaxRowWidth), "2");

    std::string expected_dict = R"({"age":0,"name":1})";
    ASSERT_EQ(find_value(MapSharedShreddingDefine::kFieldDict), expected_dict);
    // field_dict_original_size should be the length of the JSON string
    std::string field_dict_original_size =
        find_value(MapSharedShreddingDefine::kFieldDictOriginalSize);
    ASSERT_EQ(field_dict_original_size, std::to_string(expected_dict.size()));

    std::string expected_field_to_columns = R"({"0":[0],"1":[1,2]})";
    ASSERT_EQ(find_value(MapSharedShreddingDefine::kFieldColumns), expected_field_to_columns);

    // overflow_set is a JSON array of sorted field_ids
    ASSERT_EQ(find_value(MapSharedShreddingDefine::kOverflowSet), "[1,5]");

    // Roundtrip verify
    ASSERT_OK_AND_ASSIGN(auto deserialized, MapSharedShreddingUtils::DeserializeMetadata(metadata));
    ASSERT_EQ(deserialized, original);
}

TEST(MapSharedShreddingUtilsTest, MetadataRoundtripCompression) {
    MapSharedShreddingFieldMeta original;
    original.name_to_id = {{"alpha", 0}, {"beta", 1}, {"gamma", 2}};
    original.field_to_columns = {{0, {0, 1, 2}}, {1, {3}}, {2, {4, 5}}};
    original.overflow_field_set = {2};
    original.num_columns = 6;
    original.max_row_width = 3;

    auto verify_roundtrip = [&](const std::string& compression) {
        auto metadata = std::make_shared<arrow::KeyValueMetadata>();
        ASSERT_OK(
            MapSharedShreddingUtils::SerializeMetadata(original, compression, metadata.get()));
        ASSERT_OK_AND_ASSIGN(auto deserialized,
                             MapSharedShreddingUtils::DeserializeMetadata(metadata));
        ASSERT_EQ(deserialized, original);
    };

    verify_roundtrip("none");
    verify_roundtrip("lz4");
    verify_roundtrip("zstd");
}

TEST(MapSharedShreddingUtilsTest, MetadataRoundtripEmptyData) {
    MapSharedShreddingFieldMeta original;

    auto verify_roundtrip = [&](const std::string& compression) {
        auto metadata = std::make_shared<arrow::KeyValueMetadata>();
        ASSERT_OK(
            MapSharedShreddingUtils::SerializeMetadata(original, compression, metadata.get()));
        ASSERT_OK_AND_ASSIGN(auto deserialized,
                             MapSharedShreddingUtils::DeserializeMetadata(metadata));
        ASSERT_EQ(deserialized, original);
    };

    verify_roundtrip("none");
    verify_roundtrip("lz4");
    verify_roundtrip("zstd");
}

TEST(MapSharedShreddingUtilsTest, DeserializeLegacyMetadataWithoutCompression) {
    MapSharedShreddingFieldMeta original;
    original.name_to_id = {{"alpha", 0}, {"beta", 1}};
    original.field_to_columns = {{0, {0}}, {1, {1}}};
    original.overflow_field_set = {1};
    original.num_columns = 2;
    original.max_row_width = 2;

    auto metadata = std::make_shared<arrow::KeyValueMetadata>();
    ASSERT_OK(MapSharedShreddingUtils::SerializeMetadata(
        original, MapSharedShreddingDefine::kDefaultDictCompression, metadata.get()));
    ASSERT_TRUE(metadata->Delete(MapSharedShreddingDefine::kFieldDictCompression).ok());

    ASSERT_OK_AND_ASSIGN(auto deserialized, MapSharedShreddingUtils::DeserializeMetadata(metadata));
    ASSERT_EQ(deserialized, original);
}

// ---- DeserializeMetadata error cases ----

TEST(MapSharedShreddingUtilsTest, DeserializeMetadataErrors) {
    const std::string layout_error = "metadata is null or storage layout is not shared-shredding";
    // nullptr
    ASSERT_NOK_WITH_MSG(MapSharedShreddingUtils::DeserializeMetadata(nullptr), layout_error);
    // missing storage layout
    {
        auto metadata = std::make_shared<arrow::KeyValueMetadata>();
        metadata->Append("some_key", "some_value");
        ASSERT_NOK_WITH_MSG(MapSharedShreddingUtils::DeserializeMetadata(metadata), layout_error);
    }
    // wrong storage layout
    {
        auto metadata = std::make_shared<arrow::KeyValueMetadata>();
        metadata->Append(MapShreddingDefine::kStorageLayout, "default");
        ASSERT_NOK_WITH_MSG(MapSharedShreddingUtils::DeserializeMetadata(metadata), layout_error);
    }
    // missing version
    {
        auto metadata = std::make_shared<arrow::KeyValueMetadata>();
        metadata->Append(MapShreddingDefine::kStorageLayout,
                         MapShreddingDefine::kStorageLayoutSharedShredding);
        ASSERT_NOK_WITH_MSG(MapSharedShreddingUtils::DeserializeMetadata(metadata),
                            "missing shredding metadata key: paimon.map.shared-shredding.version");
    }
    // wrong version
    {
        auto metadata = std::make_shared<arrow::KeyValueMetadata>();
        metadata->Append(MapShreddingDefine::kStorageLayout,
                         MapShreddingDefine::kStorageLayoutSharedShredding);
        metadata->Append(MapSharedShreddingDefine::kVersion, "999");
        metadata->Append(MapSharedShreddingDefine::kFieldDictOriginalSize, "2");
        metadata->Append(MapSharedShreddingDefine::kFieldDict, "{}");
        ASSERT_NOK_WITH_MSG(MapSharedShreddingUtils::DeserializeMetadata(metadata),
                            "unsupported shared-shredding metadata version: 999");
    }
    // missing field_dict
    {
        auto metadata = std::make_shared<arrow::KeyValueMetadata>();
        metadata->Append(MapShreddingDefine::kStorageLayout,
                         MapShreddingDefine::kStorageLayoutSharedShredding);
        metadata->Append(MapSharedShreddingDefine::kVersion, "1");
        metadata->Append(MapSharedShreddingDefine::kFieldDictOriginalSize, "2");
        ASSERT_NOK_WITH_MSG(
            MapSharedShreddingUtils::DeserializeMetadata(metadata),
            "missing shredding metadata key: paimon.map.shared-shredding.field-dict");
    }
}

// ---- HasShreddingMetadata ----

TEST(MapSharedShreddingUtilsTest, HasShreddingMetadata) {
    ASSERT_FALSE(MapSharedShreddingUtils::HasShreddingMetadata(nullptr));
    {
        auto metadata = std::make_shared<arrow::KeyValueMetadata>();
        metadata->Append(MapShreddingDefine::kStorageLayout,
                         MapShreddingDefine::kStorageLayoutSharedShredding);
        ASSERT_TRUE(MapSharedShreddingUtils::HasShreddingMetadata(metadata));
    }
    {
        auto metadata = std::make_shared<arrow::KeyValueMetadata>();
        metadata->Append(MapShreddingDefine::kStorageLayout, "default");
        ASSERT_FALSE(MapSharedShreddingUtils::HasShreddingMetadata(metadata));
    }
    {
        auto metadata = std::make_shared<arrow::KeyValueMetadata>();
        ASSERT_FALSE(MapSharedShreddingUtils::HasShreddingMetadata(metadata));
    }
}

// ---- PhysicalColumnName ----

TEST(MapSharedShreddingUtilsTest, PhysicalColumnName) {
    ASSERT_EQ(MapSharedShreddingDefine::PhysicalColumnName(0), "__col_0");
    ASSERT_EQ(MapSharedShreddingDefine::PhysicalColumnName(1), "__col_1");
    ASSERT_EQ(MapSharedShreddingDefine::PhysicalColumnName(99), "__col_99");
}

// ---- GetPhysicalColumnIndices ----

// Normal: single physical column per field
TEST(MapSharedShreddingUtilsTest, GetPhysicalColumnIndicesSingleColumn) {
    MapSharedShreddingFieldMeta meta;
    meta.name_to_id = {{"age", 0}, {"name", 1}};
    meta.field_to_columns = {{0, {2}}, {1, {5}}};

    ASSERT_OK_AND_ASSIGN(auto cols_age,
                         MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "age"));
    ASSERT_EQ(cols_age, (std::vector<int32_t>{2}));

    ASSERT_OK_AND_ASSIGN(auto cols_name,
                         MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "name"));
    ASSERT_EQ(cols_name, (std::vector<int32_t>{5}));
}

// Normal: multiple physical columns for one field
TEST(MapSharedShreddingUtilsTest, GetPhysicalColumnIndicesMultipleColumns) {
    MapSharedShreddingFieldMeta meta;
    meta.name_to_id = {{"tags", 0}};
    meta.field_to_columns = {{0, {0, 3, 7}}};

    ASSERT_OK_AND_ASSIGN(auto cols,
                         MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "tags"));
    ASSERT_EQ(cols, (std::vector<int32_t>{0, 3, 7}));
}

// Normal: many fields each mapping to different physical columns
TEST(MapSharedShreddingUtilsTest, GetPhysicalColumnIndicesMultipleFields) {
    MapSharedShreddingFieldMeta meta;
    meta.name_to_id = {{"a", 0}, {"b", 1}, {"c", 2}};
    meta.field_to_columns = {{0, {0, 1}}, {1, {2, 3, 4}}, {2, {5}}};

    ASSERT_OK_AND_ASSIGN(auto cols_a, MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "a"));
    ASSERT_EQ(cols_a, (std::vector<int32_t>{0, 1}));

    ASSERT_OK_AND_ASSIGN(auto cols_b, MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "b"));
    ASSERT_EQ(cols_b, (std::vector<int32_t>{2, 3, 4}));

    ASSERT_OK_AND_ASSIGN(auto cols_c, MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "c"));
    ASSERT_EQ(cols_c, (std::vector<int32_t>{5}));
}

// Error: field name not found in name_to_id
TEST(MapSharedShreddingUtilsTest, GetPhysicalColumnIndicesFieldNotFound) {
    MapSharedShreddingFieldMeta meta;
    meta.name_to_id = {{"age", 0}};
    meta.field_to_columns = {{0, {1}}};

    ASSERT_NOK_WITH_MSG(MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "nonexistent"),
                        "cannot find field nonexistent in map shared shredding meta");
}

// Error: field name not found in empty meta
TEST(MapSharedShreddingUtilsTest, GetPhysicalColumnIndicesEmptyMeta) {
    MapSharedShreddingFieldMeta meta;
    ASSERT_NOK_WITH_MSG(MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "any"),
                        "cannot find field any in map shared shredding meta");
}

// Error: field id exists in name_to_id but is missing from field_to_columns
TEST(MapSharedShreddingUtilsTest, GetPhysicalColumnIndicesFieldIdMissingInFieldToColumns) {
    MapSharedShreddingFieldMeta meta;
    // "score" -> field_id 42, but field_to_columns has no entry for 42
    meta.name_to_id = {{"score", 42}};
    meta.field_to_columns = {};

    ASSERT_NOK_WITH_MSG(MapSharedShreddingUtils::GetPhysicalColumnIndices(meta, "score"),
                        "cannot find field id 42 in field_to_columns in map shared shredding meta");
}

TEST(MapSharedShreddingUtilsTest, IsOverflowField) {
    MapSharedShreddingFieldMeta meta;
    meta.name_to_id = {{"a", 0}, {"b", 1}, {"c", 2}};
    meta.overflow_field_set = {0, 2};

    ASSERT_OK_AND_ASSIGN(bool a_overflow, MapSharedShreddingUtils::IsOverflowField(meta, "a"));
    ASSERT_TRUE(a_overflow);

    ASSERT_OK_AND_ASSIGN(bool b_overflow, MapSharedShreddingUtils::IsOverflowField(meta, "b"));
    ASSERT_FALSE(b_overflow);

    ASSERT_OK_AND_ASSIGN(bool c_overflow, MapSharedShreddingUtils::IsOverflowField(meta, "c"));
    ASSERT_TRUE(c_overflow);

    ASSERT_NOK_WITH_MSG(MapSharedShreddingUtils::IsOverflowField(meta, "missing"),
                        "cannot find field missing in map shared shredding meta");
}

}  // namespace paimon::test
