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 a35707da74f [fix](variant) Write Variant V2 sparse keys in byte order 
(#70001)
a35707da74f is described below

commit a35707da74f8df0d6502df95feaf734dd9d20d9d
Author: lihangyu <[email protected]>
AuthorDate: Tue Nov 3 16:37:26 2026 +0800

    [fix](variant) Write Variant V2 sparse keys in byte order (#70001)
---
 .../segment/variant/v2/variant_shredder.cpp        |   5 +
 .../variant/variant_column_writer_reader_test.cpp  |  87 ++++++++++++++
 .../data/variant_p0/sparse_key_byte_order.out      | 129 +++++++++++++++++++++
 .../suites/variant_p0/sparse_key_byte_order.groovy |  69 +++++++++++
 4 files changed, 290 insertions(+)

diff --git a/be/src/storage/segment/variant/v2/variant_shredder.cpp 
b/be/src/storage/segment/variant/v2/variant_shredder.cpp
index e9164a513d7..6175cbaef4c 100644
--- a/be/src/storage/segment/variant/v2/variant_shredder.cpp
+++ b/be/src/storage/segment/variant/v2/variant_shredder.cpp
@@ -509,6 +509,11 @@ struct VariantShredder::Impl {
 
         DorisVector<SparsePlan> sparse_plan = build_sparse_plan(selection, 
candidate_builders);
         select_sparse_statistics(&sparse_plan);
+        // Readers search full path bytes: a-b must precede a.c, unlike 
component order.
+        // Select statistics first to retain the existing encounter-order 
budget tie-break.
+        std::ranges::sort(sparse_plan, [](const SparsePlan& left, const 
SparsePlan& right) {
+            return *left.path < *right.path;
+        });
         RETURN_IF_ERROR(append_binary_rows(sparse_plan, sparse_maps));
         DorisVector<VariantStatistics> 
bucket_statistics(options.sparse_bucket_count);
         publish_sparse_statistics(sparse_plan, &bucket_statistics, result);
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 0e8ebdaf46e..4b63f624480 100644
--- a/be/test/storage/variant/variant_column_writer_reader_test.cpp
+++ b/be/test/storage/variant/variant_column_writer_reader_test.cpp
@@ -74,6 +74,7 @@
 #include "storage/segment/variant/nested_group_provider.h"
 #include "storage/segment/variant/nested_group_streaming_write_plan.h"
 #include "storage/segment/variant/sparse_column_merge_iterator.h"
+#include "storage/segment/variant/v2/variant_assembler.h"
 #include "storage/segment/variant/v2/variant_column_writer.h"
 #include "storage/segment/variant/v2/variant_path_builder.h"
 #include "storage/segment/variant/v2/variant_shredder.h"
@@ -968,6 +969,92 @@ TEST(VariantPathBuilderTest, 
ShredderReusesCanonicalPathsAcrossAppends) {
     EXPECT_EQ(selected.type->to_string(*selected.column, 1), "2");
 }
 
+TEST(VariantShredderTest, 
SparseRowsUseByteOrderWithoutChangingStatisticsSelection) {
+    ColumnPtr source;
+    DataTypePtr source_type;
+    ASSERT_TRUE(create_variant_writer_source(
+                        VariantWriterInput::V2,
+                        {R"({"z":1,"a-b":1,"a 
b":3,"a$b":4,"a%2Eb":5,"a":{"c":2}})", R"({"z":2})",
+                         R"({"z":3})"},
+                        1, false, {}, &source, &source_type)
+                        .ok());
+    const auto& values = assert_cast<const ColumnVariantV2&>(*source);
+    for (const auto layout : 
{segment_v2::VariantShredderPhysicalLayout::ORDINARY,
+                              segment_v2::VariantShredderPhysicalLayout::DOC}) 
{
+        for (const uint32_t buckets : {1, 3}) {
+            SCOPED_TRACE(testing::Message()
+                         << "layout=" << static_cast<int>(layout) << " 
buckets=" << buckets);
+            segment_v2::VariantShredderOptions options;
+            options.physical_layout = layout;
+            options.max_subcolumns_count = 1;
+            options.sparse_bucket_count = buckets;
+            options.max_sparse_column_statistics_size = 1;
+            options.doc_bucket_count = buckets;
+            options.doc_materialization_min_rows = 4;
+            segment_v2::VariantShredder shredder(options);
+            
segment_v2::VariantShredder::TestAccess::set_binary_cells_per_chunk(shredder, 
2);
+            ASSERT_TRUE(shredder.append(values.read_view(), 0, 
values.size()).ok());
+            segment_v2::VariantShreddedColumns output;
+            ASSERT_TRUE(shredder.finish(&output).ok());
+            ASSERT_EQ(output.binary_buckets.size(), buckets);
+            for (const auto& bucket : output.binary_buckets) {
+                const auto& map = assert_cast<const 
ColumnMap&>(*bucket.column);
+                const auto& keys = assert_cast<const 
ColumnString&>(map.get_keys());
+                for (size_t row = 0; row < map.size(); ++row) {
+                    std::vector<std::string> actual;
+                    for (size_t cell = map.offset_at(row);
+                         cell < map.offset_at(row) + map.size_at(row); ++cell) 
{
+                        actual.push_back(keys.get_data_at(cell).to_string());
+                    }
+                    auto expected = actual;
+                    std::ranges::sort(expected);
+                    EXPECT_EQ(actual, expected) << "row=" << row;
+                }
+            }
+            if (layout == segment_v2::VariantShredderPhysicalLayout::ORDINARY) 
{
+                ASSERT_EQ(output.materialized.size(), 1);
+                EXPECT_EQ(output.materialized.front().path.get_path(), "z");
+                const std::vector<std::pair<std::string, std::string>> cases {
+                        {"a.c", "2"}, {"a-b", "1"}, {"a b", "3"}, {"a$b", 
"4"}, {"a%2Eb", "5"}};
+                for (const auto& [path, expected] : cases) {
+                    SCOPED_TRACE(path);
+                    const uint32_t bucket = 
variant_util::variant_binary_shard_of(
+                            {path.data(), path.size()}, buckets);
+                    const auto& map =
+                            assert_cast<const 
ColumnMap&>(*output.binary_buckets[bucket].column);
+                    segment_v2::variant_v2::VariantAssemblerOptions 
read_options;
+                    read_options.requested_path = PathInData(path);
+                    read_options.storage_map_kind = 
segment_v2::variant_v2::StorageMapKind::SPARSE;
+                    auto assembler = 
segment_v2::variant_v2::VariantAssembler::create(
+                            std::move(read_options));
+                    ASSERT_TRUE(assembler.has_value()) << assembler.error();
+                    ColumnNullable::MutablePtr result;
+                    ASSERT_TRUE((*assembler)
+                                        ->assemble({.num_rows = values.size(),
+                                                    .materialized_columns = {},
+                                                    .storage_map = &map},
+                                                   &result)
+                                        .ok());
+                    const auto& read_values =
+                            assert_cast<const 
ColumnVariantV2&>(result->get_nested_column());
+                    EXPECT_EQ(variant_v2_json_at(read_values, 0), expected);
+                    EXPECT_FALSE(result->is_null_at(0));
+                    EXPECT_TRUE(result->is_null_at(1));
+                    EXPECT_TRUE(result->is_null_at(2));
+                }
+                if (buckets == 1) {
+                    // Component order encounters a.c first; physical byte 
order must not
+                    // change which path wins the one-entry statistics budget.
+                    
EXPECT_EQ(output.statistics.sparse_column_non_null_size.size(), 1);
+                    
EXPECT_EQ(output.statistics.sparse_column_non_null_size.at("a.c"), 1);
+                    
EXPECT_EQ(output.binary_buckets.front().statistics.sparse_column_non_null_size,
+                              output.statistics.sparse_column_non_null_size);
+                }
+            }
+        }
+    }
+}
+
 TEST(VariantShredderTest, ReusesRootAndNestedPathsForSharedMetadata) {
     DataTypeVariantV2SerDe serde;
     DataTypeSerDe::FormatOptions format_options;
diff --git a/regression-test/data/variant_p0/sparse_key_byte_order.out 
b/regression-test/data/variant_p0/sparse_key_byte_order.out
new file mode 100644
index 00000000000..785f7cbcb32
--- /dev/null
+++ b/regression-test/data/variant_p0/sparse_key_byte_order.out
@@ -0,0 +1,129 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !before_compaction --
+1      11      12      13      14      15      {"c":15}        
{"a":{"c":15},"a b":12,"a$b":13,"a%2Eb":14,"a-b":11,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
+-- !after_compaction --
+1      11      12      13      14      15      {"c":15}        
{"a":{"c":15},"a b":12,"a$b":13,"a%2Eb":14,"a-b":11,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
+-- !before_compaction --
+1      11      12      13      14      15      {"c":15}        
{"a":{"c":15},"a b":12,"a$b":13,"a%2Eb":14,"a-b":11,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
+-- !after_compaction --
+1      11      12      13      14      15      {"c":15}        
{"a":{"c":15},"a b":12,"a$b":13,"a%2Eb":14,"a-b":11,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
+-- !before_compaction --
+1      11      12      13      14      15      {"c":15}        
{"a":{"c":15},"a b":12,"a$b":13,"a%2Eb":14,"a-b":11,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
+-- !after_compaction --
+1      11      12      13      14      15      {"c":15}        
{"a":{"c":15},"a b":12,"a$b":13,"a%2Eb":14,"a-b":11,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
+-- !before_compaction --
+1      11      12      13      14      15      {"c":15}        
{"a":{"c":15},"a b":12,"a$b":13,"a%2Eb":14,"a-b":11,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
+-- !after_compaction --
+1      11      12      13      14      15      {"c":15}        
{"a":{"c":15},"a b":12,"a$b":13,"a%2Eb":14,"a-b":11,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
+-- !before_compaction --
+1      31      32      33      34      35      {"c":35}        
{"a":{"c":35},"a b":32,"a$b":33,"a%2Eb":34,"a-b":31,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
+-- !after_compaction --
+1      31      32      33      34      35      {"c":35}        
{"a":{"c":35},"a b":32,"a$b":33,"a%2Eb":34,"a-b":31,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
+-- !before_compaction --
+1      31      32      33      34      35      {"c":35}        
{"a":{"c":35},"a b":32,"a$b":33,"a%2Eb":34,"a-b":31,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
+-- !after_compaction --
+1      31      32      33      34      35      {"c":35}        
{"a":{"c":35},"a b":32,"a$b":33,"a%2Eb":34,"a-b":31,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
+-- !before_compaction --
+1      31      32      33      34      35      {"c":35}        
{"a":{"c":35},"a b":32,"a$b":33,"a%2Eb":34,"a-b":31,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
+-- !after_compaction --
+1      31      32      33      34      35      {"c":35}        
{"a":{"c":35},"a b":32,"a$b":33,"a%2Eb":34,"a-b":31,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
+-- !before_compaction --
+1      31      32      33      34      35      {"c":35}        
{"a":{"c":35},"a b":32,"a$b":33,"a%2Eb":34,"a-b":31,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
+-- !after_compaction --
+1      31      32      33      34      35      {"c":35}        
{"a":{"c":35},"a b":32,"a$b":33,"a%2Eb":34,"a-b":31,"z":1}
+2      \N      \N      \N      \N      \N      \N      {"z":2}
+3      \N      \N      \N      \N      \N      \N      {"z":3}
+4      21      22      23      24      25      {"c":25}        
{"a":{"c":25},"a b":22,"a$b":23,"a%2Eb":24,"a-b":21,"z":4}
+5      \N      \N      \N      \N      \N      \N      {"z":5}
+6      \N      \N      \N      \N      \N      \N      {"z":6}
+
diff --git a/regression-test/suites/variant_p0/sparse_key_byte_order.groovy 
b/regression-test/suites/variant_p0/sparse_key_byte_order.groovy
new file mode 100644
index 00000000000..654d80ca3ce
--- /dev/null
+++ b/regression-test/suites/variant_p0/sparse_key_byte_order.groovy
@@ -0,0 +1,69 @@
+// 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.
+
+suite("sparse_key_byte_order", "p0") {
+    // The parser rejects an explicit doc-mode property beside sparse 
properties.
+    sql "SET default_variant_enable_doc_mode = false"
+    for (def keyModel : ["DUPLICATE", "UNIQUE"]) {
+        for (def shardCount : [1, 3]) {
+            for (def docMode : [false, true]) {
+                def tableName = 
"sparse_key_byte_order_${keyModel.toLowerCase()}_${shardCount}_${docMode}"
+                def mowProperty = keyModel == "UNIQUE" ? ', 
"enable_unique_key_merge_on_write"="true"' : ''
+                def variantProperties = docMode ?
+                    '"variant_enable_doc_mode"="true", 
"variant_enable_nested_group"="false", ' +
+                    '"variant_doc_materialization_min_rows"="1000000", ' +
+                    "\"variant_doc_hash_shard_count\"=\"${shardCount}\"" :
+                    '"variant_max_subcolumns_count"="1", 
"variant_enable_typed_paths_to_sparse"="false", ' +
+                    '"variant_enable_nested_group"="false", ' +
+                    "\"variant_sparse_hash_shard_count\"=\"${shardCount}\""
+                sql "DROP TABLE IF EXISTS ${tableName}"
+                sql """
+                    CREATE TABLE ${tableName} (
+                        id INT,
+                        v VARIANT<PROPERTIES(${variantProperties})>
+                    ) ${keyModel} KEY(id)
+                    DISTRIBUTED BY HASH(id) BUCKETS 1
+                    PROPERTIES("replication_num"="1", 
"disable_auto_compaction"="true"${mowProperty})
+                """
+                // z is hotter than every other leaf, leaving the punctuation 
keys sparse.
+                sql """INSERT INTO ${tableName} VALUES
+                    (1, parse_to_variant('{"z":1,"a-b":11,"a 
b":12,"a\$b":13,"a%2Eb":14,"a":{"c":15}}')),
+                    (2, parse_to_variant('{"z":2}')),
+                    (3, parse_to_variant('{"z":3}'))
+                """
+                sql """INSERT INTO ${tableName} VALUES
+                    (4, parse_to_variant('{"z":4,"a-b":21,"a 
b":22,"a\$b":23,"a%2Eb":24,"a":{"c":25}}')),
+                    (5, parse_to_variant('{"z":5}')),
+                    (6, parse_to_variant('{"z":6}'))
+                """
+                if (keyModel == "UNIQUE") {
+                    sql """INSERT INTO ${tableName} VALUES
+                        (1, parse_to_variant('{"z":1,"a-b":31,"a 
b":32,"a\$b":33,"a%2Eb":34,"a":{"c":35}}')),
+                        (2, parse_to_variant('{"z":2}')),
+                        (3, parse_to_variant('{"z":3}'))
+                    """
+                }
+                def projection = """SELECT id, v['a-b'], v['a b'], v['a\$b'], 
v['a%2Eb'],
+                    v['a']['c'], CAST(v['a'] AS STRING), CAST(v AS STRING)
+                    FROM ${tableName} ORDER BY id"""
+                qt_before_compaction projection
+                trigger_and_wait_compaction(tableName, "full", 1800)
+                qt_after_compaction projection
+            }
+        }
+    }
+}


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

Reply via email to