mirror of
https://github.com/apache/doris.git
synced 2026-11-04 07:31:17 +00:00
[Refactor](Variant) add NestedGroup path metadata support (#62848)
Add NestedGroup path metadata through Variant storage and query planning. - Write indexed NestedGroup paths into Variant subcolumn metadata so top-array nested fields keep positional nulls. - Teach ColumnVariant and DataTypeVariant to carry nested-group typed paths through sparse/subcolumn IO, casting, element access, and compaction. - Align FE Variant type SQL, property analysis, and expression-to-thrift translation with NestedGroup path planning. - Regenerate nested_in_top_array expected output for the NestedGroup positional layout.
This commit is contained in:
1 parent
98dec70384
commit
9cd2008070
25 files changed
+1425
-549
No files matched your search
@@ -70,6 +70,8 @@
|
||||
#include "storage/olap_common.h"
|
||||
#include "util/defer_op.h"
|
||||
#include "util/json/path_in_data.h"
|
||||
#include "util/jsonb_document.h"
|
||||
#include "util/jsonb_document_cast.h"
|
||||
#include "util/jsonb_parser_simd.h"
|
||||
#include "util/jsonb_utils.h"
|
||||
#include "util/simd/bits.h"
|
||||
@@ -383,17 +385,22 @@ void ColumnVariant::Subcolumn::insert_range_from(const Subcolumn& src, size_t st
|
||||
data.back()->insert_range_from(*column, from, n);
|
||||
return;
|
||||
}
|
||||
// When LCT is Array<Variant> (NG data) and the part is scalar, cast
|
||||
// would crash. Under DISCARD_SCALAR the scalar part becomes defaults.
|
||||
// When LCT is Array<Variant> (NG data), only true scalar sources should
|
||||
// be discarded under DISCARD_SCALAR. Mixed regular arrays such as
|
||||
// [null, "plain_text", 123, {"k":"v"}] must still get a chance to cast
|
||||
// into the NG-compatible array type during query-side merges.
|
||||
if (is_nested_group_type(least_common_type.get())) {
|
||||
if (!config::variant_nested_group_discard_scalar_on_conflict) {
|
||||
throw doris::Exception(ErrorCode::INVALID_ARGUMENT,
|
||||
"NestedGroup type conflict: cannot cast scalar type {} to "
|
||||
"Array<Variant>",
|
||||
column_type->get_name());
|
||||
const bool src_is_scalar = get_number_of_dimensions(*column_type) == 0;
|
||||
if (src_is_scalar) {
|
||||
if (!config::variant_nested_group_discard_scalar_on_conflict) {
|
||||
throw doris::Exception(ErrorCode::INVALID_ARGUMENT,
|
||||
"NestedGroup type conflict: cannot cast scalar type "
|
||||
"{} to Array<Variant>",
|
||||
column_type->get_name());
|
||||
}
|
||||
data.back()->insert_many_defaults(n);
|
||||
return;
|
||||
}
|
||||
data.back()->insert_many_defaults(n);
|
||||
return;
|
||||
}
|
||||
/// If we need to insert large range, there is no sense to cut part of column and cast it.
|
||||
/// Casting of all column and inserting from it can be faster.
|
||||
@@ -403,6 +410,11 @@ void ColumnVariant::Subcolumn::insert_range_from(const Subcolumn& src, size_t st
|
||||
Status st = variant_util::cast_column({column, column_type, ""},
|
||||
least_common_type.get(), &casted_column);
|
||||
if (!st.ok()) {
|
||||
if (is_nested_group_type(least_common_type.get()) &&
|
||||
config::variant_nested_group_discard_scalar_on_conflict) {
|
||||
data.back()->insert_many_defaults(n);
|
||||
return;
|
||||
}
|
||||
throw doris::Exception(ErrorCode::INVALID_ARGUMENT, st.to_string());
|
||||
}
|
||||
data.back()->insert_range_from(*casted_column, from, n);
|
||||
@@ -412,6 +424,11 @@ void ColumnVariant::Subcolumn::insert_range_from(const Subcolumn& src, size_t st
|
||||
Status st = variant_util::cast_column({casted_column, column_type, ""},
|
||||
least_common_type.get(), &casted_column);
|
||||
if (!st.ok()) {
|
||||
if (is_nested_group_type(least_common_type.get()) &&
|
||||
config::variant_nested_group_discard_scalar_on_conflict) {
|
||||
data.back()->insert_many_defaults(n);
|
||||
return;
|
||||
}
|
||||
throw doris::Exception(ErrorCode::INVALID_ARGUMENT, st.to_string());
|
||||
}
|
||||
data.back()->insert_range_from(*casted_column, 0, n);
|
||||
@@ -667,6 +684,9 @@ ColumnVariant::Subcolumn::LeastCommonType::LeastCommonType(DataTypePtr type_, bo
|
||||
base_type_id = base_type->get_primitive_type();
|
||||
}
|
||||
|
||||
ColumnVariant::ColumnVariant(int32_t max_subcolumns_count)
|
||||
: ColumnVariant(max_subcolumns_count, false) {}
|
||||
|
||||
ColumnVariant::ColumnVariant(int32_t max_subcolumns_count, bool enable_doc_mode)
|
||||
: is_nullable(true),
|
||||
num_rows(0),
|
||||
@@ -676,6 +696,11 @@ ColumnVariant::ColumnVariant(int32_t max_subcolumns_count, bool enable_doc_mode)
|
||||
ENABLE_CHECK_CONSISTENCY(this);
|
||||
}
|
||||
|
||||
ColumnVariant::ColumnVariant(int32_t max_subcolumns_count, DataTypePtr root_type,
|
||||
MutableColumnPtr&& root_column)
|
||||
: ColumnVariant(max_subcolumns_count, false, std::move(root_type), std::move(root_column)) {
|
||||
}
|
||||
|
||||
ColumnVariant::ColumnVariant(int32_t max_subcolumns_count, bool enable_doc_mode,
|
||||
DataTypePtr root_type, MutableColumnPtr&& root_column)
|
||||
: is_nullable(true),
|
||||
@@ -689,6 +714,9 @@ ColumnVariant::ColumnVariant(int32_t max_subcolumns_count, bool enable_doc_mode,
|
||||
ENABLE_CHECK_CONSISTENCY(this);
|
||||
}
|
||||
|
||||
ColumnVariant::ColumnVariant(int32_t max_subcolumns_count, Subcolumns&& subcolumns_)
|
||||
: ColumnVariant(max_subcolumns_count, false, std::move(subcolumns_)) {}
|
||||
|
||||
ColumnVariant::ColumnVariant(int32_t max_subcolumns_count, bool enable_doc_mode,
|
||||
Subcolumns&& subcolumns_)
|
||||
: is_nullable(true),
|
||||
@@ -706,6 +734,9 @@ ColumnVariant::ColumnVariant(int32_t max_subcolumns_count, bool enable_doc_mode,
|
||||
serialized_doc_value_column->resize(num_rows);
|
||||
}
|
||||
|
||||
ColumnVariant::ColumnVariant(int32_t max_subcolumns_count, size_t size)
|
||||
: ColumnVariant(max_subcolumns_count, false, size) {}
|
||||
|
||||
ColumnVariant::ColumnVariant(int32_t max_subcolumns_count, bool enable_doc_mode, size_t size)
|
||||
: is_nullable(true),
|
||||
num_rows(0),
|
||||
@@ -811,9 +842,12 @@ void ColumnVariant::insert_from(const IColumn& src, size_t n) {
|
||||
const auto* src_v = check_and_get_column<ColumnVariant>(src);
|
||||
ENABLE_CHECK_CONSISTENCY(src_v);
|
||||
ENABLE_CHECK_CONSISTENCY(this);
|
||||
// doc mode fast path: both sides root-only, direct copy root + sparse + doc_value
|
||||
if (_enable_doc_mode) {
|
||||
DCHECK(src_v->_enable_doc_mode) << "dst is doc mode but src is not";
|
||||
// Preserve the original root-only copy path for ordinary variant columns.
|
||||
// Reconstructing through try_insert() loses sparse/doc_value structure for
|
||||
// mixed-shape rows and nested-group data.
|
||||
if (src_v->get_subcolumns().size() == 1 && get_subcolumns().size() == 1) {
|
||||
DCHECK(_enable_doc_mode == src_v->_enable_doc_mode)
|
||||
<< "root-only variant copy requires matching doc mode";
|
||||
FieldWithDataType field;
|
||||
src_v->subcolumns.get_root()->data.get(n, field);
|
||||
subcolumns.get_mutable_root()->data.insert(field);
|
||||
@@ -1631,36 +1665,132 @@ struct Prefix {
|
||||
bool root_is_first_flag = true;
|
||||
};
|
||||
|
||||
// skip empty nested json:
|
||||
// 1. nested array with only nulls, eg. [null. null],todo: think a better way to deal distinguish array null value and real null value.
|
||||
// 2. type is nothing
|
||||
bool ColumnVariant::Subcolumn::is_empty_nested(size_t row) const {
|
||||
PrimitiveType base_type_id = least_common_type.get_base_type_id();
|
||||
const DataTypePtr& type = least_common_type.get();
|
||||
if (type->get_primitive_type() == PrimitiveType::TYPE_ARRAY) {
|
||||
// check if it is empty nested json array, then skip
|
||||
FieldWithDataType field;
|
||||
get(row, field);
|
||||
if (field.field.get_type() == PrimitiveType::TYPE_ARRAY) {
|
||||
const auto& array = field.field.get<TYPE_ARRAY>();
|
||||
bool only_nulls_inside = true;
|
||||
for (const auto& elem : array) {
|
||||
if (elem.get_type() != PrimitiveType::TYPE_NULL) {
|
||||
only_nulls_inside = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if only nulls then skip
|
||||
return only_nulls_inside;
|
||||
}
|
||||
enum class NestedJsonSkipKind {
|
||||
kVisible,
|
||||
kNullOnlyArray,
|
||||
kEmptyShellArray,
|
||||
kInvalidType,
|
||||
};
|
||||
|
||||
static bool is_semantically_empty_jsonb_value(const JsonbValue* value) {
|
||||
if (value == nullptr || value->isNull()) {
|
||||
return true;
|
||||
}
|
||||
// skip nothing type
|
||||
if (base_type_id == PrimitiveType::INVALID_TYPE) {
|
||||
if (value->isArray()) {
|
||||
const auto* array = value->unpack<ArrayVal>();
|
||||
for (auto it = array->begin(); it != array->end(); ++it) {
|
||||
if (!is_semantically_empty_jsonb_value(&*it)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (value->isObject()) {
|
||||
const auto* object = value->unpack<ObjectVal>();
|
||||
for (auto it = object->begin(); it != object->end(); ++it) {
|
||||
if (!is_semantically_empty_jsonb_value(it->value())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_semantically_empty_nested_field(const Field& field) {
|
||||
switch (field.get_type()) {
|
||||
case PrimitiveType::TYPE_NULL:
|
||||
return true;
|
||||
case PrimitiveType::TYPE_ARRAY: {
|
||||
const auto& array = field.get<TYPE_ARRAY>();
|
||||
for (const auto& elem : array) {
|
||||
if (!is_semantically_empty_nested_field(elem)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case PrimitiveType::TYPE_VARIANT: {
|
||||
const auto& object = field.get<TYPE_VARIANT>();
|
||||
for (const auto& [_, value] : object) {
|
||||
if (!is_semantically_empty_nested_field(value.field)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case PrimitiveType::TYPE_JSONB: {
|
||||
const auto& jsonb = field.get<TYPE_JSONB>();
|
||||
const JsonbDocument* doc = nullptr;
|
||||
Status st =
|
||||
JsonbDocument::checkAndCreateDocument(jsonb.get_value(), jsonb.get_size(), &doc);
|
||||
if (!st.ok() || doc == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return is_semantically_empty_jsonb_value(doc->getValue());
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static NestedJsonSkipKind classify_nested_json_skip_kind(const Field& field,
|
||||
PrimitiveType base_type_id,
|
||||
const DataTypePtr& type) {
|
||||
if (base_type_id == PrimitiveType::INVALID_TYPE) {
|
||||
return NestedJsonSkipKind::kInvalidType;
|
||||
}
|
||||
if (type->get_primitive_type() != PrimitiveType::TYPE_ARRAY ||
|
||||
field.get_type() != PrimitiveType::TYPE_ARRAY) {
|
||||
return NestedJsonSkipKind::kVisible;
|
||||
}
|
||||
|
||||
const auto& array = field.get<TYPE_ARRAY>();
|
||||
bool only_nulls_inside = true;
|
||||
bool only_empty_shells_inside = true;
|
||||
for (const auto& elem : array) {
|
||||
if (elem.get_type() != PrimitiveType::TYPE_NULL) {
|
||||
only_nulls_inside = false;
|
||||
}
|
||||
if (!is_semantically_empty_nested_field(elem)) {
|
||||
only_empty_shells_inside = false;
|
||||
}
|
||||
if (!only_nulls_inside && !only_empty_shells_inside) {
|
||||
return NestedJsonSkipKind::kVisible;
|
||||
}
|
||||
}
|
||||
|
||||
if (only_nulls_inside) {
|
||||
return NestedJsonSkipKind::kNullOnlyArray;
|
||||
}
|
||||
if (only_empty_shells_inside) {
|
||||
return NestedJsonSkipKind::kEmptyShellArray;
|
||||
}
|
||||
return NestedJsonSkipKind::kVisible;
|
||||
}
|
||||
|
||||
NestedJsonSkipKind get_nested_json_skip_kind(const ColumnVariant::Subcolumn& subcolumn,
|
||||
size_t row) {
|
||||
FieldWithDataType field;
|
||||
subcolumn.get(row, field);
|
||||
return classify_nested_json_skip_kind(field.field, subcolumn.get_least_common_base_type_id(),
|
||||
subcolumn.get_least_common_type());
|
||||
}
|
||||
|
||||
// Skip nested JSON during row serialization when it carries no visible payload.
|
||||
// For non-root nested subcolumns we ignore array payloads that recursively serialize to
|
||||
// nothing, e.g. [], [null], [{}], or [{"L2": []}].
|
||||
bool ColumnVariant::Subcolumn::is_empty_nested(size_t row) const {
|
||||
return get_nested_json_skip_kind(*this, row) != NestedJsonSkipKind::kVisible;
|
||||
}
|
||||
|
||||
// Root array visibility keeps the historical semantics: skip only null-like arrays and
|
||||
// INVALID_TYPE. Empty object arrays such as [{}] must remain visible at the root level.
|
||||
bool ColumnVariant::Subcolumn::is_empty_nested_root_value(size_t row) const {
|
||||
NestedJsonSkipKind kind = get_nested_json_skip_kind(*this, row);
|
||||
return kind == NestedJsonSkipKind::kNullOnlyArray || kind == NestedJsonSkipKind::kInvalidType;
|
||||
}
|
||||
|
||||
bool ColumnVariant::is_visible_root_value(size_t nrow) const {
|
||||
if (is_null_root()) {
|
||||
return false;
|
||||
@@ -1673,7 +1803,7 @@ bool ColumnVariant::is_visible_root_value(size_t nrow) const {
|
||||
// for top level array we should also use field to check if it is empty
|
||||
if (root->data.least_common_type.get_type_id() == PrimitiveType::TYPE_ARRAY) {
|
||||
// nested field which field is Array
|
||||
return !root->data.is_empty_nested(nrow);
|
||||
return !root->data.is_empty_nested_root_value(nrow);
|
||||
}
|
||||
for (const auto& subcolumn : subcolumns) {
|
||||
if (subcolumn->data.is_root) {
|
||||
|
||||
@@ -197,6 +197,7 @@ public:
|
||||
friend class ColumnVariant;
|
||||
|
||||
bool is_empty_nested(size_t row) const;
|
||||
bool is_empty_nested_root_value(size_t row) const;
|
||||
|
||||
void resize(size_t n);
|
||||
|
||||
@@ -298,14 +299,19 @@ public:
|
||||
private:
|
||||
friend class COWHelper<IColumn, ColumnVariant>;
|
||||
// always create root: data type nothing
|
||||
explicit ColumnVariant(int32_t max_subcolumns_count);
|
||||
explicit ColumnVariant(int32_t max_subcolumns_count, bool enable_doc_mode);
|
||||
|
||||
// always create root: data type nothing
|
||||
explicit ColumnVariant(int32_t max_subcolumns_count, size_t size);
|
||||
explicit ColumnVariant(int32_t max_subcolumns_count, bool enable_doc_mode, size_t size);
|
||||
|
||||
explicit ColumnVariant(int32_t max_subcolumns_count, DataTypePtr root_type,
|
||||
MutableColumnPtr&& root_column);
|
||||
explicit ColumnVariant(int32_t max_subcolumns_count, bool enable_doc_mode,
|
||||
DataTypePtr root_type, MutableColumnPtr&& root_column);
|
||||
|
||||
explicit ColumnVariant(int32_t max_subcolumns_count, Subcolumns&& subcolumns_);
|
||||
explicit ColumnVariant(int32_t max_subcolumns_count, bool enable_doc_mode,
|
||||
Subcolumns&& subcolumns_);
|
||||
|
||||
|
||||
@@ -45,6 +45,9 @@ class IColumn;
|
||||
|
||||
namespace doris {
|
||||
|
||||
DataTypeVariant::DataTypeVariant(int32_t max_subcolumns_count)
|
||||
: DataTypeVariant(max_subcolumns_count, false) {}
|
||||
|
||||
DataTypeVariant::DataTypeVariant(int32_t max_subcolumns_count, bool enable_doc_mode)
|
||||
: _max_subcolumns_count(max_subcolumns_count), _enable_doc_mode(enable_doc_mode) {
|
||||
name = fmt::format("Variant(max subcolumns count = {}, enable doc mode = {})",
|
||||
@@ -242,4 +245,4 @@ MutableColumnPtr DataTypeVariant::create_column() const {
|
||||
return ColumnVariant::create(_max_subcolumns_count, _enable_doc_mode);
|
||||
}
|
||||
|
||||
} // namespace doris
|
||||
} // namespace doris
|
||||
@@ -55,6 +55,7 @@ public:
|
||||
static constexpr PrimitiveType PType = TYPE_VARIANT;
|
||||
PrimitiveType get_primitive_type() const override { return PrimitiveType::TYPE_VARIANT; }
|
||||
DataTypeVariant() = default;
|
||||
DataTypeVariant(int32_t max_subcolumns_count);
|
||||
DataTypeVariant(int32_t max_subcolumns_count, bool enable_doc_mode);
|
||||
String do_get_name() const override { return name; }
|
||||
const std::string get_family_name() const override { return "Variant"; }
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
#include "storage/rowset/rowset.h"
|
||||
#include "storage/rowset/rowset_fwd.h"
|
||||
#include "storage/segment/segment_loader.h"
|
||||
#include "storage/segment/variant/nested_group_path.h"
|
||||
#include "storage/segment/variant/variant_column_reader.h"
|
||||
#include "storage/segment/variant/variant_column_writer_impl.h"
|
||||
#include "storage/tablet/tablet.h"
|
||||
@@ -259,6 +260,131 @@ bool glob_match_re2(const std::string& glob_pattern, const std::string& candidat
|
||||
return RE2::FullMatch(candidate_path, *compiled);
|
||||
}
|
||||
|
||||
bool is_regular_ng_compaction_subpath(const PathInData& path) {
|
||||
const std::string& relative_path = path.get_path();
|
||||
return !relative_path.empty() && !path.has_nested_part() &&
|
||||
!segment_v2::contains_nested_group_marker(relative_path) &&
|
||||
!segment_v2::is_root_nested_group_path(relative_path) &&
|
||||
relative_path != SPARSE_COLUMN_PATH &&
|
||||
relative_path.find(DOC_VALUE_COLUMN_PATH) == std::string::npos;
|
||||
}
|
||||
|
||||
bool should_keep_existing_ng_compaction_subcolumn(const TabletColumn& column) {
|
||||
if (!column.is_extracted_column()) {
|
||||
return false;
|
||||
}
|
||||
return is_regular_ng_compaction_subpath(column.path_info_ptr()->copy_pop_front());
|
||||
}
|
||||
|
||||
TabletIndexes clone_tablet_indexes(const std::vector<const TabletIndex*>& indexes) {
|
||||
TabletIndexes cloned_indexes;
|
||||
cloned_indexes.reserve(indexes.size());
|
||||
for (const auto* index : indexes) {
|
||||
cloned_indexes.emplace_back(std::make_shared<TabletIndex>(*index));
|
||||
}
|
||||
return cloned_indexes;
|
||||
}
|
||||
|
||||
void keep_existing_ng_compaction_subcolumn(const TabletSchemaSPtr& source_schema,
|
||||
const TabletColumnPtr& extracted_column,
|
||||
TabletSchemaSPtr& output_schema,
|
||||
TabletSchema::PathsSetInfo& paths_set_info) {
|
||||
DCHECK(extracted_column->is_extracted_column());
|
||||
DCHECK(should_keep_existing_ng_compaction_subcolumn(*extracted_column));
|
||||
|
||||
auto relative_path = extracted_column->path_info_ptr()->copy_pop_front();
|
||||
const std::string path = relative_path.get_path();
|
||||
output_schema->append_column(*extracted_column);
|
||||
|
||||
auto indexes = clone_tablet_indexes(source_schema->inverted_indexs(*extracted_column));
|
||||
if (extracted_column->path_info_ptr()->get_is_typed()) {
|
||||
TabletSchema::SubColumnInfo sub_column_info {.column = *extracted_column,
|
||||
.indexes = std::move(indexes)};
|
||||
paths_set_info.typed_path_set.emplace(path, std::move(sub_column_info));
|
||||
return;
|
||||
}
|
||||
|
||||
paths_set_info.sub_path_set.emplace(path);
|
||||
if (!indexes.empty()) {
|
||||
paths_set_info.subcolumn_indexes.emplace(path, std::move(indexes));
|
||||
}
|
||||
}
|
||||
|
||||
using ExistingNgCompactionSubcolumns = std::unordered_map<int32_t, std::vector<TabletColumnPtr>>;
|
||||
|
||||
struct NestedGroupCompactionMaterializationPlan {
|
||||
std::vector<TabletColumnPtr> preserved_regular_subcolumns;
|
||||
std::unordered_set<PathInData, PathInData::Hash> materialized_regular_paths;
|
||||
PathToDataTypes additional_regular_path_to_data_types;
|
||||
};
|
||||
|
||||
bool should_preserve_existing_ng_compaction_subcolumns(
|
||||
const TabletColumnPtr& column,
|
||||
const std::unordered_map<int32_t, VariantExtendedInfo>& uid_to_variant_extended_info) {
|
||||
const auto info_it = uid_to_variant_extended_info.find(column->unique_id());
|
||||
return column->variant_enable_nested_group() ||
|
||||
(info_it != uid_to_variant_extended_info.end() && info_it->second.has_nested_group);
|
||||
}
|
||||
|
||||
std::unordered_set<int32_t> collect_ng_compaction_root_uids(
|
||||
const TabletSchemaSPtr& target,
|
||||
const std::unordered_map<int32_t, VariantExtendedInfo>& uid_to_variant_extended_info) {
|
||||
std::unordered_set<int32_t> root_uids;
|
||||
for (const TabletColumnPtr& column : target->columns()) {
|
||||
if (column->is_variant_type() && should_preserve_existing_ng_compaction_subcolumns(
|
||||
column, uid_to_variant_extended_info)) {
|
||||
root_uids.insert(column->unique_id());
|
||||
}
|
||||
}
|
||||
return root_uids;
|
||||
}
|
||||
|
||||
ExistingNgCompactionSubcolumns collect_existing_ng_compaction_subcolumns(
|
||||
const TabletSchemaSPtr& target, const std::unordered_set<int32_t>& ng_root_uids) {
|
||||
ExistingNgCompactionSubcolumns uid_to_existing_subcolumns;
|
||||
for (const TabletColumnPtr& column : target->columns()) {
|
||||
if (!column->is_extracted_column() || !ng_root_uids.contains(column->parent_unique_id()) ||
|
||||
!should_keep_existing_ng_compaction_subcolumn(*column)) {
|
||||
continue;
|
||||
}
|
||||
uid_to_existing_subcolumns[column->parent_unique_id()].push_back(column);
|
||||
}
|
||||
return uid_to_existing_subcolumns;
|
||||
}
|
||||
|
||||
NestedGroupCompactionMaterializationPlan build_nested_group_compaction_materialization_plan(
|
||||
const std::vector<TabletColumnPtr>& existing_subcolumns,
|
||||
const VariantExtendedInfo& extended_info) {
|
||||
NestedGroupCompactionMaterializationPlan plan;
|
||||
plan.preserved_regular_subcolumns = existing_subcolumns;
|
||||
for (const auto& column : existing_subcolumns) {
|
||||
plan.materialized_regular_paths.emplace(column->path_info_ptr()->copy_pop_front());
|
||||
}
|
||||
for (const auto& [path, data_types] : extended_info.path_to_data_types) {
|
||||
if (!is_regular_ng_compaction_subpath(path) ||
|
||||
plan.materialized_regular_paths.contains(path)) {
|
||||
continue;
|
||||
}
|
||||
plan.materialized_regular_paths.emplace(path);
|
||||
plan.additional_regular_path_to_data_types.emplace(path, data_types);
|
||||
}
|
||||
return plan;
|
||||
}
|
||||
|
||||
void append_nested_group_compaction_columns(const TabletSchemaSPtr& target,
|
||||
const TabletColumnPtr& column,
|
||||
const NestedGroupCompactionMaterializationPlan& plan,
|
||||
TabletSchemaSPtr& output_schema,
|
||||
TabletSchema::PathsSetInfo& paths_set_info) {
|
||||
for (const auto& existing_column : plan.preserved_regular_subcolumns) {
|
||||
keep_existing_ng_compaction_subcolumn(target, existing_column, output_schema,
|
||||
paths_set_info);
|
||||
}
|
||||
VariantCompactionUtil::get_compaction_subcolumns_from_data_types(
|
||||
paths_set_info, column, target, plan.additional_regular_path_to_data_types,
|
||||
output_schema);
|
||||
}
|
||||
|
||||
size_t get_number_of_dimensions(const IDataType& type) {
|
||||
if (const auto* type_array = typeid_cast<const DataTypeArray*>(&type)) {
|
||||
return type_array->get_number_of_dimensions();
|
||||
@@ -857,9 +983,9 @@ Status VariantCompactionUtil::aggregate_variant_extended_info(
|
||||
if (!column->is_variant_type()) {
|
||||
continue;
|
||||
}
|
||||
auto& extended_info = (*uid_to_variant_extended_info)[column->unique_id()];
|
||||
if (column->variant_enable_nested_group()) {
|
||||
(*uid_to_variant_extended_info)[column->unique_id()].has_nested_group = true;
|
||||
continue;
|
||||
extended_info.has_nested_group = true;
|
||||
}
|
||||
for (const auto& segment : segment_cache.get_segments()) {
|
||||
std::shared_ptr<ColumnReader> column_reader;
|
||||
@@ -878,30 +1004,29 @@ Status VariantCompactionUtil::aggregate_variant_extended_info(
|
||||
const auto* source_stats = variant_column_reader->get_stats();
|
||||
CHECK(source_stats);
|
||||
|
||||
// 1. agg path -> stats
|
||||
for (const auto& [path, size] : source_stats->sparse_column_non_null_size) {
|
||||
(*uid_to_variant_extended_info)[column->unique_id()]
|
||||
.path_to_none_null_values[path] += size;
|
||||
(*uid_to_variant_extended_info)[column->unique_id()].sparse_paths.emplace(path);
|
||||
}
|
||||
if (!column->variant_enable_nested_group()) {
|
||||
// NG roots still need type metadata for regular subpaths such as `v.owner`,
|
||||
// but their compaction schema should not be driven by flat path stats.
|
||||
for (const auto& [path, size] : source_stats->sparse_column_non_null_size) {
|
||||
extended_info.path_to_none_null_values[path] += size;
|
||||
extended_info.sparse_paths.emplace(path);
|
||||
}
|
||||
|
||||
for (const auto& [path, size] : source_stats->subcolumns_non_null_size) {
|
||||
(*uid_to_variant_extended_info)[column->unique_id()]
|
||||
.path_to_none_null_values[path] += size;
|
||||
for (const auto& [path, size] : source_stats->subcolumns_non_null_size) {
|
||||
extended_info.path_to_none_null_values[path] += size;
|
||||
}
|
||||
}
|
||||
|
||||
//2. agg path -> schema
|
||||
auto& paths_types =
|
||||
(*uid_to_variant_extended_info)[column->unique_id()].path_to_data_types;
|
||||
variant_column_reader->get_subcolumns_types(&paths_types);
|
||||
variant_column_reader->get_subcolumns_types(&extended_info.path_to_data_types);
|
||||
|
||||
// 3. extract typed paths
|
||||
auto& typed_paths = (*uid_to_variant_extended_info)[column->unique_id()].typed_paths;
|
||||
variant_column_reader->get_typed_paths(&typed_paths);
|
||||
variant_column_reader->get_typed_paths(&extended_info.typed_paths);
|
||||
|
||||
// 4. extract nested paths
|
||||
auto& nested_paths = (*uid_to_variant_extended_info)[column->unique_id()].nested_paths;
|
||||
variant_column_reader->get_nested_paths(&nested_paths);
|
||||
if (!column->variant_enable_nested_group()) {
|
||||
variant_column_reader->get_nested_paths(&extended_info.nested_paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
@@ -1183,7 +1308,7 @@ void VariantCompactionUtil::get_compaction_subcolumns_from_data_types(
|
||||
DataTypePtr data_type;
|
||||
get_least_supertype_jsonb(data_types, &data_type);
|
||||
auto column_name = parent_column->name_lower_case() + "." + path.get_path();
|
||||
auto column_path = PathInData(column_name);
|
||||
auto column_path = PathInData(column_name, path.get_is_typed());
|
||||
TabletColumn sub_column =
|
||||
get_column_by_type(data_type, column_name,
|
||||
ExtraInfo {.unique_id = -1,
|
||||
@@ -1192,7 +1317,14 @@ void VariantCompactionUtil::get_compaction_subcolumns_from_data_types(
|
||||
inherit_column_attributes(*parent_column, sub_column);
|
||||
TabletIndexes sub_column_indexes;
|
||||
inherit_index(parent_indexes, sub_column_indexes, sub_column);
|
||||
paths_set_info.subcolumn_indexes.emplace(path.get_path(), std::move(sub_column_indexes));
|
||||
if (path.get_is_typed()) {
|
||||
TabletSchema::SubColumnInfo sub_column_info {.column = sub_column,
|
||||
.indexes = std::move(sub_column_indexes)};
|
||||
paths_set_info.typed_path_set.emplace(path.get_path(), std::move(sub_column_info));
|
||||
} else {
|
||||
paths_set_info.subcolumn_indexes.emplace(path.get_path(),
|
||||
std::move(sub_column_indexes));
|
||||
}
|
||||
output_schema->append_column(sub_column);
|
||||
VLOG_DEBUG << "append sub column " << path.get_path() << " data type "
|
||||
<< data_type->get_name();
|
||||
@@ -1224,6 +1356,9 @@ Status VariantCompactionUtil::get_extended_compaction_schema(
|
||||
TabletSchemaSPtr output_schema = std::make_shared<TabletSchema>();
|
||||
output_schema->shawdow_copy_without_columns(*target);
|
||||
std::unordered_map<int32_t, TabletSchema::PathsSetInfo> uid_to_paths_set_info;
|
||||
const auto ng_root_uids = collect_ng_compaction_root_uids(target, uid_to_variant_extended_info);
|
||||
const auto uid_to_existing_ng_subcolumns =
|
||||
collect_existing_ng_compaction_subcolumns(target, ng_root_uids);
|
||||
for (const TabletColumnPtr& column : target->columns()) {
|
||||
if (!column->is_extracted_column()) {
|
||||
output_schema->append_column(*column);
|
||||
@@ -1238,6 +1373,20 @@ Status VariantCompactionUtil::get_extended_compaction_schema(
|
||||
const VariantExtendedInfo& extended_info = info_it == uid_to_variant_extended_info.end()
|
||||
? empty_extended_info
|
||||
: info_it->second;
|
||||
auto& paths_set_info = uid_to_paths_set_info[column->unique_id()];
|
||||
if (ng_root_uids.contains(column->unique_id())) {
|
||||
const auto plan = build_nested_group_compaction_materialization_plan(
|
||||
uid_to_existing_ng_subcolumns.contains(column->unique_id())
|
||||
? uid_to_existing_ng_subcolumns.at(column->unique_id())
|
||||
: std::vector<TabletColumnPtr> {},
|
||||
extended_info);
|
||||
append_nested_group_compaction_columns(target, column, plan, output_schema,
|
||||
paths_set_info);
|
||||
LOG(INFO) << "Variant column uid=" << column->unique_id()
|
||||
<< " keeps nested-group root with regular extracted columns in compaction "
|
||||
"schema";
|
||||
continue;
|
||||
}
|
||||
if (!should_check_variant_path_stats(*column)) {
|
||||
VLOG_DEBUG << "skip extended schema compaction for variant uid=" << column->unique_id()
|
||||
<< " because the column disables variant path stats";
|
||||
@@ -1263,29 +1412,28 @@ Status VariantCompactionUtil::get_extended_compaction_schema(
|
||||
|
||||
// 1. append typed columns
|
||||
RETURN_IF_ERROR(get_compaction_typed_columns(target, extended_info.typed_paths, column,
|
||||
output_schema,
|
||||
uid_to_paths_set_info[column->unique_id()]));
|
||||
output_schema, paths_set_info));
|
||||
// 2. append nested columns
|
||||
RETURN_IF_ERROR(get_compaction_nested_columns(
|
||||
extended_info.nested_paths, extended_info.path_to_data_types, column, output_schema,
|
||||
uid_to_paths_set_info[column->unique_id()]));
|
||||
RETURN_IF_ERROR(get_compaction_nested_columns(extended_info.nested_paths,
|
||||
extended_info.path_to_data_types, column,
|
||||
output_schema, paths_set_info));
|
||||
|
||||
// 3. get the subpaths
|
||||
get_subpaths(column->variant_max_subcolumns_count(), extended_info.path_to_none_null_values,
|
||||
uid_to_paths_set_info[column->unique_id()]);
|
||||
paths_set_info);
|
||||
|
||||
// 4. append subcolumns
|
||||
if (column->variant_max_subcolumns_count() > 0 || !column->get_sub_columns().empty()) {
|
||||
get_compaction_subcolumns_from_subpaths(
|
||||
uid_to_paths_set_info[column->unique_id()], column, target,
|
||||
extended_info.path_to_data_types, extended_info.sparse_paths, output_schema);
|
||||
get_compaction_subcolumns_from_subpaths(paths_set_info, column, target,
|
||||
extended_info.path_to_data_types,
|
||||
extended_info.sparse_paths, output_schema);
|
||||
}
|
||||
// variant_max_subcolumns_count == 0 and no typed paths materialized
|
||||
// it means that all subcolumns are materialized, may be from old data
|
||||
else {
|
||||
get_compaction_subcolumns_from_data_types(
|
||||
uid_to_paths_set_info[column->unique_id()], column, target,
|
||||
extended_info.path_to_data_types, output_schema);
|
||||
get_compaction_subcolumns_from_data_types(paths_set_info, column, target,
|
||||
extended_info.path_to_data_types,
|
||||
output_schema);
|
||||
}
|
||||
|
||||
// append sparse column(s)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/column/column_nullable.h"
|
||||
#include "core/data_type/data_type_variant.h"
|
||||
#include "exprs/function/cast/cast_base.h"
|
||||
#include "exprs/function/cast/cast_to_string.h"
|
||||
@@ -26,12 +27,15 @@ namespace doris::CastWrapper {
|
||||
// shared implementation for casting from variant to arbitrary non-nullable target type
|
||||
inline Status cast_from_variant_impl(FunctionContext* context, Block& block,
|
||||
const ColumnNumbers& arguments, uint32_t result,
|
||||
size_t input_rows_count,
|
||||
const NullMap::value_type* /*null_map*/,
|
||||
size_t input_rows_count, const NullMap::value_type* null_map,
|
||||
const DataTypePtr& data_type_to) {
|
||||
const auto& col_with_type_and_name = block.get_by_position(arguments[0]);
|
||||
const auto& col_from = col_with_type_and_name.column;
|
||||
const auto& variant = assert_cast<const ColumnVariant&>(*col_from);
|
||||
const IColumn* variant_column = col_from.get();
|
||||
if (const auto* nullable = check_and_get_column<ColumnNullable>(*variant_column)) {
|
||||
variant_column = &nullable->get_nested_column();
|
||||
}
|
||||
const auto& variant = assert_cast<const ColumnVariant&>(*variant_column);
|
||||
ColumnPtr col_to = data_type_to->create_column();
|
||||
|
||||
if (!variant.is_finalized()) {
|
||||
@@ -79,10 +83,11 @@ inline Status cast_from_variant_impl(FunctionContext* context, Block& block,
|
||||
col_to = make_nullable(col_to, true);
|
||||
} else {
|
||||
col_to = tmp_block.get_by_position(1).column;
|
||||
// Note: here we should return the nullable result column
|
||||
col_to = wrap_in_nullable(
|
||||
col_to, Block({{nested, nested_from_type, ""}, {col_to, data_type_to, ""}}),
|
||||
{0}, input_rows_count);
|
||||
col_to = wrap_in_nullable(col_to,
|
||||
Block({{nested, nested_from_type, ""},
|
||||
{col_from, col_with_type_and_name.type, ""},
|
||||
{col_to, data_type_to, ""}}),
|
||||
{0, 1}, input_rows_count);
|
||||
}
|
||||
} else {
|
||||
if (variant.only_have_default_values()) {
|
||||
@@ -106,6 +111,13 @@ inline Status cast_from_variant_impl(FunctionContext* context, Block& block,
|
||||
}
|
||||
}
|
||||
|
||||
if (null_map == nullptr) {
|
||||
if (const auto* nullable_result = check_and_get_column<ColumnNullable>(*col_to);
|
||||
nullable_result != nullptr && !nullable_result->has_null()) {
|
||||
col_to = nullable_result->get_nested_column_ptr();
|
||||
}
|
||||
}
|
||||
|
||||
if (col_to->size() != input_rows_count) {
|
||||
return Status::InternalError("Unmatched row count {}, expected {}", col_to->size(),
|
||||
input_rows_count);
|
||||
|
||||
@@ -207,13 +207,17 @@ private:
|
||||
assert_cast<const ColumnString&>(doc_value_data_map.get_keys());
|
||||
const auto& src_doc_value_data_values =
|
||||
assert_cast<const ColumnString&>(doc_value_data_map.get_values());
|
||||
// Write extracted data into target's doc_value column (not sparse) to preserve
|
||||
// doc mode invariant: doc_mode columns must not have sparse data.
|
||||
auto& doc_value_offsets =
|
||||
assert_cast<ColumnMap&>(*target_ptr->get_doc_value_column()->assume_mutable())
|
||||
const bool write_to_doc_value = target_ptr->enable_doc_mode();
|
||||
// Ordinary Variant extraction keeps the selected prefix in sparse data, matching the
|
||||
// source branch behavior. Only doc-mode columns keep extracted data in doc_value.
|
||||
auto& extracted_offsets =
|
||||
assert_cast<ColumnMap&>(*(write_to_doc_value ? target_ptr->get_doc_value_column()
|
||||
: target_ptr->get_sparse_column())
|
||||
->assume_mutable())
|
||||
.get_offsets();
|
||||
auto [doc_value_paths, doc_value_values] =
|
||||
target_ptr->get_doc_value_data_paths_and_values();
|
||||
auto [extracted_paths, extracted_values] =
|
||||
write_to_doc_value ? target_ptr->get_doc_value_data_paths_and_values()
|
||||
: target_ptr->get_sparse_data_paths_and_values();
|
||||
StringRef prefix_ref(path.get_path());
|
||||
std::string_view path_prefix(prefix_ref.data, prefix_ref.size);
|
||||
for (size_t i = 0; i != src_doc_value_data_offsets.size(); ++i) {
|
||||
@@ -233,20 +237,24 @@ private:
|
||||
continue;
|
||||
}
|
||||
std::string_view sub_path = *sub_path_optional;
|
||||
doc_value_paths->insert_data(sub_path.data(), sub_path.size());
|
||||
doc_value_values->insert_from(src_doc_value_data_values, lower_bound_index);
|
||||
extracted_paths->insert_data(sub_path.data(), sub_path.size());
|
||||
extracted_values->insert_from(src_doc_value_data_values, lower_bound_index);
|
||||
} else {
|
||||
root.deserialize_from_binary_column(&src_doc_value_data_values,
|
||||
lower_bound_index);
|
||||
}
|
||||
}
|
||||
if (root.size() == doc_value_offsets.size()) {
|
||||
if (root.size() == extracted_offsets.size()) {
|
||||
root.insert_default();
|
||||
}
|
||||
doc_value_offsets.push_back(doc_value_paths->size());
|
||||
extracted_offsets.push_back(extracted_paths->size());
|
||||
}
|
||||
target_ptr->get_subcolumns().create_root(root);
|
||||
target_ptr->get_sparse_column()->assume_mutable()->resize(src_ptr->size());
|
||||
if (write_to_doc_value) {
|
||||
target_ptr->get_sparse_column()->assume_mutable()->resize(src_ptr->size());
|
||||
} else {
|
||||
target_ptr->get_doc_value_column()->assume_mutable()->resize(src_ptr->size());
|
||||
}
|
||||
target_ptr->set_num_rows(src_ptr->size());
|
||||
}
|
||||
|
||||
|
||||
@@ -263,6 +263,9 @@ Status Compaction::merge_input_rowsets() {
|
||||
}
|
||||
|
||||
RowsetWriterContext ctx;
|
||||
// Propagate input rowset readers into the rowset writer context before the writer is created.
|
||||
// Variant nested-group compaction uses this metadata to enable the streaming writer path.
|
||||
ctx.input_rs_readers = input_rs_readers;
|
||||
RETURN_IF_ERROR(construct_output_rowset_writer(ctx));
|
||||
|
||||
// write merged rows to output rowset
|
||||
|
||||
@@ -912,7 +912,8 @@ Status OffsetColumnWriter::append_data(const uint8_t** ptr, size_t num_rows) {
|
||||
while (remaining > 0) {
|
||||
size_t num_written = remaining;
|
||||
RETURN_IF_ERROR(append_data_in_current_page(ptr, &num_written));
|
||||
// _next_offset after append_data_in_current_page is the offset of next data, which will used in finish_current_page() to set next_array_item_ordinal
|
||||
// Callers provide one extra tail offset after the written rows so the page footer can
|
||||
// store the next array item ordinal for the current page.
|
||||
_next_offset = *(const uint64_t*)(*ptr);
|
||||
remaining -= num_written;
|
||||
|
||||
@@ -1185,14 +1186,10 @@ Status ArrayColumnWriter::write_ordinal_index() {
|
||||
}
|
||||
|
||||
Status ArrayColumnWriter::append_nulls(size_t num_rows) {
|
||||
size_t num_lengths = num_rows;
|
||||
const ordinal_t offset = _item_writer->get_next_rowid();
|
||||
while (num_lengths > 0) {
|
||||
// TODO llj bulk write
|
||||
const auto* offset_ptr = reinterpret_cast<const uint8_t*>(&offset);
|
||||
RETURN_IF_ERROR(_offset_writer->append_data(&offset_ptr, 1));
|
||||
--num_lengths;
|
||||
}
|
||||
const UInt64 offset = cast_set<UInt64>(_item_writer->get_next_rowid());
|
||||
std::vector<UInt64> offsets_data(num_rows + 1, offset);
|
||||
const uint8_t* offsets_ptr = reinterpret_cast<const uint8_t*>(offsets_data.data());
|
||||
RETURN_IF_ERROR(_offset_writer->append_data(&offsets_ptr, num_rows));
|
||||
return write_null_column(num_rows, true);
|
||||
}
|
||||
|
||||
@@ -1327,9 +1324,9 @@ Status MapColumnWriter::append_nulls(size_t num_rows) {
|
||||
for (auto& sub_writer : _kv_writers) {
|
||||
RETURN_IF_ERROR(sub_writer->append_nulls(num_rows));
|
||||
}
|
||||
const ordinal_t offset = _kv_writers[0]->get_next_rowid();
|
||||
std::vector<UInt8> offsets_data(num_rows, cast_set<uint8_t>(offset));
|
||||
const uint8_t* offsets_ptr = offsets_data.data();
|
||||
const UInt64 offset = cast_set<UInt64>(_kv_writers[0]->get_next_rowid());
|
||||
std::vector<UInt64> offsets_data(num_rows + 1, offset);
|
||||
const uint8_t* offsets_ptr = reinterpret_cast<const uint8_t*>(offsets_data.data());
|
||||
RETURN_IF_ERROR(_offsets_writer->append_data(&offsets_ptr, num_rows));
|
||||
|
||||
if (is_nullable()) {
|
||||
|
||||
@@ -65,6 +65,38 @@ inline std::string strip_nested_group_marker(std::string_view path) {
|
||||
return out;
|
||||
}
|
||||
|
||||
inline std::string strip_root_nested_group_path(std::string_view path) {
|
||||
if (path == kRootNestedGroupPath) {
|
||||
return {};
|
||||
}
|
||||
const std::string root_prefix = std::string(kRootNestedGroupPath) + ".";
|
||||
if (path.starts_with(root_prefix)) {
|
||||
return std::string(path.substr(root_prefix.size()));
|
||||
}
|
||||
return std::string(path);
|
||||
}
|
||||
|
||||
inline std::string build_nested_group_logical_child_path(std::string_view variant_name,
|
||||
std::string_view group_logical_path,
|
||||
std::string_view child_relative_path) {
|
||||
std::string path(variant_name);
|
||||
auto append_path = [&path](std::string_view part) {
|
||||
if (part.empty()) {
|
||||
return;
|
||||
}
|
||||
if (!path.empty()) {
|
||||
path.push_back('.');
|
||||
}
|
||||
path.append(part);
|
||||
};
|
||||
|
||||
const std::string group_path =
|
||||
strip_root_nested_group_path(strip_nested_group_marker(group_logical_path));
|
||||
append_path(group_path);
|
||||
append_path(child_relative_path);
|
||||
return path;
|
||||
}
|
||||
|
||||
inline std::string build_nested_group_offsets_column_name(std::string_view variant_name,
|
||||
std::string_view full_path) {
|
||||
std::string name;
|
||||
|
||||
@@ -65,6 +65,85 @@ bool is_compaction_or_checksum_reader(const StorageReadOptions* opts) {
|
||||
opts->io_ctx.reader_type == ReaderType::READER_CHECKSUM);
|
||||
}
|
||||
|
||||
// Nested-group whole/root-merge iterators dereference NestedGroupReader state that is owned by
|
||||
// VariantColumnReader. Hold the owning reader until the iterator itself is destroyed so query-time
|
||||
// iterator initialization cannot outlive the reader and hit a UAF.
|
||||
class ReaderOwnedColumnIterator final : public ColumnIterator {
|
||||
public:
|
||||
ReaderOwnedColumnIterator(ColumnIteratorUPtr inner, std::shared_ptr<ColumnReader> owner)
|
||||
: _inner(std::move(inner)), _owner(std::move(owner)) {
|
||||
DCHECK(_inner != nullptr);
|
||||
set_column_name(_inner->column_name());
|
||||
set_reading_flag(_inner->reading_flag());
|
||||
}
|
||||
|
||||
Status init(const ColumnIteratorOptions& opts) override { return _inner->init(opts); }
|
||||
|
||||
Status seek_to_ordinal(ordinal_t ord) override { return _inner->seek_to_ordinal(ord); }
|
||||
|
||||
Status next_batch(size_t* n, MutableColumnPtr& dst, bool* has_null) override {
|
||||
return _inner->next_batch(n, dst, has_null);
|
||||
}
|
||||
|
||||
Status next_batch_of_zone_map(size_t* n, MutableColumnPtr& dst) override {
|
||||
return _inner->next_batch_of_zone_map(n, dst);
|
||||
}
|
||||
|
||||
Status read_by_rowids(const rowid_t* rowids, const size_t count,
|
||||
MutableColumnPtr& dst) override {
|
||||
return _inner->read_by_rowids(rowids, count, dst);
|
||||
}
|
||||
|
||||
ordinal_t get_current_ordinal() const override { return _inner->get_current_ordinal(); }
|
||||
|
||||
Status get_row_ranges_by_zone_map(
|
||||
const AndBlockColumnPredicate* col_predicates,
|
||||
const std::vector<std::shared_ptr<const ColumnPredicate>>* delete_predicates,
|
||||
RowRanges* row_ranges) override {
|
||||
return _inner->get_row_ranges_by_zone_map(col_predicates, delete_predicates, row_ranges);
|
||||
}
|
||||
|
||||
Status get_row_ranges_by_bloom_filter(const AndBlockColumnPredicate* col_predicates,
|
||||
RowRanges* row_ranges) override {
|
||||
return _inner->get_row_ranges_by_bloom_filter(col_predicates, row_ranges);
|
||||
}
|
||||
|
||||
Status get_row_ranges_by_dict(const AndBlockColumnPredicate* col_predicates,
|
||||
RowRanges* row_ranges) override {
|
||||
return _inner->get_row_ranges_by_dict(col_predicates, row_ranges);
|
||||
}
|
||||
|
||||
bool is_all_dict_encoding() const override { return _inner->is_all_dict_encoding(); }
|
||||
|
||||
Status set_access_paths(const TColumnAccessPaths& all_access_paths,
|
||||
const TColumnAccessPaths& predicate_access_paths) override {
|
||||
RETURN_IF_ERROR(_inner->set_access_paths(all_access_paths, predicate_access_paths));
|
||||
set_reading_flag(_inner->reading_flag());
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void set_need_to_read() override {
|
||||
_inner->set_need_to_read();
|
||||
set_reading_flag(_inner->reading_flag());
|
||||
}
|
||||
|
||||
void remove_pruned_sub_iterators() override { _inner->remove_pruned_sub_iterators(); }
|
||||
|
||||
Status init_prefetcher(const SegmentPrefetchParams& params) override {
|
||||
return _inner->init_prefetcher(params);
|
||||
}
|
||||
|
||||
void collect_prefetchers(
|
||||
std::map<PrefetcherInitMethod, std::vector<SegmentPrefetcher*>>& prefetchers,
|
||||
PrefetcherInitMethod init_method) override {
|
||||
_inner->collect_prefetchers(prefetchers, init_method);
|
||||
}
|
||||
|
||||
private:
|
||||
ColumnIteratorUPtr _inner;
|
||||
std::shared_ptr<ColumnReader> _owner;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
const SubcolumnColumnMetaInfo::Node* VariantColumnReader::get_subcolumn_meta_by_path(
|
||||
@@ -326,13 +405,39 @@ Status VariantColumnReader::_build_read_plan_flat_leaves(
|
||||
int32_t col_uid =
|
||||
target_col.unique_id() >= 0 ? target_col.unique_id() : target_col.parent_unique_id();
|
||||
auto relative_path = target_col.path_info_ptr()->copy_pop_front();
|
||||
const auto* root = _subcolumns_meta_info->get_root();
|
||||
const auto* node =
|
||||
target_col.has_path_info() ? _subcolumns_meta_info->find_leaf(relative_path) : nullptr;
|
||||
|
||||
if (relative_path.empty()) {
|
||||
plan->type = create_variant_type(target_col);
|
||||
plan->relative_path = relative_path;
|
||||
plan->needs_root_merge = _needs_root_nested_group_merge(relative_path);
|
||||
if (_statistics->has_doc_value_column_non_null_size()) {
|
||||
plan->kind = ReadKind::HIERARCHICAL_DOC;
|
||||
plan->root = root;
|
||||
return Status::OK();
|
||||
}
|
||||
plan->kind = ReadKind::ROOT_FLAT;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
if (!relative_path.empty() && _can_use_nested_group_read_path() &&
|
||||
_try_fill_nested_group_plan(plan, target_col, opts, col_uid, relative_path)) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
const std::string dot_prefix = relative_path.get_path() + ".";
|
||||
if (target_col.variant_enable_doc_mode() &&
|
||||
_statistics->has_prefix_path_in_doc_value_column(dot_prefix)) {
|
||||
plan->kind = ReadKind::HIERARCHICAL_DOC;
|
||||
plan->type = create_variant_type(target_col);
|
||||
plan->relative_path = relative_path;
|
||||
plan->root = root;
|
||||
plan->needs_root_merge = _needs_root_nested_group_merge(relative_path);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// compaction need to read flat leaves nodes data to prevent from amplification
|
||||
if (!node) {
|
||||
// Handle sparse column reads in flat-leaf compaction.
|
||||
@@ -385,7 +490,6 @@ Status VariantColumnReader::_build_read_plan_flat_leaves(
|
||||
plan->relative_path = relative_path;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// If the path is typed, it means the path is not a sparse column, so we can't read the sparse column
|
||||
// even if the sparse column size is reached limit
|
||||
bool existed_in_sparse_column =
|
||||
@@ -411,14 +515,6 @@ Status VariantColumnReader::_build_read_plan_flat_leaves(
|
||||
plan->relative_path = relative_path;
|
||||
return Status::OK();
|
||||
}
|
||||
if (relative_path.empty()) {
|
||||
// root path, use VariantRootColumnIterator
|
||||
plan->kind = ReadKind::ROOT_FLAT;
|
||||
plan->type = create_variant_type(target_col);
|
||||
plan->relative_path = relative_path;
|
||||
plan->needs_root_merge = _needs_root_nested_group_merge(relative_path);
|
||||
return Status::OK();
|
||||
}
|
||||
VLOG_DEBUG << "new iterator: " << target_col.path_info_ptr()->get_path();
|
||||
std::shared_ptr<ColumnReader> column_reader;
|
||||
RETURN_IF_ERROR(column_reader_cache->get_path_column_reader(
|
||||
@@ -777,19 +873,24 @@ Status VariantColumnReader::_build_read_plan(ReadPlan* plan, const TabletColumn&
|
||||
// which means the path maybe exist in sparse_column
|
||||
bool exceeded_sparse_column_limit = _is_exceeded_sparse_column_limit_unlocked();
|
||||
|
||||
const std::string dot_prefix = relative_path.get_path() + ".";
|
||||
if (target_col.variant_enable_doc_mode() &&
|
||||
_statistics->has_prefix_path_in_doc_value_column(dot_prefix)) {
|
||||
plan->kind = ReadKind::HIERARCHICAL_DOC;
|
||||
plan->type = create_variant_type(target_col);
|
||||
plan->relative_path = relative_path;
|
||||
plan->root = root;
|
||||
plan->needs_root_merge = _needs_root_nested_group_merge(relative_path);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Check if path is prefix, example sparse columns path: a.b.c, a.b.e, access prefix: a.b.
|
||||
// Or access root path
|
||||
if (_has_prefix_path_unlocked(relative_path)) {
|
||||
// Example {"b" : {"c":456,"e":7.111}}
|
||||
// b.c is sparse column, b.e is subcolumn, so b is both the prefix of sparse column and
|
||||
// subcolumn.
|
||||
// Doc mode: prefer extracting hierarchy from doc_value column to preserve doc mode
|
||||
// invariant (root-only + doc_value). Non-doc mode: read from subcolumns + sparse.
|
||||
if (target_col.variant_enable_doc_mode()) {
|
||||
plan->kind = ReadKind::HIERARCHICAL_DOC;
|
||||
} else {
|
||||
plan->kind = ReadKind::HIERARCHICAL;
|
||||
}
|
||||
// subcolumn
|
||||
plan->kind = ReadKind::HIERARCHICAL;
|
||||
plan->type = create_variant_type(target_col);
|
||||
plan->relative_path = relative_path;
|
||||
plan->node = node;
|
||||
@@ -828,10 +929,7 @@ Status VariantColumnReader::_build_read_plan(ReadPlan* plan, const TabletColumn&
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
const std::string dot_prefix = relative_path.get_path() + ".";
|
||||
bool has_prefix_in_doc_column =
|
||||
_statistics->has_prefix_path_in_doc_value_column(dot_prefix);
|
||||
if (has_prefix_in_doc_column) {
|
||||
if (_statistics->has_prefix_path_in_doc_value_column(dot_prefix)) {
|
||||
plan->kind = ReadKind::HIERARCHICAL_DOC;
|
||||
plan->type = create_variant_type(target_col);
|
||||
plan->relative_path = relative_path;
|
||||
@@ -1013,8 +1111,16 @@ Status VariantColumnReader::new_iterator(ColumnIteratorUPtr* iterator,
|
||||
RETURN_IF_ERROR(_build_read_plan(&plan, *target_col, opt, column_reader_cache,
|
||||
binary_column_cache_ptr));
|
||||
// Caller of this overload does not need the storage type; only iterator is used.
|
||||
return _create_iterator_from_plan(iterator, plan, *target_col, opt, column_reader_cache,
|
||||
binary_column_cache_ptr);
|
||||
RETURN_IF_ERROR(_create_iterator_from_plan(iterator, plan, *target_col, opt,
|
||||
column_reader_cache, binary_column_cache_ptr));
|
||||
const bool needs_reader_owner = plan.needs_root_merge ||
|
||||
plan.kind == ReadKind::NESTED_GROUP_WHOLE ||
|
||||
plan.kind == ReadKind::NESTED_GROUP_CHILD;
|
||||
if (needs_reader_owner) {
|
||||
*iterator = std::make_unique<ReaderOwnedColumnIterator>(std::move(*iterator),
|
||||
shared_from_this());
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status VariantColumnReader::init(const ColumnReaderOptions& opts, ColumnMetaAccessor* accessor,
|
||||
@@ -1328,6 +1434,23 @@ TabletIndexes VariantColumnReader::find_subcolumn_tablet_indexes(const TabletCol
|
||||
TabletSchema::SubColumnInfo sub_column_info;
|
||||
const auto& parent_index = _tablet_schema->inverted_indexs(column.parent_unique_id());
|
||||
auto relative_path = column.path_info_ptr()->copy_pop_front();
|
||||
DataTypePtr index_data_type = data_type;
|
||||
|
||||
if (!relative_path.empty()) {
|
||||
auto [found, group_chain, child_path] =
|
||||
collect_nested_group_chain(relative_path.get_path());
|
||||
(void)child_path;
|
||||
if (found && !group_chain.empty()) {
|
||||
// NestedGroup leaf readers store the flattened element type.
|
||||
if (data_type->is_nullable()) {
|
||||
auto base = variant_util::get_base_type_of_array(remove_nullable(data_type));
|
||||
index_data_type = base->is_nullable() ? base : make_nullable(base);
|
||||
} else {
|
||||
index_data_type = variant_util::get_base_type_of_array(data_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if subcolumn has index, add index to _variant_subcolumns_indexes
|
||||
if (variant_util::generate_sub_column_info(*_tablet_schema, column.parent_unique_id(),
|
||||
relative_path.get_path(), &sub_column_info) &&
|
||||
@@ -1336,35 +1459,11 @@ TabletIndexes VariantColumnReader::find_subcolumn_tablet_indexes(const TabletCol
|
||||
}
|
||||
|
||||
// Otherwise, inherit index from the VARIANT parent column.
|
||||
if (!parent_index.empty() && data_type->get_primitive_type() != PrimitiveType::TYPE_VARIANT &&
|
||||
data_type->get_primitive_type() != PrimitiveType::TYPE_MAP /*SPARSE COLUMN*/) {
|
||||
if (!parent_index.empty() &&
|
||||
index_data_type->get_primitive_type() != PrimitiveType::TYPE_VARIANT &&
|
||||
index_data_type->get_primitive_type() != PrimitiveType::TYPE_MAP /*SPARSE COLUMN*/) {
|
||||
// type in column maynot be real type, so use data_type to get the real type
|
||||
PathInData index_path {*column.path_info_ptr()};
|
||||
DataTypePtr index_data_type = data_type;
|
||||
if (!relative_path.empty()) {
|
||||
auto [nested_reader, _] = find_nested_group_for_path(relative_path.get_path());
|
||||
const std::string root_path(kRootNestedGroupPath);
|
||||
|
||||
if (nested_reader != nullptr) {
|
||||
const bool is_root_ng = nested_reader->array_path == root_path;
|
||||
if (!is_root_ng) {
|
||||
// Named NG — use variant-relative path (consistent with write path)
|
||||
index_path = relative_path;
|
||||
} else {
|
||||
// $root NG — prefix path with __D0_root__
|
||||
index_path = PathInData(root_path + "." + relative_path.get_path());
|
||||
}
|
||||
|
||||
// Unwrap Nullable(Array(...)) → element type for NG subcolumns
|
||||
if (data_type->is_nullable()) {
|
||||
auto base = variant_util::get_base_type_of_array(remove_nullable(data_type));
|
||||
index_data_type = base->is_nullable() ? base : make_nullable(base);
|
||||
} else {
|
||||
index_data_type = variant_util::get_base_type_of_array(data_type);
|
||||
}
|
||||
}
|
||||
// else: non-NG scalar field — keep index_path and index_data_type unchanged
|
||||
}
|
||||
TabletColumn target_column =
|
||||
variant_util::get_column_by_type(index_data_type, column.name(),
|
||||
{.unique_id = -1,
|
||||
|
||||
@@ -1178,7 +1178,9 @@ bool VariantColumnWriterImpl::_can_use_nested_group_streaming_compaction() const
|
||||
}
|
||||
|
||||
Status VariantColumnWriterImpl::init() {
|
||||
if (_can_use_nested_group_streaming_compaction()) {
|
||||
const bool can_use_streaming = _can_use_nested_group_streaming_compaction();
|
||||
|
||||
if (can_use_streaming) {
|
||||
_streaming_compaction_writer = std::make_unique<VariantStreamingCompactionWriter>(
|
||||
_opts, _tablet_column, _nested_group_provider.get(), &_statistics);
|
||||
return _streaming_compaction_writer->init();
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "storage/segment/variant/variant_streaming_compaction_writer.h"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "common/cast_set.h"
|
||||
#include "core/column/column_nullable.h"
|
||||
@@ -25,6 +26,7 @@
|
||||
#include "exec/common/variant_util.h"
|
||||
#include "storage/index/indexed_column_writer.h"
|
||||
#include "storage/iterator/olap_data_convertor.h"
|
||||
#include "storage/rowset/rowset_writer_context.h"
|
||||
#include "storage/segment/variant/variant_writer_helpers.h"
|
||||
#include "storage/types.h"
|
||||
|
||||
@@ -62,7 +64,26 @@ Status VariantStreamingCompactionWriter::_init_root_writer() {
|
||||
|
||||
Status VariantStreamingCompactionWriter::_init_regular_subcolumn_writers(int& column_id) {
|
||||
_streaming_regular_subcolumn_writers.clear();
|
||||
const auto* path_set_info =
|
||||
_opts.rowset_ctx->tablet_schema->try_path_set_info(_tablet_column->unique_id());
|
||||
std::unordered_set<std::string> schema_regular_paths;
|
||||
for (const auto& column : _opts.rowset_ctx->tablet_schema->columns()) {
|
||||
if (!column->is_extracted_column() ||
|
||||
column->parent_unique_id() != _tablet_column->unique_id()) {
|
||||
continue;
|
||||
}
|
||||
schema_regular_paths.emplace(column->path_info_ptr()->copy_pop_front().get_path());
|
||||
}
|
||||
for (const auto& plan_entry : _streaming_plan.regular_subcolumns) {
|
||||
const bool is_materialized_regular_path =
|
||||
schema_regular_paths.contains(plan_entry.path) ||
|
||||
(path_set_info != nullptr &&
|
||||
path_set_info->contains_materialized_regular_path(plan_entry.path));
|
||||
if (is_materialized_regular_path) {
|
||||
// Compaction schema already decided to materialize this regular path as a standalone
|
||||
// extracted column. Reopening it here would duplicate the same writer target.
|
||||
continue;
|
||||
}
|
||||
TabletColumn tablet_column;
|
||||
TabletIndexes subcolumn_indexes;
|
||||
ColumnWriterOptions opts;
|
||||
|
||||
@@ -694,6 +694,12 @@ public:
|
||||
std::unordered_map<std::string, TabletIndexes> subcolumn_indexes; // subcolumns indexes
|
||||
PathSet sub_path_set; // extracted columns
|
||||
PathSet sparse_path_set; // sparse columns
|
||||
|
||||
// "Materialized regular path" means compaction chose to store this path as a dedicated
|
||||
// column in the schema, either typed or extracted, instead of re-emitting it dynamically.
|
||||
bool contains_materialized_regular_path(const std::string& path) const {
|
||||
return typed_path_set.contains(path) || sub_path_set.contains(path);
|
||||
}
|
||||
};
|
||||
|
||||
void set_path_set_info(std::unordered_map<int32_t, PathsSetInfo>&& path_set_info_map) {
|
||||
@@ -704,6 +710,11 @@ public:
|
||||
return _path_set_info_map.at(unique_id);
|
||||
}
|
||||
|
||||
const PathsSetInfo* try_path_set_info(int32_t unique_id) const {
|
||||
auto it = _path_set_info_map.find(unique_id);
|
||||
return it == _path_set_info_map.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
bool need_record_variant_extended_schema() const { return variant_max_subcolumns_count() == 0; }
|
||||
|
||||
int32_t variant_max_subcolumns_count() const {
|
||||
|
||||
@@ -61,6 +61,21 @@ void construct_column(ColumnPB* column_pb, TabletIndexPB* tablet_index, int64_t
|
||||
tablet_index->add_col_unique_id(col_unique_id);
|
||||
}
|
||||
|
||||
void append_inverted_index_for_path(TabletSchemaSPtr schema, int64_t index_id,
|
||||
const std::string& index_name, int32_t col_unique_id,
|
||||
const std::string& suffix_path) {
|
||||
TabletIndexPB index_pb;
|
||||
index_pb.set_index_id(index_id);
|
||||
index_pb.set_index_name(index_name);
|
||||
index_pb.set_index_type(IndexType::INVERTED);
|
||||
index_pb.add_col_unique_id(col_unique_id);
|
||||
|
||||
TabletIndex index;
|
||||
index.init_from_pb(index_pb);
|
||||
index.set_escaped_escaped_index_suffix_path(suffix_path);
|
||||
schema->append_index(std::move(index));
|
||||
}
|
||||
|
||||
void construct_subcolumn(TabletSchemaSPtr schema, const FieldType& type, int32_t col_unique_id,
|
||||
std::string_view path, std::vector<TabletColumn>* subcolumns) {
|
||||
TabletColumn subcol;
|
||||
@@ -1178,6 +1193,53 @@ TEST_F(SchemaUtilTest, TestGetCompactionSchema) {
|
||||
EXPECT_EQ(variant_col.get_sub_columns().size(), 0);
|
||||
}
|
||||
|
||||
TEST_F(SchemaUtilTest, get_extended_compaction_schema_nested_group_preserves_typed_subcolumns) {
|
||||
TabletColumn variant;
|
||||
variant.set_unique_id(1);
|
||||
variant.set_name("v1");
|
||||
variant.set_type(FieldType::OLAP_FIELD_TYPE_VARIANT);
|
||||
variant.set_is_nullable(true);
|
||||
variant.set_variant_enable_nested_group(true);
|
||||
variant.set_variant_max_subcolumns_count(0);
|
||||
|
||||
TabletColumn typed_subcolumn;
|
||||
typed_subcolumn.set_unique_id(-1);
|
||||
typed_subcolumn.set_name("v1.owner");
|
||||
typed_subcolumn.set_type(FieldType::OLAP_FIELD_TYPE_STRING);
|
||||
typed_subcolumn.set_is_nullable(true);
|
||||
typed_subcolumn.set_parent_unique_id(1);
|
||||
typed_subcolumn.set_path_info(PathInData("v1.owner", true));
|
||||
|
||||
TabletSchemaSPtr target_schema = std::make_shared<TabletSchema>();
|
||||
target_schema->append_column(variant);
|
||||
target_schema->append_column(typed_subcolumn);
|
||||
append_inverted_index_for_path(target_schema, 30000, "v1_owner_idx", 1, "v1.owner");
|
||||
|
||||
auto source_indexes = target_schema->inverted_indexs(typed_subcolumn);
|
||||
ASSERT_EQ(source_indexes.size(), 1);
|
||||
EXPECT_EQ(source_indexes[0]->index_name(), "v1_owner_idx");
|
||||
|
||||
std::vector<RowsetSharedPtr> rowsets;
|
||||
auto status = variant_util::VariantCompactionUtil::get_extended_compaction_schema(
|
||||
rowsets, target_schema);
|
||||
ASSERT_TRUE(status.ok()) << status.to_string();
|
||||
|
||||
EXPECT_EQ(target_schema->num_columns(), 2);
|
||||
const PathInData typed_path("v1.owner", true);
|
||||
const auto typed_column_index = target_schema->field_index(typed_path);
|
||||
ASSERT_NE(typed_column_index, -1);
|
||||
|
||||
const auto& preserved_typed_column = target_schema->column(typed_column_index);
|
||||
EXPECT_TRUE(preserved_typed_column.path_info_ptr()->get_is_typed());
|
||||
EXPECT_EQ(preserved_typed_column.type(), FieldType::OLAP_FIELD_TYPE_STRING);
|
||||
|
||||
const auto* path_set_info = target_schema->try_path_set_info(1);
|
||||
ASSERT_NE(path_set_info, nullptr);
|
||||
ASSERT_TRUE(path_set_info->typed_path_set.contains("owner"));
|
||||
EXPECT_EQ(path_set_info->typed_path_set.at("owner").indexes.size(), 1);
|
||||
EXPECT_EQ(path_set_info->typed_path_set.at("owner").indexes[0]->index_name(), "v1_owner_idx");
|
||||
}
|
||||
|
||||
TEST_F(SchemaUtilTest, TestGetSortedSubcolumns) {
|
||||
// Create test subcolumns
|
||||
ColumnVariant::Subcolumns subcolumns;
|
||||
@@ -1553,6 +1615,9 @@ TEST_F(SchemaUtilTest, get_compaction_subcolumns_from_data_types) {
|
||||
path_to_data_types[PathInData("a")] = {std::make_shared<DataTypeInt32>(),
|
||||
std::make_shared<DataTypeInt64>()}; // -> BIGINT
|
||||
path_to_data_types[PathInData("b")] = {std::make_shared<DataTypeString>()}; // -> STRING
|
||||
path_to_data_types[PathInData("typed", true)] = {std::make_shared<DataTypeString>()};
|
||||
path_to_data_types[PathInData("shared")] = {std::make_shared<DataTypeInt32>()};
|
||||
path_to_data_types[PathInData("shared", true)] = {std::make_shared<DataTypeString>()};
|
||||
|
||||
TabletSchemaSPtr output_schema = std::make_shared<TabletSchema>();
|
||||
TabletSchema::PathsSetInfo paths_set_info;
|
||||
@@ -1560,22 +1625,41 @@ TEST_F(SchemaUtilTest, get_compaction_subcolumns_from_data_types) {
|
||||
variant_util::VariantCompactionUtil::get_compaction_subcolumns_from_data_types(
|
||||
paths_set_info, parent_column, target, path_to_data_types, output_schema);
|
||||
|
||||
EXPECT_EQ(output_schema->num_columns(), 2);
|
||||
bool found_a = false, found_b = false;
|
||||
EXPECT_EQ(output_schema->num_columns(), 5);
|
||||
bool found_a = false, found_b = false, found_typed = false, found_shared = false,
|
||||
found_typed_shared = false;
|
||||
for (const auto& col : output_schema->columns()) {
|
||||
if (col->name() == "v1.a") {
|
||||
found_a = true;
|
||||
EXPECT_EQ(col->type(), FieldType::OLAP_FIELD_TYPE_BIGINT);
|
||||
EXPECT_EQ(col->parent_unique_id(), 1);
|
||||
EXPECT_EQ(col->path_info_ptr()->get_path(), "v1.a");
|
||||
EXPECT_FALSE(col->path_info_ptr()->get_is_typed());
|
||||
} else if (col->name() == "v1.b") {
|
||||
found_b = true;
|
||||
EXPECT_EQ(col->type(), FieldType::OLAP_FIELD_TYPE_STRING);
|
||||
EXPECT_EQ(col->parent_unique_id(), 1);
|
||||
EXPECT_EQ(col->path_info_ptr()->get_path(), "v1.b");
|
||||
EXPECT_FALSE(col->path_info_ptr()->get_is_typed());
|
||||
} else if (col->name() == "v1.typed") {
|
||||
found_typed = true;
|
||||
EXPECT_EQ(col->type(), FieldType::OLAP_FIELD_TYPE_STRING);
|
||||
EXPECT_EQ(col->parent_unique_id(), 1);
|
||||
EXPECT_EQ(col->path_info_ptr()->get_path(), "v1.typed");
|
||||
EXPECT_TRUE(col->path_info_ptr()->get_is_typed());
|
||||
} else if (col->name() == "v1.shared" && !col->path_info_ptr()->get_is_typed()) {
|
||||
found_shared = true;
|
||||
EXPECT_EQ(col->type(), FieldType::OLAP_FIELD_TYPE_INT);
|
||||
EXPECT_EQ(col->parent_unique_id(), 1);
|
||||
EXPECT_EQ(col->path_info_ptr()->get_path(), "v1.shared");
|
||||
} else if (col->name() == "v1.shared" && col->path_info_ptr()->get_is_typed()) {
|
||||
found_typed_shared = true;
|
||||
EXPECT_EQ(col->type(), FieldType::OLAP_FIELD_TYPE_STRING);
|
||||
EXPECT_EQ(col->parent_unique_id(), 1);
|
||||
EXPECT_EQ(col->path_info_ptr()->get_path(), "v1.shared");
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(found_a && found_b);
|
||||
EXPECT_TRUE(found_a && found_b && found_typed && found_shared && found_typed_shared);
|
||||
|
||||
ASSERT_TRUE(paths_set_info.subcolumn_indexes.find("a") !=
|
||||
paths_set_info.subcolumn_indexes.end());
|
||||
@@ -1583,6 +1667,13 @@ TEST_F(SchemaUtilTest, get_compaction_subcolumns_from_data_types) {
|
||||
paths_set_info.subcolumn_indexes.end());
|
||||
EXPECT_EQ(paths_set_info.subcolumn_indexes["a"].size(), 1);
|
||||
EXPECT_EQ(paths_set_info.subcolumn_indexes["b"].size(), 1);
|
||||
EXPECT_FALSE(paths_set_info.subcolumn_indexes.contains("typed"));
|
||||
ASSERT_TRUE(paths_set_info.typed_path_set.contains("typed"));
|
||||
EXPECT_EQ(paths_set_info.typed_path_set.at("typed").indexes.size(), 1);
|
||||
ASSERT_TRUE(paths_set_info.subcolumn_indexes.contains("shared"));
|
||||
ASSERT_TRUE(paths_set_info.typed_path_set.contains("shared"));
|
||||
EXPECT_EQ(paths_set_info.subcolumn_indexes.at("shared").size(), 1);
|
||||
EXPECT_EQ(paths_set_info.typed_path_set.at("shared").indexes.size(), 1);
|
||||
}
|
||||
|
||||
// Test has_different_structure_in_same_path function indirectly through check_variant_has_no_ambiguous_paths
|
||||
|
||||
@@ -134,4 +134,4 @@ TEST(HierarchicalDataIteratorTest, ProcessSparseExtractSubpaths) {
|
||||
|
||||
EXPECT_EQ(read_offs[0], 0);
|
||||
EXPECT_EQ(read_offs[1], 1);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@
|
||||
#include <thread>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "core/data_type/data_type_map.h"
|
||||
#include "core/data_type/data_type_string.h"
|
||||
#include "core/data_type_serde/data_type_serde.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "storage/rowset/rowset_factory.h"
|
||||
@@ -28,6 +30,7 @@
|
||||
#include "storage/segment/column_reader_cache.h"
|
||||
#include "storage/segment/variant/binary_column_extract_iterator.h"
|
||||
#include "storage/segment/variant/hierarchical_data_iterator.h"
|
||||
#include "storage/segment/variant/nested_group_path.h"
|
||||
#include "storage/segment/variant/nested_group_streaming_write_plan.h"
|
||||
#include "storage/segment/variant/sparse_column_merge_iterator.h"
|
||||
#include "storage/segment/variant/variant_column_reader.h"
|
||||
@@ -71,13 +74,13 @@ static void construct_column(ColumnPB* column_pb, int32_t col_unique_id,
|
||||
}
|
||||
}
|
||||
|
||||
// static void construct_tablet_index(TabletIndexPB* tablet_index, int64_t index_id,
|
||||
// const std::string& index_name, int32_t col_unique_id) {
|
||||
// tablet_index->set_index_id(index_id);
|
||||
// tablet_index->set_index_name(index_name);
|
||||
// tablet_index->set_index_type(IndexType::INVERTED);
|
||||
// tablet_index->add_col_unique_id(col_unique_id);
|
||||
// }
|
||||
static void construct_tablet_index(TabletIndexPB* tablet_index, int64_t index_id,
|
||||
const std::string& index_name, int32_t col_unique_id) {
|
||||
tablet_index->set_index_id(index_id);
|
||||
tablet_index->set_index_name(index_name);
|
||||
tablet_index->set_index_type(IndexType::INVERTED);
|
||||
tablet_index->add_col_unique_id(col_unique_id);
|
||||
}
|
||||
|
||||
// MockColumnReaderCache class for testing
|
||||
class MockColumnReaderCache : public segment_v2::ColumnReaderCache {
|
||||
@@ -145,9 +148,6 @@ static Status create_variant_root_reader(const SegmentFooterPB& footer,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
static std::string expected_doc_bucket_json_from_full(const std::string& full_json, int bucket_num,
|
||||
int bucket_index);
|
||||
|
||||
class VariantColumnWriterReaderTest : public testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
@@ -337,193 +337,6 @@ protected:
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void validate_doc_compact_writer_roundtrip(bool repeat_finish_write_calls) {
|
||||
constexpr int kRows = 200;
|
||||
constexpr int kDocBuckets = 4;
|
||||
constexpr int kBucket = 0;
|
||||
|
||||
TabletSchemaPB schema_pb;
|
||||
schema_pb.set_keys_type(KeysType::DUP_KEYS);
|
||||
construct_column(schema_pb.add_column(), 1, "VARIANT", "V1", 3, false, false,
|
||||
/*variant_sparse_hash_shard_count=*/0,
|
||||
/*variant_enable_doc_mode=*/true,
|
||||
/*variant_doc_materialization_min_rows=*/0,
|
||||
/*variant_doc_hash_shard_count=*/kDocBuckets);
|
||||
_tablet_schema = std::make_shared<TabletSchema>();
|
||||
_tablet_schema->init_from_pb(schema_pb);
|
||||
|
||||
TabletColumn parent_column = _tablet_schema->column(0);
|
||||
TabletColumn extracted_doc_bucket =
|
||||
variant_util::create_doc_value_column(parent_column, kBucket);
|
||||
extracted_doc_bucket.set_type(FieldType::OLAP_FIELD_TYPE_VARIANT);
|
||||
extracted_doc_bucket.set_is_nullable(false);
|
||||
_tablet_schema->append_column(extracted_doc_bucket);
|
||||
|
||||
TabletMetaSharedPtr tablet_meta(new TabletMeta(_tablet_schema));
|
||||
_tablet_schema->set_external_segment_meta_used_default(false);
|
||||
tablet_meta->_tablet_id = 33000;
|
||||
_tablet = std::make_shared<Tablet>(*_engine_ref, tablet_meta, _data_dir.get());
|
||||
EXPECT_TRUE(_tablet->init().ok());
|
||||
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
|
||||
EXPECT_TRUE(io::global_local_filesystem()->create_directory(_tablet->tablet_path()).ok());
|
||||
|
||||
io::FileWriterPtr file_writer;
|
||||
auto file_path = local_segment_path(_tablet->tablet_path(), "0", 0);
|
||||
auto st = io::global_local_filesystem()->create_file(file_path, &file_writer);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
|
||||
SegmentFooterPB footer;
|
||||
|
||||
RowsetWriterContext rowset_ctx;
|
||||
rowset_ctx.write_type = DataWriteType::TYPE_DIRECT;
|
||||
rowset_ctx.tablet_schema = _tablet_schema;
|
||||
|
||||
ColumnWriterOptions root_opts;
|
||||
root_opts.meta = footer.add_columns();
|
||||
root_opts.compression_type = CompressionTypePB::LZ4;
|
||||
root_opts.file_writer = file_writer.get();
|
||||
root_opts.footer = &footer;
|
||||
root_opts.rowset_ctx = &rowset_ctx;
|
||||
_init_column_meta(root_opts.meta, 0, parent_column, CompressionTypePB::LZ4);
|
||||
|
||||
std::unique_ptr<ColumnWriter> root_writer;
|
||||
EXPECT_TRUE(ColumnWriter::create(root_opts, &parent_column, file_writer.get(), &root_writer)
|
||||
.ok());
|
||||
EXPECT_TRUE(root_writer->init().ok());
|
||||
|
||||
TabletColumn extracted_doc_bucket_col = _tablet_schema->column(1);
|
||||
ColumnWriterOptions doc_compact_opts = root_opts;
|
||||
doc_compact_opts.meta = footer.add_columns();
|
||||
_init_column_meta(doc_compact_opts.meta, 0, extracted_doc_bucket_col,
|
||||
CompressionTypePB::LZ4);
|
||||
std::unique_ptr<ColumnWriter> doc_compact_writer;
|
||||
EXPECT_TRUE(ColumnWriter::create(doc_compact_opts, &extracted_doc_bucket_col,
|
||||
file_writer.get(), &doc_compact_writer)
|
||||
.ok());
|
||||
EXPECT_TRUE(doc_compact_writer->init().ok());
|
||||
|
||||
std::unordered_map<int, std::string> inserted_full_json;
|
||||
auto type_string = std::make_shared<DataTypeString>();
|
||||
auto full_json_column = type_string->create_column();
|
||||
auto* full_strings = assert_cast<ColumnString*>(full_json_column.get());
|
||||
VariantUtil::fill_string_column_with_test_data(full_strings, kRows, &inserted_full_json);
|
||||
|
||||
std::unordered_map<int, std::string> expected_bucket_json;
|
||||
auto bucket_json_column = type_string->create_column();
|
||||
auto* bucket_strings = assert_cast<ColumnString*>(bucket_json_column.get());
|
||||
for (int i = 0; i < kRows; ++i) {
|
||||
const std::string& full = inserted_full_json[i];
|
||||
std::string bucket_json =
|
||||
expected_doc_bucket_json_from_full(full, kDocBuckets, kBucket);
|
||||
expected_bucket_json.emplace(i, bucket_json);
|
||||
bucket_strings->insert_data(bucket_json.data(), bucket_json.size());
|
||||
}
|
||||
|
||||
ParseConfig config;
|
||||
config.deprecated_enable_flatten_nested = false;
|
||||
config.parse_to = ParseConfig::ParseTo::OnlyDocValueColumn;
|
||||
|
||||
MutableColumnPtr root_variant =
|
||||
ColumnVariant::create(parent_column.variant_max_subcolumns_count(), true);
|
||||
variant_util::parse_json_to_variant(*root_variant, *full_strings, config);
|
||||
|
||||
MutableColumnPtr bucket_variant =
|
||||
ColumnVariant::create(parent_column.variant_max_subcolumns_count(), true);
|
||||
variant_util::parse_json_to_variant(*bucket_variant, *bucket_strings, config);
|
||||
|
||||
auto root_data = std::make_unique<VariantColumnData>();
|
||||
root_data->column_data = root_variant.get();
|
||||
root_data->row_pos = 0;
|
||||
const auto* root_ptr = reinterpret_cast<const uint8_t*>(root_data.get());
|
||||
EXPECT_TRUE(root_writer->append_data(&root_ptr, kRows).ok());
|
||||
|
||||
auto bucket_data = std::make_unique<VariantColumnData>();
|
||||
bucket_data->column_data = bucket_variant.get();
|
||||
bucket_data->row_pos = 0;
|
||||
const auto* bucket_ptr = reinterpret_cast<const uint8_t*>(bucket_data.get());
|
||||
EXPECT_TRUE(doc_compact_writer->append_data(&bucket_ptr, kRows).ok());
|
||||
|
||||
EXPECT_TRUE(root_writer->finish().ok());
|
||||
EXPECT_TRUE(doc_compact_writer->finish().ok());
|
||||
if (repeat_finish_write_calls) {
|
||||
EXPECT_TRUE(doc_compact_writer->finish().ok());
|
||||
}
|
||||
EXPECT_TRUE(root_writer->write_data().ok());
|
||||
EXPECT_TRUE(doc_compact_writer->write_data().ok());
|
||||
if (repeat_finish_write_calls) {
|
||||
EXPECT_TRUE(doc_compact_writer->write_data().ok());
|
||||
EXPECT_TRUE(doc_compact_writer->finish().ok());
|
||||
}
|
||||
EXPECT_TRUE(root_writer->write_ordinal_index().ok());
|
||||
EXPECT_TRUE(doc_compact_writer->write_ordinal_index().ok());
|
||||
EXPECT_TRUE(file_writer->close().ok());
|
||||
footer.set_num_rows(kRows);
|
||||
|
||||
io::FileReaderSPtr file_reader;
|
||||
st = io::global_local_filesystem()->open_file(file_path, &file_reader);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
std::shared_ptr<ColumnReader> column_reader;
|
||||
st = create_variant_root_reader(footer, file_reader, _tablet_schema, &column_reader);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
auto* variant_column_reader = assert_cast<VariantColumnReader*>(column_reader.get());
|
||||
EXPECT_TRUE(variant_column_reader != nullptr);
|
||||
|
||||
bool checked_one_key = false;
|
||||
for (int j = 0; j < 10; ++j) {
|
||||
const std::string key = "key" + std::to_string(j);
|
||||
StringRef ref {key.data(), key.size()};
|
||||
if (variant_util::variant_binary_shard_of(ref, kDocBuckets) ==
|
||||
static_cast<uint32_t>(kBucket)) {
|
||||
EXPECT_TRUE(variant_column_reader->get_subcolumn_meta_by_path(PathInData(key)) !=
|
||||
nullptr);
|
||||
checked_one_key = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(checked_one_key);
|
||||
|
||||
MockColumnReaderCache column_reader_cache(footer, file_reader, _tablet_schema);
|
||||
StorageReadOptions storage_read_opts;
|
||||
storage_read_opts.io_ctx.reader_type = ReaderType::READER_BASE_COMPACTION;
|
||||
storage_read_opts.tablet_schema = _tablet_schema;
|
||||
OlapReaderStatistics stats;
|
||||
storage_read_opts.stats = &stats;
|
||||
|
||||
TabletColumn doc_bucket_map = variant_util::create_doc_value_column(parent_column, kBucket);
|
||||
ColumnIteratorUPtr it;
|
||||
st = variant_column_reader->new_iterator(&it, &doc_bucket_map, &storage_read_opts,
|
||||
&column_reader_cache);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
EXPECT_TRUE(dynamic_cast<segment_v2::VariantDocValueCompactIterator*>(it.get()) != nullptr);
|
||||
|
||||
ColumnIteratorOptions column_iter_opts;
|
||||
column_iter_opts.stats = &stats;
|
||||
column_iter_opts.file_reader = file_reader.get();
|
||||
st = it->init(column_iter_opts);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
|
||||
DataTypeSerDe::FormatOptions options;
|
||||
auto tz = cctz::utc_time_zone();
|
||||
options.timezone = &tz;
|
||||
|
||||
MutableColumnPtr dst =
|
||||
ColumnVariant::create(parent_column.variant_max_subcolumns_count(), false);
|
||||
size_t nrows = kRows;
|
||||
st = it->seek_to_ordinal(0);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
st = it->next_batch(&nrows, dst);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
EXPECT_EQ(nrows, kRows);
|
||||
|
||||
for (int i = 0; i < kRows; ++i) {
|
||||
std::string value;
|
||||
assert_cast<ColumnVariant*>(dst.get())->serialize_one_row_to_string(i, &value, options);
|
||||
EXPECT_EQ(value, expected_bucket_json[i]);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
|
||||
}
|
||||
|
||||
TabletSchemaSPtr _tablet_schema = nullptr;
|
||||
StorageEngine* _engine_ref = nullptr;
|
||||
std::unique_ptr<DataDir> _data_dir = nullptr;
|
||||
@@ -1721,6 +1534,34 @@ TEST_F(VariantColumnWriterReaderTest, test_read_doc_compact_from_doc_value_bucke
|
||||
auto tz = cctz::utc_time_zone();
|
||||
options.timezone = &tz;
|
||||
|
||||
ColumnIteratorUPtr root_it;
|
||||
st = variant_column_reader->new_iterator(&root_it, &parent_column, &storage_read_opts,
|
||||
&column_reader_cache);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
EXPECT_TRUE(dynamic_cast<HierarchicalDataIterator*>(root_it.get()) != nullptr);
|
||||
|
||||
ColumnIteratorOptions root_iter_opts;
|
||||
root_iter_opts.stats = &stats;
|
||||
root_iter_opts.file_reader = file_reader.get();
|
||||
st = root_it->init(root_iter_opts);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
|
||||
MutableColumnPtr root_dst =
|
||||
ColumnVariant::create(parent_column.variant_max_subcolumns_count(), false);
|
||||
size_t root_nrows = kRows;
|
||||
st = root_it->seek_to_ordinal(0);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
st = root_it->next_batch(&root_nrows, root_dst);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
EXPECT_EQ(root_nrows, kRows);
|
||||
|
||||
for (int i = 0; i < kRows; ++i) {
|
||||
std::string value;
|
||||
assert_cast<ColumnVariant*>(root_dst.get())
|
||||
->serialize_one_row_to_string(i, &value, options);
|
||||
EXPECT_EQ(value, inserted_jsonstr[i]);
|
||||
}
|
||||
|
||||
// 6. Read and validate each doc value bucket column: should choose ReadKind::DOC_COMPACT.
|
||||
for (int bucket = 0; bucket < kDocBuckets; ++bucket) {
|
||||
TabletColumn doc_bucket_col = variant_util::create_doc_value_column(parent_column, bucket);
|
||||
@@ -1756,12 +1597,198 @@ TEST_F(VariantColumnWriterReaderTest, test_read_doc_compact_from_doc_value_bucke
|
||||
}
|
||||
|
||||
TEST_F(VariantColumnWriterReaderTest, test_write_doc_compact_writer_and_read_doc_compact) {
|
||||
validate_doc_compact_writer_roundtrip(false);
|
||||
}
|
||||
constexpr int kRows = 200;
|
||||
constexpr int kDocBuckets = 4;
|
||||
constexpr int kBucket = 0;
|
||||
|
||||
TEST_F(VariantColumnWriterReaderTest,
|
||||
test_write_doc_compact_writer_finish_write_data_idempotent_and_read_doc_compact) {
|
||||
validate_doc_compact_writer_roundtrip(true);
|
||||
// 1. create tablet_schema: root variant is in doc mode; plus one extracted doc bucket column
|
||||
TabletSchemaPB schema_pb;
|
||||
schema_pb.set_keys_type(KeysType::DUP_KEYS);
|
||||
construct_column(schema_pb.add_column(), 1, "VARIANT", "V1", 3, false, false,
|
||||
/*variant_sparse_hash_shard_count=*/0,
|
||||
/*variant_enable_doc_mode=*/true,
|
||||
/*variant_doc_materialization_min_rows=*/0,
|
||||
/*variant_doc_hash_shard_count=*/kDocBuckets);
|
||||
_tablet_schema = std::make_shared<TabletSchema>();
|
||||
_tablet_schema->init_from_pb(schema_pb);
|
||||
|
||||
TabletColumn parent_column = _tablet_schema->column(0);
|
||||
TabletColumn extracted_doc_bucket =
|
||||
variant_util::create_doc_value_column(parent_column, kBucket);
|
||||
// This matches VariantCompactionUtil::get_extended_compaction_schema behavior:
|
||||
// extracted doc bucket columns are represented as VARIANT to trigger VariantDocCompactWriter.
|
||||
extracted_doc_bucket.set_type(FieldType::OLAP_FIELD_TYPE_VARIANT);
|
||||
extracted_doc_bucket.set_is_nullable(false);
|
||||
_tablet_schema->append_column(extracted_doc_bucket);
|
||||
|
||||
// 2. create tablet
|
||||
TabletMetaSharedPtr tablet_meta(new TabletMeta(_tablet_schema));
|
||||
_tablet_schema->set_external_segment_meta_used_default(false);
|
||||
tablet_meta->_tablet_id = 33000;
|
||||
_tablet = std::make_shared<Tablet>(*_engine_ref, tablet_meta, _data_dir.get());
|
||||
EXPECT_TRUE(_tablet->init().ok());
|
||||
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
|
||||
EXPECT_TRUE(io::global_local_filesystem()->create_directory(_tablet->tablet_path()).ok());
|
||||
|
||||
// 3. create file_writer
|
||||
io::FileWriterPtr file_writer;
|
||||
auto file_path = local_segment_path(_tablet->tablet_path(), "0", 0);
|
||||
auto st = io::global_local_filesystem()->create_file(file_path, &file_writer);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
|
||||
// 4. create column writers: root VariantColumnWriter + extracted VariantDocCompactWriter
|
||||
SegmentFooterPB footer;
|
||||
|
||||
RowsetWriterContext rowset_ctx;
|
||||
rowset_ctx.write_type = DataWriteType::TYPE_DIRECT;
|
||||
rowset_ctx.tablet_schema = _tablet_schema;
|
||||
|
||||
ColumnWriterOptions root_opts;
|
||||
root_opts.meta = footer.add_columns();
|
||||
root_opts.compression_type = CompressionTypePB::LZ4;
|
||||
root_opts.file_writer = file_writer.get();
|
||||
root_opts.footer = &footer;
|
||||
root_opts.rowset_ctx = &rowset_ctx;
|
||||
_init_column_meta(root_opts.meta, 0, parent_column, CompressionTypePB::LZ4);
|
||||
|
||||
std::unique_ptr<ColumnWriter> root_writer;
|
||||
EXPECT_TRUE(
|
||||
ColumnWriter::create(root_opts, &parent_column, file_writer.get(), &root_writer).ok());
|
||||
EXPECT_TRUE(root_writer->init().ok());
|
||||
|
||||
TabletColumn extracted_doc_bucket_col = _tablet_schema->column(1);
|
||||
ColumnWriterOptions doc_compact_opts = root_opts;
|
||||
doc_compact_opts.meta = footer.add_columns();
|
||||
_init_column_meta(doc_compact_opts.meta, 0, extracted_doc_bucket_col, CompressionTypePB::LZ4);
|
||||
std::unique_ptr<ColumnWriter> doc_compact_writer;
|
||||
EXPECT_TRUE(ColumnWriter::create(doc_compact_opts, &extracted_doc_bucket_col, file_writer.get(),
|
||||
&doc_compact_writer)
|
||||
.ok());
|
||||
EXPECT_TRUE(doc_compact_writer->init().ok());
|
||||
|
||||
// 5. build doc-value-only data:
|
||||
// - root column uses the full JSON (doc values only is enough for this test)
|
||||
// - extracted doc bucket column uses bucket-filtered JSON so that doc bucket data matches
|
||||
// the bucket index expected by VariantDocCompactWriter.
|
||||
std::unordered_map<int, std::string> inserted_full_json;
|
||||
auto type_string = std::make_shared<DataTypeString>();
|
||||
auto full_json_column = type_string->create_column();
|
||||
auto* full_strings = assert_cast<ColumnString*>(full_json_column.get());
|
||||
VariantUtil::fill_string_column_with_test_data(full_strings, kRows, &inserted_full_json);
|
||||
|
||||
std::unordered_map<int, std::string> expected_bucket_json;
|
||||
auto bucket_json_column = type_string->create_column();
|
||||
auto* bucket_strings = assert_cast<ColumnString*>(bucket_json_column.get());
|
||||
for (int i = 0; i < kRows; ++i) {
|
||||
const std::string& full = inserted_full_json[i];
|
||||
std::string bucket_json = expected_doc_bucket_json_from_full(full, kDocBuckets, kBucket);
|
||||
expected_bucket_json.emplace(i, bucket_json);
|
||||
bucket_strings->insert_data(bucket_json.data(), bucket_json.size());
|
||||
}
|
||||
|
||||
ParseConfig config;
|
||||
config.deprecated_enable_flatten_nested = false;
|
||||
config.parse_to = ParseConfig::ParseTo::OnlyDocValueColumn;
|
||||
|
||||
MutableColumnPtr root_variant = ColumnVariant::create(
|
||||
parent_column.variant_max_subcolumns_count(), parent_column.variant_enable_doc_mode());
|
||||
variant_util::parse_json_to_variant(*root_variant, *full_strings, config);
|
||||
|
||||
MutableColumnPtr bucket_variant = ColumnVariant::create(
|
||||
parent_column.variant_max_subcolumns_count(), parent_column.variant_enable_doc_mode());
|
||||
variant_util::parse_json_to_variant(*bucket_variant, *bucket_strings, config);
|
||||
|
||||
// 6. append and write
|
||||
{
|
||||
auto root_data = std::make_unique<VariantColumnData>();
|
||||
root_data->column_data = root_variant.get();
|
||||
root_data->row_pos = 0;
|
||||
const auto* data = reinterpret_cast<const uint8_t*>(root_data.get());
|
||||
EXPECT_TRUE(root_writer->append_data(&data, kRows).ok());
|
||||
}
|
||||
{
|
||||
auto bucket_data = std::make_unique<VariantColumnData>();
|
||||
bucket_data->column_data = bucket_variant.get();
|
||||
bucket_data->row_pos = 0;
|
||||
const auto* data = reinterpret_cast<const uint8_t*>(bucket_data.get());
|
||||
EXPECT_TRUE(doc_compact_writer->append_data(&data, kRows).ok());
|
||||
}
|
||||
|
||||
EXPECT_TRUE(root_writer->finish().ok());
|
||||
EXPECT_TRUE(doc_compact_writer->finish().ok());
|
||||
EXPECT_TRUE(root_writer->write_data().ok());
|
||||
EXPECT_TRUE(doc_compact_writer->write_data().ok());
|
||||
EXPECT_TRUE(root_writer->write_ordinal_index().ok());
|
||||
EXPECT_TRUE(doc_compact_writer->write_ordinal_index().ok());
|
||||
EXPECT_TRUE(file_writer->close().ok());
|
||||
footer.set_num_rows(kRows);
|
||||
|
||||
// 7. open reader and validate:
|
||||
// - doc bucket can be read via DOC_COMPACT iterator in flat-leaf compaction mode
|
||||
// - materialized leaf meta exists for at least one key in this bucket
|
||||
io::FileReaderSPtr file_reader;
|
||||
st = io::global_local_filesystem()->open_file(file_path, &file_reader);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
std::shared_ptr<ColumnReader> column_reader;
|
||||
st = create_variant_root_reader(footer, file_reader, _tablet_schema, &column_reader);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
auto* variant_column_reader = assert_cast<VariantColumnReader*>(column_reader.get());
|
||||
EXPECT_TRUE(variant_column_reader != nullptr);
|
||||
|
||||
bool checked_one_key = false;
|
||||
for (int j = 0; j < 10; ++j) {
|
||||
const std::string key = "key" + std::to_string(j);
|
||||
StringRef ref {key.data(), key.size()};
|
||||
if (variant_util::variant_binary_shard_of(ref, kDocBuckets) ==
|
||||
static_cast<uint32_t>(kBucket)) {
|
||||
EXPECT_TRUE(variant_column_reader->get_subcolumn_meta_by_path(PathInData(key)) !=
|
||||
nullptr);
|
||||
checked_one_key = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(checked_one_key);
|
||||
|
||||
MockColumnReaderCache column_reader_cache(footer, file_reader, _tablet_schema);
|
||||
StorageReadOptions storage_read_opts;
|
||||
storage_read_opts.io_ctx.reader_type = ReaderType::READER_BASE_COMPACTION;
|
||||
storage_read_opts.tablet_schema = _tablet_schema;
|
||||
OlapReaderStatistics stats;
|
||||
storage_read_opts.stats = &stats;
|
||||
|
||||
TabletColumn doc_bucket_map = variant_util::create_doc_value_column(parent_column, kBucket);
|
||||
ColumnIteratorUPtr it;
|
||||
st = variant_column_reader->new_iterator(&it, &doc_bucket_map, &storage_read_opts,
|
||||
&column_reader_cache);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
EXPECT_TRUE(dynamic_cast<segment_v2::VariantDocValueCompactIterator*>(it.get()) != nullptr);
|
||||
|
||||
ColumnIteratorOptions column_iter_opts;
|
||||
column_iter_opts.stats = &stats;
|
||||
column_iter_opts.file_reader = file_reader.get();
|
||||
st = it->init(column_iter_opts);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
|
||||
DataTypeSerDe::FormatOptions options;
|
||||
auto tz = cctz::utc_time_zone();
|
||||
options.timezone = &tz;
|
||||
|
||||
MutableColumnPtr dst =
|
||||
ColumnVariant::create(parent_column.variant_max_subcolumns_count(), false);
|
||||
size_t nrows = kRows;
|
||||
st = it->seek_to_ordinal(0);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
st = it->next_batch(&nrows, dst);
|
||||
EXPECT_TRUE(st.ok()) << st.msg();
|
||||
EXPECT_EQ(nrows, kRows);
|
||||
|
||||
for (int i = 0; i < kRows; ++i) {
|
||||
std::string value;
|
||||
assert_cast<ColumnVariant*>(dst.get())->serialize_one_row_to_string(i, &value, options);
|
||||
EXPECT_EQ(value, expected_bucket_json[i]);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
|
||||
}
|
||||
|
||||
TEST_F(VariantColumnWriterReaderTest, test_doc_compact_sparse_write_array_gap) {
|
||||
@@ -1838,8 +1865,8 @@ TEST_F(VariantColumnWriterReaderTest, test_doc_compact_sparse_write_array_gap) {
|
||||
parse_cfg.deprecated_enable_flatten_nested = false;
|
||||
parse_cfg.parse_to = ParseConfig::ParseTo::OnlyDocValueColumn;
|
||||
|
||||
MutableColumnPtr bucket_variant =
|
||||
ColumnVariant::create(parent_column.variant_max_subcolumns_count(), true);
|
||||
MutableColumnPtr bucket_variant = ColumnVariant::create(
|
||||
parent_column.variant_max_subcolumns_count(), parent_column.variant_enable_doc_mode());
|
||||
variant_util::parse_json_to_variant(*bucket_variant, *strings, parse_cfg);
|
||||
|
||||
auto bucket_data = std::make_unique<VariantColumnData>();
|
||||
@@ -1939,8 +1966,8 @@ TEST_F(VariantColumnWriterReaderTest, test_write_doc_sparse_write_array_gap_and_
|
||||
parse_cfg.deprecated_enable_flatten_nested = false;
|
||||
parse_cfg.parse_to = ParseConfig::ParseTo::OnlyDocValueColumn;
|
||||
|
||||
MutableColumnPtr variant_column =
|
||||
ColumnVariant::create(parent_column.variant_max_subcolumns_count(), true);
|
||||
MutableColumnPtr variant_column = ColumnVariant::create(
|
||||
parent_column.variant_max_subcolumns_count(), parent_column.variant_enable_doc_mode());
|
||||
variant_util::parse_json_to_variant(*variant_column, *strings, parse_cfg);
|
||||
|
||||
auto variant_data = std::make_unique<VariantColumnData>();
|
||||
@@ -2303,6 +2330,274 @@ TEST_F(VariantColumnWriterReaderTest, test_write_sub_index) {
|
||||
EXPECT_EQ(column_meta.type(), (int)FieldType::OLAP_FIELD_TYPE_VARIANT);
|
||||
}
|
||||
|
||||
TEST_F(VariantColumnWriterReaderTest, test_find_subcolumn_tablet_indexes_inherits_full_path) {
|
||||
TabletSchemaPB schema_pb;
|
||||
schema_pb.set_keys_type(KeysType::DUP_KEYS);
|
||||
schema_pb.set_inverted_index_storage_format(InvertedIndexStorageFormatPB::V2);
|
||||
construct_column(schema_pb.add_column(), 1, "VARIANT", "v", 10, false);
|
||||
_tablet_schema = std::make_shared<TabletSchema>();
|
||||
_tablet_schema->init_from_pb(schema_pb);
|
||||
|
||||
TabletMetaSharedPtr tablet_meta(new TabletMeta(_tablet_schema));
|
||||
_tablet_schema->set_external_segment_meta_used_default(false);
|
||||
tablet_meta->_tablet_id = 10001;
|
||||
_tablet = std::make_shared<Tablet>(*_engine_ref, tablet_meta, _data_dir.get());
|
||||
ASSERT_TRUE(_tablet->init().ok());
|
||||
ASSERT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
|
||||
ASSERT_TRUE(io::global_local_filesystem()->create_directory(_tablet->tablet_path()).ok());
|
||||
|
||||
io::FileWriterPtr file_writer;
|
||||
auto file_path = local_segment_path(_tablet->tablet_path(), "0", 0);
|
||||
auto st = io::global_local_filesystem()->create_file(file_path, &file_writer);
|
||||
ASSERT_TRUE(st.ok()) << st.msg();
|
||||
|
||||
SegmentFooterPB footer;
|
||||
ColumnWriterOptions opts;
|
||||
opts.meta = footer.add_columns();
|
||||
opts.compression_type = CompressionTypePB::LZ4;
|
||||
opts.file_writer = file_writer.get();
|
||||
opts.footer = &footer;
|
||||
RowsetWriterContext rowset_ctx;
|
||||
rowset_ctx.write_type = DataWriteType::TYPE_DIRECT;
|
||||
rowset_ctx.tablet_schema = _tablet_schema;
|
||||
opts.rowset_ctx = &rowset_ctx;
|
||||
TabletColumn column = _tablet_schema->column(0);
|
||||
_init_column_meta(opts.meta, 0, column, CompressionTypePB::LZ4);
|
||||
|
||||
std::unique_ptr<ColumnWriter> writer;
|
||||
ASSERT_TRUE(ColumnWriter::create(opts, &column, file_writer.get(), &writer).ok());
|
||||
ASSERT_TRUE(writer->init().ok());
|
||||
ASSERT_TRUE(append_json_batch(writer.get(), {R"({"a": "x"})"}).ok());
|
||||
ASSERT_TRUE(writer->finish().ok());
|
||||
ASSERT_TRUE(writer->write_data().ok());
|
||||
ASSERT_TRUE(writer->write_ordinal_index().ok());
|
||||
ASSERT_TRUE(writer->write_zone_map().ok());
|
||||
ASSERT_TRUE(file_writer->close().ok());
|
||||
footer.set_num_rows(1);
|
||||
|
||||
TabletIndexPB index_pb;
|
||||
construct_tablet_index(&index_pb, 10001, "idx_v", column.unique_id());
|
||||
TabletIndex parent_index;
|
||||
parent_index.init_from_pb(index_pb);
|
||||
_tablet_schema->append_index(std::move(parent_index));
|
||||
|
||||
io::FileReaderSPtr file_reader;
|
||||
st = io::global_local_filesystem()->open_file(file_path, &file_reader);
|
||||
ASSERT_TRUE(st.ok()) << st.msg();
|
||||
std::shared_ptr<segment_v2::ColumnReader> column_reader;
|
||||
st = create_variant_root_reader(footer, file_reader, _tablet_schema, &column_reader);
|
||||
ASSERT_TRUE(st.ok()) << st.msg();
|
||||
auto* variant_reader = assert_cast<segment_v2::VariantColumnReader*>(column_reader.get());
|
||||
ASSERT_NE(variant_reader, nullptr);
|
||||
|
||||
TabletColumn subcolumn;
|
||||
subcolumn.set_name("v.a");
|
||||
subcolumn.set_type(FieldType::OLAP_FIELD_TYPE_STRING);
|
||||
subcolumn.set_parent_unique_id(column.unique_id());
|
||||
subcolumn.set_path_info(PathInData("v.a"));
|
||||
subcolumn.set_is_nullable(true);
|
||||
|
||||
auto indexes = variant_reader->find_subcolumn_tablet_indexes(
|
||||
subcolumn, std::make_shared<DataTypeString>());
|
||||
ASSERT_EQ(indexes.size(), 1);
|
||||
EXPECT_EQ(indexes[0]->index_id(), 10001);
|
||||
EXPECT_EQ(indexes[0]->get_index_suffix(), "v%2Ea");
|
||||
EXPECT_NE(indexes[0]->get_index_suffix(), "a");
|
||||
}
|
||||
|
||||
TEST_F(VariantColumnWriterReaderTest, test_nested_group_logical_index_path_uses_variant_root) {
|
||||
EXPECT_EQ(segment_v2::build_nested_group_logical_child_path("v", "arr", "x"), "v.arr.x");
|
||||
EXPECT_EQ(segment_v2::build_nested_group_logical_child_path("v", "arr.inner", "z"),
|
||||
"v.arr.inner.z");
|
||||
EXPECT_EQ(segment_v2::build_nested_group_logical_child_path(
|
||||
"v", std::string(segment_v2::kRootNestedGroupPath), "x"),
|
||||
"v.x");
|
||||
EXPECT_EQ(segment_v2::build_nested_group_logical_child_path(
|
||||
"v", std::string(segment_v2::kRootNestedGroupPath) + ".inner", "z"),
|
||||
"v.inner.z");
|
||||
}
|
||||
|
||||
TEST_F(VariantColumnWriterReaderTest, test_find_subcolumn_tablet_indexes_branch_coverage) {
|
||||
TabletSchemaPB schema_pb;
|
||||
schema_pb.set_keys_type(KeysType::DUP_KEYS);
|
||||
schema_pb.set_inverted_index_storage_format(InvertedIndexStorageFormatPB::V2);
|
||||
construct_column(schema_pb.add_column(), 1, "VARIANT", "v", 10, false);
|
||||
_tablet_schema = std::make_shared<TabletSchema>();
|
||||
_tablet_schema->init_from_pb(schema_pb);
|
||||
|
||||
TabletMetaSharedPtr tablet_meta(new TabletMeta(_tablet_schema));
|
||||
_tablet_schema->set_external_segment_meta_used_default(false);
|
||||
tablet_meta->_tablet_id = 10002;
|
||||
_tablet = std::make_shared<Tablet>(*_engine_ref, tablet_meta, _data_dir.get());
|
||||
ASSERT_TRUE(_tablet->init().ok());
|
||||
ASSERT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
|
||||
ASSERT_TRUE(io::global_local_filesystem()->create_directory(_tablet->tablet_path()).ok());
|
||||
|
||||
io::FileWriterPtr file_writer;
|
||||
auto file_path = local_segment_path(_tablet->tablet_path(), "0", 0);
|
||||
auto st = io::global_local_filesystem()->create_file(file_path, &file_writer);
|
||||
ASSERT_TRUE(st.ok()) << st.msg();
|
||||
|
||||
SegmentFooterPB footer;
|
||||
ColumnWriterOptions opts;
|
||||
opts.meta = footer.add_columns();
|
||||
opts.compression_type = CompressionTypePB::LZ4;
|
||||
opts.file_writer = file_writer.get();
|
||||
opts.footer = &footer;
|
||||
RowsetWriterContext rowset_ctx;
|
||||
rowset_ctx.write_type = DataWriteType::TYPE_DIRECT;
|
||||
rowset_ctx.tablet_schema = _tablet_schema;
|
||||
opts.rowset_ctx = &rowset_ctx;
|
||||
TabletColumn root_column = _tablet_schema->column(0);
|
||||
_init_column_meta(opts.meta, 0, root_column, CompressionTypePB::LZ4);
|
||||
|
||||
std::unique_ptr<ColumnWriter> writer;
|
||||
ASSERT_TRUE(ColumnWriter::create(opts, &root_column, file_writer.get(), &writer).ok());
|
||||
ASSERT_TRUE(writer->init().ok());
|
||||
ASSERT_TRUE(append_json_batch(writer.get(), {R"({"a": "x", "own": "y"})"}).ok());
|
||||
ASSERT_TRUE(writer->finish().ok());
|
||||
ASSERT_TRUE(writer->write_data().ok());
|
||||
ASSERT_TRUE(writer->write_ordinal_index().ok());
|
||||
ASSERT_TRUE(writer->write_zone_map().ok());
|
||||
ASSERT_TRUE(file_writer->close().ok());
|
||||
footer.set_num_rows(1);
|
||||
|
||||
io::FileReaderSPtr file_reader;
|
||||
st = io::global_local_filesystem()->open_file(file_path, &file_reader);
|
||||
ASSERT_TRUE(st.ok()) << st.msg();
|
||||
std::shared_ptr<segment_v2::ColumnReader> column_reader;
|
||||
st = create_variant_root_reader(footer, file_reader, _tablet_schema, &column_reader);
|
||||
ASSERT_TRUE(st.ok()) << st.msg();
|
||||
auto* variant_reader = assert_cast<segment_v2::VariantColumnReader*>(column_reader.get());
|
||||
ASSERT_NE(variant_reader, nullptr);
|
||||
|
||||
const int32_t root_unique_id = root_column.unique_id();
|
||||
auto make_subcolumn = [&](std::string name, FieldType type, std::string path,
|
||||
int32_t parent_unique_id) {
|
||||
TabletColumn subcolumn;
|
||||
subcolumn.set_name(std::move(name));
|
||||
subcolumn.set_type(type);
|
||||
subcolumn.set_parent_unique_id(parent_unique_id);
|
||||
subcolumn.set_unique_id(2001);
|
||||
subcolumn.set_path_info(PathInData(std::move(path)));
|
||||
subcolumn.set_is_nullable(true);
|
||||
return subcolumn;
|
||||
};
|
||||
|
||||
{
|
||||
auto no_parent_index = variant_reader->find_subcolumn_tablet_indexes(
|
||||
make_subcolumn("v.missing", FieldType::OLAP_FIELD_TYPE_STRING, "v.missing",
|
||||
root_unique_id),
|
||||
std::make_shared<DataTypeString>());
|
||||
EXPECT_TRUE(no_parent_index.empty());
|
||||
}
|
||||
|
||||
TabletIndexPB parent_index_pb;
|
||||
construct_tablet_index(&parent_index_pb, 10002, "idx_v", root_column.unique_id());
|
||||
TabletIndex parent_index;
|
||||
parent_index.init_from_pb(parent_index_pb);
|
||||
_tablet_schema->append_index(std::move(parent_index));
|
||||
|
||||
{
|
||||
auto inherited = variant_reader->find_subcolumn_tablet_indexes(
|
||||
make_subcolumn("v.a", FieldType::OLAP_FIELD_TYPE_STRING, "v.a", root_unique_id),
|
||||
std::make_shared<DataTypeString>());
|
||||
ASSERT_EQ(inherited.size(), 1);
|
||||
EXPECT_EQ(inherited[0]->index_id(), 10002);
|
||||
EXPECT_EQ(inherited[0]->get_index_suffix(), "v%2Ea");
|
||||
}
|
||||
|
||||
{
|
||||
auto plain_array = variant_reader->find_subcolumn_tablet_indexes(
|
||||
make_subcolumn("v.plainarr", FieldType::OLAP_FIELD_TYPE_ARRAY, "v.plainarr",
|
||||
root_unique_id),
|
||||
std::make_shared<DataTypeNullable>(
|
||||
std::make_shared<DataTypeArray>(std::make_shared<DataTypeString>())));
|
||||
ASSERT_EQ(plain_array.size(), 1);
|
||||
EXPECT_EQ(plain_array[0]->index_id(), 10002);
|
||||
EXPECT_EQ(plain_array[0]->get_index_suffix(), "v%2Eplainarr");
|
||||
EXPECT_NE(plain_array[0]->get_index_suffix(), "plainarr");
|
||||
}
|
||||
|
||||
{
|
||||
auto variant_type = variant_reader->find_subcolumn_tablet_indexes(
|
||||
make_subcolumn("v.object", FieldType::OLAP_FIELD_TYPE_VARIANT, "v.object",
|
||||
root_unique_id),
|
||||
std::make_shared<DataTypeVariant>(10, false));
|
||||
EXPECT_TRUE(variant_type.empty());
|
||||
}
|
||||
|
||||
{
|
||||
auto sparse_map_type = variant_reader->find_subcolumn_tablet_indexes(
|
||||
make_subcolumn("v.__sparse", FieldType::OLAP_FIELD_TYPE_MAP, "v.__sparse",
|
||||
root_unique_id),
|
||||
std::make_shared<DataTypeMap>(std::make_shared<DataTypeString>(),
|
||||
std::make_shared<DataTypeString>()));
|
||||
EXPECT_TRUE(sparse_map_type.empty());
|
||||
}
|
||||
|
||||
TabletColumn indexed_subcolumn;
|
||||
indexed_subcolumn.set_name("own");
|
||||
indexed_subcolumn.set_type(FieldType::OLAP_FIELD_TYPE_STRING);
|
||||
_tablet_schema->mutable_column_by_uid(root_column.unique_id())
|
||||
.add_sub_column(indexed_subcolumn);
|
||||
|
||||
TabletIndexPB own_index_pb;
|
||||
construct_tablet_index(&own_index_pb, 10003, "idx_v_own", root_column.unique_id());
|
||||
(*own_index_pb.mutable_properties())["field_pattern"] = "own";
|
||||
TabletIndex own_index;
|
||||
own_index.init_from_pb(own_index_pb);
|
||||
_tablet_schema->append_index(std::move(own_index));
|
||||
|
||||
{
|
||||
auto own = variant_reader->find_subcolumn_tablet_indexes(
|
||||
make_subcolumn("v.own", FieldType::OLAP_FIELD_TYPE_STRING, "v.own", root_unique_id),
|
||||
std::make_shared<DataTypeString>());
|
||||
ASSERT_EQ(own.size(), 1);
|
||||
EXPECT_EQ(own[0]->index_id(), 10003);
|
||||
EXPECT_EQ(own[0]->get_index_suffix(), "v%2Eown");
|
||||
}
|
||||
|
||||
auto group_reader = std::make_unique<segment_v2::NestedGroupReader>();
|
||||
group_reader->array_path = "arr";
|
||||
group_reader->offsets_reader = std::make_shared<segment_v2::ColumnReader>();
|
||||
group_reader->child_readers.emplace("x", nullptr);
|
||||
auto& nested_group_readers =
|
||||
const_cast<segment_v2::NestedGroupReaders&>(variant_reader->get_nested_group_readers());
|
||||
nested_group_readers.emplace("arr", std::move(group_reader));
|
||||
|
||||
{
|
||||
auto nested = variant_reader->find_subcolumn_tablet_indexes(
|
||||
make_subcolumn("v.arr.x", FieldType::OLAP_FIELD_TYPE_ARRAY, "v.arr.x",
|
||||
root_unique_id),
|
||||
std::make_shared<DataTypeNullable>(
|
||||
std::make_shared<DataTypeArray>(std::make_shared<DataTypeString>())));
|
||||
ASSERT_EQ(nested.size(), 1);
|
||||
EXPECT_EQ(nested[0]->index_id(), 10002);
|
||||
EXPECT_EQ(nested[0]->get_index_suffix(), "v%2Earr%2Ex");
|
||||
EXPECT_NE(nested[0]->get_index_suffix(), "arr%2Ex");
|
||||
}
|
||||
|
||||
auto nested_group_reader = std::make_unique<segment_v2::NestedGroupReader>();
|
||||
nested_group_reader->array_path = "inner";
|
||||
nested_group_reader->offsets_reader = std::make_shared<segment_v2::ColumnReader>();
|
||||
nested_group_reader->child_readers.emplace("z", nullptr);
|
||||
nested_group_readers.at("arr")->nested_group_readers.emplace("inner",
|
||||
std::move(nested_group_reader));
|
||||
|
||||
{
|
||||
auto nested = variant_reader->find_subcolumn_tablet_indexes(
|
||||
make_subcolumn("v.arr.inner.z", FieldType::OLAP_FIELD_TYPE_ARRAY, "v.arr.inner.z",
|
||||
root_unique_id),
|
||||
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeArray>(
|
||||
std::make_shared<DataTypeArray>(std::make_shared<DataTypeString>()))));
|
||||
ASSERT_EQ(nested.size(), 1);
|
||||
EXPECT_EQ(nested[0]->index_id(), 10002);
|
||||
EXPECT_EQ(nested[0]->get_index_suffix(), "v%2Earr%2Einner%2Ez");
|
||||
EXPECT_NE(nested[0]->get_index_suffix(), "arr%2Einner%2Ez");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(VariantColumnWriterReaderTest, test_write_data_nullable) {
|
||||
// 1. create tablet_schema
|
||||
TabletSchemaPB schema_pb;
|
||||
|
||||
@@ -413,37 +413,6 @@ public class ExprToThriftVisitor extends ExprVisitor<Void, TExprNode> {
|
||||
public Void visitSearchPredicate(SearchPredicate expr, TExprNode msg) {
|
||||
msg.node_type = TExprNodeType.SEARCH_EXPR;
|
||||
msg.setSearchParam(buildSearchThriftParam(expr));
|
||||
|
||||
LOG.info("SearchPredicate.toThrift: dsl='{}', num_children_in_base={}, children_size={}",
|
||||
expr.getDslString(), msg.num_children, expr.getChildren().size());
|
||||
|
||||
if (expr.getQsPlan() != null) {
|
||||
LOG.info("SearchPredicate.toThrift: QsPlan fieldBindings.size={}",
|
||||
expr.getQsPlan().getFieldBindings() != null
|
||||
? expr.getQsPlan().getFieldBindings().size() : 0);
|
||||
if (expr.getQsPlan().getFieldBindings() != null) {
|
||||
for (int i = 0; i < expr.getQsPlan().getFieldBindings().size(); i++) {
|
||||
SearchDslParser.QsFieldBinding binding = expr.getQsPlan().getFieldBindings().get(i);
|
||||
LOG.info("SearchPredicate.toThrift: binding[{}] fieldName='{}', slotIndex={}",
|
||||
i, binding.getFieldName(), binding.getSlotIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < expr.getChildren().size(); i++) {
|
||||
Expr child = expr.getChildren().get(i);
|
||||
LOG.info("SearchPredicate.toThrift: child[{}] = {} (type={})",
|
||||
i, child.getClass().getSimpleName(), child.getType());
|
||||
if (child instanceof SlotRef) {
|
||||
SlotRef slotRef = (SlotRef) child;
|
||||
LOG.info("SearchPredicate.toThrift: SlotRef details - column={}",
|
||||
slotRef.getColumnName());
|
||||
if (slotRef.getDesc() != null) {
|
||||
LOG.info("SearchPredicate.toThrift: SlotRef analyzed - slotId={}",
|
||||
slotRef.getSlotId());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -662,9 +631,6 @@ public class ExprToThriftVisitor extends ExprVisitor<Void, TExprNode> {
|
||||
thriftBinding.setIsVariantSubcolumn(true);
|
||||
thriftBinding.setParentFieldName(parentField);
|
||||
thriftBinding.setSubcolumnPath(subcolumnPath);
|
||||
|
||||
LOG.info("buildThriftParam: variant subcolumn field='{}', parent='{}', subcolumn='{}'",
|
||||
fieldPath, parentField, subcolumnPath);
|
||||
} else {
|
||||
thriftBinding.setIsVariantSubcolumn(false);
|
||||
}
|
||||
@@ -675,10 +641,7 @@ public class ExprToThriftVisitor extends ExprVisitor<Void, TExprNode> {
|
||||
SlotRef slotRef = (SlotRef) expr.getChildren().get(i);
|
||||
int actualSlotId = slotRef.getSlotId().asInt();
|
||||
thriftBinding.setSlotIndex(actualSlotId);
|
||||
LOG.info("buildThriftParam: binding field='{}', actual slotId={}",
|
||||
binding.getFieldName(), actualSlotId);
|
||||
} else {
|
||||
LOG.warn("buildThriftParam: No corresponding SlotRef for field '{}'", binding.getFieldName());
|
||||
thriftBinding.setSlotIndex(i);
|
||||
}
|
||||
|
||||
@@ -687,8 +650,6 @@ public class ExprToThriftVisitor extends ExprVisitor<Void, TExprNode> {
|
||||
Map<String, String> properties = fieldIndexes.get(i).getProperties();
|
||||
if (properties != null && !properties.isEmpty()) {
|
||||
thriftBinding.setIndexProperties(properties);
|
||||
LOG.debug("buildThriftParam: field='{}' index_properties={}",
|
||||
fieldPath, properties);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2182,8 +2182,8 @@ public class PropertyAnalyzer {
|
||||
String bucketNumStr = properties.get(PROPERTIES_VARIANT_SPARSE_HASH_SHARD_COUNT);
|
||||
try {
|
||||
bucketNum = Integer.parseInt(bucketNumStr);
|
||||
if (bucketNum < 1 || bucketNum > 1024) {
|
||||
throw new AnalysisException("variant_sparse_hash_shard_count must between 1 and 1024 ");
|
||||
if (bucketNum < 0 || bucketNum > 1024) {
|
||||
throw new AnalysisException("variant_sparse_hash_shard_count must between 0 and 1024 ");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new AnalysisException("variant_sparse_hash_shard_count format error:" + e.getMessage());
|
||||
|
||||
+26
@@ -36,8 +36,10 @@ import org.apache.doris.analysis.LambdaFunctionExpr;
|
||||
import org.apache.doris.analysis.MatchPredicate;
|
||||
import org.apache.doris.analysis.OrderByElement;
|
||||
import org.apache.doris.analysis.SearchPredicate;
|
||||
import org.apache.doris.analysis.SlotDescriptor;
|
||||
import org.apache.doris.analysis.SlotRef;
|
||||
import org.apache.doris.analysis.TryCastExpr;
|
||||
import org.apache.doris.analysis.TupleDescriptor;
|
||||
import org.apache.doris.catalog.ArrayType;
|
||||
import org.apache.doris.catalog.Column;
|
||||
import org.apache.doris.catalog.Function;
|
||||
@@ -207,6 +209,27 @@ public class ExpressionTranslator extends DefaultExpressionVisitor<Expr, PlanTra
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private Index getInvertedIndexFromTranslatedSlot(Expr translatedSlot, PlanTranslatorContext context) {
|
||||
if (!(translatedSlot instanceof SlotRef)) {
|
||||
return null;
|
||||
}
|
||||
SlotRef slotRef = (SlotRef) translatedSlot;
|
||||
SlotDescriptor slotDesc = slotRef.getDesc();
|
||||
if (slotDesc == null) {
|
||||
return null;
|
||||
}
|
||||
TupleDescriptor tupleDesc = context.getTupleDesc(slotDesc.getParentId());
|
||||
if (tupleDesc == null || !(tupleDesc.getTable() instanceof OlapTable)) {
|
||||
return null;
|
||||
}
|
||||
Column column = slotRef.getColumn();
|
||||
if (column == null) {
|
||||
return null;
|
||||
}
|
||||
OlapTable olapTbl = (OlapTable) tupleDesc.getTable();
|
||||
return olapTbl.getInvertedIndex(column, slotDesc.getSubColLables());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expr visitElementAt(ElementAt elementAt, PlanTranslatorContext context) {
|
||||
return visitScalarFunction(elementAt, context);
|
||||
@@ -683,6 +706,9 @@ public class ExpressionTranslator extends DefaultExpressionVisitor<Expr, PlanTra
|
||||
}
|
||||
}
|
||||
}
|
||||
if (invertedIndex == null) {
|
||||
invertedIndex = getInvertedIndexFromTranslatedSlot(translatedSlot, context);
|
||||
}
|
||||
fieldIndexes.add(invertedIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -185,6 +185,11 @@ public class VariantType extends PrimitiveType {
|
||||
.append(String.valueOf(variantSparseHashShardCount))
|
||||
.append("\"");
|
||||
}
|
||||
if (enableNestedGroup) {
|
||||
sb.append(",");
|
||||
sb.append("\"variant_enable_nested_group\" = \"")
|
||||
.append(String.valueOf(enableNestedGroup)).append("\"");
|
||||
}
|
||||
sb.append(")>");
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -287,4 +292,8 @@ public class VariantType extends PrimitiveType {
|
||||
public int getVariantDocShardCount() {
|
||||
return variantDocShardCount;
|
||||
}
|
||||
|
||||
public boolean getEnableNestedGroup() {
|
||||
return enableNestedGroup;
|
||||
}
|
||||
}
|
||||
@@ -128,6 +128,14 @@ public class TypeTest {
|
||||
Assert.assertFalse(Type.matchExactType(v1, v4, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariantToSqlDoesNotSerializeUnsupportedNestedGroupProperty() {
|
||||
VariantType variantType = new VariantType(new ArrayList<>(), 0, false, 10000, 0,
|
||||
false, 0L, 64, true);
|
||||
|
||||
Assert.assertFalse(variantType.toSql().contains("variant_enable_nested_group"));
|
||||
}
|
||||
|
||||
// ===================== Mixed Nesting & Precision =====================
|
||||
@Test
|
||||
public void testArrayMapStructCombinationWithPrecision() {
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.apache.doris.nereids.StatementContext;
|
||||
import org.apache.doris.nereids.analyzer.UnboundFunction;
|
||||
import org.apache.doris.nereids.analyzer.UnboundOneRowRelation;
|
||||
import org.apache.doris.nereids.exceptions.AnalysisException;
|
||||
import org.apache.doris.nereids.exceptions.NotSupportedException;
|
||||
import org.apache.doris.nereids.exceptions.ParseException;
|
||||
import org.apache.doris.nereids.exceptions.SyntaxParseException;
|
||||
import org.apache.doris.nereids.glue.LogicalPlanAdapter;
|
||||
@@ -1362,6 +1363,17 @@ public class NereidsParserTest extends ParserTestBase {
|
||||
Assertions.assertTrue(createTableCommand.getCtasQuery().isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTableVariantNestedGroupPropertyIsRejected() {
|
||||
NereidsParser parser = new NereidsParser();
|
||||
String sql = "CREATE TABLE t_variant_ng (k1 INT, v VARIANT<PROPERTIES("
|
||||
+ "\"variant_enable_nested_group\" = \"true\")>) "
|
||||
+ "DISTRIBUTED BY HASH(k1) BUCKETS 1";
|
||||
NotSupportedException exception =
|
||||
Assertions.assertThrowsExactly(NotSupportedException.class, () -> parser.parseSingle(sql));
|
||||
Assertions.assertTrue(exception.getMessage().contains("variant_enable_nested_group is not supported now"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateViewWithoutAs() {
|
||||
NereidsParser parser = new NereidsParser();
|
||||
|
||||
@@ -1073,4 +1073,5 @@ public class ScalarType extends Type {
|
||||
}
|
||||
return false; // Default to false for backward compatibility.
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,109 +1,109 @@
|
||||
-- This file is automatically generated. You should know what you did if you want to edit this
|
||||
-- !sql_0 --
|
||||
1 {"a":[1],"b":["1"],"c":[1.1]}
|
||||
1 {"a":[1, null],"b":[null, "1"],"c":[1.1, null]}
|
||||
|
||||
-- !sql_1 --
|
||||
[1]
|
||||
[1, null]
|
||||
|
||||
-- !sql_2 --
|
||||
["1"]
|
||||
[null, "1"]
|
||||
|
||||
-- !sql_3 --
|
||||
[1.1]
|
||||
[1.1, null]
|
||||
|
||||
-- !sql_4 --
|
||||
{"a":[1],"b":["1"],"c":[1.1]}
|
||||
{"a":[1, null],"b":[null, "1"],"c":[1.1, null]}
|
||||
|
||||
-- !sql_8 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
|
||||
-- !sql_9 --
|
||||
[1] 1
|
||||
[null, 1] 2
|
||||
|
||||
-- !sql_10 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
|
||||
-- !sql_11 --
|
||||
["1"] 1
|
||||
["1", null] 2
|
||||
|
||||
-- !sql_12 --
|
||||
["1"] 1
|
||||
[null, "1"] 2
|
||||
|
||||
-- !sql_13 --
|
||||
["1.1"] 1
|
||||
["1.1", null] 2
|
||||
|
||||
-- !sql_14 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
|
||||
-- !sql_15 --
|
||||
[1] 1
|
||||
[null, 1] 2
|
||||
|
||||
-- !sql_16 --
|
||||
[1.1] 1
|
||||
[1.1, null] 2
|
||||
|
||||
-- !sql_17 --
|
||||
\N \N \N
|
||||
|
||||
-- !sql_18 --
|
||||
[1] ["1"] [1.1]
|
||||
[1, null] [null, "1"] [1.1, null]
|
||||
|
||||
-- !sql_19 --
|
||||
\N \N \N
|
||||
|
||||
-- !sql_0 --
|
||||
1 {"a":[1],"b":["1"],"c":[1.1]}
|
||||
1 {"a":[1, null],"b":[null, "1"],"c":[1.1, null]}
|
||||
2 {"a":["2.5"],"b":[123.1]}
|
||||
|
||||
-- !sql_1 --
|
||||
[1]
|
||||
[1, null]
|
||||
["2.5"]
|
||||
|
||||
-- !sql_2 --
|
||||
["1"]
|
||||
[null, "1"]
|
||||
[123.1]
|
||||
|
||||
-- !sql_3 --
|
||||
[1.1]
|
||||
[1.1, null]
|
||||
\N
|
||||
|
||||
-- !sql_4 --
|
||||
{"a":[1],"b":["1"],"c":[1.1]}
|
||||
{"a":[1, null],"b":[null, "1"],"c":[1.1, null]}
|
||||
{"a":["2.5"],"b":[123.1]}
|
||||
|
||||
-- !sql_8 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
[2] 1
|
||||
|
||||
-- !sql_9 --
|
||||
[1] 1
|
||||
[null, 1] 2
|
||||
[123] 1
|
||||
|
||||
-- !sql_10 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
\N \N
|
||||
|
||||
-- !sql_11 --
|
||||
["1"] 1
|
||||
["1", null] 2
|
||||
["2.5"] 1
|
||||
|
||||
-- !sql_12 --
|
||||
["1"] 1
|
||||
[null, "1"] 2
|
||||
["123.1"] 1
|
||||
|
||||
-- !sql_13 --
|
||||
["1.1"] 1
|
||||
["1.1", null] 2
|
||||
\N \N
|
||||
|
||||
-- !sql_14 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
[2.5] 1
|
||||
|
||||
-- !sql_15 --
|
||||
[1] 1
|
||||
[null, 1] 2
|
||||
[123.1] 1
|
||||
|
||||
-- !sql_16 --
|
||||
[1.1] 1
|
||||
[1.1, null] 2
|
||||
\N \N
|
||||
|
||||
-- !sql_17 --
|
||||
@@ -111,7 +111,7 @@
|
||||
\N \N \N
|
||||
|
||||
-- !sql_18 --
|
||||
["1"] [""1""] [1.1]
|
||||
["1", null] [null, ""1""] [1.1, null]
|
||||
[""2.5""] ["123.1"] \N
|
||||
|
||||
-- !sql_19 --
|
||||
@@ -119,72 +119,72 @@
|
||||
\N \N \N
|
||||
|
||||
-- !sql_0 --
|
||||
1 {"a":[1],"b":["1"],"c":[1.1]}
|
||||
1 {"a":[1, null],"b":[null, "1"],"c":[1.1, null]}
|
||||
2 {"a":["2.5"],"b":[123.1]}
|
||||
3 {"a":{"b":[2],"c":[1]}}
|
||||
3 {"a":{"b":[null, 2],"c":[1, null]}}
|
||||
|
||||
-- !sql_1 --
|
||||
[1]
|
||||
[1, null]
|
||||
["2.5"]
|
||||
{"b":[2],"c":[1]}
|
||||
{"b":[null, 2],"c":[1, null]}
|
||||
|
||||
-- !sql_2 --
|
||||
["1"]
|
||||
[null, "1"]
|
||||
[123.1]
|
||||
\N
|
||||
|
||||
-- !sql_3 --
|
||||
[1.1]
|
||||
[1.1, null]
|
||||
\N
|
||||
\N
|
||||
|
||||
-- !sql_4 --
|
||||
{"a":[1],"b":["1"],"c":[1.1]}
|
||||
{"a":[1, null],"b":[null, "1"],"c":[1.1, null]}
|
||||
{"a":["2.5"],"b":[123.1]}
|
||||
{"a":{"b":[2],"c":[1]}}
|
||||
{"a":{"b":[null, 2],"c":[1, null]}}
|
||||
|
||||
-- !sql_8 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
[2] 1
|
||||
\N \N
|
||||
|
||||
-- !sql_9 --
|
||||
[1] 1
|
||||
[null, 1] 2
|
||||
[123] 1
|
||||
\N \N
|
||||
|
||||
-- !sql_10 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
\N \N
|
||||
\N \N
|
||||
|
||||
-- !sql_11 --
|
||||
["1"] 1
|
||||
["1", null] 2
|
||||
["2.5"] 1
|
||||
\N \N
|
||||
|
||||
-- !sql_12 --
|
||||
["1"] 1
|
||||
[null, "1"] 2
|
||||
["123.1"] 1
|
||||
\N \N
|
||||
|
||||
-- !sql_13 --
|
||||
["1.1"] 1
|
||||
["1.1", null] 2
|
||||
\N \N
|
||||
\N \N
|
||||
|
||||
-- !sql_14 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
[2.5] 1
|
||||
\N \N
|
||||
|
||||
-- !sql_15 --
|
||||
[1] 1
|
||||
[null, 1] 2
|
||||
[123.1] 1
|
||||
\N \N
|
||||
|
||||
-- !sql_16 --
|
||||
[1.1] 1
|
||||
[1.1, null] 2
|
||||
\N \N
|
||||
\N \N
|
||||
|
||||
@@ -194,9 +194,9 @@
|
||||
\N \N \N
|
||||
|
||||
-- !sql_18 --
|
||||
[1] [""1""] [1.1]
|
||||
[1, null] [null, ""1""] [1.1, null]
|
||||
["2.5"] ["123.1"] \N
|
||||
{"b":[2],"c":[1]} \N \N
|
||||
{"b":[null, 2],"c":[1, null]} \N \N
|
||||
|
||||
-- !sql_19 --
|
||||
\N \N \N
|
||||
@@ -204,85 +204,85 @@
|
||||
\N \N \N
|
||||
|
||||
-- !sql_0 --
|
||||
1 {"a":[1],"b":["1"],"c":[1.1]}
|
||||
1 {"a":[1, null],"b":[null, "1"],"c":[1.1, null]}
|
||||
2 {"a":["2.5"],"b":[123.1]}
|
||||
3 {"a":{"b":[2],"c":[1]}}
|
||||
5 {"a":{"c":[1]},"b":["1"],"c":{"a":[2]}}
|
||||
3 {"a":{"b":[null, 2],"c":[1, null]}}
|
||||
5 {"a":{"c":[1, null, null]},"b":[null, null, "1"],"c":{"a":[null, 2, null]}}
|
||||
|
||||
-- !sql_1 --
|
||||
[1]
|
||||
[1, null]
|
||||
["2.5"]
|
||||
{"b":[2],"c":[1]}
|
||||
{"c":[1]}
|
||||
{"b":[null, 2],"c":[1, null]}
|
||||
{"c":[1, null, null]}
|
||||
|
||||
-- !sql_2 --
|
||||
["1"]
|
||||
[null, "1"]
|
||||
[123.1]
|
||||
\N
|
||||
["1"]
|
||||
[null, null, "1"]
|
||||
|
||||
-- !sql_3 --
|
||||
[1.1]
|
||||
[1.1, null]
|
||||
\N
|
||||
\N
|
||||
{"a":[2]}
|
||||
{"a":[null, 2, null]}
|
||||
|
||||
-- !sql_4 --
|
||||
{"a":[1],"b":["1"],"c":[1.1]}
|
||||
{"a":[1, null],"b":[null, "1"],"c":[1.1, null]}
|
||||
{"a":["2.5"],"b":[123.1]}
|
||||
{"a":{"b":[2],"c":[1]}}
|
||||
{"a":{"c":[1]},"b":["1"],"c":{"a":[2]}}
|
||||
{"a":{"b":[null, 2],"c":[1, null]}}
|
||||
{"a":{"c":[1, null, null]},"b":[null, null, "1"],"c":{"a":[null, 2, null]}}
|
||||
|
||||
-- !sql_8 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
[2] 1
|
||||
\N \N
|
||||
\N \N
|
||||
|
||||
-- !sql_9 --
|
||||
[1] 1
|
||||
[null, 1] 2
|
||||
[123] 1
|
||||
\N \N
|
||||
[1] 1
|
||||
[null, null, 1] 3
|
||||
|
||||
-- !sql_10 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
|
||||
-- !sql_11 --
|
||||
["1"] 1
|
||||
["1", null] 2
|
||||
["2.5"] 1
|
||||
\N \N
|
||||
\N \N
|
||||
|
||||
-- !sql_12 --
|
||||
["1"] 1
|
||||
[null, "1"] 2
|
||||
["123.1"] 1
|
||||
\N \N
|
||||
["1"] 1
|
||||
[null, null, "1"] 3
|
||||
|
||||
-- !sql_13 --
|
||||
["1.1"] 1
|
||||
["1.1", null] 2
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
|
||||
-- !sql_14 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
[2.5] 1
|
||||
\N \N
|
||||
\N \N
|
||||
|
||||
-- !sql_15 --
|
||||
[1] 1
|
||||
[null, 1] 2
|
||||
[123.1] 1
|
||||
\N \N
|
||||
[1] 1
|
||||
[null, null, 1] 3
|
||||
|
||||
-- !sql_16 --
|
||||
[1.1] 1
|
||||
[1.1, null] 2
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
@@ -294,10 +294,10 @@
|
||||
\N \N \N
|
||||
|
||||
-- !sql_18 --
|
||||
[1] [""1""] [1.1]
|
||||
[1, null] [null, ""1""] [1.1, null]
|
||||
["2.5"] ["123.1"] \N
|
||||
{"b":[2],"c":[1]} \N \N
|
||||
{"c":[1]} [""1""] {"a":[2]}
|
||||
{"b":[null, 2],"c":[1, null]} \N \N
|
||||
{"c":[1, null, null]} [null, null, ""1""] {"a":[null, 2, null]}
|
||||
|
||||
-- !sql_19 --
|
||||
\N \N \N
|
||||
@@ -306,98 +306,98 @@
|
||||
\N \N \N
|
||||
|
||||
-- !sql_0 --
|
||||
1 {"a":[1],"b":["1"],"c":[1.1]}
|
||||
1 {"a":[1, null],"b":[null, "1"],"c":[1.1, null]}
|
||||
2 {"a":["2.5"],"b":[123.1]}
|
||||
3 {"a":{"b":[2],"c":[1]}}
|
||||
5 {"a":{"c":[1]},"b":["1"],"c":{"a":[2]}}
|
||||
6 {"a":[1],"b":[1, 2, 3]}
|
||||
3 {"a":{"b":[null, 2],"c":[1, null]}}
|
||||
5 {"a":{"c":[1, null, null]},"b":[null, null, "1"],"c":{"a":[null, 2, null]}}
|
||||
6 {"a":[1, null, null],"b":[1, 2, 3]}
|
||||
|
||||
-- !sql_1 --
|
||||
[1]
|
||||
[1, null]
|
||||
["2.5"]
|
||||
{"b":[2],"c":[1]}
|
||||
{"c":[1]}
|
||||
[1]
|
||||
{"b":[null, 2],"c":[1, null]}
|
||||
{"c":[1, null, null]}
|
||||
[1, null, null]
|
||||
|
||||
-- !sql_2 --
|
||||
["1"]
|
||||
[null, "1"]
|
||||
[123.1]
|
||||
\N
|
||||
["1"]
|
||||
[null, null, "1"]
|
||||
[1, 2, 3]
|
||||
|
||||
-- !sql_3 --
|
||||
[1.1]
|
||||
[1.1, null]
|
||||
\N
|
||||
\N
|
||||
{"a":[2]}
|
||||
{"a":[null, 2, null]}
|
||||
\N
|
||||
|
||||
-- !sql_4 --
|
||||
{"a":[1],"b":["1"],"c":[1.1]}
|
||||
{"a":[1, null],"b":[null, "1"],"c":[1.1, null]}
|
||||
{"a":["2.5"],"b":[123.1]}
|
||||
{"a":{"b":[2],"c":[1]}}
|
||||
{"a":{"c":[1]},"b":["1"],"c":{"a":[2]}}
|
||||
{"a":[1],"b":[1, 2, 3]}
|
||||
{"a":{"b":[null, 2],"c":[1, null]}}
|
||||
{"a":{"c":[1, null, null]},"b":[null, null, "1"],"c":{"a":[null, 2, null]}}
|
||||
{"a":[1, null, null],"b":[1, 2, 3]}
|
||||
|
||||
-- !sql_8 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
[2] 1
|
||||
\N \N
|
||||
\N \N
|
||||
[1] 1
|
||||
[1, null, null] 3
|
||||
|
||||
-- !sql_9 --
|
||||
[1] 1
|
||||
[null, 1] 2
|
||||
[123] 1
|
||||
\N \N
|
||||
[1] 1
|
||||
[null, null, 1] 3
|
||||
[1, 2, 3] 3
|
||||
|
||||
-- !sql_10 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
|
||||
-- !sql_11 --
|
||||
["1"] 1
|
||||
["1", null] 2
|
||||
["2.5"] 1
|
||||
\N \N
|
||||
\N \N
|
||||
["1"] 1
|
||||
["1", null, null] 3
|
||||
|
||||
-- !sql_12 --
|
||||
["1"] 1
|
||||
[null, "1"] 2
|
||||
["123.1"] 1
|
||||
\N \N
|
||||
["1"] 1
|
||||
[null, null, "1"] 3
|
||||
["1", "2", "3"] 3
|
||||
|
||||
-- !sql_13 --
|
||||
["1.1"] 1
|
||||
["1.1", null] 2
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
|
||||
-- !sql_14 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
[2.5] 1
|
||||
\N \N
|
||||
\N \N
|
||||
[1] 1
|
||||
[1, null, null] 3
|
||||
|
||||
-- !sql_15 --
|
||||
[1] 1
|
||||
[null, 1] 2
|
||||
[123.1] 1
|
||||
\N \N
|
||||
[1] 1
|
||||
[null, null, 1] 3
|
||||
[1, 2, 3] 3
|
||||
|
||||
-- !sql_16 --
|
||||
[1.1] 1
|
||||
[1.1, null] 2
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
@@ -411,11 +411,11 @@
|
||||
\N \N \N
|
||||
|
||||
-- !sql_18 --
|
||||
[1] [""1""] [1.1]
|
||||
[1, null] [null, ""1""] [1.1, null]
|
||||
["2.5"] ["123.1"] \N
|
||||
{"b":[2],"c":[1]} \N \N
|
||||
{"c":[1]} [""1""] {"a":[2]}
|
||||
[1] ["1", "2", "3"] \N
|
||||
{"b":[null, 2],"c":[1, null]} \N \N
|
||||
{"c":[1, null, null]} [null, null, ""1""] {"a":[null, 2, null]}
|
||||
[1, null, null] ["1", "2", "3"] \N
|
||||
|
||||
-- !sql_19 --
|
||||
\N \N \N
|
||||
@@ -425,98 +425,98 @@
|
||||
\N \N \N
|
||||
|
||||
-- !sql_0 --
|
||||
1 {"a":[1],"b":["1"],"c":[1.1]}
|
||||
1 {"a":[1, null],"b":[null, "1"],"c":[1.1, null]}
|
||||
2 {"a":["2.5"],"b":[123.1]}
|
||||
3 {"a":{"b":[2],"c":[1]}}
|
||||
5 {"a":{"c":[1]},"b":["1"],"c":{"a":[2]}}
|
||||
6 {"a":[1],"b":[1, 2, 3]}
|
||||
3 {"a":{"b":[null, 2],"c":[1, null]}}
|
||||
5 {"a":{"c":[1, null, null]},"b":[null, null, "1"],"c":{"a":[null, 2, null]}}
|
||||
6 {"a":[1, null, null],"b":[1, 2, 3]}
|
||||
|
||||
-- !sql_1 --
|
||||
[1]
|
||||
[1, null]
|
||||
["2.5"]
|
||||
{"b":[2],"c":[1]}
|
||||
{"c":[1]}
|
||||
[1]
|
||||
{"b":[null, 2],"c":[1, null]}
|
||||
{"c":[1, null, null]}
|
||||
[1, null, null]
|
||||
|
||||
-- !sql_2 --
|
||||
["1"]
|
||||
[null, "1"]
|
||||
[123.1]
|
||||
\N
|
||||
["1"]
|
||||
[null, null, "1"]
|
||||
[1, 2, 3]
|
||||
|
||||
-- !sql_3 --
|
||||
[1.1]
|
||||
[1.1, null]
|
||||
{}
|
||||
{}
|
||||
{"a":[2]}
|
||||
{"a":[null, 2, null]}
|
||||
{}
|
||||
|
||||
-- !sql_4 --
|
||||
{"a":[1],"b":["1"],"c":[1.1]}
|
||||
{"a":[1, null],"b":[null, "1"],"c":[1.1, null]}
|
||||
{"a":["2.5"],"b":[123.1]}
|
||||
{"a":{"b":[2],"c":[1]}}
|
||||
{"a":{"c":[1]},"b":["1"],"c":{"a":[2]}}
|
||||
{"a":[1],"b":[1, 2, 3]}
|
||||
{"a":{"b":[null, 2],"c":[1, null]}}
|
||||
{"a":{"c":[1, null, null]},"b":[null, null, "1"],"c":{"a":[null, 2, null]}}
|
||||
{"a":[1, null, null],"b":[1, 2, 3]}
|
||||
|
||||
-- !sql_8 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
[2] 1
|
||||
\N \N
|
||||
\N \N
|
||||
[1] 1
|
||||
[1, null, null] 3
|
||||
|
||||
-- !sql_9 --
|
||||
[1] 1
|
||||
[null, 1] 2
|
||||
[123] 1
|
||||
\N \N
|
||||
[1] 1
|
||||
[null, null, 1] 3
|
||||
[1, 2, 3] 3
|
||||
|
||||
-- !sql_10 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
|
||||
-- !sql_11 --
|
||||
["1"] 1
|
||||
["1", null] 2
|
||||
["2.5"] 1
|
||||
\N \N
|
||||
\N \N
|
||||
["1"] 1
|
||||
["1", null, null] 3
|
||||
|
||||
-- !sql_12 --
|
||||
["1"] 1
|
||||
[null, "1"] 2
|
||||
["123.1"] 1
|
||||
\N \N
|
||||
["1"] 1
|
||||
[null, null, "1"] 3
|
||||
["1", "2", "3"] 3
|
||||
|
||||
-- !sql_13 --
|
||||
["1.1"] 1
|
||||
["1.1", null] 2
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
|
||||
-- !sql_14 --
|
||||
[1] 1
|
||||
[1, null] 2
|
||||
[2.5] 1
|
||||
\N \N
|
||||
\N \N
|
||||
[1] 1
|
||||
[1, null, null] 3
|
||||
|
||||
-- !sql_15 --
|
||||
[1] 1
|
||||
[null, 1] 2
|
||||
[123.1] 1
|
||||
\N \N
|
||||
[1] 1
|
||||
[null, null, 1] 3
|
||||
[1, 2, 3] 3
|
||||
|
||||
-- !sql_16 --
|
||||
[1.1] 1
|
||||
[1.1, null] 2
|
||||
\N \N
|
||||
\N \N
|
||||
\N \N
|
||||
@@ -530,11 +530,11 @@
|
||||
\N \N \N
|
||||
|
||||
-- !sql_18 --
|
||||
[1] [""1""] [1.1]
|
||||
[1, null] [null, ""1""] [1.1, null]
|
||||
["2.5"] ["123.1"] {}
|
||||
{"b":[2],"c":[1]} \N {}
|
||||
{"c":[1]} [""1""] {"a":[2]}
|
||||
[1] ["1", "2", "3"] {}
|
||||
{"b":[null, 2],"c":[1, null]} \N {}
|
||||
{"c":[1, null, null]} [null, null, ""1""] {"a":[null, 2, null]}
|
||||
[1, null, null] ["1", "2", "3"] {}
|
||||
|
||||
-- !sql_19 --
|
||||
\N \N \N
|
||||
|
||||
Reference in new issue
Block a user