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 115d5c831b9 [fix](variant) Read exact sparse cells during path 
promotion (#70212)
115d5c831b9 is described below

commit 115d5c831b9a7aa59fd1c82b00ca740db85c9590
Author: lihangyu <[email protected]>
AuthorDate: Fri Nov 6 11:07:59 2026 +0800

    [fix](variant) Read exact sparse cells during path promotion (#70212)
---
 .../variant/binary_column_extract_iterator.cpp     |  32 +-
 .../binary_column_extract_iterator_test.cpp        | 217 ++++++-
 .../data/variant_p0/sparse_ancestor_promotion.out  | 673 +++++++++++++++++++++
 .../sparse_ancestor_promotion_extended.out         | 196 ++++++
 .../variant_p0/sparse_ancestor_promotion.groovy    |  92 +++
 .../sparse_ancestor_promotion_extended.groovy      | 140 +++++
 6 files changed, 1339 insertions(+), 11 deletions(-)

diff --git a/be/src/storage/segment/variant/binary_column_extract_iterator.cpp 
b/be/src/storage/segment/variant/binary_column_extract_iterator.cpp
index d908688fa3b..94c19b12c8d 100644
--- a/be/src/storage/segment/variant/binary_column_extract_iterator.cpp
+++ b/be/src/storage/segment/variant/binary_column_extract_iterator.cpp
@@ -20,9 +20,9 @@
 #include "core/column/column_nullable.h"
 #include "core/column/column_string.h"
 #include "core/column/variant_column_utils.h"
-#include "storage/segment/variant/v2/variant_assembler.h"
+#include "core/custom_allocator.h"
 #include "storage/segment/variant/v2/variant_column_reader.h"
-#include "util/json/path_in_data.h"
+#include "storage/segment/variant/v2/variant_storage_cell.h"
 
 namespace doris::segment_v2 {
 
@@ -91,13 +91,29 @@ Status 
BinaryColumnExtractIterator::_fill_variant_v2_path(MutableColumnPtr& dst,
         return Status::Corruption("Variant sparse offsets consume {} of {} 
cells",
                                   map->get_offsets().back(), paths->size());
     }
-    variant_v2::VariantAssemblerOptions options;
-    options.requested_path = PathInData(_path);
-    options.storage_map_kind = variant_v2::StorageMapKind::SPARSE;
-    auto assembler = 
DORIS_TRY(variant_v2::VariantAssembler::create(std::move(options)));
+    // Compaction promotes one physical path. For a sparse row containing only 
a.b,
+    // assembling the a subtree would invent a non-null a and break path 
statistics.
+    DorisVector<StringRef> cells(num_rows);
+    DorisVector<uint8_t> missing(num_rows, 1);
+    const StringRef requested {_path.data(), _path.size()};
+    const auto& offsets = map->get_offsets();
+    for (size_t row = 0; row < num_rows; ++row) {
+        const size_t start = offsets[row - 1];
+        const size_t end = offsets[row];
+        // Corrupt offsets must not reach the binary search or cell decoder.
+        if (start > end || end > paths->size()) {
+            return Status::Corruption("Invalid Variant sparse offsets [{}, {}) 
for {} cells", start,
+                                      end, paths->size());
+        }
+        const size_t pos = find_variant_sparse_path_lower_bound(requested, 
*paths, start, end);
+        // Prefix matches such as a.b belong to another physical path.
+        if (pos < end && paths->get_data_at(pos) == requested) {
+            cells[row] = values->get_data_at(pos);
+            missing[row] = 0;
+        }
+    }
     ColumnNullable::MutablePtr assembled;
-    RETURN_IF_ERROR(assembler->assemble(
-            {.num_rows = num_rows, .materialized_columns = {}, .storage_map = 
map}, &assembled));
+    RETURN_IF_ERROR(variant_v2::decode_v1_storage_cells(cells, {}, missing, 
&assembled));
     if (has_null != nullptr) {
         *has_null = assembled->has_null();
     }
diff --git a/be/test/storage/segment/binary_column_extract_iterator_test.cpp 
b/be/test/storage/segment/binary_column_extract_iterator_test.cpp
index d8087d65d55..416b1a01f6e 100644
--- a/be/test/storage/segment/binary_column_extract_iterator_test.cpp
+++ b/be/test/storage/segment/binary_column_extract_iterator_test.cpp
@@ -25,6 +25,7 @@
 #include <string>
 #include <string_view>
 #include <utility>
+#include <vector>
 
 #include "core/column/column_nullable.h"
 #include "core/column/column_string.h"
@@ -70,11 +71,12 @@ public:
 
     Status next_batch(size_t* n, MutableColumnPtr& dst, bool* has_null) 
override {
         ++_counters->next_batch;
-        const size_t rows = std::min(*n, _source->size());
-        if (rows == _source->size()) {
+        const size_t rows = std::min(*n, _source->size() - _ordinal);
+        if (_ordinal == 0 && rows == _source->size()) {
+            // Preserve deliberately invalid offsets in corruption tests.
             dst = std::move(_source);
         } else {
-            dst->insert_range_from(*_source, 0, rows);
+            dst->insert_range_from(*_source, _ordinal, rows);
         }
         *n = rows;
         _ordinal += rows;
@@ -410,6 +412,215 @@ TEST(BinaryColumnExtractIteratorV2Test, 
SharedCacheReusesRowidBatchAcrossPaths)
     EXPECT_EQ(counters->next_batch, 0);
 }
 
+void check_exact_sparse_promotion_result(const IColumn& dst, const rowid_t* 
rowids) {
+    ASSERT_EQ(dst.size(), 6);
+    const auto& nullable = assert_cast<const ColumnNullable&>(dst);
+    const auto& variant = assert_cast<const 
ColumnVariantV2&>(nullable.get_nested_column());
+    constexpr std::string_view expected[] {"11", "", "", R"({"c":44})", 
"null", ""};
+    for (size_t row = 0; row < 6; ++row) {
+        const size_t source_row = rowids != nullptr ? rowids[row] : row;
+        const bool missing = source_row == 1 || source_row == 2 || source_row 
== 5;
+        EXPECT_EQ(nullable.is_null_at(row), missing) << source_row;
+        if (!missing) {
+            EXPECT_EQ(variant_v2_json_at(variant, row), expected[source_row]);
+        }
+    }
+}
+
+// Compaction promotes one physical path at a time. A row containing only a.b 
must
+// stay NULL in the promoted a column; hierarchical query assembly reads a.b 
separately.
+void check_exact_sparse_promotion(const std::string& path, bool by_rowids) {
+    SCOPED_TRACE(path + (by_rowids ? " rowids" : " batch"));
+    auto sparse = variant_util::create_variant_binary_column();
+    auto& map = assert_cast<ColumnMap&>(*sparse);
+    DataTypeInt64 int_type;
+    auto ints = ColumnInt64::create();
+    ints->insert_value(11);
+    ints->insert_value(22);
+    ints->insert_value(33);
+
+    append_storage_cell(map, path, int_type, *ints, 0);
+    map.get_offsets().push_back(1);
+    append_storage_cell(map, path + ".b", int_type, *ints, 1);
+    map.get_offsets().push_back(2);
+    append_storage_cell(map, path + "-", int_type, *ints, 2);
+    map.get_offsets().push_back(3);
+    // Objects stored at the exact path must keep their complete value.
+    append_raw_storage_cell(map, path, jsonb_storage_cell(R"({"c":44})"));
+    map.get_offsets().push_back(4);
+    const char none = static_cast<char>(FieldType::OLAP_FIELD_TYPE_NONE);
+    append_raw_storage_cell(map, path, std::string_view(&none, 1));
+    map.get_offsets().push_back(5);
+    map.get_offsets().push_back(5);
+
+    auto counters = std::make_shared<SparseReadCounters>();
+    auto cache = std::make_shared<BinaryColumnCache>(
+            std::make_unique<FixedSparseIterator>(std::move(sparse), counters),
+            variant_util::create_variant_binary_column());
+    OlapReaderStatistics stats;
+    StorageReadOptions read_options(stats);
+    BinaryColumnExtractIterator extract(path, std::move(cache), &read_options);
+    ColumnIteratorOptions iterator_options;
+    iterator_options.stats = &stats;
+    ASSERT_TRUE(extract.init(iterator_options).ok());
+    auto dst = make_v2_destination();
+    // Exercise reordered rows as well as the sequential compaction read.
+    const rowid_t rowids[] {3, 1, 0, 5, 4, 2};
+    if (by_rowids) {
+        ASSERT_TRUE(extract.read_by_rowids(rowids, std::size(rowids), 
dst).ok());
+    } else {
+        ASSERT_TRUE(extract.seek_to_ordinal(0).ok());
+        size_t rows = 6;
+        bool has_null = false;
+        ASSERT_TRUE(extract.next_batch(&rows, dst, &has_null).ok());
+        ASSERT_EQ(rows, 6);
+        EXPECT_TRUE(has_null);
+    }
+    check_exact_sparse_promotion_result(*dst, by_rowids ? rowids : nullptr);
+}
+
+TEST(BinaryColumnExtractIteratorV2Test, PromotionReadsOnlyExactSparseCells) {
+    for (const std::string path : {"a", "p.a"}) {
+        for (const bool by_rowids : {false, true}) {
+            check_exact_sparse_promotion(path, by_rowids);
+        }
+    }
+}
+
+void check_sparse_payload_rows(const IColumn& destination, const 
std::vector<std::string>& payloads,
+                               const rowid_t* rowids, size_t rows) {
+    const auto& nullable = assert_cast<const ColumnNullable&>(destination);
+    const auto& variant = assert_cast<const 
ColumnVariantV2&>(nullable.get_nested_column());
+    ASSERT_EQ(destination.size(), rows);
+    for (size_t i = 0; i < rows; ++i) {
+        const size_t source_row = rowids != nullptr ? rowids[i] : i;
+        EXPECT_EQ(nullable.is_null_at(i), source_row >= payloads.size());
+        if (source_row < payloads.size()) {
+            EXPECT_EQ(variant_v2_json_at(variant, i), payloads[source_row]);
+        }
+    }
+}
+
+void check_sparse_promotion_payloads(const std::string& path, bool by_rowids) {
+    SCOPED_TRACE(path + (by_rowids ? " rowids" : " batch"));
+    const std::vector<std::string> payloads {"{}", "[]", 
R"([1,null,{"x":2}])", R"("")"};
+    auto sparse = variant_util::create_variant_binary_column();
+    auto& map = assert_cast<ColumnMap&>(*sparse);
+    for (const auto& payload : payloads) {
+        append_raw_storage_cell(map, path.substr(0, path.size() - 1), 
jsonb_storage_cell("901"));
+        append_raw_storage_cell(map, path, jsonb_storage_cell(payload));
+        append_raw_storage_cell(map, path + "%", jsonb_storage_cell("902"));
+        append_raw_storage_cell(map, path + "-", jsonb_storage_cell("903"));
+        append_raw_storage_cell(map, path + ".child", 
jsonb_storage_cell("904"));
+        append_raw_storage_cell(map, path + "z", jsonb_storage_cell("905"));
+        map.get_offsets().push_back(map.get_keys().size());
+    }
+    append_raw_storage_cell(map, path + ".child", jsonb_storage_cell("905"));
+    map.get_offsets().push_back(map.get_keys().size());
+    map.get_offsets().push_back(map.get_keys().size());
+    auto counters = std::make_shared<SparseReadCounters>();
+    auto cache = std::make_shared<BinaryColumnCache>(
+            std::make_unique<FixedSparseIterator>(std::move(sparse), counters),
+            variant_util::create_variant_binary_column());
+    OlapReaderStatistics stats;
+    StorageReadOptions read_options(stats);
+    BinaryColumnExtractIterator extract(path, cache, &read_options);
+    ColumnIteratorOptions options;
+    options.stats = &stats;
+    ASSERT_TRUE(extract.init(options).ok());
+    auto dst = make_v2_destination();
+    const rowid_t rowids[] {5, 2, 0, 4, 1, 3, 2};
+    size_t rows = payloads.size() + 2;
+    if (by_rowids) {
+        ASSERT_TRUE(extract.read_by_rowids(rowids, std::size(rowids), 
dst).ok());
+        rows = std::size(rowids);
+    } else {
+        ASSERT_TRUE(extract.seek_to_ordinal(0).ok());
+        bool has_null = false;
+        ASSERT_TRUE(extract.next_batch(&rows, dst, &has_null).ok());
+        EXPECT_TRUE(has_null);
+    }
+    check_sparse_payload_rows(*dst, payloads, by_rowids ? rowids : nullptr, 
rows);
+}
+
+TEST(BinaryColumnExtractIteratorV2Test, 
PromotionReadsEncodedAndDeepPathsAmongPrefixNeighbors) {
+    // Encoded literal-dot names and structural paths must remain independent 
even
+    // when a single sparse bucket contains their byte-order neighbors.
+    for (const std::string path : {"p.q.a", "a%2Eb", "a%252Eb"}) {
+        for (const bool by_rowids : {false, true}) {
+            check_sparse_promotion_payloads(path, by_rowids);
+        }
+    }
+}
+
+void check_cached_ancestor_rows(const IColumn& destination, size_t path_index) 
{
+    const auto& nullable = assert_cast<const ColumnNullable&>(destination);
+    ASSERT_EQ(nullable.size(), 11);
+    const rowid_t expected_rows[] {0, 1, 2, 3, 2, 0, 1, 2, 3, 1, 2};
+    const size_t expected_counts[] {2, 3, 4};
+    size_t physical_count = 0;
+    for (size_t row = 0; row < nullable.size(); ++row) {
+        EXPECT_EQ(nullable.is_null_at(row), expected_rows[row] != path_index);
+        physical_count += !nullable.is_null_at(row);
+    }
+    EXPECT_EQ(physical_count, expected_counts[path_index]);
+}
+
+void read_cached_ancestor_batch(const 
std::vector<BinaryColumnExtractIterator*>& readers,
+                                std::vector<MutableColumnPtr>& destinations, 
ordinal_t offset) {
+    for (size_t i = 0; i < readers.size(); ++i) {
+        ASSERT_TRUE(readers[i]->seek_to_ordinal(offset).ok());
+        size_t rows = 2;
+        ASSERT_TRUE(readers[i]->next_batch(&rows, destinations[i], 
nullptr).ok());
+        EXPECT_EQ(rows, 2);
+    }
+}
+
+TEST(BinaryColumnExtractIteratorV2Test, 
AncestorChainSharesCacheAcrossBatchesAndRowids) {
+    auto sparse = variant_util::create_variant_binary_column();
+    auto& map = assert_cast<ColumnMap&>(*sparse);
+    for (const auto& path : {"a", "a.b", "a.b.c"}) {
+        append_raw_storage_cell(map, path, jsonb_storage_cell("11"));
+        map.get_offsets().push_back(map.get_keys().size());
+    }
+    map.get_offsets().push_back(map.get_keys().size());
+    auto counters = std::make_shared<SparseReadCounters>();
+    auto cache = std::make_shared<BinaryColumnCache>(
+            std::make_unique<FixedSparseIterator>(std::move(sparse), counters),
+            variant_util::create_variant_binary_column());
+    OlapReaderStatistics stats;
+    StorageReadOptions read_options(stats);
+    BinaryColumnExtractIterator a("a", cache, &read_options);
+    BinaryColumnExtractIterator ab("a.b", cache, &read_options);
+    BinaryColumnExtractIterator abc("a.b.c", cache, &read_options);
+    const std::vector<BinaryColumnExtractIterator*> readers {&a, &ab, &abc};
+    ColumnIteratorOptions options;
+    options.stats = &stats;
+    for (auto* reader : readers) {
+        ASSERT_TRUE(reader->init(options).ok());
+    }
+    std::vector<MutableColumnPtr> destinations;
+    for (size_t i = 0; i < readers.size(); ++i) {
+        destinations.push_back(make_v2_destination());
+    }
+    // All readers consume the first batch before moving to the second batch.
+    read_cached_ancestor_batch(readers, destinations, 0);
+    read_cached_ancestor_batch(readers, destinations, 2);
+    EXPECT_EQ(counters->next_batch, 2);
+    const rowid_t rowids[] {2, 0, 1, 2, 3};
+    for (size_t i = 0; i < readers.size(); ++i) {
+        ASSERT_TRUE(readers[i]->read_by_rowids(rowids, std::size(rowids), 
destinations[i]).ok());
+    }
+    EXPECT_EQ(counters->read_by_rowids, 1);
+    // Changing back from random reads must invalidate the shared cache.
+    read_cached_ancestor_batch(readers, destinations, 1);
+    for (size_t i = 0; i < readers.size(); ++i) {
+        check_cached_ancestor_rows(*destinations[i], i);
+    }
+    EXPECT_EQ(counters->next_batch, 3);
+    EXPECT_EQ(counters->init, 1);
+}
+
 TEST(BinaryColumnExtractIteratorV2Test, RejectsInvalidOffsets) {
     auto sparse = variant_util::create_variant_binary_column();
     auto& map = assert_cast<ColumnMap&>(*sparse);
diff --git a/regression-test/data/variant_p0/sparse_ancestor_promotion.out 
b/regression-test/data/variant_p0/sparse_ancestor_promotion.out
new file mode 100644
index 00000000000..59655fc08d5
--- /dev/null
+++ b/regression-test/data/variant_p0/sparse_ancestor_promotion.out
@@ -0,0 +1,673 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !before --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !full_status --
+[OK]
+
+-- !after_first --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !before_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
+-- !full_status --
+[OK]
+
+-- !after_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
+-- !before --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !full_status --
+[OK]
+
+-- !after_first --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !before_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
+-- !full_status --
+[OK]
+
+-- !after_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
+-- !before --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !full_status --
+[OK]
+
+-- !after_first --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !before_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
+-- !full_status --
+[OK]
+
+-- !after_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
+-- !before --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !full_status --
+[OK]
+
+-- !after_first --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !before_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
+-- !full_status --
+[OK]
+
+-- !after_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
+-- !before --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !full_status --
+[OK]
+
+-- !after_first --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !before_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
+-- !full_status --
+[OK]
+
+-- !after_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
+-- !before --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !full_status --
+[OK]
+
+-- !after_first --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !before_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
+-- !full_status --
+[OK]
+
+-- !after_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
+-- !before --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !full_status --
+[OK]
+
+-- !after_first --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !before_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
+-- !full_status --
+[OK]
+
+-- !after_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
+-- !before --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !full_status --
+[OK]
+
+-- !after_first --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+
+-- !before_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
+-- !full_status --
+[OK]
+
+-- !after_second --
+1      1       \N      {"a":1,"hot_0":1}
+2      2       \N      {"a":2,"hot_0":2}
+3      \N      \N      {"hot_0":3}
+4      \N      \N      {"hot_0":4}
+5      {"b":5} 5       {"a":{"b":5},"hot_0":5}
+6      101     \N      {"a":101,"hot_1":1}
+7      102     \N      {"a":102,"hot_1":2}
+8      \N      \N      {"hot_1":3}
+9      \N      \N      {"hot_1":4}
+10     {"b":105}       105     {"a":{"b":105},"hot_1":5}
+11     201     \N      {"a":201,"hot_2":1}
+12     202     \N      {"a":202,"hot_2":2}
+13     \N      \N      {"hot_2":3}
+14     \N      \N      {"hot_2":4}
+15     {"b":205}       205     {"a":{"b":205},"hot_2":5}
+16     301     \N      {"a":301,"hot_3":1}
+17     302     \N      {"a":302,"hot_3":2}
+18     \N      \N      {"hot_3":3}
+19     \N      \N      {"hot_3":4}
+20     {"b":305}       305     {"a":{"b":305},"hot_3":5}
+
diff --git 
a/regression-test/data/variant_p0/sparse_ancestor_promotion_extended.out 
b/regression-test/data/variant_p0/sparse_ancestor_promotion_extended.out
new file mode 100644
index 00000000000..8f810ae1237
--- /dev/null
+++ b/regression-test/data/variant_p0/sparse_ancestor_promotion_extended.out
@@ -0,0 +1,196 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !before --
+1      11      \N      {"hot_0":0,"p":{"q":{"a":11}}}
+2      text    \N      {"hot_0":1,"p":{"q":{"a":"text"}}}
+3      1       \N      {"hot_0":2,"p":{"q":{"a":true}}}
+4      {"child":{"leaf":100}}  100     
{"hot_0":3,"p":{"q":{"a":{"child":{"leaf":100}}}}}
+5      \N      \N      {"a%2Eb":300,"a-":200,"hot_0":4}
+6      \N      \N      {"hot_0":5}
+7      []      \N      {"hot_1":0,"p":{"q":{"a":[]}}}
+8      [1,null,{"x":2}]        \N      
{"hot_1":1,"p":{"q":{"a":[1,null,{"x":2}]}}}
+9      1.5     \N      {"hot_1":2,"p":{"q":{"a":1.5}}}
+10     {"child":{"leaf":101}}  101     
{"hot_1":3,"p":{"q":{"a":{"child":{"leaf":101}}}}}
+11     \N      \N      {"a%2Eb":301,"a-":201,"hot_1":4}
+12     \N      \N      {"hot_1":5}
+13     \N      \N      {"hot_2":0}
+14     \N      \N      {"hot_2":1}
+15     [{"x":3},{"x":4}]       \N      
{"hot_2":2,"p":{"q":{"a":[{"x":3},{"x":4}]}}}
+16     {"child":{"leaf":102}}  102     
{"hot_2":3,"p":{"q":{"a":{"child":{"leaf":102}}}}}
+17     \N      \N      {"a%2Eb":302,"a-":202,"hot_2":4}
+18     \N      \N      {"hot_2":5}
+
+-- !compaction_status --
+[OK]
+
+-- !output_rows --
+1      1       18
+
+-- !promoted --
+1      11      \N      {"hot_0":0,"p":{"q":{"a":11}}}
+2      text    \N      {"hot_0":1,"p":{"q":{"a":"text"}}}
+3      1       \N      {"hot_0":2,"p":{"q":{"a":true}}}
+4      {"child":{"leaf":100}}  100     
{"hot_0":3,"p":{"q":{"a":{"child":{"leaf":100}}}}}
+5      \N      \N      {"a%2Eb":300,"a-":200,"hot_0":4}
+6      \N      \N      {"hot_0":5}
+7      []      \N      {"hot_1":0,"p":{"q":{"a":[]}}}
+8      [1,null,{"x":2}]        \N      
{"hot_1":1,"p":{"q":{"a":[1,null,{"x":2}]}}}
+9      1.5     \N      {"hot_1":2,"p":{"q":{"a":1.5}}}
+10     {"child":{"leaf":101}}  101     
{"hot_1":3,"p":{"q":{"a":{"child":{"leaf":101}}}}}
+11     \N      \N      {"a%2Eb":301,"a-":201,"hot_1":4}
+12     \N      \N      {"hot_1":5}
+13     \N      \N      {"hot_2":0}
+14     \N      \N      {"hot_2":1}
+15     [{"x":3},{"x":4}]       \N      
{"hot_2":2,"p":{"q":{"a":[{"x":3},{"x":4}]}}}
+16     {"child":{"leaf":102}}  102     
{"hot_2":3,"p":{"q":{"a":{"child":{"leaf":102}}}}}
+17     \N      \N      {"a%2Eb":302,"a-":202,"hot_2":4}
+18     \N      \N      {"hot_2":5}
+
+-- !promoted_layout --
+id     int     Yes     true    \N      
+v      variant<PROPERTIES ("variant_max_subcolumns_count" = 
"1","variant_enable_typed_paths_to_sparse" = 
"false","variant_max_sparse_column_statistics_size" = 
"10000","variant_sparse_hash_shard_count" = "1")>       Yes     false   \N      
NONE
+v.p.q.a        json    Yes     false   \N      NONE
+
+-- !compaction_status --
+[OK]
+
+-- !output_rows --
+1      1       28
+
+-- !demoted --
+1      11      \N      {"hot_0":0,"p":{"q":{"a":11}}}
+2      text    \N      {"hot_0":1,"p":{"q":{"a":"text"}}}
+3      1       \N      {"hot_0":2,"p":{"q":{"a":true}}}
+4      {"child":{"leaf":100}}  100     
{"hot_0":3,"p":{"q":{"a":{"child":{"leaf":100}}}}}
+5      \N      \N      {"a%2Eb":300,"a-":200,"hot_0":4}
+6      \N      \N      {"hot_0":5}
+7      []      \N      {"hot_1":0,"p":{"q":{"a":[]}}}
+8      [1,null,{"x":2}]        \N      
{"hot_1":1,"p":{"q":{"a":[1,null,{"x":2}]}}}
+9      1.5     \N      {"hot_1":2,"p":{"q":{"a":1.5}}}
+10     {"child":{"leaf":101}}  101     
{"hot_1":3,"p":{"q":{"a":{"child":{"leaf":101}}}}}
+11     \N      \N      {"a%2Eb":301,"a-":201,"hot_1":4}
+12     \N      \N      {"hot_1":5}
+13     \N      \N      {"hot_2":0}
+14     \N      \N      {"hot_2":1}
+15     [{"x":3},{"x":4}]       \N      
{"hot_2":2,"p":{"q":{"a":[{"x":3},{"x":4}]}}}
+16     {"child":{"leaf":102}}  102     
{"hot_2":3,"p":{"q":{"a":{"child":{"leaf":102}}}}}
+17     \N      \N      {"a%2Eb":302,"a-":202,"hot_2":4}
+18     \N      \N      {"hot_2":5}
+100    \N      \N      {"takeover":0}
+101    \N      \N      {"takeover":1}
+102    \N      \N      {"takeover":2}
+103    \N      \N      {"takeover":3}
+104    \N      \N      {"takeover":4}
+105    \N      \N      {"takeover":5}
+106    \N      \N      {"takeover":6}
+107    \N      \N      {"takeover":7}
+108    \N      \N      {"takeover":8}
+109    \N      \N      {"takeover":9}
+
+-- !demoted_layout --
+id     int     Yes     true    \N      
+v      variant<PROPERTIES ("variant_max_subcolumns_count" = 
"1","variant_enable_typed_paths_to_sparse" = 
"false","variant_max_sparse_column_statistics_size" = 
"10000","variant_sparse_hash_shard_count" = "1")>       Yes     false   \N      
NONE
+v.takeover     bigint  Yes     false   \N      NONE
+
+-- !compaction_status --
+[OK]
+
+-- !output_rows --
+1      1       46
+
+-- !repromoted --
+1      11      \N      {"hot_0":0,"p":{"q":{"a":11}}}
+2      text    \N      {"hot_0":1,"p":{"q":{"a":"text"}}}
+3      1       \N      {"hot_0":2,"p":{"q":{"a":true}}}
+4      {"child":{"leaf":100}}  100     
{"hot_0":3,"p":{"q":{"a":{"child":{"leaf":100}}}}}
+5      \N      \N      {"a%2Eb":300,"a-":200,"hot_0":4}
+6      \N      \N      {"hot_0":5}
+7      []      \N      {"hot_1":0,"p":{"q":{"a":[]}}}
+8      [1,null,{"x":2}]        \N      
{"hot_1":1,"p":{"q":{"a":[1,null,{"x":2}]}}}
+9      1.5     \N      {"hot_1":2,"p":{"q":{"a":1.5}}}
+10     {"child":{"leaf":101}}  101     
{"hot_1":3,"p":{"q":{"a":{"child":{"leaf":101}}}}}
+11     \N      \N      {"a%2Eb":301,"a-":201,"hot_1":4}
+12     \N      \N      {"hot_1":5}
+13     \N      \N      {"hot_2":0}
+14     \N      \N      {"hot_2":1}
+15     [{"x":3},{"x":4}]       \N      
{"hot_2":2,"p":{"q":{"a":[{"x":3},{"x":4}]}}}
+16     {"child":{"leaf":102}}  102     
{"hot_2":3,"p":{"q":{"a":{"child":{"leaf":102}}}}}
+17     \N      \N      {"a%2Eb":302,"a-":202,"hot_2":4}
+18     \N      \N      {"hot_2":5}
+100    \N      \N      {"takeover":0}
+101    \N      \N      {"takeover":1}
+102    \N      \N      {"takeover":2}
+103    \N      \N      {"takeover":3}
+104    \N      \N      {"takeover":4}
+105    \N      \N      {"takeover":5}
+106    \N      \N      {"takeover":6}
+107    \N      \N      {"takeover":7}
+108    \N      \N      {"takeover":8}
+109    \N      \N      {"takeover":9}
+200    400     \N      {"new_hot_0":0,"p":{"q":{"a":400}}}
+201    401     \N      {"new_hot_0":1,"p":{"q":{"a":401}}}
+202    402     \N      {"new_hot_0":2,"p":{"q":{"a":402}}}
+203    403     \N      {"new_hot_0":3,"p":{"q":{"a":403}}}
+204    {"child":{"leaf":500}}  500     
{"new_hot_0":4,"p":{"q":{"a":{"child":{"leaf":500}}}}}
+205    \N      \N      {"new_hot_0":5}
+206    410     \N      {"new_hot_1":0,"p":{"q":{"a":410}}}
+207    411     \N      {"new_hot_1":1,"p":{"q":{"a":411}}}
+208    412     \N      {"new_hot_1":2,"p":{"q":{"a":412}}}
+209    413     \N      {"new_hot_1":3,"p":{"q":{"a":413}}}
+210    {"child":{"leaf":501}}  501     
{"new_hot_1":4,"p":{"q":{"a":{"child":{"leaf":501}}}}}
+211    \N      \N      {"new_hot_1":5}
+212    420     \N      {"new_hot_2":0,"p":{"q":{"a":420}}}
+213    421     \N      {"new_hot_2":1,"p":{"q":{"a":421}}}
+214    422     \N      {"new_hot_2":2,"p":{"q":{"a":422}}}
+215    423     \N      {"new_hot_2":3,"p":{"q":{"a":423}}}
+216    {"child":{"leaf":502}}  502     
{"new_hot_2":4,"p":{"q":{"a":{"child":{"leaf":502}}}}}
+217    \N      \N      {"new_hot_2":5}
+
+-- !repromoted_layout --
+id     int     Yes     true    \N      
+v      variant<PROPERTIES ("variant_max_subcolumns_count" = 
"1","variant_enable_typed_paths_to_sparse" = 
"false","variant_max_sparse_column_statistics_size" = 
"10000","variant_sparse_hash_shard_count" = "1")>       Yes     false   \N      
NONE
+v.p.q.a        json    Yes     false   \N      NONE
+
+-- !compaction_status --
+[OK]
+
+-- !output_rows --
+1      1       15
+
+-- !mow_before_second --
+1      {"a":{"b":901}} {"b":901}       901     {"p":{"a":{"b":901}}}
+3      {"a":903}       903     \N      {"p":{"a":903}}
+4      \N      \N      \N      {"hot_0":4}
+5      \N      \N      \N      {"hot_0":5}
+6      {"a":[]}        []      \N      {"p":{"a":[]}}
+7      {"a":21}        21      \N      {"hot_1":2,"p":{"a":21}}
+8      \N      \N      \N      {}
+9      \N      \N      \N      {"hot_1":4}
+10     \N      \N      \N      {"hot_1":5}
+11     {"a":12}        12      \N      {"hot_2":1,"p":{"a":12}}
+12     {"a":22}        22      \N      {"hot_2":2,"p":{"a":22}}
+13     {"a":{"b":32}}  {"b":32}        32      {"hot_2":3,"p":{"a":{"b":32}}}
+14     \N      \N      \N      {"hot_2":4}
+15     \N      \N      \N      {"hot_2":5}
+
+-- !compaction_status --
+[OK]
+
+-- !output_rows --
+1      1       14
+
+-- !mow_second --
+1      {"a":{"b":901}} {"b":901}       901     {"p":{"a":{"b":901}}}
+3      {"a":903}       903     \N      {"p":{"a":903}}
+4      \N      \N      \N      {"hot_0":4}
+5      \N      \N      \N      {"hot_0":5}
+6      {"a":[]}        []      \N      {"p":{"a":[]}}
+7      {"a":21}        21      \N      {"hot_1":2,"p":{"a":21}}
+8      \N      \N      \N      {}
+9      \N      \N      \N      {"hot_1":4}
+10     \N      \N      \N      {"hot_1":5}
+11     {"a":12}        12      \N      {"hot_2":1,"p":{"a":12}}
+12     {"a":22}        22      \N      {"hot_2":2,"p":{"a":22}}
+13     {"a":{"b":32}}  {"b":32}        32      {"hot_2":3,"p":{"a":{"b":32}}}
+14     \N      \N      \N      {"hot_2":4}
+15     \N      \N      \N      {"hot_2":5}
+
diff --git a/regression-test/suites/variant_p0/sparse_ancestor_promotion.groovy 
b/regression-test/suites/variant_p0/sparse_ancestor_promotion.groovy
new file mode 100644
index 00000000000..f489baa9cf6
--- /dev/null
+++ b/regression-test/suites/variant_p0/sparse_ancestor_promotion.groovy
@@ -0,0 +1,92 @@
+// 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_ancestor_promotion", "p0") {
+    sql "SET default_variant_enable_doc_mode = false"
+    def backendIPs = [:]
+    def backendHttpPorts = [:]
+    getBackendIpHttpPort(backendIPs, backendHttpPorts)
+    def checkFullStatus = { String tableName ->
+        // Waiting for completion alone also accepts a failed compaction with 
intact old
+        // rowsets. Record its status so unchanged query results cannot hide 
that failure.
+        for (def tablet : sql_return_maparray("SHOW TABLETS FROM 
${tableName}")) {
+            def (code, out, err) = be_show_tablet_status(
+                backendIPs["${tablet.BackendId}"], 
backendHttpPorts["${tablet.BackendId}"], tablet.TabletId)
+            if (code != 0) {
+                throw new Exception("Unable to read compaction status: ${err}")
+            }
+            def status = parseJson(out)["last full 
status"].toString().replace("'", "''")
+            qt_full_status "SELECT '${status}'"
+        }
+    }
+    for (def storageFormat : ["V2", "V3"]) {
+        for (def shardCount : [1, 3]) {
+            for (def keyModel : ["DUPLICATE", "UNIQUE"]) {
+                def tableName = 
"sparse_ancestor_promotion_${storageFormat}_${shardCount}_${keyModel}"
+                def mowProperty = keyModel == "UNIQUE" ? ', 
"enable_unique_key_merge_on_write"="true"' : ''
+                sql "DROP TABLE IF EXISTS ${tableName}"
+                sql """
+                    CREATE TABLE ${tableName} (
+                        id INT,
+                        v VARIANT<PROPERTIES(
+                            "variant_max_subcolumns_count"="1",
+                            "variant_enable_nested_group"="false",
+                            "variant_sparse_hash_shard_count"="${shardCount}"
+                        )>
+                    ) ${keyModel} KEY(id)
+                    DISTRIBUTED BY HASH(id) BUCKETS 1
+                    PROPERTIES("replication_num"="1", 
"disable_auto_compaction"="true",
+                               
"storage_format"="${storageFormat}"${mowProperty})
+                """
+                // Each rowset materializes hot_i (5 rows), leaving a (2) and 
a.b (1)
+                // sparse. Across rowsets a (6) wins over every hot_i (5) and 
is promoted.
+                // Its physical count must stay 6: a.b must not become another 
non-null a.
+                for (int batch = 0; batch < 3; ++batch) {
+                    int id = batch * 5
+                    int value = batch * 100
+                    sql """INSERT INTO ${tableName} VALUES
+                        (${id + 1}, parse_to_variant('{"a":${value + 
1},"hot_${batch}":1}')),
+                        (${id + 2}, parse_to_variant('{"a":${value + 
2},"hot_${batch}":2}')),
+                        (${id + 3}, parse_to_variant('{"hot_${batch}":3}')),
+                        (${id + 4}, parse_to_variant('{"hot_${batch}":4}')),
+                        (${id + 5}, parse_to_variant('{"a":{"b":${value + 
5}},"hot_${batch}":5}'))
+                    """
+                }
+                def projection = """SELECT id, CAST(v['a'] AS STRING), 
v['a']['b'],
+                    CAST(v AS STRING) FROM ${tableName} ORDER BY id"""
+                qt_before projection
+                trigger_and_wait_compaction(tableName, "full", 1800)
+                checkFullStatus(tableName)
+                qt_after_first projection
+
+                // Add another source with a sparse ancestor and compact 
against the new
+                // materialized ancestor, exercising both sides of the 
promotion boundary.
+                sql """INSERT INTO ${tableName} VALUES
+                    (16, parse_to_variant('{"a":301,"hot_3":1}')),
+                    (17, parse_to_variant('{"a":302,"hot_3":2}')),
+                    (18, parse_to_variant('{"hot_3":3}')),
+                    (19, parse_to_variant('{"hot_3":4}')),
+                    (20, parse_to_variant('{"a":{"b":305},"hot_3":5}'))
+                """
+                qt_before_second projection
+                trigger_and_wait_compaction(tableName, "full", 1800)
+                checkFullStatus(tableName)
+                qt_after_second projection
+            }
+        }
+    }
+}
diff --git 
a/regression-test/suites/variant_p0/sparse_ancestor_promotion_extended.groovy 
b/regression-test/suites/variant_p0/sparse_ancestor_promotion_extended.groovy
new file mode 100644
index 00000000000..823438b66e7
--- /dev/null
+++ 
b/regression-test/suites/variant_p0/sparse_ancestor_promotion_extended.groovy
@@ -0,0 +1,140 @@
+// 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.
+
+import groovy.json.JsonOutput
+
+suite("sparse_ancestor_promotion_extended", "p0,nonConcurrent") {
+    sql "SET default_variant_enable_doc_mode = false"
+    sql "SET describe_extend_variant_column = true"
+    def backendIPs = [:]
+    def backendHttpPorts = [:]
+    getBackendIpHttpPort(backendIPs, backendHttpPorts)
+    def statusOf = { tablet ->
+        def (code, out, err) = be_show_tablet_status(
+            backendIPs["${tablet.BackendId}"], 
backendHttpPorts["${tablet.BackendId}"], tablet.TabletId)
+        assert code == 0 : err
+        parseJson(out)
+    }
+    def compact = { String tableName ->
+        def tablet = sql_return_maparray("SHOW TABLETS FROM ${tableName}")[0]
+        def before = statusOf(tablet)
+        assert before.rowsets.size() > 1 : before
+        trigger_and_wait_compaction(tableName, "full", 300)
+        def after = statusOf(tablet)
+        // Query equality alone also passes when compaction fails and leaves 
inputs intact.
+        qt_compaction_status "SELECT '${after['last full 
status'].toString().replace("'", "''")}'"
+        assert after.rowsets.size() == 1 : after
+        assert after.rowsets.intersect(before.rowsets).isEmpty() : after
+        qt_output_rows """SELECT count(*), sum(NUM_SEGMENTS), 
sum(ROWSET_NUM_ROWS)
+            FROM information_schema.rowsets WHERE TABLET_ID=${tablet.TabletId} 
AND NUM_SEGMENTS>0"""
+        logger.info("${tableName}: replaced ${before.rowsets} with 
${after.rowsets}")
+    }
+    // The existing sparse_ancestor_promotion suite covers the 
format/shard/key-model
+    // matrix. Add one deep-path case with heterogeneous values and three 
generations
+    // of promotion, demotion and promotion, all on the same tablet.
+    sql "DROP TABLE IF EXISTS sparse_ancestor_extended_deep"
+    sql """CREATE TABLE sparse_ancestor_extended_deep (id INT, v 
VARIANT<PROPERTIES(
+        "variant_max_subcolumns_count"="1", 
"variant_enable_nested_group"="false",
+        "variant_sparse_hash_shard_count"="1")>)
+        DUPLICATE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES("replication_num"="1", "disable_auto_compaction"="true",
+                   "storage_format"="V3")"""
+    def wrap = { value -> [q: [a: value]] }
+    def payloads = [[11, 'text', true], [[], [1, null, [x: 2]], 1.5],
+                    [[:], null, [[x: 3], [x: 4]]]]
+    for (int batch = 0; batch < 3; ++batch) {
+        def rows = []
+        for (int row = 0; row < 6; ++row) {
+            def value = [("hot_${batch}".toString()): row]
+            if (row < 3) {
+                value['p'] = wrap(payloads[batch][row])
+            } else if (row == 3) {
+                value['p'] = wrap([child: [leaf: batch + 100]])
+            } else if (row == 4) {
+                value['a-'] = batch + 200
+                value['a%2Eb'] = batch + 300
+            }
+            rows.add("(${batch * 6 + row + 1}, 
parse_to_variant('${JsonOutput.toJson(value)}'))")
+        }
+        sql "INSERT INTO sparse_ancestor_extended_deep VALUES 
${rows.join(',')}"
+    }
+    def projection = """SELECT id, CAST(v['p']['q']['a'] AS STRING), 
v['p']['q']['a']['child']['leaf'],
+        CAST(v AS STRING) FROM sparse_ancestor_extended_deep ORDER BY id"""
+    qt_before projection
+    setBeConfigTemporary([enable_vertical_compaction:true,
+            enable_vertical_compact_variant_subcolumns:true,
+            vertical_compaction_num_columns_per_group:1]) {
+        compact('sparse_ancestor_extended_deep')
+        qt_promoted projection
+        order_qt_promoted_layout "DESC sparse_ancestor_extended_deep"
+        // Ten occurrences force the ancestor back into sparse storage.
+        sql """INSERT INTO sparse_ancestor_extended_deep SELECT 100 + number,
+            parse_to_variant(concat('{"takeover":', number, '}'))
+            FROM numbers("number"="10")"""
+        compact('sparse_ancestor_extended_deep')
+        qt_demoted projection
+        order_qt_demoted_layout "DESC sparse_ancestor_extended_deep"
+        // A third generation promotes the ancestor from both old sparse
+        // output and newly written rowsets, while descendants stay sparse.
+        for (int batch = 0; batch < 3; ++batch) {
+            def rows = []
+            for (int row = 0; row < 6; ++row) {
+                def value = [("new_hot_${batch}".toString()): row]
+                if (row < 4) {
+                    value['p'] = wrap(batch * 10 + row + 400)
+                } else if (row == 4) {
+                    value['p'] = wrap([child: [leaf: batch + 500]])
+                }
+                rows.add("(${200 + batch * 6 + row}, 
parse_to_variant('${JsonOutput.toJson(value)}'))")
+            }
+            sql "INSERT INTO sparse_ancestor_extended_deep VALUES 
${rows.join(',')}"
+        }
+        compact('sparse_ancestor_extended_deep')
+        qt_repromoted projection
+        order_qt_repromoted_layout "DESC sparse_ancestor_extended_deep"
+    }
+
+    // Updates and delete predicates exercise paths where physical count 
equality
+    // cannot replace checking the visible latest row and its complete root 
value.
+    sql "DROP TABLE IF EXISTS sparse_ancestor_extended_mow"
+    sql """CREATE TABLE sparse_ancestor_extended_mow (id INT, v 
VARIANT<PROPERTIES(
+        "variant_max_subcolumns_count"="1", 
"variant_enable_nested_group"="false",
+        "variant_sparse_hash_shard_count"="1")>) UNIQUE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 1 PROPERTIES("replication_num"="1",
+        "disable_auto_compaction"="true", 
"enable_unique_key_merge_on_write"="true",
+        "storage_format"="V3")"""
+    for (int batch = 0; batch < 3; ++batch) {
+        sql """INSERT INTO sparse_ancestor_extended_mow VALUES
+            
(${batch*5+1},parse_to_variant('{"p":{"a":${batch+10}},"hot_${batch}":1}')),
+            
(${batch*5+2},parse_to_variant('{"p":{"a":${batch+20}},"hot_${batch}":2}')),
+            
(${batch*5+3},parse_to_variant('{"p":{"a":{"b":${batch+30}}},"hot_${batch}":3}')),
+            (${batch*5+4},parse_to_variant('{"hot_${batch}":4}')),
+            (${batch*5+5},parse_to_variant('{"hot_${batch}":5}'))"""
+    }
+    def mowProjection = """SELECT id, CAST(v['p'] AS STRING), CAST(v['p']['a'] 
AS STRING),
+        v['p']['a']['b'], CAST(v AS STRING) FROM sparse_ancestor_extended_mow 
ORDER BY id"""
+    compact('sparse_ancestor_extended_mow')
+    sql """INSERT INTO sparse_ancestor_extended_mow VALUES
+        (1,parse_to_variant('{"p":{"a":{"b":901}}}')),
+        (3,parse_to_variant('{"p":{"a":903}}')),
+        (6,parse_to_variant('{"p":{"a":[]}}')),
+        (8,parse_to_variant('{"p":{"a":null}}'))"""
+    sql "DELETE FROM sparse_ancestor_extended_mow WHERE id=2"
+    qt_mow_before_second mowProjection
+    compact('sparse_ancestor_extended_mow')
+    qt_mow_second mowProjection
+}


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

Reply via email to