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

eldenmoon pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new f5bb482e2f9 [improvement](variant) Remove BE configs that change 
Variant JSON parse errors (#68447)
f5bb482e2f9 is described below

commit f5bb482e2f9d27c548bcc3824a0e401314ca87d2
Author: lihangyu <[email protected]>
AuthorDate: Mon Oct 19 10:47:02 2026 +0800

    [improvement](variant) Remove BE configs that change Variant JSON parse 
errors (#68447)
---
 be/src/common/config.cpp                           |   2 -
 be/src/common/config.h                             |   4 -
 .../data_type_serde/data_type_variant_v2_serde.cpp |  12 +-
 .../exprs/function/parse/variant_string_parse.cpp  |  48 +------
 be/src/exprs/function/parse/variant_string_parse.h |   6 +-
 .../segment/variant/v2/variant_shredder.cpp        |  15 +-
 .../storage/segment/variant/v2/variant_shredder.h  |   1 -
 .../segment/variant/variant_writer_helpers.cpp     |   2 -
 be/test/core/column/column_variant_v2_test.cpp     |  14 +-
 ...type_variant_v2_serde_binary_roundtrip_test.cpp |   4 +-
 .../data_type_variant_v2_serde_input_test.cpp      |  50 +++----
 .../data_type_variant_v2_serde_output_test.cpp     |   5 +-
 .../function_variant_element_v2_encoded_test.cpp   |   5 +-
 .../exprs/function/function_variant_parse_test.cpp |  69 +++++----
 .../variant/variant_column_writer_reader_test.cpp  |  80 +++--------
 be/test/testutil/index_storage_test_util.cpp       |   4 +-
 be/test/testutil/index_storage_test_util.h         |   1 -
 be/test/util/variant/variant_field_test.cpp        |   4 +-
 be/test/util/variant/variant_string_parse_test.cpp |  75 ++--------
 .../data/variant_p0/duplicate_json_path.json       |   3 +-
 .../data/variant_p0/duplicate_json_path.out        |  12 ++
 .../data/variant_p0/load_invalid_json.out          |  16 +++
 .../data/variant_p0/v2/variant_parse_functions.out |   8 +-
 .../suites/variant_p0/column_name.groovy           |  15 +-
 .../suites/variant_p0/duplicate_json_path.groovy   | 157 ++++++++-------------
 .../suites/variant_p0/load_invalid_json.groovy     | 109 ++++++++++++++
 .../suites/variant_p0/v2/variant_compute_v2.groovy |  32 ++---
 .../variant_p0/v2/variant_parse_functions.groovy   |  85 +++++------
 28 files changed, 367 insertions(+), 471 deletions(-)

diff --git a/be/src/common/config.cpp b/be/src/common/config.cpp
index af6755e7f4d..1361137bae7 100644
--- a/be/src/common/config.cpp
+++ b/be/src/common/config.cpp
@@ -1208,8 +1208,6 @@ DEFINE_Bool(enable_debug_points, "false");
 DEFINE_Int32(pipeline_executor_size, "0");
 DEFINE_Int32(blocking_pipeline_executor_size, "0");
 DEFINE_mInt32(variant_max_json_key_length, "255");
-DEFINE_mBool(variant_throw_exeception_on_invalid_json, "false");
-DEFINE_mBool(variant_enable_duplicate_json_path_check, "false");
 // Controls storage-layer parse target for plain non-doc VARIANT columns:
 // 0 = auto, 1 = force parse-time subcolumns, 2 = force doc-value KV staging.
 // NestedGroup, deprecated flatten-nested, and persistent doc mode keep their 
required paths.
diff --git a/be/src/common/config.h b/be/src/common/config.h
index c32496630b8..5abf4867249 100644
--- a/be/src/common/config.h
+++ b/be/src/common/config.h
@@ -1628,10 +1628,6 @@ size_t get_lookup_connection_cache_block_pool_size();
 DECLARE_mInt64(LZ4_HC_compression_level);
 // Max json key length in bytes when parsing json into variant 
subcolumns/jsonb.
 DECLARE_mInt32(variant_max_json_key_length);
-// Treat invalid json format str as string, instead of throwing exception if 
false
-DECLARE_mBool(variant_throw_exeception_on_invalid_json);
-// Enable duplicate path check when parsing json into variant subcolumns/jsonb.
-DECLARE_mBool(variant_enable_duplicate_json_path_check);
 // Controls storage-layer parse target for plain non-doc VARIANT columns:
 // 0 = auto, 1 = force parse-time subcolumns, 2 = force doc-value KV staging.
 DECLARE_mInt32(variant_storage_parse_mode);
diff --git a/be/src/core/data_type_serde/data_type_variant_v2_serde.cpp 
b/be/src/core/data_type_serde/data_type_variant_v2_serde.cpp
index 6c183a8eb68..ef2d3a1536c 100644
--- a/be/src/core/data_type_serde/data_type_variant_v2_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_variant_v2_serde.cpp
@@ -586,12 +586,20 @@ void require_jsonb_write(bool written, const char* 
operation) {
     }
 }
 
+// Most JSON text read here was written by a Variant serde, for example into 
an aggregate state, so
+// it must read back every value a Variant can hold, including keys longer 
than the parse limit. A
+// CAST from a JSON string to ARRAY<VARIANT> or STRUCT also passes the 
elements it splits out.
+JsonToVariantOptions serde_json_options() {
+    return {.max_json_key_length = std::numeric_limits<uint32_t>::max(),
+            .throw_on_invalid_json = false};
+}
+
 } // namespace
 
 Status DataTypeVariantV2SerDe::deserialize_one_cell_from_json(IColumn& column, 
Slice& slice,
                                                               const 
FormatOptions&) const {
     RETURN_IF_CATCH_EXCEPTION({
-        JsonStringToVariantEncoder encoder;
+        JsonStringToVariantEncoder encoder(serde_json_options());
         encoder.add_json({slice.data, slice.size});
         VariantBatchBuilder block = encoder.finish_batch();
         destination(column).insert_encoded_batch(block);
@@ -634,7 +642,7 @@ Status 
DataTypeVariantV2SerDe::deserialize_column_from_json_vector(IColumn& colu
         if (slices.empty()) {
             return Status::OK();
         }
-        JsonStringToVariantEncoder encoder;
+        JsonStringToVariantEncoder encoder(serde_json_options());
         for (const Slice& slice : slices) {
             encoder.add_json({slice.data, slice.size});
         }
diff --git a/be/src/exprs/function/parse/variant_string_parse.cpp 
b/be/src/exprs/function/parse/variant_string_parse.cpp
index 1c24037747b..289b607fdc5 100644
--- a/be/src/exprs/function/parse/variant_string_parse.cpp
+++ b/be/src/exprs/function/parse/variant_string_parse.cpp
@@ -27,7 +27,6 @@
 #include <cstdint>
 #include <limits>
 #include <string_view>
-#include <unordered_set>
 #include <utility>
 
 #include "common/config.h"
@@ -171,44 +170,14 @@ private:
 
     void collect_object(const SimdJSONParser::Object& object, uint32_t depth) {
         auto scope = _builder.start_object();
-        if (!_options.check_duplicate_json_path) {
-            for (const auto& [key, child] : object) {
-                require_json_key_length(key, _options.max_json_key_length);
-                scope.add_key(to_string_ref(key));
-                collect(child, depth + 1);
-            }
-            scope.finish();
-            return;
-        }
-
-        std::unordered_set<std::string_view> seen_keys;
-        seen_keys.reserve(object.size());
         for (const auto& [key, child] : object) {
             require_json_key_length(key, _options.max_json_key_length);
-            if (seen_keys.emplace(key).second) {
-                scope.add_key(to_string_ref(key));
-                collect(child, depth + 1);
-            } else {
-                validate_ignored(child, depth + 1);
-            }
+            scope.add_key(to_string_ref(key));
+            collect(child, depth + 1);
         }
         scope.finish();
     }
 
-    void validate_ignored(SimdJSONParser::Element element, uint32_t depth) 
const {
-        variant_json::require_json_depth(depth);
-        if (element.isArray()) {
-            for (SimdJSONParser::Element child : element.getArray()) {
-                validate_ignored(child, depth + 1);
-            }
-        } else if (element.isObject()) {
-            for (const auto& [key, child] : element.getObject()) {
-                require_json_key_length(key, _options.max_json_key_length);
-                validate_ignored(child, depth + 1);
-            }
-        }
-    }
-
     VariantBatchBuilder::Row& _builder;
     const JsonToVariantOptions& _options;
 };
@@ -385,9 +354,7 @@ FormattedScalar format_json_uuid(const std::array<uint8_t, 
16>& value) {
 } // namespace variant_json
 
 JsonToVariantOptions JsonToVariantOptions::current_config() {
-    return {.max_json_key_length = 
static_cast<uint32_t>(config::variant_max_json_key_length),
-            .throw_on_invalid_json = 
config::variant_throw_exeception_on_invalid_json,
-            .check_duplicate_json_path = 
config::variant_enable_duplicate_json_path_check};
+    return {.max_json_key_length = 
static_cast<uint32_t>(config::variant_max_json_key_length)};
 }
 
 struct JsonStringToVariantEncoder::Impl {
@@ -417,15 +384,8 @@ struct JsonStringToVariantEncoder::Impl {
             throw Exception(ErrorCode::INVALID_ARGUMENT,
                             "Variant JSON input has a null data pointer");
         }
-        if (json.size == 0) {
-            auto object = row.start_object();
-            object.finish();
-            row.finish();
-            return;
-        }
-
         SimdJSONParser::Element root;
-        if (!parser.parse(json.data, json.size, root)) {
+        if (json.size == 0 || !parser.parse(json.data, json.size, root)) {
             if (options.throw_on_invalid_json) {
                 throw Exception(ErrorCode::INVALID_ARGUMENT, "Failed to parse 
JSON as Variant");
             }
diff --git a/be/src/exprs/function/parse/variant_string_parse.h 
b/be/src/exprs/function/parse/variant_string_parse.h
index 8fe10f73b64..a2e8fc75bde 100644
--- a/be/src/exprs/function/parse/variant_string_parse.h
+++ b/be/src/exprs/function/parse/variant_string_parse.h
@@ -41,8 +41,10 @@ struct VariantJsonFormatOptions {
 
 struct JsonToVariantOptions {
     uint32_t max_json_key_length = 255;
-    bool throw_on_invalid_json = false;
-    bool check_duplicate_json_path = false;
+    // Text that is not a JSON document, including an empty string, fails the 
row. The Variant
+    // serde turns this off because it mostly re-reads JSON that a Variant 
serde wrote: a value the
+    // parser cannot read back, such as an integer beyond 64 bits, then keeps 
its text.
+    bool throw_on_invalid_json = true;
 
     // Takes a value snapshot. Later changes to mutable config do not affect 
an encoder.
     static JsonToVariantOptions current_config();
diff --git a/be/src/storage/segment/variant/v2/variant_shredder.cpp 
b/be/src/storage/segment/variant/v2/variant_shredder.cpp
index fcb4e95aa41..e9164a513d7 100644
--- a/be/src/storage/segment/variant/v2/variant_shredder.cpp
+++ b/be/src/storage/segment/variant/v2/variant_shredder.cpp
@@ -157,16 +157,11 @@ struct VariantShredder::Impl {
         PathState& path_state = paths[path_index];
         const size_t row_marker = row + 1;
         if (path_state.last_row_marker == row_marker) {
-            if (!options.check_duplicate_json_path) {
-                return Status::InvalidArgument("may contains duplicated entry 
: {}",
-                                               path_state.path.get_path());
-            }
-            return Status::OK();
+            return Status::InvalidArgument("may contains duplicated entry : 
{}",
+                                           path_state.path.get_path());
         }
         path_state.last_row_marker = row_marker;
-        if (value.is_null()) {
-            return Status::OK();
-        }
+        DCHECK(!value.is_null());
         return get_or_create_builder(path_index)->append(value, row);
     }
 
@@ -209,9 +204,9 @@ struct VariantShredder::Impl {
 
     Status visit(VariantRef value, MetadataPathCache& metadata_cache, 
PathIndex path_index,
                  size_t row) {
+        // A null member occupies no path, so {"a":{"b":null},"a.b":3} does 
not collide.
         if (value.is_null()) {
-            return options.check_duplicate_json_path ? append_leaf(value, 
path_index, row)
-                                                     : Status::OK();
+            return Status::OK();
         }
         if (value.basic_type() != VariantBasicType::OBJECT) {
             return append_leaf(value, path_index, row);
diff --git a/be/src/storage/segment/variant/v2/variant_shredder.h 
b/be/src/storage/segment/variant/v2/variant_shredder.h
index 91b8c8b4944..b9bbb14daf9 100644
--- a/be/src/storage/segment/variant/v2/variant_shredder.h
+++ b/be/src/storage/segment/variant/v2/variant_shredder.h
@@ -54,7 +54,6 @@ struct VariantShredderOptions {
             BeConsts::DEFAULT_VARIANT_MAX_SPARSE_COLUMN_STATS_SIZE;
     uint32_t doc_bucket_count = 1;
     size_t doc_materialization_min_rows = 0;
-    bool check_duplicate_json_path = false;
 };
 
 struct VariantShreddedColumns {
diff --git a/be/src/storage/segment/variant/variant_writer_helpers.cpp 
b/be/src/storage/segment/variant/variant_writer_helpers.cpp
index ef7710bc1cc..f5cf7d9ab3b 100644
--- a/be/src/storage/segment/variant/variant_writer_helpers.cpp
+++ b/be/src/storage/segment/variant/variant_writer_helpers.cpp
@@ -25,7 +25,6 @@
 #include <utility>
 
 #include "common/cast_set.h"
-#include "common/config.h"
 #include "common/exception.h"
 #include "core/assert_cast.h"
 #include "core/column/column_nullable.h"
@@ -86,7 +85,6 @@ Status make_variant_shredder_options(const TabletSchema& 
tablet_schema,
                     cast_set<uint32_t>(std::max(1, 
parent_column.variant_doc_hash_shard_count())),
             .doc_materialization_min_rows = cast_set<size_t>(
                     std::max<int64_t>(0, 
parent_column.variant_doc_materialization_min_rows())),
-            .check_duplicate_json_path = 
config::variant_enable_duplicate_json_path_check,
     };
     *options = std::move(result);
     return Status::OK();
diff --git a/be/test/core/column/column_variant_v2_test.cpp 
b/be/test/core/column/column_variant_v2_test.cpp
index 0a20eafca37..fa1cb3dc289 100644
--- a/be/test/core/column/column_variant_v2_test.cpp
+++ b/be/test/core/column/column_variant_v2_test.cpp
@@ -81,9 +81,7 @@ namespace {
 using MetaIdsColumn = ColumnVector<TYPE_UINT32>;
 
 VariantField encode_json(std::string_view json) {
-    JsonStringToVariantEncoder encoder({.max_json_key_length = 255,
-                                        .throw_on_invalid_json = true,
-                                        .check_duplicate_json_path = false});
+    JsonStringToVariantEncoder encoder({.max_json_key_length = 255, 
.throw_on_invalid_json = true});
     encoder.add_json({json.data(), json.size()});
     VariantBatchBuilder block = encoder.finish_batch();
     return VariantField::from_ref(block.value_at(0));
@@ -1481,9 +1479,8 @@ TEST(ColumnVariantV2Test, 
InsertsCodecOwnedBatchesDirectly) {
     }
 
     {
-        JsonStringToVariantEncoder encoder({.max_json_key_length = 255,
-                                            .throw_on_invalid_json = true,
-                                            .check_duplicate_json_path = 
false});
+        JsonStringToVariantEncoder encoder(
+                {.max_json_key_length = 255, .throw_on_invalid_json = true});
         encoder.add_json(StringRef("7", 1));
         VariantBatchBuilder block = encoder.finish_batch();
         auto column = ColumnVariantV2::create();
@@ -1493,9 +1490,8 @@ TEST(ColumnVariantV2Test, 
InsertsCodecOwnedBatchesDirectly) {
     }
 
     {
-        JsonStringToVariantEncoder encoder({.max_json_key_length = 255,
-                                            .throw_on_invalid_json = true,
-                                            .check_duplicate_json_path = 
false});
+        JsonStringToVariantEncoder encoder(
+                {.max_json_key_length = 255, .throw_on_invalid_json = true});
         constexpr std::string_view FIRST = R"({"a":[1,{"b":true}]})";
         constexpr std::string_view SECOND = R"({"b":2,"a":[]})";
         encoder.add_json({FIRST.data(), FIRST.size()});
diff --git 
a/be/test/core/data_type_serde/data_type_variant_v2_serde_binary_roundtrip_test.cpp
 
b/be/test/core/data_type_serde/data_type_variant_v2_serde_binary_roundtrip_test.cpp
index 006007f3f90..4474b62969b 100644
--- 
a/be/test/core/data_type_serde/data_type_variant_v2_serde_binary_roundtrip_test.cpp
+++ 
b/be/test/core/data_type_serde/data_type_variant_v2_serde_binary_roundtrip_test.cpp
@@ -62,9 +62,7 @@ namespace doris {
 namespace {
 
 VariantField encode_json(std::string_view json) {
-    JsonStringToVariantEncoder encoder({.max_json_key_length = 255,
-                                        .throw_on_invalid_json = true,
-                                        .check_duplicate_json_path = false});
+    JsonStringToVariantEncoder encoder({.max_json_key_length = 255, 
.throw_on_invalid_json = true});
     encoder.add_json({json.data(), json.size()});
     VariantBatchBuilder block = encoder.finish_batch();
     return VariantField::from_ref(block.value_at(0));
diff --git 
a/be/test/core/data_type_serde/data_type_variant_v2_serde_input_test.cpp 
b/be/test/core/data_type_serde/data_type_variant_v2_serde_input_test.cpp
index 2bb8b74691c..9d126b93231 100644
--- a/be/test/core/data_type_serde/data_type_variant_v2_serde_input_test.cpp
+++ b/be/test/core/data_type_serde/data_type_variant_v2_serde_input_test.cpp
@@ -22,7 +22,6 @@
 #include <string_view>
 #include <vector>
 
-#include "common/config.h"
 #include "core/column/column_nullable.h"
 #include "core/column/column_string.h"
 #include "core/column/column_vector.h"
@@ -37,18 +36,6 @@
 namespace doris {
 namespace {
 
-class ScopedInvalidJsonMode {
-public:
-    explicit ScopedInvalidJsonMode(bool value)
-            : _old(config::variant_throw_exeception_on_invalid_json) {
-        config::variant_throw_exeception_on_invalid_json = value;
-    }
-    ~ScopedInvalidJsonMode() { 
config::variant_throw_exeception_on_invalid_json = _old; }
-
-private:
-    bool _old;
-};
-
 Status deserialize_json(const DataTypeVariantV2SerDe& serde, ColumnVariantV2& 
column,
                         std::string_view text) {
     Slice slice(text.data(), text.size());
@@ -91,30 +78,26 @@ ColumnVariantV2::MutablePtr typed_int(int32_t value) {
 TEST(DataTypeVariantV2SerdeInputTest, JsonUsesT15ValidEmptyAndInvalidPolicies) 
{
     DataTypeVariantV2SerDe serde;
     auto column = ColumnVariantV2::create();
-    {
-        ScopedInvalidJsonMode mode(false);
-        EXPECT_TRUE(deserialize_json(serde, *column, 
R"({"b":[1,null]})").ok());
-        EXPECT_TRUE(deserialize_json(serde, *column, {}).ok());
-        EXPECT_TRUE(deserialize_json(serde, *column, "not-json").ok());
-    }
-    ASSERT_EQ(column->size(), 3);
+    // The serde re-reads JSON that it wrote, so text the parser rejects keeps 
its text instead of
+    // failing, for example an integer beyond 64 bits in a serialized 
aggregate state.
+    EXPECT_TRUE(deserialize_json(serde, *column, R"({"b":[1,null]})").ok());
+    EXPECT_TRUE(deserialize_json(serde, *column, {}).ok());
+    EXPECT_TRUE(deserialize_json(serde, *column, "not-json").ok());
+    EXPECT_TRUE(deserialize_json(serde, *column, "18446744073709551616").ok());
+    ASSERT_EQ(column->size(), 4);
     EXPECT_EQ(json_at(serde, *column, 0), R"({"b":[1,null]})");
-    EXPECT_EQ(json_at(serde, *column, 1), "{}");
+    EXPECT_EQ(json_at(serde, *column, 1), R"("")");
     EXPECT_EQ(json_at(serde, *column, 2), R"("not-json")");
+    EXPECT_EQ(json_at(serde, *column, 3), R"("18446744073709551616")");
 
-    const size_t before = column->size();
-    {
-        ScopedInvalidJsonMode mode(true);
-        EXPECT_EQ(deserialize_json(serde, *column, "not-json").code(), 
ErrorCode::INVALID_ARGUMENT);
-    }
-    EXPECT_EQ(column->size(), before);
+    const std::string long_key(300, 'k');
+    const std::string long_key_json = R"({")" + long_key + R"(":1})";
+    ASSERT_TRUE(deserialize_json(serde, *column, long_key_json).ok());
+    EXPECT_EQ(json_at(serde, *column, 4), long_key_json);
 
+    const size_t before = column->size();
     const std::string invalid_utf8("\xC3\x28", 2);
-    {
-        ScopedInvalidJsonMode mode(false);
-        EXPECT_EQ(deserialize_json(serde, *column, invalid_utf8).code(),
-                  ErrorCode::INVALID_ARGUMENT);
-    }
+    EXPECT_EQ(deserialize_json(serde, *column, invalid_utf8).code(), 
ErrorCode::INVALID_ARGUMENT);
     EXPECT_EQ(column->size(), before);
 }
 
@@ -125,11 +108,10 @@ TEST(DataTypeVariantV2SerdeInputTest, 
VectorFailureIsAtomicAndCounterIsNotAdvanc
     const size_t before = column->size();
 
     std::string valid = R"({"a":1})";
-    std::string invalid = "{";
+    std::string invalid("\xC3\x28", 2);
     std::vector<Slice> slices {{valid.data(), valid.size()}, {invalid.data(), 
invalid.size()}};
     uint64_t num_deserialized = 0;
     DataTypeSerDe::FormatOptions options;
-    ScopedInvalidJsonMode mode(true);
     const Status status =
             serde.deserialize_column_from_json_vector(*column, slices, 
&num_deserialized, options);
     EXPECT_EQ(status.code(), ErrorCode::INVALID_ARGUMENT);
diff --git 
a/be/test/core/data_type_serde/data_type_variant_v2_serde_output_test.cpp 
b/be/test/core/data_type_serde/data_type_variant_v2_serde_output_test.cpp
index 10697c9645c..bf9be8ac2f2 100644
--- a/be/test/core/data_type_serde/data_type_variant_v2_serde_output_test.cpp
+++ b/be/test/core/data_type_serde/data_type_variant_v2_serde_output_test.cpp
@@ -99,9 +99,8 @@ ColumnPtr encoded_copy(const ColumnVariantV2& typed) {
 }
 
 ColumnVariantV2::MutablePtr 
encoded_json(std::initializer_list<std::string_view> rows) {
-    JsonStringToVariantEncoder encoder({.max_json_key_length = 1024,
-                                        .throw_on_invalid_json = true,
-                                        .check_duplicate_json_path = false});
+    JsonStringToVariantEncoder encoder(
+            {.max_json_key_length = 1024, .throw_on_invalid_json = true});
     for (std::string_view row : rows) {
         encoder.add_json({row.data(), row.size()});
     }
diff --git 
a/be/test/exprs/function/function_variant_element_v2_encoded_test.cpp 
b/be/test/exprs/function/function_variant_element_v2_encoded_test.cpp
index cd00f159121..39cba07078f 100644
--- a/be/test/exprs/function/function_variant_element_v2_encoded_test.cpp
+++ b/be/test/exprs/function/function_variant_element_v2_encoded_test.cpp
@@ -35,9 +35,8 @@ namespace {
 using Segment = VariantElementV2PathSegment;
 
 VariantField encode_json(std::string_view json) {
-    JsonStringToVariantEncoder encoder({.max_json_key_length = 1024,
-                                        .throw_on_invalid_json = true,
-                                        .check_duplicate_json_path = false});
+    JsonStringToVariantEncoder encoder(
+            {.max_json_key_length = 1024, .throw_on_invalid_json = true});
     encoder.add_json({json.data(), json.size()});
     VariantBatchBuilder block = encoder.finish_batch();
     return VariantField::from_ref(block.value_at(0));
diff --git a/be/test/exprs/function/function_variant_parse_test.cpp 
b/be/test/exprs/function/function_variant_parse_test.cpp
index 9cdfcbccc7c..7481dd17b43 100644
--- a/be/test/exprs/function/function_variant_parse_test.cpp
+++ b/be/test/exprs/function/function_variant_parse_test.cpp
@@ -177,7 +177,6 @@ TEST(FunctionVariantParseTest, 
PreservesSqlNullAndErrorToNull) {
 
     const DataTypePtr string_type = std::make_shared<DataTypeString>();
     const std::string invalid_utf8(1, static_cast<char>(0xFF));
-    ScopedValue strict(config::variant_throw_exeception_on_invalid_json, true);
     ExecutionResult failure = execute_parse(
             "parse_to_variant", make_strings({R"({"before":1})", invalid_utf8, 
R"({"after":2})"}),
             string_type, 3);
@@ -248,15 +247,22 @@ TEST(FunctionVariantParseTest, 
ConfiguredVariantReturnTypeBuildsAndExecutes) {
 
 TEST(FunctionVariantParseTest, DistinguishesSqlNullJsonNullEmptyAndConst) {
     const DataTypePtr string_type = std::make_shared<DataTypeString>();
-    ExecutionResult values = execute_parse(
-            "parse_to_variant", make_strings({R"({"a":1})", "null", ""}), 
string_type, 3);
+    ExecutionResult values =
+            execute_parse("parse_to_variant", make_strings({R"({"a":1})", 
"null"}), string_type, 2);
     ASSERT_TRUE(values.status.ok()) << values.status.to_string();
-    ASSERT_EQ(values.output->size(), 3);
+    ASSERT_EQ(values.output->size(), 2);
     EXPECT_FALSE(is_sql_null_at(values.output, 0));
     EXPECT_FALSE(is_sql_null_at(values.output, 1));
     EXPECT_EQ(variant_json_at(values.output, 0), R"({"a":1})");
     EXPECT_EQ(variant_json_at(values.output, 1), "null");
-    EXPECT_EQ(variant_json_at(values.output, 2), "{}");
+
+    // An empty string is not a JSON document.
+    ExecutionResult empty = execute_parse("parse_to_variant", 
make_strings({""}), string_type, 1);
+    EXPECT_EQ(empty.status.code(), ErrorCode::INVALID_ARGUMENT) << 
empty.status.to_string();
+    ExecutionResult empty_to_null =
+            execute_parse("try_parse_to_variant", make_strings({""}), 
string_type, 1);
+    ASSERT_TRUE(empty_to_null.status.ok()) << empty_to_null.status.to_string();
+    EXPECT_TRUE(is_sql_null_at(empty_to_null.output, 0));
 
     const DataTypePtr nullable_string_type = 
make_nullable(std::make_shared<DataTypeString>());
     ExecutionResult nullable = execute_parse(
@@ -279,7 +285,6 @@ TEST(FunctionVariantParseTest, 
DistinguishesSqlNullJsonNullEmptyAndConst) {
 }
 
 TEST(FunctionVariantParseTest, StrictFailureDoesNotPublishPartialBatch) {
-    ScopedValue strict(config::variant_throw_exeception_on_invalid_json, true);
     const DataTypePtr string_type = std::make_shared<DataTypeString>();
     ExecutionResult result =
             execute_parse("parse_to_variant",
@@ -297,26 +302,17 @@ TEST(FunctionVariantParseTest, 
StrictFailureDoesNotPublishPartialBatch) {
 
 TEST(FunctionVariantParseTest, ErrorToNullOnlyNullsRecoverableFailures) {
     const DataTypePtr string_type = std::make_shared<DataTypeString>();
-    {
-        ScopedValue strict(config::variant_throw_exeception_on_invalid_json, 
true);
-        ExecutionResult result = execute_parse(
-                "try_parse_to_variant",
-                make_strings({R"({"before":1})", "{", "null", 
R"({"after":2})"}), string_type, 4);
-        ASSERT_TRUE(result.status.ok()) << result.status.to_string();
-        EXPECT_FALSE(is_sql_null_at(result.output, 0));
-        EXPECT_TRUE(is_sql_null_at(result.output, 1));
-        EXPECT_FALSE(is_sql_null_at(result.output, 2));
-        EXPECT_EQ(variant_json_at(result.output, 2), "null");
-        EXPECT_EQ(variant_json_at(result.output, 3), R"({"after":2})");
-    }
-    {
-        ScopedValue 
permissive(config::variant_throw_exeception_on_invalid_json, false);
-        ExecutionResult result =
-                execute_parse("try_parse_to_variant", make_strings({"{"}), 
string_type, 1);
-        ASSERT_TRUE(result.status.ok()) << result.status.to_string();
-        EXPECT_FALSE(is_sql_null_at(result.output, 0));
-        EXPECT_EQ(variant_json_at(result.output, 0), R"("{")");
-    }
+    ExecutionResult result =
+            execute_parse("try_parse_to_variant",
+                          make_strings({R"({"before":1})", "{", "null", 
"hello", R"({"after":2})"}),
+                          string_type, 5);
+    ASSERT_TRUE(result.status.ok()) << result.status.to_string();
+    EXPECT_FALSE(is_sql_null_at(result.output, 0));
+    EXPECT_TRUE(is_sql_null_at(result.output, 1));
+    EXPECT_FALSE(is_sql_null_at(result.output, 2));
+    EXPECT_EQ(variant_json_at(result.output, 2), "null");
+    EXPECT_TRUE(is_sql_null_at(result.output, 3));
+    EXPECT_EQ(variant_json_at(result.output, 4), R"({"after":2})");
 }
 
 TEST(FunctionVariantParseTest, ConfiguredInputValidationUsesFailOrOuterNull) {
@@ -333,7 +329,6 @@ TEST(FunctionVariantParseTest, 
ConfiguredInputValidationUsesFailOrOuterNull) {
         EXPECT_TRUE(is_sql_null_at(null.output, 0));
     }
     {
-        ScopedValue 
reject_duplicates(config::variant_enable_duplicate_json_path_check, false);
         ExecutionResult fail = execute_parse("parse_to_variant", 
make_strings({R"({"a":1,"a":2})"}),
                                              string_type, 1);
         EXPECT_FALSE(fail.status.ok());
@@ -343,15 +338,6 @@ TEST(FunctionVariantParseTest, 
ConfiguredInputValidationUsesFailOrOuterNull) {
         EXPECT_TRUE(is_sql_null_at(null.output, 0));
     }
     {
-        ScopedValue 
keep_first(config::variant_enable_duplicate_json_path_check, true);
-        ExecutionResult result = execute_parse("try_parse_to_variant",
-                                               
make_strings({R"({"a":1,"a":2})"}), string_type, 1);
-        ASSERT_TRUE(result.status.ok()) << result.status.to_string();
-        EXPECT_FALSE(is_sql_null_at(result.output, 0));
-        EXPECT_EQ(variant_json_at(result.output, 0), R"({"a":1})");
-    }
-    {
-        ScopedValue 
permissive(config::variant_throw_exeception_on_invalid_json, false);
         const std::string invalid_utf8(1, static_cast<char>(0xFF));
         ExecutionResult fail =
                 execute_parse("parse_to_variant", 
make_strings({invalid_utf8}), string_type, 1);
@@ -361,6 +347,17 @@ TEST(FunctionVariantParseTest, 
ConfiguredInputValidationUsesFailOrOuterNull) {
         ASSERT_TRUE(null.status.ok()) << null.status.to_string();
         EXPECT_TRUE(is_sql_null_at(null.output, 0));
     }
+    {
+        // The DOM parser cannot read an integer beyond 64 bits, so the 
document is invalid JSON.
+        ExecutionResult fail = execute_parse(
+                "parse_to_variant", make_strings({"18446744073709551616"}), 
string_type, 1);
+        EXPECT_FALSE(fail.status.ok());
+        ExecutionResult null =
+                execute_parse("try_parse_to_variant",
+                              make_strings({R"({"n":18446744073709551616})"}), 
string_type, 1);
+        ASSERT_TRUE(null.status.ok()) << null.status.to_string();
+        EXPECT_TRUE(is_sql_null_at(null.output, 0));
+    }
     {
         const std::string too_deep = 
nested_array_json(VARIANT_MAX_NESTING_DEPTH + 1);
         ExecutionResult fail =
diff --git a/be/test/storage/variant/variant_column_writer_reader_test.cpp 
b/be/test/storage/variant/variant_column_writer_reader_test.cpp
index a5a011f84b4..423152955ed 100644
--- a/be/test/storage/variant/variant_column_writer_reader_test.cpp
+++ b/be/test/storage/variant/variant_column_writer_reader_test.cpp
@@ -121,20 +121,6 @@ static std::string variant_writer_test_name(
     return variant_writer_input_name(info.param);
 }
 
-class ScopedDuplicateJsonPathCheck {
-public:
-    explicit ScopedDuplicateJsonPathCheck(bool enabled)
-            : _old_value(config::variant_enable_duplicate_json_path_check) {
-        config::variant_enable_duplicate_json_path_check = enabled;
-    }
-    ~ScopedDuplicateJsonPathCheck() {
-        config::variant_enable_duplicate_json_path_check = _old_value;
-    }
-
-private:
-    bool _old_value;
-};
-
 static Status create_variant_writer_source(VariantWriterInput input,
                                            const std::vector<std::string>& 
jsons,
                                            int max_subcolumns_count, bool 
enable_doc_mode,
@@ -2168,7 +2154,6 @@ TEST_P(VariantWriterCompatibilityTest, 
materialized_array_preserves_middle_row_g
 }
 
 TEST_P(VariantWriterCompatibilityTest, 
dotted_and_nested_paths_follow_writer_semantics) {
-    ScopedDuplicateJsonPathCheck duplicate_path_check(false);
     init_variant_tablet(10015 + static_cast<int>(GetParam()), 2);
 
     const std::vector<std::string> jsons {
@@ -2201,11 +2186,15 @@ TEST_P(VariantWriterCompatibilityTest, 
dotted_and_nested_paths_follow_writer_sem
                       }));
 }
 
-TEST_P(VariantWriterCompatibilityTest, 
dotted_null_path_is_first_when_duplicate_check_enabled) {
-    ScopedDuplicateJsonPathCheck duplicate_path_check(true);
+TEST_P(VariantWriterCompatibilityTest, 
dotted_and_nested_paths_without_collision_are_written) {
     init_variant_tablet(10019 + static_cast<int>(GetParam()), 2);
 
-    const std::vector<std::string> jsons {R"({"a":{"b":null},"a.b":5})"};
+    // A null member occupies no path, and a dotted key whose children differ 
from the nested
+    // object's children reaches different paths, so neither row collides.
+    const std::vector<std::string> jsons {
+            R"({"a.b":null,"a":{"b":5}})",
+            R"({"a.b":{"c":8},"a":{"b":{"d":9}}})",
+    };
     ColumnPtr source;
     DataTypePtr source_type;
     ASSERT_TRUE(create_variant_writer_source(GetParam(), jsons, 2, false, {}, 
&source, &source_type)
@@ -2214,12 +2203,17 @@ TEST_P(VariantWriterCompatibilityTest, 
dotted_null_path_is_first_when_duplicate_
     SegmentFooterPB footer;
     std::string file_path;
     const std::string rowset_id =
-            "shared_dotted_null_first_" + 
variant_writer_input_name(GetParam());
-    ASSERT_TRUE(write_variant_segment(source, source_type, rowset_id, &footer, 
&file_path).ok());
+            "shared_dotted_without_collision_" + 
variant_writer_input_name(GetParam());
+    const Status write_status =
+            write_variant_segment(source, source_type, rowset_id, &footer, 
&file_path);
+    ASSERT_TRUE(write_status.ok()) << write_status;
 
     std::vector<std::optional<std::string>> actual;
     ASSERT_TRUE(read_variant_root_rows(footer, file_path, &actual).ok());
-    EXPECT_EQ(actual, (std::vector<std::optional<std::string>> {"{}"}));
+    EXPECT_EQ(actual, (std::vector<std::optional<std::string>> {
+                              R"({"a":{"b":5}})",
+                              R"({"a":{"b":{"c":8,"d":9}}})",
+                      }));
 }
 
 TEST_P(VariantWriterCompatibilityTest, nullable_round_trip) {
@@ -2301,47 +2295,7 @@ TEST_P(VariantWriterCompatibilityTest, 
doc_mode_round_trip) {
     }
 }
 
-TEST_F(VariantColumnWriterReaderTest, 
test_write_column_variant_v2_duplicate_dotted_paths) {
-    ScopedDuplicateJsonPathCheck duplicate_path_check(true);
-    init_variant_tablet(10003, 2);
-
-    const std::vector<std::string> jsons {
-            R"({"a.b":1,"a":{"b":2}})",
-            R"({"a":{"b":3},"a.b":4})",
-            R"({"a.b":null,"a":{"b":5}})",
-            R"({"a.b":6})",
-            R"({"a":{"b":7}})",
-            R"({"a.b":{"c":8},"a":{"b":{"d":9}}})",
-    };
-    auto source = ColumnVariantV2::create();
-    DataTypeVariantV2SerDe serde;
-    DataTypeSerDe::FormatOptions serde_options;
-    for (const auto& json : jsons) {
-        Slice slice(json.data(), json.size());
-        ASSERT_TRUE(serde.deserialize_one_cell_from_json(*source, slice, 
serde_options).ok());
-    }
-
-    SegmentFooterPB footer;
-    std::string file_path;
-    const auto source_type = std::make_shared<DataTypeVariantV2>(2, false);
-    const Status write_status = write_variant_segment(source->get_ptr(), 
source_type,
-                                                      "v2_duplicate_paths", 
&footer, &file_path, 3);
-    ASSERT_TRUE(write_status.ok()) << write_status;
-
-    std::vector<std::optional<std::string>> actual;
-    ASSERT_TRUE(read_variant_root_rows(footer, file_path, &actual).ok());
-    // ColumnVariantV2 stores object entries in metadata field-id order, so 
the nested form is the
-    // first canonical leaf for a dotted-path collision regardless of the 
source JSON key order.
-    const std::vector<std::optional<std::string>> expected {
-            R"({"a":{"b":2}})", R"({"a":{"b":3}})", R"({"a":{"b":5}})",
-            R"({"a":{"b":6}})", R"({"a":{"b":7}})", 
R"({"a":{"b":{"c":8,"d":9}}})",
-    };
-    EXPECT_EQ(actual, expected);
-}
-
-TEST_F(VariantColumnWriterReaderTest,
-       
test_write_column_variant_v2_rejects_duplicate_dotted_paths_without_check) {
-    ScopedDuplicateJsonPathCheck duplicate_path_check(false);
+TEST_F(VariantColumnWriterReaderTest, 
test_write_column_variant_v2_rejects_duplicate_dotted_paths) {
     init_variant_tablet(10008, 2);
 
     auto source = ColumnVariantV2::create();
@@ -3339,7 +3293,6 @@ TEST_F(VariantColumnWriterReaderTest, 
v2_shredder_uses_only_rows_in_requested_ra
     segment_v2::VariantShredder shredder({
             .max_subcolumns_count = 1,
             .sparse_bucket_count = 1,
-            .check_duplicate_json_path = 
config::variant_enable_duplicate_json_path_check,
     });
     ASSERT_TRUE(shredder.append(combined->read_view(), 1, 1).ok());
     segment_v2::VariantShreddedColumns shredded;
@@ -3416,7 +3369,6 @@ TEST_F(VariantColumnWriterReaderTest, 
v2_shredder_drops_typed_cast_null_from_spa
             .max_subcolumns_count = 1,
             .typed_paths_to_sparse = true,
             .sparse_bucket_count = 1,
-            .check_duplicate_json_path = 
config::variant_enable_duplicate_json_path_check,
     });
     ASSERT_TRUE(shredder.append(source->read_view(), 0, source->size()).ok());
     segment_v2::VariantShreddedColumns shredded;
diff --git a/be/test/testutil/index_storage_test_util.cpp 
b/be/test/testutil/index_storage_test_util.cpp
index 944dd568b33..ee7fa90ee04 100644
--- a/be/test/testutil/index_storage_test_util.cpp
+++ b/be/test/testutil/index_storage_test_util.cpp
@@ -406,9 +406,7 @@ Status fill_variant_column(const VariantColumnSpec& 
column_spec, const IndexBatc
                            size_t column_pos, MutableColumnPtr* output) {
     ensure_variant_json_shape(batch, column_pos);
     auto variant_column = ColumnVariantV2::create();
-    JsonStringToVariantEncoder encoder(
-            {.throw_on_invalid_json = true,
-             .check_duplicate_json_path = batch.check_duplicate_json_path});
+    JsonStringToVariantEncoder encoder({.throw_on_invalid_json = true});
     for (const auto& json : batch.variant_jsons_by_column[column_pos]) {
         encoder.add_json({json.data(), json.size()});
     }
diff --git a/be/test/testutil/index_storage_test_util.h 
b/be/test/testutil/index_storage_test_util.h
index 873ce5db4fc..4715fa897d4 100644
--- a/be/test/testutil/index_storage_test_util.h
+++ b/be/test/testutil/index_storage_test_util.h
@@ -115,7 +115,6 @@ struct IndexBatch {
     std::vector<std::vector<std::string>> variant_jsons_by_column;
     std::vector<ColumnPtr> variant_columns_by_column;
     bool deprecated_enable_flatten_nested = false;
-    bool check_duplicate_json_path = false;
 
     static IndexBatch single_text(std::vector<std::string> values, int32_t 
first_key = 0);
     static IndexBatch single_variant(std::vector<std::string> jsons, int32_t 
first_key = 0);
diff --git a/be/test/util/variant/variant_field_test.cpp 
b/be/test/util/variant/variant_field_test.cpp
index 9e932f784f2..ef90da9d01a 100644
--- a/be/test/util/variant/variant_field_test.cpp
+++ b/be/test/util/variant/variant_field_test.cpp
@@ -213,9 +213,7 @@ void expect_encode_and_decode_preservation(const 
std::string& metadata_bytes,
 }
 
 VariantField encode_json(std::string_view json) {
-    JsonStringToVariantEncoder encoder({.max_json_key_length = 255,
-                                        .throw_on_invalid_json = true,
-                                        .check_duplicate_json_path = false});
+    JsonStringToVariantEncoder encoder({.max_json_key_length = 255, 
.throw_on_invalid_json = true});
     encoder.add_json({json.data(), json.size()});
     VariantBatchBuilder block = encoder.finish_batch();
     return VariantField::from_ref(block.value_at(0));
diff --git a/be/test/util/variant/variant_string_parse_test.cpp 
b/be/test/util/variant/variant_string_parse_test.cpp
index d34f5057395..b3cb5db74bb 100644
--- a/be/test/util/variant/variant_string_parse_test.cpp
+++ b/be/test/util/variant/variant_string_parse_test.cpp
@@ -280,64 +280,16 @@ TEST(VariantJsonTest, 
EncoderCopiesRowsBeforeParserReuseAndProducesCanonicalValu
     EXPECT_TRUE(canonical_equals(block.value_at(0), reparsed.value_at(0)));
 }
 
-TEST(VariantJsonTest, DuplicateKeysKeepFirstCompleteSubtreeWhenEnabled) {
-    JsonToVariantOptions options;
-    options.check_duplicate_json_path = true;
-    VariantBatchBuilder block = encode_jsons(
-            {R"({"a":1,"a":[2]})", R"({"a":[1],"a":2})", 
R"({"a":{"b":1},"a":{"c":2}})"}, options);
-    ASSERT_EQ(block.num_rows(), 3);
-    EXPECT_EQ(print_json(block.value_at(0)), R"({"a":1})");
-    EXPECT_EQ(print_json(block.value_at(1)), R"({"a":[1]})");
-    EXPECT_EQ(print_json(block.value_at(2)), R"({"a":{"b":1}})");
-
-    // This is member-level first-wins semantics. It deliberately removes the 
old PathInData
-    // flatten/merge artifact; it is not evidence that the broader T0.2 
comparison is complete.
-    VariantBatchBuilder dotted = encode_jsons({R"({"a.b":1,"a":{"b":2}})"}, 
options);
-    EXPECT_EQ(print_json(dotted.value_at(0)), R"({"a":{"b":2},"a.b":1})");
-}
-
-TEST(VariantJsonTest, DuplicateKeysFailWithoutPerObjectHashWhenDisabled) {
-    JsonToVariantOptions options;
-    options.check_duplicate_json_path = false;
-    expect_exception_code(ErrorCode::INVALID_ARGUMENT,
-                          [&] { 
static_cast<void>(encode_jsons({R"({"a":1,"a":2})"}, options)); });
-}
-
-TEST(VariantJsonTest, 
RepeatedDuplicateSchemasStayStableAcrossSchemaAndScalarRows) {
-    JsonToVariantOptions options;
-    options.check_duplicate_json_path = true;
-    VariantBatchBuilder block =
-            encode_jsons({R"({"a":1,"a":2})", R"({"a":3,"a":4})", "null", 
R"({"a":5,"a":6})",
-                          R"({"b":7,"b":8})", R"({"b":9,"b":10})"},
-                         options);
-    ASSERT_EQ(block.num_rows(), 6);
-    EXPECT_EQ(print_json(block.value_at(0)), R"({"a":1})");
-    EXPECT_EQ(print_json(block.value_at(1)), R"({"a":3})");
-    EXPECT_TRUE(block.value_at(2).is_null());
-    EXPECT_EQ(print_json(block.value_at(3)), R"({"a":5})");
-    EXPECT_EQ(print_json(block.value_at(4)), R"({"b":7})");
-    EXPECT_EQ(print_json(block.value_at(5)), R"({"b":9})");
-    std::vector<VariantRef> rows;
-    rows.reserve(block.num_rows());
-    for (size_t index = 0; index < block.num_rows(); ++index) {
-        rows.push_back(block.value_at(index));
+TEST(VariantJsonTest, DuplicateKeysFail) {
+    for (const std::string_view json :
+         {R"({"a":1,"a":2})", R"({"a":1,"a":[2]})", 
R"({"a":{"b":1},"a":{"c":2}})"}) {
+        expect_exception_code(ErrorCode::INVALID_ARGUMENT,
+                              [&] { static_cast<void>(encode_jsons({json})); 
});
     }
-    validate_canonical(block.metadata_ref(), rows);
-}
-
-TEST(VariantJsonTest, IgnoredDuplicateSubtreeStillValidatesKeysAndDepth) {
-    JsonToVariantOptions options;
-    options.max_json_key_length = 255;
-    options.check_duplicate_json_path = true;
-    const std::string oversized_key(256, 'k');
-    const std::string oversized = R"({"a":1,"a":{")" + oversized_key + 
R"(":2}})";
-    expect_exception_code(ErrorCode::INVALID_ARGUMENT,
-                          [&] { static_cast<void>(encode_jsons({oversized}, 
options)); });
 
-    const std::string too_deep =
-            R"({"a":1,"a":)" + nested_array_json(VARIANT_MAX_NESTING_DEPTH) + 
"}";
-    expect_exception_code(ErrorCode::INVALID_ARGUMENT,
-                          [&] { static_cast<void>(encode_jsons({too_deep}, 
options)); });
+    // A dotted key and a nested key are different members of a parsed value.
+    VariantBatchBuilder dotted = encode_jsons({R"({"a.b":1,"a":{"b":2}})"});
+    EXPECT_EQ(print_json(dotted.value_at(0)), R"({"a":{"b":2},"a.b":1})");
 }
 
 TEST(VariantJsonTest, KeyLengthBoundaryIsMeasuredInUtf8Bytes) {
@@ -357,13 +309,15 @@ TEST(VariantJsonTest, 
EmptyAndInvalidInputFollowExplicitPolicy) {
     permissive.throw_on_invalid_json = false;
     VariantBatchBuilder block = encode_jsons({std::string_view {}, "{"}, 
permissive);
     ASSERT_EQ(block.num_rows(), 2);
-    EXPECT_EQ(print_json(block.value_at(0)), "{}");
+    EXPECT_EQ(print_json(block.value_at(0)), R"("")");
     EXPECT_EQ(print_json(block.value_at(1)), R"("{")");
 
-    JsonToVariantOptions strict;
-    strict.throw_on_invalid_json = true;
+    const JsonToVariantOptions strict;
+    EXPECT_TRUE(strict.throw_on_invalid_json);
     expect_exception_code(ErrorCode::INVALID_ARGUMENT,
                           [&] { static_cast<void>(encode_jsons({"{"}, 
strict)); });
+    expect_exception_code(ErrorCode::INVALID_ARGUMENT,
+                          [&] { 
static_cast<void>(encode_jsons({std::string_view {}}, strict)); });
 
     const std::string invalid_utf8(1, static_cast<char>(0xFF));
     expect_exception_code(ErrorCode::INVALID_ARGUMENT, [&] {
@@ -518,8 +472,7 @@ TEST(VariantJsonTest, 
CurrentConfigIsSnapshottedAndExplicitOptionsAreValidated)
     const JsonToVariantOptions options = 
JsonToVariantOptions::current_config();
     EXPECT_EQ(options.max_json_key_length,
               static_cast<uint32_t>(config::variant_max_json_key_length));
-    EXPECT_EQ(options.throw_on_invalid_json, 
config::variant_throw_exeception_on_invalid_json);
-    EXPECT_EQ(options.check_duplicate_json_path, 
config::variant_enable_duplicate_json_path_check);
+    EXPECT_TRUE(options.throw_on_invalid_json);
 
     JsonToVariantOptions invalid;
     invalid.max_json_key_length = 0;
diff --git a/regression-test/data/variant_p0/duplicate_json_path.json 
b/regression-test/data/variant_p0/duplicate_json_path.json
index e065c9b2331..378e90371fd 100644
--- a/regression-test/data/variant_p0/duplicate_json_path.json
+++ b/regression-test/data/variant_p0/duplicate_json_path.json
@@ -1,7 +1,6 @@
 {"k":8,"v":{"a":42,"a":{"b":42}}}
 {"k":9,"v":{"a":123,"a":"123"}}
-{"k":10,"v":{"a.b":8,"a":{"b":9}}}
-{"k":11,"v":{"a":{"b":10},"a.b":11}}
 {"k":12,"v":{"a":{"b":11},"a":{"c":12}}}
 {"k":13,"v":{"a":[13],"a":14}}
 {"k":14,"v":{"a":14,"a":[13]}}
+{"k":15,"v":{"a":{"b":15},"c":16}}
diff --git a/regression-test/data/variant_p0/duplicate_json_path.out 
b/regression-test/data/variant_p0/duplicate_json_path.out
new file mode 100644
index 00000000000..fd763ded85d
--- /dev/null
+++ b/regression-test/data/variant_p0/duplicate_json_path.out
@@ -0,0 +1,12 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !rows --
+12     \N      true
+13     \N      true
+14     \N      true
+15     {"a":{"b":15},"c":16}   false
+2      \N      true
+3      \N      true
+4      {"a":{"b":5},"c":6}     false
+8      \N      true
+9      \N      true
+
diff --git a/regression-test/data/variant_p0/load_invalid_json.out 
b/regression-test/data/variant_p0/load_invalid_json.out
new file mode 100644
index 00000000000..fe61c68613d
--- /dev/null
+++ b/regression-test/data/variant_p0/load_invalid_json.out
@@ -0,0 +1,16 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !rows --
+1      {"a":1} object
+11     {"a":11}        object
+12     \N      \N
+13     \N      \N
+14     \N      \N
+2      {"a":2} object
+3      123     tinyint
+4      \N      \N
+5      \N      \N
+6      \N      \N
+
+-- !not_null --
+1      {"a":1}
+
diff --git a/regression-test/data/variant_p0/v2/variant_parse_functions.out 
b/regression-test/data/variant_p0/v2/variant_parse_functions.out
index c99fe822150..7ee80984502 100644
--- a/regression-test/data/variant_p0/v2/variant_parse_functions.out
+++ b/regression-test/data/variant_p0/v2/variant_parse_functions.out
@@ -8,10 +8,7 @@
 3      false   false   false   1       1
 
 -- !strict_error_to_null --
-true   false
-
--- !permissive_invalid_json_is_string --
-{      false   {       false
+true   true    true
 
 -- !key_too_long_error_to_null --
 true
@@ -19,6 +16,3 @@ true
 -- !duplicate_key_error_to_null --
 true
 
--- !duplicate_key_first_wins --
-{"dup":1}      {"dup":1}
-
diff --git a/regression-test/suites/variant_p0/column_name.groovy 
b/regression-test/suites/variant_p0/column_name.groovy
index 549184adcdc..cc097cc40cc 100644
--- a/regression-test/suites/variant_p0/column_name.groovy
+++ b/regression-test/suites/variant_p0/column_name.groovy
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-suite("regression_test_variant_column_name", "p0, nonConcurrent"){
+suite("regression_test_variant_column_name", "p0"){
     def variantV2Function = "parse_to_variant"
     def table_name = "var_column_name"
     sql "DROP TABLE IF EXISTS ${table_name}"
@@ -73,14 +73,11 @@ suite("regression_test_variant_column_name", "p0, 
nonConcurrent"){
     """
 
     // name with `.`
-    // When parser-side duplicate path deduplication is disabled, dotted keys 
and nested paths
-    // collide during Variant materialization and should report duplicated 
entry.
-    setBeConfigTemporary([variant_enable_duplicate_json_path_check: false]) {
-        sql "truncate table var_column_name"
-        test {
-            sql """insert into var_column_name values (7, 
${variantV2Function}('{"a.b": "UPPER CASE", "a" : {"b" : 123}}'))"""
-            exception "may contains duplicated entry"
-        }
+    // A dotted key and a nested key collide when the value is stored and fail 
the write.
+    sql "truncate table var_column_name"
+    test {
+        sql """insert into var_column_name values (7, 
${variantV2Function}('{"a.b": "UPPER CASE", "a" : {"b" : 123}}'))"""
+        exception "may contains duplicated entry"
     }
     for (int i = 0; i < 7; i++) {
         sql """insert into var_column_name select * from var_column_name"""
diff --git a/regression-test/suites/variant_p0/duplicate_json_path.groovy 
b/regression-test/suites/variant_p0/duplicate_json_path.groovy
index bba9caf67f5..3c6aac3e8bc 100644
--- a/regression-test/suites/variant_p0/duplicate_json_path.groovy
+++ b/regression-test/suites/variant_p0/duplicate_json_path.groovy
@@ -16,110 +16,75 @@
 // under the License.
 
 suite("duplicate_json_path", "p0") {
-    def variantV2Function = "parse_to_variant"
-    def customBeConfig = [
-        variant_enable_duplicate_json_path_check: true
-    ]
-    setBeConfigTemporary(customBeConfig) {
-        sql "DROP TABLE IF EXISTS duplicate_json_path"
-        sql """
-            CREATE TABLE duplicate_json_path (
-                k int,
-                v variant
-            )
-            DUPLICATE KEY(k)
-            DISTRIBUTED BY HASH(k) BUCKETS 1
-            PROPERTIES (
-                "replication_num" = "1",
-                "group_commit_interval_ms" = "2000",
-                "disable_auto_compaction" = "true"
-            );
-        """
+    sql "DROP TABLE IF EXISTS duplicate_json_path"
+    sql """
+        CREATE TABLE duplicate_json_path (
+            k int,
+            v variant
+        )
+        DUPLICATE KEY(k)
+        DISTRIBUTED BY HASH(k) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1"
+        );
+    """
 
-        sql """insert into duplicate_json_path values (1, 
${variantV2Function}('{"a":42,"a":{"b":42}}'))"""
-        sql """insert into duplicate_json_path values (2, 
${variantV2Function}('{"a" : 123, "a" : "123"}'))"""
-        sql """insert into duplicate_json_path values (3, 
${variantV2Function}('{"a.b":1,"a":{"b":2}}'))"""
-        sql """insert into duplicate_json_path values (4, 
${variantV2Function}('{"a":{"b":3},"a.b":4}'))"""
-        sql """insert into duplicate_json_path values (5, 
${variantV2Function}('{"a":{"b":5},"a":{"c":6}}'))"""
-        sql """insert into duplicate_json_path values (6, 
${variantV2Function}('{"a":[1],"a":2}'))"""
-        sql """insert into duplicate_json_path values (7, 
${variantV2Function}('{"a":2,"a":[1]}'))"""
+    // A key that repeats in one object is a parse error: parse_to_variant 
fails and
+    // try_parse_to_variant returns NULL.
+    test {
+        sql """insert into duplicate_json_path values (1, 
parse_to_variant('{"a":42,"a":{"b":42}}'))"""
+        exception "Duplicate Variant object key"
+    }
+    sql """insert into duplicate_json_path values
+        (2, try_parse_to_variant('{"a" : 123, "a" : "123"}')),
+        (3, try_parse_to_variant('{"a":{"b":5},"a":{"c":6}}')),
+        (4, try_parse_to_variant('{"a":{"b":5},"c":6}'))"""
 
-        streamLoad {
-            table "duplicate_json_path"
-            set 'read_json_by_line', 'true'
-            set 'format', 'json'
-            set 'group_commit', 'async_mode'
-            unset 'label'
-            file 'duplicate_json_path.json'
-            time 10000
+    // A dotted key and a nested key are different keys of the parsed value, 
but they are the
+    // same path when the value is stored, so the write fails.
+    test {
+        sql """insert into duplicate_json_path values (5, 
parse_to_variant('{"a.b":1,"a":{"b":2}}'))"""
+        exception "may contains duplicated entry"
+    }
 
-            check { result, exception, startTime, endTime ->
-                if (exception != null) {
-                    throw exception
-                }
-                def json = parseJson(result)
-                assertEquals("success", json.Status.toLowerCase())
-                assertEquals(7, json.NumberTotalRows)
-                assertEquals(7, json.NumberLoadedRows)
-            }
-        }
+    // Load jobs parse like try_parse_to_variant, so a row with duplicate keys 
loads NULL.
+    streamLoad {
+        table "duplicate_json_path"
+        set 'read_json_by_line', 'true'
+        set 'format', 'json'
+        file 'duplicate_json_path.json'
+        time 10000
 
-        for (int i = 0; i < 30; i++) {
-            def count = sql "select count(*) from duplicate_json_path"
-            if (count[0][0] == 14) {
-                break
+        check { result, exception, startTime, endTime ->
+            if (exception != null) {
+                throw exception
             }
-            sleep(1000)
+            def json = parseJson(result)
+            assertEquals("success", json.Status.toLowerCase())
+            assertEquals(6, json.NumberTotalRows)
+            assertEquals(6, json.NumberLoadedRows)
         }
-        def totalRows = sql "select count(*) from duplicate_json_path"
-        assertEquals(14, totalRows[0][0])
+    }
 
-        // When duplicate path check is enabled, duplicate Variant paths keep 
the first value.
-        def expectedResultV1 = [
-                [1, "{\"b\":42}", "42", null],
-                [2, "123", null, null],
-                [3, "{\"b\":1}", "1", null],
-                [4, "{\"b\":3}", "3", null],
-                [5, "{\"b\":5,\"c\":6}", "5", "6"],
-                [6, "[1]", null, null],
-                [7, "2", null, null],
-                [8, "{\"b\":42}", "42", null],
-                [9, "123", null, null],
-                [10, "{\"b\":8}", "8", null],
-                [11, "{\"b\":10}", "10", null],
-                [12, "{\"b\":11,\"c\":12}", "11", "12"],
-                [13, "[13]", null, null],
-                [14, "14", null, null]
-        ]
-        def expectedResultV2 = [
-                [1, "42", null, null],
-                [2, "123", null, null],
-                [3, "{\"b\":2}", "2", null],
-                [4, "{\"b\":3}", "3", null],
-                [5, "{\"b\":5}", "5", null],
-                [6, "[1]", null, null],
-                [7, "2", null, null],
-                [8, "42", null, null],
-                [9, "123", null, null],
-                [10, "{\"b\":9}", "9", null],
-                [11, "{\"b\":10}", "10", null],
-                [12, "{\"b\":11}", "11", null],
-                [13, "[13]", null, null],
-                [14, "14", null, null]
-        ]
-        def expectedResult = true
-                ? expectedResultV2 : expectedResultV1
+    // A dotted-key collision is found when the value is stored, not when it 
is parsed, so it
+    // fails the whole load instead of loading NULL.
+    streamLoad {
+        table "duplicate_json_path"
+        set 'read_json_by_line', 'true'
+        set 'format', 'json'
+        inputText '''{"k":16,"v":{"a":1}}
+{"k":17,"v":{"a.b":8,"a":{"b":9}}}'''
+        time 10000
 
-        def queryResult = {
-            sql """
-            select k, cast(v['a'] as string), cast(v['a']['b'] as string), 
cast(v['a']['c'] as string)
-            from duplicate_json_path
-            order by k
-            """
+        check { result, exception, startTime, endTime ->
+            if (exception != null) {
+                throw exception
+            }
+            def json = parseJson(result)
+            assertEquals("fail", json.Status.toLowerCase())
+            assertTrue(json.Message.contains("may contains duplicated entry"), 
json.Message)
         }
-        assertEquals(expectedResult, queryResult())
-
-        trigger_and_wait_compaction("duplicate_json_path", "full")
-        assertEquals(expectedResult, queryResult())
     }
+
+    order_qt_rows """select k, v, v is null from duplicate_json_path order by 
k"""
 }
diff --git a/regression-test/suites/variant_p0/load_invalid_json.groovy 
b/regression-test/suites/variant_p0/load_invalid_json.groovy
new file mode 100644
index 00000000000..78e5f04f908
--- /dev/null
+++ b/regression-test/suites/variant_p0/load_invalid_json.groovy
@@ -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.
+
+// A load job parses a string field into VARIANT like try_parse_to_variant: 
text that is not a
+// JSON document loads SQL NULL. A JSON-format load passes a string value 
without its quotes, so a
+// string that holds JSON text is parsed, while a plain string such as "hello" 
loads NULL.
+suite("variant_load_invalid_json", "p0") {
+    sql "DROP TABLE IF EXISTS variant_load_invalid_json"
+    sql """
+        CREATE TABLE variant_load_invalid_json (
+            k int,
+            v variant
+        )
+        DUPLICATE KEY(k)
+        DISTRIBUTED BY HASH(k) BUCKETS 1
+        PROPERTIES ("replication_num" = "1");
+    """
+
+    streamLoad {
+        table "variant_load_invalid_json"
+        set 'format', 'json'
+        set 'read_json_by_line', 'true'
+        inputText '''{"k":1,"v":{"a":1}}
+{"k":2,"v":"{\\"a\\":2}"}
+{"k":3,"v":"123"}
+{"k":4,"v":"hello"}
+{"k":5,"v":""}
+{"k":6,"v":null}'''
+        time 10000
+
+        check { result, exception, startTime, endTime ->
+            if (exception != null) {
+                throw exception
+            }
+            def json = parseJson(result)
+            assertEquals("success", json.Status.toLowerCase())
+            assertEquals(6, json.NumberLoadedRows)
+        }
+    }
+
+    streamLoad {
+        table "variant_load_invalid_json"
+        set 'column_separator', '|'
+        inputText '''11|{"a":11}
+12|not-json
+13|
+14|\\N'''
+        time 10000
+
+        check { result, exception, startTime, endTime ->
+            if (exception != null) {
+                throw exception
+            }
+            def json = parseJson(result)
+            assertEquals("success", json.Status.toLowerCase())
+            assertEquals(4, json.NumberLoadedRows)
+        }
+    }
+
+    order_qt_rows """select k, v, variant_type(v) from 
variant_load_invalid_json order by k"""
+
+    // A NOT NULL column filters the rows that parse to NULL.
+    sql "DROP TABLE IF EXISTS variant_load_invalid_json_not_null"
+    sql """
+        CREATE TABLE variant_load_invalid_json_not_null (
+            k int,
+            v variant NOT NULL
+        )
+        DUPLICATE KEY(k)
+        DISTRIBUTED BY HASH(k) BUCKETS 1
+        PROPERTIES ("replication_num" = "1");
+    """
+
+    streamLoad {
+        table "variant_load_invalid_json_not_null"
+        set 'format', 'json'
+        set 'read_json_by_line', 'true'
+        set 'max_filter_ratio', '0.5'
+        inputText '''{"k":1,"v":{"a":1}}
+{"k":2,"v":"hello"}'''
+        time 10000
+
+        check { result, exception, startTime, endTime ->
+            if (exception != null) {
+                throw exception
+            }
+            def json = parseJson(result)
+            assertEquals("success", json.Status.toLowerCase())
+            assertEquals(1, json.NumberLoadedRows)
+            assertEquals(1, json.NumberFilteredRows)
+        }
+    }
+
+    order_qt_not_null """select k, v from variant_load_invalid_json_not_null 
order by k"""
+}
diff --git a/regression-test/suites/variant_p0/v2/variant_compute_v2.groovy 
b/regression-test/suites/variant_p0/v2/variant_compute_v2.groovy
index 7c702931e60..d88469c573a 100644
--- a/regression-test/suites/variant_p0/v2/variant_compute_v2.groovy
+++ b/regression-test/suites/variant_p0/v2/variant_compute_v2.groovy
@@ -15,7 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
-suite("variant_compute_v2", "p0,nonConcurrent") {
+suite("variant_compute_v2", "p0") {
     def variantV2Function = "parse_to_variant"
     sql "SET enable_nereids_planner = true"
     sql "SET enable_fallback_to_original_planner = false"
@@ -288,23 +288,21 @@ suite("variant_compute_v2", "p0,nonConcurrent") {
         FROM numbers("number" = "1")
     """
 
-    setBeConfigTemporary([variant_throw_exeception_on_invalid_json: true]) {
-        order_qt_parse_error_to_null """
+    order_qt_parse_error_to_null """
+        SELECT number,
+               CAST(try_parse_to_variant(payload) AS STRING),
+               try_parse_to_variant(payload) IS NULL
+        FROM (
             SELECT number,
-                   CAST(try_parse_to_variant(payload) AS STRING),
-                   try_parse_to_variant(payload) IS NULL
-            FROM (
-                SELECT number,
-                       CASE number
-                           WHEN 0 THEN '{"ok":1}'
-                           WHEN 1 THEN 'not-json'
-                           ELSE NULL
-                       END AS payload
-                FROM numbers("number" = "3")
-            ) t
-            ORDER BY number
-        """
-    }
+                   CASE number
+                       WHEN 0 THEN '{"ok":1}'
+                       WHEN 1 THEN 'not-json'
+                       ELSE NULL
+                   END AS payload
+            FROM numbers("number" = "3")
+        ) t
+        ORDER BY number
+    """
 
     order_qt_direct_result """
         SELECT ${variantV2Function}(CONCAT('{"id":', number, '}'))
diff --git 
a/regression-test/suites/variant_p0/v2/variant_parse_functions.groovy 
b/regression-test/suites/variant_p0/v2/variant_parse_functions.groovy
index e20dd7e685d..433d2bf68df 100644
--- a/regression-test/suites/variant_p0/v2/variant_parse_functions.groovy
+++ b/regression-test/suites/variant_p0/v2/variant_parse_functions.groovy
@@ -36,81 +36,60 @@ suite("variant_parse_functions", "p0,nonConcurrent") {
         FROM (${nullableInput}) t ORDER BY id
     """
 
-    setBeConfigTemporary([variant_throw_exeception_on_invalid_json: true]) {
-        test {
-            sql """
-                SELECT /*+SET_VAR(enable_fold_constant_by_be=false)*/
-                    parse_to_variant('{')
-            """
-            exception "Parse json document failed at row 0, error:"
-        }
-        test {
-            sql """
-                SELECT /*+SET_VAR(enable_fold_constant_by_be=false)*/
-                    json_parse('{')
-            """
-            exception "Parse json document failed at row 0, error:"
-        }
-        order_qt_strict_error_to_null """
+    // Text that is not a JSON document, including an empty string, is an 
error; the TRY form
+    // returns NULL instead.
+    test {
+        sql """
             SELECT /*+SET_VAR(enable_fold_constant_by_be=false)*/
-                try_parse_to_variant('{') IS NULL,
-                try_parse_to_variant('') IS NULL
+                parse_to_variant('{')
         """
+        exception "Parse json document failed at row 0, error:"
     }
-
-    setBeConfigTemporary([variant_throw_exeception_on_invalid_json: false]) {
-        order_qt_permissive_invalid_json_is_string """
+    test {
+        sql """
             SELECT /*+SET_VAR(enable_fold_constant_by_be=false)*/
-                CAST(parse_to_variant('{') AS STRING),
-                parse_to_variant('{') IS NULL,
-                CAST(try_parse_to_variant('{') AS STRING),
-                try_parse_to_variant('{') IS NULL
+                parse_to_variant('')
         """
+        exception "Parse json document failed at row 0, error:"
     }
-
-    setBeConfigTemporary([
-            variant_throw_exeception_on_invalid_json: true,
-            variant_max_json_key_length: 3
-    ]) {
-        test {
-            sql """
-                SELECT /*+SET_VAR(enable_fold_constant_by_be=false)*/
-                    parse_to_variant('{"long":1}')
-            """
-            exception "Parse json document failed at row 0, error:"
-        }
-        order_qt_key_too_long_error_to_null """
+    test {
+        sql """
             SELECT /*+SET_VAR(enable_fold_constant_by_be=false)*/
-                try_parse_to_variant('{"long":1}') IS NULL
+                json_parse('{')
         """
+        exception "Parse json document failed at row 0, error:"
     }
+    order_qt_strict_error_to_null """
+        SELECT /*+SET_VAR(enable_fold_constant_by_be=false)*/
+            try_parse_to_variant('{') IS NULL,
+            try_parse_to_variant('') IS NULL,
+            try_parse_to_variant('not-json') IS NULL
+    """
 
-    setBeConfigTemporary([
-            variant_throw_exeception_on_invalid_json: true,
-            variant_enable_duplicate_json_path_check: false
-    ]) {
+    setBeConfigTemporary([variant_max_json_key_length: 3]) {
         test {
             sql """
                 SELECT /*+SET_VAR(enable_fold_constant_by_be=false)*/
-                    parse_to_variant('{"dup":1,"dup":2}')
+                    parse_to_variant('{"long":1}')
             """
             exception "Parse json document failed at row 0, error:"
         }
-        order_qt_duplicate_key_error_to_null """
+        order_qt_key_too_long_error_to_null """
             SELECT /*+SET_VAR(enable_fold_constant_by_be=false)*/
-                try_parse_to_variant('{"dup":1,"dup":2}') IS NULL
+                try_parse_to_variant('{"long":1}') IS NULL
         """
     }
 
-    setBeConfigTemporary([
-            variant_throw_exeception_on_invalid_json: true,
-            variant_enable_duplicate_json_path_check: true
-    ]) {
-        order_qt_duplicate_key_first_wins """
+    test {
+        sql """
             SELECT /*+SET_VAR(enable_fold_constant_by_be=false)*/
-                CAST(parse_to_variant('{"dup":1,"dup":2}') AS STRING),
-                CAST(try_parse_to_variant('{"dup":1,"dup":2}') AS STRING)
+                parse_to_variant('{"dup":1,"dup":2}')
         """
+        exception "Parse json document failed at row 0, error:"
     }
+    order_qt_duplicate_key_error_to_null """
+        SELECT /*+SET_VAR(enable_fold_constant_by_be=false)*/
+            try_parse_to_variant('{"dup":1,"dup":2}') IS NULL
+    """
 
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to