Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 88 additions & 6 deletions be/src/core/field.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include "core/field.h"

#include "common/compare.h"
#include "core/accurate_comparison.h"
#include "core/data_type/data_type_decimal.h"
#include "core/data_type/define_primitive_type.h"
Expand Down Expand Up @@ -678,6 +679,13 @@ std::strong_ordering Field::operator<=>(const Field& rhs) const {
return type <=> rhs.type;
}
if (type != rhs.type) {
// String-family types (STRING, CHAR, VARCHAR) all store String internally
// and are inter-comparable. This arises when comparing RowCursor fields
// (which carry the declared column type) against Column::operator[] results
// (which always return TYPE_STRING for ColumnString).
if (is_string_type(type) && is_string_type(rhs.type)) {
return get<TYPE_STRING>() <=> rhs.get<TYPE_STRING>();
}
throw Exception(Status::FatalError("lhs type not equal with rhs, lhs={}, rhs={}",
get_type_name(), rhs.get_type_name()));
}
Expand Down Expand Up @@ -724,17 +732,31 @@ std::strong_ordering Field::operator<=>(const Field& rhs) const {
case PrimitiveType::TYPE_IPV4:
return get<TYPE_IPV4>() <=> rhs.get<TYPE_IPV4>();
case PrimitiveType::TYPE_FLOAT:
return get<TYPE_FLOAT>() < rhs.get<TYPE_FLOAT>() ? std::strong_ordering::less
: get<TYPE_FLOAT>() == rhs.get<TYPE_FLOAT>() ? std::strong_ordering::equal
: std::strong_ordering::greater;
switch (Compare::compare(get<TYPE_FLOAT>(), rhs.get<TYPE_FLOAT>())) {
case -1:
return std::strong_ordering::less;
case 0:
return std::strong_ordering::equal;
case 1:
return std::strong_ordering::greater;
default:
LOG(FATAL) << "unexpected float compare result";
}
case PrimitiveType::TYPE_TIMEV2:
return get<TYPE_TIMEV2>() < rhs.get<TYPE_TIMEV2>() ? std::strong_ordering::less
: get<TYPE_TIMEV2>() == rhs.get<TYPE_TIMEV2>() ? std::strong_ordering::equal
: std::strong_ordering::greater;
case PrimitiveType::TYPE_DOUBLE:
return get<TYPE_DOUBLE>() < rhs.get<TYPE_DOUBLE>() ? std::strong_ordering::less
: get<TYPE_DOUBLE>() == rhs.get<TYPE_DOUBLE>() ? std::strong_ordering::equal
: std::strong_ordering::greater;
switch (Compare::compare(get<TYPE_DOUBLE>(), rhs.get<TYPE_DOUBLE>())) {
case -1:
return std::strong_ordering::less;
case 0:
return std::strong_ordering::equal;
case 1:
return std::strong_ordering::greater;
default:
LOG(FATAL) << "unexpected double compare result";
}
case PrimitiveType::TYPE_STRING:
return get<TYPE_STRING>() <=> rhs.get<TYPE_STRING>();
case PrimitiveType::TYPE_CHAR:
Expand Down Expand Up @@ -822,6 +844,66 @@ std::string_view Field::as_string_view() const {

#undef MATCH_PRIMITIVE_TYPE

// Important!!! This method is not accurate, for example, decimal to string, it uses scale == 0, because
// it do not know the actual scale of the decimal value. It is only used for debug printing, so it is fine.
std::string Field::to_debug_string(int scale) const {
if (is_null()) {
return "NULL";
}
switch (type) {
case PrimitiveType::TYPE_BOOLEAN:
return get<TYPE_BOOLEAN>() ? "true" : "false";
case PrimitiveType::TYPE_TINYINT:
return CastToString::from_number(get<TYPE_TINYINT>());
case PrimitiveType::TYPE_SMALLINT:
return CastToString::from_number(get<TYPE_SMALLINT>());
case PrimitiveType::TYPE_INT:
return CastToString::from_number(get<TYPE_INT>());
case PrimitiveType::TYPE_BIGINT:
return CastToString::from_number(get<TYPE_BIGINT>());
case PrimitiveType::TYPE_LARGEINT:
return CastToString::from_number(get<TYPE_LARGEINT>());
case PrimitiveType::TYPE_FLOAT:
return CastToString::from_number(get<TYPE_FLOAT>());
case PrimitiveType::TYPE_DOUBLE:
return CastToString::from_number(get<TYPE_DOUBLE>());
case PrimitiveType::TYPE_STRING:
case PrimitiveType::TYPE_CHAR:
case PrimitiveType::TYPE_VARCHAR:
return get<TYPE_STRING>();
case PrimitiveType::TYPE_VARBINARY:
return get<TYPE_VARBINARY>();
case PrimitiveType::TYPE_DATE:
return CastToString::from_date_or_datetime(get<TYPE_DATE>());
case PrimitiveType::TYPE_DATETIME:
return CastToString::from_date_or_datetime(get<TYPE_DATETIME>());
case PrimitiveType::TYPE_DATEV2:
return CastToString::from_datev2(get<TYPE_DATEV2>());
case PrimitiveType::TYPE_DATETIMEV2:
return CastToString::from_datetimev2(get<TYPE_DATETIMEV2>(), scale);
case PrimitiveType::TYPE_TIMESTAMPTZ:
return CastToString::from_timestamptz(get<TYPE_TIMESTAMPTZ>(), scale);
case PrimitiveType::TYPE_DECIMALV2:
return get<TYPE_DECIMALV2>().to_string();
case PrimitiveType::TYPE_DECIMAL32:
return CastToString::from_decimal(get<TYPE_DECIMAL32>(), scale);
case PrimitiveType::TYPE_DECIMAL64:
return CastToString::from_decimal(get<TYPE_DECIMAL64>(), scale);
case PrimitiveType::TYPE_DECIMAL128I:
return CastToString::from_decimal(get<TYPE_DECIMAL128I>(), scale);
case PrimitiveType::TYPE_DECIMAL256:
return CastToString::from_decimal(get<TYPE_DECIMAL256>(), scale);
case PrimitiveType::TYPE_IPV4:
return CastToString::from_ip(get<TYPE_IPV4>());
case PrimitiveType::TYPE_IPV6:
return CastToString::from_ip(get<TYPE_IPV6>());
default:
throw Exception(Status::FatalError("type not supported for to_debug_string, type={}",
get_type_name()));
__builtin_unreachable();
}
}

#define DECLARE_FUNCTION(FUNC_NAME) \
template void Field::FUNC_NAME<TYPE_NULL>(typename PrimitiveTypeTraits<TYPE_NULL>::CppType && \
rhs); \
Expand Down
5 changes: 5 additions & 0 deletions be/src/core/field.h
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,11 @@ class Field {

std::string_view as_string_view() const;

// Return a human-readable representation of the stored value for debugging.
// Unlike get_type_name() which returns the type, this prints the actual value.
// For decimal types, caller can provide scale for accurate formatting.
std::string to_debug_string(int scale) const;

private:
std::aligned_union_t<DBMS_MIN_FIELD_SIZE - sizeof(PrimitiveType), Null, UInt64, UInt128, Int64,
Int128, IPv6, Float64, String, JsonbField, StringView, Array, Tuple, Map,
Expand Down
35 changes: 31 additions & 4 deletions be/src/exec/operator/olap_scan_operator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,9 +474,13 @@ Status OlapScanLocalState::_init_scanners(std::list<ScannerSPtr>* scanners) {
_output_column_ids.emplace(uid);
}

// ranges constructed from scan keys
// Step 3: convert accumulated scan key pairs into OlapScanRange objects.
// Each OlapScanRange carries real begin/end OlapTuples with has_lower_bound = true.
RETURN_IF_ERROR(_scan_keys.get_key_range(&_cond_ranges));
// if we can't get ranges from conditions, we give it a total range
// If no key predicates were pushed down, _cond_ranges is empty.
// Create a single default-constructed OlapScanRange (has_lower_bound = false)
// to represent a full table scan. Consumers detect this and skip pushing
// key range to the tablet reader.
if (_cond_ranges.empty()) {
_cond_ranges.emplace_back(new doris::OlapScanRange());
}
Expand All @@ -492,10 +496,11 @@ Status OlapScanLocalState::_init_scanners(std::list<ScannerSPtr>* scanners) {
if (enable_parallel_scan && !p._should_run_serial &&
p._push_down_agg_type == TPushAggOp::NONE &&
(_storage_no_merge() || p._olap_scan_node.is_preaggregation)) {
// Filter out the "full scan" placeholder range (has_lower_bound == false)
// so that only ranges with real key bounds are forwarded to the parallel scanner.
std::vector<OlapScanRange*> key_ranges;
for (auto& range : _cond_ranges) {
if (range->begin_scan_range.size() == 1 &&
range->begin_scan_range.get_value(0) == NEGATIVE_INFINITY) {
if (!range->has_lower_bound) {
continue;
}
key_ranges.emplace_back(range.get());
Expand Down Expand Up @@ -878,6 +883,28 @@ inline std::string push_down_agg_to_string(const TPushAggOp::type& op) {
}
}

/// Step 2 of the scan-key generation pipeline.
///
/// Iterate key columns in schema order; for each one, look up its ColumnValueRange
/// from _slot_id_to_value_range (populated by _normalize_conjuncts) and call
/// _scan_keys.extend_scan_key() to grow the multi-column prefix key set.
///
/// Example – table t(k1 INT, k2 INT, v INT), key columns = (k1, k2):
/// Input ColumnValueRanges:
/// k1: fixed_values = {1, 2}
/// k2: fixed_values = {10}
/// After extend_scan_key(k1):
/// _begin_scan_keys = [(1), (2)] _end_scan_keys = [(1), (2)]
/// After extend_scan_key(k2):
/// _begin_scan_keys = [(1,10), (2,10)] _end_scan_keys = [(1,10), (2,10)]
///
/// Loop terminates when:
/// - A key column has no predicate (break)
/// - A range column was appended (_has_range_value, cannot extend further)
/// - The ColumnValueRange is provably empty (eos)
/// - The fixed-value set exceeds max_scan_key_num (should_break or fall back to range)
///
/// At the end, _scan_keys.get_key_range() converts these into OlapScanRange objects.
Status OlapScanLocalState::_build_key_ranges_and_filters() {
auto& p = _parent->cast<OlapScanOperatorX>();
if (p._push_down_agg_type == TPushAggOp::NONE ||
Expand Down
70 changes: 39 additions & 31 deletions be/src/exec/operator/scan_operator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,6 @@ static std::string predicates_to_string(
}
return fmt::to_string(debug_string_buffer);
}

static void init_slot_value_range(
phmap::flat_hash_map<int, ColumnValueRangeType>& slot_id_to_value_range,
SlotDescriptor* slot, const DataTypePtr type_desc) {
Expand All @@ -229,40 +228,53 @@ static void init_slot_value_range(
slot_id_to_value_range[slot->id()] = std::move(range); \
break; \
}
#define APPLY_FOR_PRIMITIVE_TYPE(M) \
M(TINYINT) \
M(SMALLINT) \
M(INT) \
M(BIGINT) \
M(LARGEINT) \
M(FLOAT) \
M(DOUBLE) \
M(CHAR) \
M(DATE) \
M(DATETIME) \
M(DATEV2) \
M(DATETIMEV2) \
M(TIMESTAMPTZ) \
M(VARCHAR) \
M(STRING) \
M(HLL) \
M(DECIMAL32) \
M(DECIMAL64) \
M(DECIMAL128I) \
M(DECIMAL256) \
M(DECIMALV2) \
M(BOOLEAN) \
M(IPV4) \
#define APPLY_FOR_SCALAR_TYPE(M) \
M(TINYINT) \
M(SMALLINT) \
M(INT) \
M(BIGINT) \
M(LARGEINT) \
M(FLOAT) \
M(DOUBLE) \
M(CHAR) \
M(DATE) \
M(DATETIME) \
M(DATEV2) \
M(DATETIMEV2) \
M(TIMESTAMPTZ) \
M(VARCHAR) \
M(STRING) \
M(DECIMAL32) \
M(DECIMAL64) \
M(DECIMAL128I) \
M(DECIMAL256) \
M(DECIMALV2) \
M(BOOLEAN) \
M(IPV4) \
M(IPV6)
APPLY_FOR_PRIMITIVE_TYPE(M)
APPLY_FOR_SCALAR_TYPE(M)
#undef M
default: {
VLOG_CRITICAL << "Unsupported Normalize Slot [ColName=" << slot->col_name() << "]";
break;
}
}
}

/// Step 1 of the scan-key generation pipeline.
///
/// Parse SQL WHERE conjuncts into per-column ColumnValueRange objects stored in
/// _slot_id_to_value_range. Each ColumnValueRange captures all constraints on
/// one column (fixed values from IN / =, or min/max bounds from < / <= / > / >=).
///
/// Example – "WHERE k1 IN (1, 2) AND k2 >= 5 AND k2 < 10 AND v > 100":
/// => ColumnValueRange<k1>: fixed_values = {1, 2}
/// => ColumnValueRange<k2>: scope [5, 10) (low=5 >=, high=10 <)
/// => ColumnValueRange<v>: scope (100, MAX] (low=100 >, high=MAX <=)
/// The k1/k2 ranges will later become scan keys (since they're key columns);
/// v's range stays as a residual predicate / olap filter.
///
/// After this step, _build_key_ranges_and_filters() picks up the key-column
/// ColumnValueRanges and feeds them to OlapScanKeys::extend_scan_key().
template <typename Derived>
Status ScanLocalState<Derived>::_normalize_conjuncts(RuntimeState* state) {
auto& p = _parent->cast<typename Derived::Parent>();
Expand Down Expand Up @@ -937,10 +949,6 @@ Status ScanLocalState<Derived>::_change_value_range(bool is_equal_op,
(PrimitiveType == TYPE_DATEV2) || (PrimitiveType == TYPE_TIMESTAMPTZ) ||
(PrimitiveType == TYPE_DATETIME) || is_string_type(PrimitiveType)) {
func(temp_range, to_olap_filter_type(fn_name), value.template get<PrimitiveType>());
} else if constexpr (PrimitiveType == TYPE_HLL) {
auto tmp = value.template get<PrimitiveType>();
func(temp_range, to_olap_filter_type(fn_name),
StringRef(reinterpret_cast<const char*>(&tmp), sizeof(tmp)));
} else {
static_assert(always_false_v<PrimitiveType>);
}
Expand Down
7 changes: 4 additions & 3 deletions be/src/exec/scan/olap_scanner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -395,10 +395,11 @@ Status OlapScanner::_init_tablet_reader_params(
tablet_schema->merge_dropped_columns(*del_pred->tablet_schema());
}

// Range
// Push key ranges to the tablet reader.
// Skip the "full scan" placeholder (has_lower_bound == false) — when no key
// predicates exist, start_key/end_key remain empty and the reader does a full scan.
for (auto* key_range : key_ranges) {
if (key_range->begin_scan_range.size() == 1 &&
key_range->begin_scan_range.get_value(0) == NEGATIVE_INFINITY) {
if (!key_range->has_lower_bound) {
continue;
}

Expand Down
2 changes: 1 addition & 1 deletion be/src/exprs/function/cast/cast_to_string.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ struct CastToString {
static inline void push_datev2(const DateV2Value<DateV2ValueType>& from, BufferWritable& bw);

static inline std::string from_datetimev2(const DateV2Value<DateTimeV2ValueType>& from,
UInt32 scale = -1);
UInt32 scale);
static inline std::string from_timestamptz(const TimestampTzValue& from, UInt32 scale,
const cctz::time_zone* timezone = nullptr);
static inline void push_datetimev2(const DateV2Value<DateTimeV2ValueType>& from, UInt32 scale,
Expand Down
44 changes: 33 additions & 11 deletions be/src/service/point_query_executor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "common/cast_set.h"
#include "common/consts.h"
#include "common/status.h"
#include "core/data_type/data_type_factory.hpp"
#include "core/data_type_serde/data_type_serde.h"
#include "exec/sink/writer/vmysql_result_writer.h"
#include "exprs/vexpr.h"
Expand All @@ -48,7 +49,6 @@
#include "runtime/runtime_profile.h"
#include "runtime/runtime_state.h"
#include "runtime/thread_context.h"
#include "storage/olap_tuple.h"
#include "storage/row_cursor.h"
#include "storage/rowset/beta_rowset.h"
#include "storage/rowset/rowset_fwd.h"
Expand Down Expand Up @@ -387,21 +387,43 @@ void PointQueryExecutor::print_profile() {

Status PointQueryExecutor::_init_keys(const PTabletKeyLookupRequest* request) {
SCOPED_TIMER(&_profile_metrics.init_key_ns);
const auto& schema = _tablet->tablet_schema();
// Point query is only supported on merge-on-write unique key tables.
DCHECK(schema->keys_type() == UNIQUE_KEYS && _tablet->enable_unique_key_merge_on_write());
if (schema->keys_type() != UNIQUE_KEYS || !_tablet->enable_unique_key_merge_on_write()) {
return Status::InvalidArgument(
"Point query is only supported on merge-on-write unique key tables, "
"tablet_id={}",
_tablet->tablet_id());
}
// 1. get primary key from conditions
std::vector<OlapTuple> olap_tuples;
olap_tuples.resize(request->key_tuples().size());
_row_read_ctxs.resize(request->key_tuples().size());
// get row cursor and encode keys
for (int i = 0; i < request->key_tuples().size(); ++i) {
const KeyTuple& key_tuple = request->key_tuples(i);
for (const std::string& key_col : key_tuple.key_column_rep()) {
olap_tuples[i].add_value(key_col);
if (UNLIKELY(cast_set<size_t>(key_tuple.key_column_literals_size()) !=
schema->num_key_columns())) {
return Status::InvalidArgument(
"Key column count mismatch. expected={}, actual={}, tablet_id={}",
schema->num_key_columns(), key_tuple.key_column_literals_size(),
_tablet->tablet_id());
}
}
_row_read_ctxs.resize(olap_tuples.size());
// get row cursor and encode keys
for (size_t i = 0; i < olap_tuples.size(); ++i) {
RowCursor cursor;
RETURN_IF_ERROR(cursor.init_scan_key(_tablet->tablet_schema(), olap_tuples[i].values()));
RETURN_IF_ERROR(cursor.from_tuple(olap_tuples[i]));
std::vector<Field> key_fields;
key_fields.reserve(key_tuple.key_column_literals_size());
for (int j = 0; j < key_tuple.key_column_literals_size(); ++j) {
const auto& literal_bytes = key_tuple.key_column_literals(j);
TExprNode expr_node;
auto len = cast_set<uint32_t>(literal_bytes.size());
RETURN_IF_ERROR(
deserialize_thrift_msg(reinterpret_cast<const uint8_t*>(literal_bytes.data()),
&len, false, &expr_node));
const auto& col = schema->column(j);
auto data_type = DataTypeFactory::instance().create_data_type(
col.type(), col.precision(), col.frac(), col.length());
key_fields.push_back(data_type->get_field(expr_node));
}
RETURN_IF_ERROR(cursor.init_scan_key(_tablet->tablet_schema(), std::move(key_fields)));
cursor.encode_key_with_padding<true>(&_row_read_ctxs[i]._primary_key,
_tablet->tablet_schema()->num_key_columns(), true);
}
Expand Down
Loading
Loading