blob: c3fd1af402bed825e660614dcf18d4a894245eb4 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "storage/schema.h"
#include <utility>
#include "common/config.h"
#include "common/logging.h"
#include "core/block/block.h"
#include "core/column/column_dictionary.h"
#include "core/column/column_nothing.h"
#include "core/column/column_nullable.h"
#include "storage/binlog.h"
namespace doris {
namespace {
bool row_binlog_value_columns_have_same_type(const TabletColumn& lhs, const TabletColumn& rhs) {
if (lhs.type() != rhs.type() || lhs.is_nullable() != rhs.is_nullable() ||
lhs.length() != rhs.length() || lhs.precision() != rhs.precision() ||
lhs.frac() != rhs.frac() || lhs.get_subtype_count() != rhs.get_subtype_count()) {
return false;
}
for (uint32_t i = 0; i < lhs.get_subtype_count(); ++i) {
if (!row_binlog_value_columns_have_same_type(lhs.get_sub_column(i),
rhs.get_sub_column(i))) {
return false;
}
}
return true;
}
} // namespace
std::vector<TabletColumnPtr> project_columns_by_ordinal(
const std::vector<TabletColumnPtr>& columns,
const std::vector<ColumnId>& source_column_ordinals) {
std::vector<TabletColumnPtr> projected_columns;
projected_columns.reserve(source_column_ordinals.size());
for (auto ordinal : source_column_ordinals) {
projected_columns.emplace_back(columns[ordinal]);
}
return projected_columns;
}
ReadSchema::ReadSchema(std::vector<TabletColumnPtr> columns)
: _read_columns(std::move(columns)), _num_block_columns(_read_columns.size()) {
_init_read_types();
_init_descriptors();
}
ReadSchema::ReadSchema(std::vector<TabletColumnPtr> columns, std::vector<DataTypePtr> read_types)
: _read_columns(std::move(columns)),
_read_types(std::move(read_types)),
_num_block_columns(_read_types.size()) {
_init_descriptors();
}
void ReadSchema::_init_read_types() {
_read_types.reserve(_read_columns.size());
for (const auto& column : _read_columns) {
auto data_type = column->get_vec_type();
DORIS_CHECK(data_type != nullptr);
_read_types.emplace_back(std::move(data_type));
}
}
void ReadSchema::append_dropped_columns(std::vector<TabletColumn> columns) {
_read_columns.reserve(_read_columns.size() + columns.size());
_read_types.reserve(_read_types.size() + columns.size());
for (auto& column : columns) {
auto column_ptr = std::make_shared<TabletColumn>(std::move(column));
auto data_type = column_ptr->get_vec_type();
auto ordinal = cast_set<ColumnId>(_read_columns.size());
if (column_ptr->unique_id() >= 0) {
_uid_to_ordinal.emplace(column_ptr->unique_id(), ordinal);
}
_read_columns.emplace_back(std::move(column_ptr));
_read_types.emplace_back(std::move(data_type));
}
}
void ReadSchema::_init_before_column_ordinals() {
std::unordered_map<std::string_view, ColumnId> name_to_ordinal;
name_to_ordinal.reserve(_num_block_columns);
for (ColumnId ordinal = 0; ordinal < _num_block_columns; ++ordinal) {
name_to_ordinal.emplace(_read_columns[ordinal]->name(), ordinal);
}
_before_column_ordinals.resize(_num_block_columns);
for (ColumnId ordinal = 0; ordinal < _num_block_columns; ++ordinal) {
auto read_ordinal = static_cast<int32_t>(ordinal);
if (read_ordinal == _tso_ordinal || read_ordinal == _lsn_ordinal ||
read_ordinal == _op_ordinal) {
_before_column_ordinals[ordinal] = ordinal;
continue;
}
auto before_name = binlog::build_before_column_name(_read_columns[ordinal]->name());
auto before = name_to_ordinal.find(before_name);
_before_column_ordinals[ordinal] =
before == name_to_ordinal.end() ? ordinal : before->second;
}
}
void ReadSchema::init_row_binlog_column_mappings(const TabletSchema& tablet_schema) {
DORIS_CHECK_GE(_op_ordinal, 0);
DORIS_CHECK_EQ(_before_column_ordinals.size(), _num_block_columns);
_row_binlog_value_column_pairs.clear();
_row_binlog_value_pairs_complete = false;
std::vector<ColumnId> value_column_ids;
value_column_ids.reserve(tablet_schema.num_columns() - tablet_schema.num_key_columns());
for (ColumnId cid = 0; cid < tablet_schema.num_columns(); ++cid) {
if (static_cast<int32_t>(cid) == tablet_schema.binlog_tso_col_idx() ||
static_cast<int32_t>(cid) == tablet_schema.binlog_lsn_col_idx() ||
static_cast<int32_t>(cid) == tablet_schema.binlog_op_col_idx() ||
tablet_schema.column(cid).is_key()) {
continue;
}
value_column_ids.push_back(cid);
}
if (value_column_ids.empty() || value_column_ids.size() % 2 != 0) {
return;
}
const size_t value_column_count = value_column_ids.size() / 2;
for (size_t i = 0; i < value_column_count; ++i) {
const auto& after = tablet_schema.column(value_column_ids[i]);
const auto& before = tablet_schema.column(value_column_ids[i + value_column_count]);
if (before.name() != binlog::build_before_column_name(after.name()) ||
!row_binlog_value_columns_have_same_type(after, before)) {
return;
}
}
// A valid physical row-binlog layout starts from an identity mapping. Only AFTER value
// columns map to their BEFORE companions; keys, metadata and BEFORE columns map to themselves.
for (ColumnId ordinal = 0; ordinal < _num_block_columns; ++ordinal) {
_before_column_ordinals[ordinal] = ordinal;
}
bool complete = true;
_row_binlog_value_column_pairs.reserve(value_column_count);
for (size_t i = 0; i < value_column_count; ++i) {
const auto after_cid = value_column_ids[i];
const auto before_cid = value_column_ids[i + value_column_count];
const int32_t after_ordinal = ordinal_by_uid(tablet_schema.column(after_cid).unique_id());
const int32_t before_ordinal = ordinal_by_uid(tablet_schema.column(before_cid).unique_id());
if (after_ordinal < 0 || before_ordinal < 0 ||
static_cast<size_t>(after_ordinal) >= _num_block_columns ||
static_cast<size_t>(before_ordinal) >= _num_block_columns) {
complete = false;
continue;
}
const auto after = cast_set<ColumnId>(after_ordinal);
const auto before = cast_set<ColumnId>(before_ordinal);
_before_column_ordinals[after] = before;
if (!_read_types[after]->equals(*_read_types[before])) {
complete = false;
continue;
}
_row_binlog_value_column_pairs.emplace_back(after, before);
}
_row_binlog_value_pairs_complete =
complete && _row_binlog_value_column_pairs.size() == value_column_count;
}
Block ReadSchema::create_read_block() const {
Block block;
for (size_t ordinal = 0; ordinal < _num_block_columns; ++ordinal) {
const auto& data_type = _read_types[ordinal];
DORIS_CHECK(data_type != nullptr);
MutableColumnPtr column;
if (_read_columns[ordinal]->name().starts_with(BeConsts::VIRTUAL_COLUMN_PREFIX)) {
column = ColumnNothing::create(0);
} else {
column = data_type->create_column();
}
block.insert({std::move(column), data_type, _read_columns[ordinal]->name()});
}
return block;
}
std::string ReadSchema::read_columns_to_string() const {
// Avoid lines that are too long to display in SHOW PROFILE.
constexpr int columns_per_line = 10;
int column_index = 0;
std::string result = "[";
for (auto it = _read_columns.cbegin(); it != _read_columns.cend(); ++it) {
if (it != _read_columns.cbegin()) {
result += ", ";
}
result += (*it)->name();
if (column_index >= columns_per_line) {
result += "\n";
column_index = 0;
} else {
++column_index;
}
}
result += "]";
return result;
}
Status ReadSchema::init_sequence_map(const TabletSchema& tablet_schema) {
if (tablet_schema.has_sequence_col()) {
auto msg = "sequence columns conflict, both seq_col and seq_map are true!";
LOG(WARNING) << msg;
return Status::InternalError(msg);
}
_sequence_map.clear();
for (const auto& [sequence_cid, value_cids] : tablet_schema.seq_col_idx_to_value_cols_idx()) {
std::vector<ColumnId> value_ordinals;
for (auto value_cid : value_cids) {
int32_t value_ordinal = ordinal_by_uid(tablet_schema.column(value_cid).unique_id());
if (value_ordinal >= 0 && static_cast<size_t>(value_ordinal) < num_block_columns()) {
value_ordinals.emplace_back(value_ordinal);
}
}
int32_t sequence_ordinal = ordinal_by_uid(tablet_schema.column(sequence_cid).unique_id());
if (sequence_ordinal < 0 || static_cast<size_t>(sequence_ordinal) >= num_block_columns()) {
if (value_ordinals.empty()) {
continue;
}
return Status::InvalidArgument(
"Sequence column {} must be present in the read Block schema",
tablet_schema.column(sequence_cid).name());
}
_sequence_map.emplace(sequence_ordinal, std::move(value_ordinals));
}
return Status::OK();
}
IColumn::MutablePtr ReadSchema::get_predicate_column_ptr(const DataTypePtr& data_type,
const ReaderType reader_type) {
// Low-cardinality dictionary optimization substitutes a ColumnDictI32 for the
// canonical string column during query reads. Every other case just materializes
// the data type's own canonical column (which already wraps nullable for us).
if (config::enable_low_cardinality_optimize && reader_type == ReaderType::READER_QUERY &&
is_string_type(data_type->get_primitive_type())) {
IColumn::MutablePtr ptr = doris::ColumnDictI32::create();
if (data_type->is_nullable()) {
return doris::ColumnNullable::create(std::move(ptr), doris::ColumnUInt8::create());
}
return ptr;
}
return data_type->create_column();
}
} // namespace doris