mirror of
https://github.com/apache/doris.git
synced 2026-11-04 07:31:17 +00:00
[fix](variant) Write Variant V2 sparse keys in byte order (#70001)
This commit is contained in:
1 parent
c7d1df40c0
commit
a35707da74
4 files changed
+290
No files matched your search
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user