This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new c29075a7e10 [opt](exprs) Remove unreachable mixed-width decimal
registrations for add/subtract/mod (#66617)
c29075a7e10 is described below
commit c29075a7e10069705617289c22bfccf5b78bf32e
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Tue Aug 11 22:51:37 2026 +0800
[opt](exprs) Remove unreachable mixed-width decimal registrations for
add/subtract/mod (#66617)
> Split out of **https://github.com/apache/doris/pull/66510**. That PR
carries the
> whole BE build-time batch and its end-to-end measurements — **please
refer to
> #66510 for the complete benefit numbers**. This is the only piece of
that batch
> that touches the **function registration surface**, so it is broken
out here to
> get a proper review from the expression owners. It has no dependency
on the
> rest of the batch.
### What problem does this PR solve?
Related PR: #66510
Problem Summary:
The mixed-width decimal registrations for `add` / `subtract` / `mod`
cannot be
reached at runtime, and each one costs a full set of template
instantiations in
some of the heaviest TUs in `be/src/exprs/function/`.
FE casts *both* children of a decimal `Add` / `Subtract` / `Mod` to
exactly the
return type (`TypeCoercionUtils#processDecimalV3BinaryArithmetic`, since
2.0 via
#17393), so only same-width pairs can ever reach BE.
For **add** and **subtract** the registrations were not merely
unreachable, they
were already broken: since #52837 the impl carried `PTypeB = TypeA`,
which made
every mixed-width variant register under the *same-width* factory key,
the last
one overwriting the diagonal. Mixed-width add/subtract has therefore
been
unresolvable on 4.0 / 4.1 / master for over a year with zero field
reports.
That key collapse also had a runtime consequence worth calling out: the
entries
that survived on the diagonal were the `<Type, DECIMAL256>` variants,
whose
plain (non-overflow-checked) path promoted `Decimal32/64/128` arithmetic
to
`Int256`. Collapsing the impl to a single type parameter puts same-width
inputs
back on their natural width.
For **mod** there was no such typo — its 16 decimal combinations were
all live in
the factory — so this is a genuine narrowing of the registered signature
set.
It is still safe across the supported upgrade window (old FE + new BE):
- Nereids has cast both children of `Mod` to exactly the return type
since 2.0
(#17393; briefly removed and restored within two days in May 2023, a
master-only window);
- the legacy planner's `ArithmeticExpr#analyzeDecimalV3Op` cast both
children
unconditionally for `MOD` (only `ADD`/`SUBTRACT` had the
scale-only-comparison hole), and its builtin table only ever registered
same-width decimal `MOD`;
- supported upgrade sources for master (4.0 / 4.1) are Nereids-only —
the legacy
expression analyzer no longer exists there.
A mixed-width lookup now fails loudly with function name, argument types
and
return type instead of silently resolving to the wrong instantiation.
`Multiply` is exempt from the FE cast and keeps its full width cross
product.
### Why this is a build-time win
Dropping mixed-width removes roughly two thirds of the decimal
instantiations of
these TUs — a whole template family per width pair, not a handful of
functions.
Measured with the real build's own compile commands (Release, PCH on,
the exact
flags `build.sh --be` produces), recompiling each TU serialized and
uncontended,
alternating base/PR across three rounds. `multiply.cpp` is the control:
this PR
deliberately does not touch it, so it must not move.
| TU | wall before | wall after | delta | peak RSS before | after |
delta |
|---|---:|---:|---:|---:|---:|---:|
| `plus.cpp` | 10.19s | 6.06s | **-40.5%** | 1518M | 1159M | **-23.7%**
|
| `minus.cpp` | 9.94s | 5.89s | **-40.7%** | 1523M | 1162M | **-23.7%**
|
| `modulo.cpp` | 8.46s | 5.89s | **-30.4%** | 1394M | 1151M | **-17.4%**
|
| `multiply.cpp` *(control)* | 15.95s | 15.88s | -0.4% | 1959M | 1961M |
+0.1% |
Run-to-run spread is tight (`plus.cpp` base 9.93 / 10.19 / 10.24s, after
6.01 /
6.06 / 6.11s) and the control is flat, so these deltas are the change
rather than
scheduling noise. The ~350MB drop in peak RSS per TU is worth as much as
the wall
time if you build at high `-j`.
**What this does not claim.** These are 3 TUs out of 8382 in a cold BE
build, and
35.5s out of 7237s of total TU CPU. The ~11s of CPU saved is about
**0.15% of a
full build** — real, but well below what a single end-to-end run can
resolve, so
I am deliberately not quoting an end-to-end percentage for it. The value
of this
PR is the per-TU cost of these three files, the peak-memory drop, the
removal of
code that cannot be reached, and the contract test that keeps it gone.
### Release note
None
### Check List (For Author)
- Test
- [x] Regression test — the 56 decimal regression suites match the
pre-change baseline suite for suite (the only failure is a known,
unrelated S3-credential `outfile` case that also fails on the baseline).
- [x] Unit Test — new `BinaryArithmeticRegistrationTest`
(`be/test/exprs/function/binary_arithmetic_registration_test.cpp`, 5/5
passing) pins the registration surface: same-width add/subtract/mod
lookups must resolve, mixed-width ones must return `nullptr` (there is
no
bare-name fallback), multiply's 4x4 cross product must stay, and
DecimalV2
stays resolvable. If someone re-adds mixed-width registrations — paying
~2/3 of those TUs' instantiations for unreachable code — or drops a
reachable signature, this fails fast.
- [x] Manual test — microbenchmark A/B, below.
#### Runtime A/B: no regression on any case
The first commit adds `benchmark_binary_arithmetic` (13 cases through
the real
`SimpleFunctionFactory` dispatch: multiply int64/same-width/mixed-width/
vector_constant/constant_constant, plus add and subtract in int64
vec_vec,
same-width DECIMAL32 and DECIMAL64 vec_vec, vector_constant and one-row
constant_constant). Two `benchmark_test` binaries were built from one
tree
differing only in `be/src/exprs/function/`, then run alternately so both
share
thermal and scheduling conditions. Figures are the better of two rounds'
5-repetition medians, CPU ns/iteration, 4096-row blocks, macOS arm64 /
clang 20
/ Release.
| case | before | after | delta |
|---|---:|---:|---:|
| `add_d64_d64_const_const` | 98.1 | 98.9 | +0.8% |
| `subtract_d64_d64_const_const` | 97.8 | 96.7 | -1.2% |
| `multiply_d64_d64_const_const` | 107.8 | 107.5 | -0.3% |
| `add_d64_d64_vec_vec` | 1578.5 | 1551.0 | -1.7% |
| `add_d32_d32_vec_vec` | 1487.8 | 1474.4 | -0.9% |
| `subtract_d64_d64_vec_vec` | 1558.9 | 1537.8 | -1.4% |
| `add_d64_d64_vec_const` | 5877.4 | 5770.7 | -1.8% |
| `add_int64_vec_vec` | 565.1 | 545.2 | -3.5% |
| `subtract_int64_vec_vec` | 568.2 | 550.3 | -3.1% |
| `multiply_d64_d64_vec_vec` | 14816.0 | 14806.1 | -0.1% |
| `multiply_d32_d64_vec_vec` | 14746.9 | 14705.4 | -0.3% |
| `multiply_d64_d64_vec_const` | 14673.3 | 14641.3 | -0.2% |
| `multiply_int64_vec_vec` | 1211.6 | 1189.2 | -1.8% |
Every case is inside the +-3.5% round-to-round noise band; nothing
regresses.
- Behavior changed:
- [x] Yes.
1. A **mixed-width** decimal `add`/`subtract`/`mod` lookup now fails
loudly (`Could not find function ...` with argument and return types)
instead of resolving. For add/subtract nothing changes in practice —
those keys were already unreachable. For `mod` this is a real
narrowing, argued safe above; FE never emits such a call.
2. On the **plain, non-overflow-checked** branch, same-width decimal
`add`/`subtract` now compute at their natural width instead of being
promoted to `Int256`. Results are unchanged; the intermediate width
is not.
- Does this need documentation?
- [x] No.
### Proactive disclosure
- **The claimed narrow-width restoration is not visible in the A/B, by
construction.** Same-width decimal add/subtract come out flat. The
`<Type, DECIMAL256>` promotion sits on the *plain*, non-overflow-checked
branch of the kernel, and the benchmark runner pins
`check_overflow_for_decimal` to the production default (`true`), so it
never
executes that branch. The restoration is a code-level fact you can read
in the
diff; it is not something this A/B measured, and I would rather say so
than
let the table imply otherwise.
- **`mod` is the only place where a signature genuinely disappears.**
add and
subtract were already unresolvable. If a reviewer disagrees with the mod
argument, that is the single hunk to contest, and the contract UT is
what
would need updating.
- Runtime numbers were measured on macOS arm64 / clang 20 only, on a
binary that
links system malloc rather than tcmalloc (an arm64 branch-range
workaround).
Nothing here is platform-specific — it is registration and template code
— but
the numbers are single-platform.
- A companion change that removed the hand-written `constant_constant`
paths was
originally part of this PR and has been **taken back out**: isolating it
showed
it carried a reproducible +60-70% per-call regression on the
constant-folding
path, while this registration change measured flat. It will be proposed
separately, on its own evidence.
---------
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
be/benchmark/benchmark_binary_arithmetic.hpp | 346 +++++++++++++++++++++
be/benchmark/benchmark_main.cpp | 1 +
be/src/exprs/function/minus.cpp | 65 ++--
be/src/exprs/function/modulo.cpp | 74 ++---
be/src/exprs/function/plus.cpp | 65 ++--
.../binary_arithmetic_registration_test.cpp | 116 +++++++
6 files changed, 525 insertions(+), 142 deletions(-)
diff --git a/be/benchmark/benchmark_binary_arithmetic.hpp
b/be/benchmark/benchmark_binary_arithmetic.hpp
new file mode 100644
index 00000000000..6a3a0156bc2
--- /dev/null
+++ b/be/benchmark/benchmark_binary_arithmetic.hpp
@@ -0,0 +1,346 @@
+// 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.
+
+#pragma once
+
+#include <benchmark/benchmark.h>
+
+#include <cstdint>
+#include <random>
+#include <stdexcept>
+#include <string>
+
+#include "core/block/block.h"
+#include "core/column/column_const.h"
+#include "core/column/column_decimal.h"
+#include "core/column/column_vector.h"
+#include "core/data_type/data_type_decimal.h"
+#include "core/data_type/data_type_number.h"
+#include "exprs/function/simple_function_factory.h"
+#include "exprs/function_context.h"
+
+namespace doris {
+namespace {
+
+// Runtime guardrails for the binary-arithmetic template refactors
+// (compile-opt Phase 2/4/5): every case goes through the real
+// SimpleFunctionFactory dispatch, so kernel swaps and dead-registration
+// removals are covered end to end. check_overflow_for_decimal is pinned to
+// the production default (true). Data is deterministic and sized so the
+// overflow check never actually throws.
+
+constexpr size_t kBinaryArithmeticRows = 4096;
+
+// Drives one binary arithmetic function on a prepared two-column block.
+// Constructed outside the timed loop; run_once is the measured unit.
+struct BinaryArithmeticRunner {
+ Block block;
+ FunctionBasePtr func;
+ std::unique_ptr<FunctionContext> ctx;
+ uint32_t result_idx;
+
+ BinaryArithmeticRunner(const std::string& name, ColumnPtr col_a,
DataTypePtr type_a,
+ ColumnPtr col_b, DataTypePtr type_b, DataTypePtr
res_type) {
+ block.insert({std::move(col_a), type_a, "a"});
+ block.insert({std::move(col_b), type_b, "b"});
+ func = SimpleFunctionFactory::instance().get_function(
+ name, block.get_columns_with_type_and_name(), res_type);
+ if (func == nullptr) {
+ throw std::runtime_error(name + " not found for benchmark argument
types");
+ }
+ ctx = FunctionContext::create_context(nullptr, res_type, {type_a,
type_b});
+ ctx->set_check_overflow_for_decimal(true);
+ if (!func->open(ctx.get(), FunctionContext::FRAGMENT_LOCAL).ok() ||
+ !func->open(ctx.get(), FunctionContext::THREAD_LOCAL).ok()) {
+ throw std::runtime_error(name + " open failed");
+ }
+ block.insert({nullptr, res_type, "result"});
+ result_idx = block.columns() - 1;
+ }
+
+ void run_once(size_t rows) {
+ Status st = func->execute(ctx.get(), block, {0, 1}, result_idx, rows);
+ if (!st.ok()) {
+ throw std::runtime_error(st.to_string());
+ }
+ benchmark::DoNotOptimize(block.get_by_position(result_idx).column);
+ }
+};
+
+template <PrimitiveType PT>
+ColumnPtr make_decimal_bench_column(size_t rows, UInt32 scale, int64_t
native_lo, int64_t native_hi,
+ uint64_t seed) {
+ auto col = ColumnDecimal<PT>::create(rows, scale);
+ std::mt19937_64 rng(seed);
+ std::uniform_int_distribution<int64_t> dist(native_lo, native_hi - 1);
+ auto& data = col->get_data();
+ for (size_t i = 0; i < rows; ++i) {
+ data[i] = typename ColumnDecimal<PT>::value_type(dist(rng));
+ }
+ return col;
+}
+
+ColumnPtr make_int64_bench_column(size_t rows, uint64_t seed) {
+ auto col = ColumnVector<TYPE_BIGINT>::create(rows);
+ std::mt19937_64 rng(seed);
+ std::uniform_int_distribution<int64_t> dist(1, 999'999);
+ auto& data = col->get_data();
+ for (size_t i = 0; i < rows; ++i) {
+ data[i] = dist(rng);
+ }
+ return col;
+}
+
+// BIGINT * BIGINT -> BIGINT, vector_vector.
+void BM_multiply_int64_vec_vec(benchmark::State& state) {
+ auto type = std::make_shared<DataTypeInt64>();
+ BinaryArithmeticRunner runner(
+ "multiply", make_int64_bench_column(kBinaryArithmeticRows,
0x1001), type,
+ make_int64_bench_column(kBinaryArithmeticRows, 0x1002), type,
type);
+ for (auto _ : state) {
+ runner.run_once(kBinaryArithmeticRows);
+ }
+ state.SetItemsProcessed(state.iterations() * kBinaryArithmeticRows);
+}
+
+// DECIMAL64(18,4) * DECIMAL64(18,4) -> DECIMAL128(36,8): the same-width kernel
+// that survives every phase of the refactor. Native values stay < 1e9 so the
+// int128 product never trips the overflow check, and 4+4 == 8 means no scale
+// adjustment (the common FE-planned shape).
+void BM_multiply_d64_d64_vec_vec(benchmark::State& state) {
+ auto type_a = std::make_shared<DataTypeDecimal64>(18, 4);
+ auto res_type = std::make_shared<DataTypeDecimal128>(36, 8);
+ BinaryArithmeticRunner runner("multiply",
+ make_decimal_bench_column<TYPE_DECIMAL64>(
+ kBinaryArithmeticRows, 4, 10'000,
999'999'999, 0x2001),
+ type_a,
+ make_decimal_bench_column<TYPE_DECIMAL64>(
+ kBinaryArithmeticRows, 4, 10'000,
999'999'999, 0x2002),
+ type_a, res_type);
+ for (auto _ : state) {
+ runner.run_once(kBinaryArithmeticRows);
+ }
+ state.SetItemsProcessed(state.iterations() * kBinaryArithmeticRows);
+}
+
+// DECIMAL32(9,2) * DECIMAL64(18,4) -> DECIMAL128(27,6): the mixed-width shape
+// Phase 5 wants to eliminate; its rows/s before/after is the A/B material for
+// the FE cast-to-same-width decision.
+void BM_multiply_d32_d64_vec_vec(benchmark::State& state) {
+ auto type_a = std::make_shared<DataTypeDecimal32>(9, 2);
+ auto type_b = std::make_shared<DataTypeDecimal64>(18, 4);
+ auto res_type = std::make_shared<DataTypeDecimal128>(27, 6);
+ BinaryArithmeticRunner runner("multiply",
+ make_decimal_bench_column<TYPE_DECIMAL32>(
+ kBinaryArithmeticRows, 2, 100,
9'999'999, 0x3001),
+ type_a,
+ make_decimal_bench_column<TYPE_DECIMAL64>(
+ kBinaryArithmeticRows, 4, 10'000,
999'999'999, 0x3002),
+ type_b, res_type);
+ for (auto _ : state) {
+ runner.run_once(kBinaryArithmeticRows);
+ }
+ state.SetItemsProcessed(state.iterations() * kBinaryArithmeticRows);
+}
+
+// DECIMAL64 column * DECIMAL64 constant: the vector_constant fast path.
+void BM_multiply_d64_d64_vec_const(benchmark::State& state) {
+ auto type_a = std::make_shared<DataTypeDecimal64>(18, 4);
+ auto res_type = std::make_shared<DataTypeDecimal128>(36, 8);
+ auto const_col = ColumnConst::create(
+ make_decimal_bench_column<TYPE_DECIMAL64>(1, 4, 12'345, 12'346,
0x4002),
+ kBinaryArithmeticRows);
+ BinaryArithmeticRunner runner("multiply",
+ make_decimal_bench_column<TYPE_DECIMAL64>(
+ kBinaryArithmeticRows, 4, 10'000,
999'999'999, 0x4001),
+ type_a, std::move(const_col), type_a,
res_type);
+ for (auto _ : state) {
+ runner.run_once(kBinaryArithmeticRows);
+ }
+ state.SetItemsProcessed(state.iterations() * kBinaryArithmeticRows);
+}
+
+// DECIMAL64 constant * DECIMAL64 constant, one row: measures per-call cost of
+// the constant_constant path Phase 2b removes in favor of the default
+// unwrap-execute-rewrap implementation. Items == calls, not rows.
+void BM_multiply_d64_d64_const_const(benchmark::State& state) {
+ auto type_a = std::make_shared<DataTypeDecimal64>(18, 4);
+ auto res_type = std::make_shared<DataTypeDecimal128>(36, 8);
+ auto const_a = ColumnConst::create(
+ make_decimal_bench_column<TYPE_DECIMAL64>(1, 4, 54'321, 54'322,
0x5001), 1);
+ auto const_b = ColumnConst::create(
+ make_decimal_bench_column<TYPE_DECIMAL64>(1, 4, 12'345, 12'346,
0x5002), 1);
+ BinaryArithmeticRunner runner("multiply", std::move(const_a), type_a,
std::move(const_b),
+ type_a, res_type);
+ for (auto _ : state) {
+ runner.run_once(1);
+ }
+ state.SetItemsProcessed(state.iterations());
+}
+
+// --- add / subtract -------------------------------------------------------
+//
+// FE casts both children of a decimal Add/Subtract to exactly the return type,
+// so - unlike multiply - only the same-width shapes below are reachable, and
+// they are the ones the mixed-width registration removal rewrites.
+//
+// Before that removal the same-width factory keys were won by the
+// <Type, DECIMAL256> variants; collapsing the impl to a single type parameter
+// puts them back on their natural width. Do not expect these cases to show
+// that: the runner pins check_overflow_for_decimal to the production default
+// (true), and the Int256 promotion sat on the plain, non-overflow-checked
+// branch of the same kernel. What they do pin is that rewriting the
+// registration surface costs the reachable shapes nothing.
+
+// BIGINT +/- BIGINT -> BIGINT, vector_vector: the integral kernel, untouched
by
+// the decimal registration change and therefore the control case.
+void BM_add_int64_vec_vec(benchmark::State& state) {
+ auto type = std::make_shared<DataTypeInt64>();
+ BinaryArithmeticRunner runner("add",
make_int64_bench_column(kBinaryArithmeticRows, 0x6001),
+ type,
make_int64_bench_column(kBinaryArithmeticRows, 0x6002),
+ type, type);
+ for (auto _ : state) {
+ runner.run_once(kBinaryArithmeticRows);
+ }
+ state.SetItemsProcessed(state.iterations() * kBinaryArithmeticRows);
+}
+
+void BM_subtract_int64_vec_vec(benchmark::State& state) {
+ auto type = std::make_shared<DataTypeInt64>();
+ BinaryArithmeticRunner runner(
+ "subtract", make_int64_bench_column(kBinaryArithmeticRows,
0x7001), type,
+ make_int64_bench_column(kBinaryArithmeticRows, 0x7002), type,
type);
+ for (auto _ : state) {
+ runner.run_once(kBinaryArithmeticRows);
+ }
+ state.SetItemsProcessed(state.iterations() * kBinaryArithmeticRows);
+}
+
+// DECIMAL32(9,2) + DECIMAL32(9,2) -> DECIMAL32(9,2): the narrowest same-width
+// shape, i.e. the one that paid the largest promotion penalty when the
+// DECIMAL256 variant owned this key. Native values stay well under 1e9 so the
+// sum never trips the overflow check.
+void BM_add_d32_d32_vec_vec(benchmark::State& state) {
+ auto type = std::make_shared<DataTypeDecimal32>(9, 2);
+ BinaryArithmeticRunner runner("add",
+ make_decimal_bench_column<TYPE_DECIMAL32>(
+ kBinaryArithmeticRows, 2, 100,
9'999'999, 0x8001),
+ type,
+ make_decimal_bench_column<TYPE_DECIMAL32>(
+ kBinaryArithmeticRows, 2, 100,
9'999'999, 0x8002),
+ type, type);
+ for (auto _ : state) {
+ runner.run_once(kBinaryArithmeticRows);
+ }
+ state.SetItemsProcessed(state.iterations() * kBinaryArithmeticRows);
+}
+
+// DECIMAL64(18,4) +/- DECIMAL64(18,4) -> DECIMAL64(18,4): the same-width shape
+// FE actually plans for decimal add/subtract.
+void BM_add_d64_d64_vec_vec(benchmark::State& state) {
+ auto type = std::make_shared<DataTypeDecimal64>(18, 4);
+ BinaryArithmeticRunner runner("add",
+ make_decimal_bench_column<TYPE_DECIMAL64>(
+ kBinaryArithmeticRows, 4, 10'000,
999'999'999, 0x9001),
+ type,
+ make_decimal_bench_column<TYPE_DECIMAL64>(
+ kBinaryArithmeticRows, 4, 10'000,
999'999'999, 0x9002),
+ type, type);
+ for (auto _ : state) {
+ runner.run_once(kBinaryArithmeticRows);
+ }
+ state.SetItemsProcessed(state.iterations() * kBinaryArithmeticRows);
+}
+
+void BM_subtract_d64_d64_vec_vec(benchmark::State& state) {
+ auto type = std::make_shared<DataTypeDecimal64>(18, 4);
+ BinaryArithmeticRunner runner("subtract",
+ make_decimal_bench_column<TYPE_DECIMAL64>(
+ kBinaryArithmeticRows, 4, 10'000,
999'999'999, 0xa001),
+ type,
+ make_decimal_bench_column<TYPE_DECIMAL64>(
+ kBinaryArithmeticRows, 4, 10'000,
999'999'999, 0xa002),
+ type, type);
+ for (auto _ : state) {
+ runner.run_once(kBinaryArithmeticRows);
+ }
+ state.SetItemsProcessed(state.iterations() * kBinaryArithmeticRows);
+}
+
+// DECIMAL64 column + DECIMAL64 constant: the vector_constant fast path.
+void BM_add_d64_d64_vec_const(benchmark::State& state) {
+ auto type = std::make_shared<DataTypeDecimal64>(18, 4);
+ auto const_col = ColumnConst::create(
+ make_decimal_bench_column<TYPE_DECIMAL64>(1, 4, 12'345, 12'346,
0xb002),
+ kBinaryArithmeticRows);
+ BinaryArithmeticRunner runner("add",
+ make_decimal_bench_column<TYPE_DECIMAL64>(
+ kBinaryArithmeticRows, 4, 10'000,
999'999'999, 0xb001),
+ type, std::move(const_col), type, type);
+ for (auto _ : state) {
+ runner.run_once(kBinaryArithmeticRows);
+ }
+ state.SetItemsProcessed(state.iterations() * kBinaryArithmeticRows);
+}
+
+// Constant +/- constant, one row: per-call cost of the path that loses its
+// hand-written constant_constant specialization in favour of the default
+// unwrap-execute-rewrap implementation. Items == calls, not rows.
+void BM_add_d64_d64_const_const(benchmark::State& state) {
+ auto type = std::make_shared<DataTypeDecimal64>(18, 4);
+ auto const_a = ColumnConst::create(
+ make_decimal_bench_column<TYPE_DECIMAL64>(1, 4, 54'321, 54'322,
0xc001), 1);
+ auto const_b = ColumnConst::create(
+ make_decimal_bench_column<TYPE_DECIMAL64>(1, 4, 12'345, 12'346,
0xc002), 1);
+ BinaryArithmeticRunner runner("add", std::move(const_a), type,
std::move(const_b), type, type);
+ for (auto _ : state) {
+ runner.run_once(1);
+ }
+ state.SetItemsProcessed(state.iterations());
+}
+
+void BM_subtract_d64_d64_const_const(benchmark::State& state) {
+ auto type = std::make_shared<DataTypeDecimal64>(18, 4);
+ auto const_a = ColumnConst::create(
+ make_decimal_bench_column<TYPE_DECIMAL64>(1, 4, 54'321, 54'322,
0xd001), 1);
+ auto const_b = ColumnConst::create(
+ make_decimal_bench_column<TYPE_DECIMAL64>(1, 4, 12'345, 12'346,
0xd002), 1);
+ BinaryArithmeticRunner runner("subtract", std::move(const_a), type,
std::move(const_b), type,
+ type);
+ for (auto _ : state) {
+ runner.run_once(1);
+ }
+ state.SetItemsProcessed(state.iterations());
+}
+
+BENCHMARK(BM_multiply_int64_vec_vec);
+BENCHMARK(BM_multiply_d64_d64_vec_vec);
+BENCHMARK(BM_multiply_d32_d64_vec_vec);
+BENCHMARK(BM_multiply_d64_d64_vec_const);
+BENCHMARK(BM_multiply_d64_d64_const_const);
+
+BENCHMARK(BM_add_int64_vec_vec);
+BENCHMARK(BM_subtract_int64_vec_vec);
+BENCHMARK(BM_add_d32_d32_vec_vec);
+BENCHMARK(BM_add_d64_d64_vec_vec);
+BENCHMARK(BM_subtract_d64_d64_vec_vec);
+BENCHMARK(BM_add_d64_d64_vec_const);
+BENCHMARK(BM_add_d64_d64_const_const);
+BENCHMARK(BM_subtract_d64_d64_const_const);
+
+} // namespace
+} // namespace doris
diff --git a/be/benchmark/benchmark_main.cpp b/be/benchmark/benchmark_main.cpp
index 7cce267d01e..aed5c6eb84e 100644
--- a/be/benchmark/benchmark_main.cpp
+++ b/be/benchmark/benchmark_main.cpp
@@ -23,6 +23,7 @@
#include <vector>
#include "benchmark_arrow_validation.hpp"
+#include "benchmark_binary_arithmetic.hpp"
#include "benchmark_bit_pack.hpp"
#include "benchmark_column_array_view.hpp"
#include "benchmark_column_array_view_distance.hpp"
diff --git a/be/src/exprs/function/minus.cpp b/be/src/exprs/function/minus.cpp
index a7f911f6984..9635c39951f 100644
--- a/be/src/exprs/function/minus.cpp
+++ b/be/src/exprs/function/minus.cpp
@@ -22,18 +22,20 @@
#include "exprs/function/simple_function_factory.h"
namespace doris {
-template <PrimitiveType TypeA, PrimitiveType TypeB>
+// FE casts both children of decimal subtract to exactly the same type as the
+// return type (TypeCoercionUtils#processDecimalV3BinaryArithmetic), so only
+// same-width pairs are reachable at runtime. Keep a single type parameter so
+// mixed-width instantiations cannot be registered again.
+template <PrimitiveType Type>
struct MinusDecimalImpl {
- static_assert(is_decimal(TypeA) && is_decimal(TypeB));
- static_assert((TypeA == TYPE_DECIMALV2 && TypeB == TYPE_DECIMALV2) ||
- (TypeA != TYPE_DECIMALV2 && TypeB != TYPE_DECIMALV2));
+ static_assert(is_decimal(Type));
constexpr static bool need_replace_null_data_to_default = true;
static constexpr auto name = "subtract";
- static constexpr PrimitiveType PTypeA = TypeA;
- static constexpr PrimitiveType PTypeB = TypeA;
- using ArgNativeTypeA = typename
PrimitiveTypeTraits<TypeA>::CppType::NativeType;
- using ArgNativeTypeB = typename
PrimitiveTypeTraits<TypeB>::CppType::NativeType;
+ static constexpr PrimitiveType PTypeA = Type;
+ static constexpr PrimitiveType PTypeB = Type;
+ using ArgNativeTypeA = typename
PrimitiveTypeTraits<Type>::CppType::NativeType;
+ using ArgNativeTypeB = typename
PrimitiveTypeTraits<Type>::CppType::NativeType;
template <PrimitiveType Result>
requires(is_decimal(Result) && Result != TYPE_DECIMALV2)
@@ -67,44 +69,17 @@ struct MinusImpl {
};
void register_function_minus(SimpleFunctionFactory& factory) {
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMALV2,
TYPE_DECIMALV2>>>>();
-
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL32,
TYPE_DECIMAL32>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL32,
TYPE_DECIMAL64>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL32,
TYPE_DECIMAL128I>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL32,
TYPE_DECIMAL256>>>>();
-
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL64,
TYPE_DECIMAL32>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL64,
TYPE_DECIMAL64>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL64,
TYPE_DECIMAL128I>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL64,
TYPE_DECIMAL256>>>>();
-
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL128I,
TYPE_DECIMAL32>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL128I,
TYPE_DECIMAL64>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL128I,
TYPE_DECIMAL128I>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL128I,
TYPE_DECIMAL256>>>>();
+ factory.register_function<
+
FunctionPlusMinus<PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMALV2>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL256,
TYPE_DECIMAL32>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL256,
TYPE_DECIMAL64>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL256,
TYPE_DECIMAL128I>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL256,
TYPE_DECIMAL256>>>>();
+ factory.register_function<
+
FunctionPlusMinus<PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL32>>>>();
+ factory.register_function<
+
FunctionPlusMinus<PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL64>>>>();
+ factory.register_function<
+
FunctionPlusMinus<PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL128I>>>>();
+ factory.register_function<
+
FunctionPlusMinus<PlusMinusDecimalImpl<MinusDecimalImpl<TYPE_DECIMAL256>>>>();
factory.register_function<FunctionPlusMinus<PlusMinusIntegralImpl<MinusImpl<TYPE_TINYINT>>>>();
factory.register_function<FunctionPlusMinus<PlusMinusIntegralImpl<MinusImpl<TYPE_SMALLINT>>>>();
diff --git a/be/src/exprs/function/modulo.cpp b/be/src/exprs/function/modulo.cpp
index 8942475e75b..78ebf9afde8 100644
--- a/be/src/exprs/function/modulo.cpp
+++ b/be/src/exprs/function/modulo.cpp
@@ -558,25 +558,27 @@ struct PModuloNumericImpl {
}
};
-template <PrimitiveType TypeA, PrimitiveType TypeB>
+// FE casts both children of decimal mod to exactly the same type as the return
+// type (TypeCoercionUtils#processDecimalV3BinaryArithmetic), so only
same-width
+// pairs are reachable at runtime. Keep a single type parameter so mixed-width
+// instantiations cannot be registered again.
+template <PrimitiveType Type>
struct ModuloDecimalImpl {
- static_assert(is_decimal(TypeA) && is_decimal(TypeB));
- static_assert((TypeA == TYPE_DECIMALV2 && TypeB == TYPE_DECIMALV2) ||
- (TypeA != TYPE_DECIMALV2 && TypeB != TYPE_DECIMALV2));
+ static_assert(is_decimal(Type));
static constexpr auto name = "mod";
static constexpr auto is_pmod = false;
- using ArgA = typename PrimitiveTypeTraits<TypeA>::CppType;
- using ArgB = typename PrimitiveTypeTraits<TypeB>::CppType;
- using ArgNativeTypeA = typename
PrimitiveTypeTraits<TypeA>::CppType::NativeType;
- using ArgNativeTypeB = typename
PrimitiveTypeTraits<TypeB>::CppType::NativeType;
- using DataTypeA = typename PrimitiveTypeTraits<TypeA>::DataType;
- using DataTypeB = typename PrimitiveTypeTraits<TypeB>::DataType;
- using ColumnTypeA = typename PrimitiveTypeTraits<TypeA>::ColumnType;
- using ColumnTypeB = typename PrimitiveTypeTraits<TypeB>::ColumnType;
+ using ArgA = typename PrimitiveTypeTraits<Type>::CppType;
+ using ArgB = typename PrimitiveTypeTraits<Type>::CppType;
+ using ArgNativeTypeA = typename
PrimitiveTypeTraits<Type>::CppType::NativeType;
+ using ArgNativeTypeB = typename
PrimitiveTypeTraits<Type>::CppType::NativeType;
+ using DataTypeA = typename PrimitiveTypeTraits<Type>::DataType;
+ using DataTypeB = typename PrimitiveTypeTraits<Type>::DataType;
+ using ColumnTypeA = typename PrimitiveTypeTraits<Type>::ColumnType;
+ using ColumnTypeB = typename PrimitiveTypeTraits<Type>::ColumnType;
static DataTypes get_variadic_argument_types() {
- return {std::make_shared<typename
PrimitiveTypeTraits<TypeA>::DataType>(),
- std::make_shared<typename
PrimitiveTypeTraits<TypeB>::DataType>()};
+ return {std::make_shared<typename
PrimitiveTypeTraits<Type>::DataType>(),
+ std::make_shared<typename
PrimitiveTypeTraits<Type>::DataType>()};
}
static inline DecimalV2Value apply(DecimalV2Value a, DecimalV2Value b,
UInt8& is_null) {
@@ -935,44 +937,12 @@ void register_function_modulo(SimpleFunctionFactory&
factory) {
factory.register_function<FunctionMod<ModNumericImpl<PModuloNumericImpl<TYPE_BIGINT>>>>();
factory.register_function<FunctionMod<ModNumericImpl<PModuloNumericImpl<TYPE_DOUBLE>>>>();
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMALV2,
TYPE_DECIMALV2>>>>();
-
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL32,
TYPE_DECIMAL32>>>>();
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL32,
TYPE_DECIMAL64>>>>();
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL32,
TYPE_DECIMAL128I>>>>();
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL32,
TYPE_DECIMAL256>>>>();
-
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL64,
TYPE_DECIMAL32>>>>();
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL64,
TYPE_DECIMAL64>>>>();
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL64,
TYPE_DECIMAL128I>>>>();
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL64,
TYPE_DECIMAL256>>>>();
-
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL128I,
TYPE_DECIMAL32>>>>();
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL128I,
TYPE_DECIMAL64>>>>();
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL128I,
TYPE_DECIMAL128I>>>>();
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL128I,
TYPE_DECIMAL256>>>>();
-
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL256,
TYPE_DECIMAL32>>>>();
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL256,
TYPE_DECIMAL64>>>>();
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL256,
TYPE_DECIMAL128I>>>>();
- factory.register_function<
- FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL256,
TYPE_DECIMAL256>>>>();
+
factory.register_function<FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMALV2>>>>();
+
+
factory.register_function<FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL32>>>>();
+
factory.register_function<FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL64>>>>();
+
factory.register_function<FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL128I>>>>();
+
factory.register_function<FunctionMod<ModDecimalImpl<ModuloDecimalImpl<TYPE_DECIMAL256>>>>();
factory.register_alias("mod", "fmod");
}
diff --git a/be/src/exprs/function/plus.cpp b/be/src/exprs/function/plus.cpp
index a67812eab15..a87353e0009 100644
--- a/be/src/exprs/function/plus.cpp
+++ b/be/src/exprs/function/plus.cpp
@@ -30,19 +30,21 @@ struct PlusImpl {
NO_SANITIZE_UNDEFINED static inline Arg apply(Arg a, Arg b) { return a +
b; }
};
-template <PrimitiveType TypeA, PrimitiveType TypeB>
+// FE casts both children of decimal add to exactly the same type as the return
+// type (TypeCoercionUtils#processDecimalV3BinaryArithmetic), so only
same-width
+// pairs are reachable at runtime. Keep a single type parameter so mixed-width
+// instantiations cannot be registered again.
+template <PrimitiveType Type>
struct PlusDecimalImpl {
- static_assert(is_decimal(TypeA) && is_decimal(TypeB));
- static_assert((TypeA == TYPE_DECIMALV2 && TypeB == TYPE_DECIMALV2) ||
- (TypeA != TYPE_DECIMALV2 && TypeB != TYPE_DECIMALV2));
+ static_assert(is_decimal(Type));
constexpr static bool need_replace_null_data_to_default = true;
static constexpr auto name = "add";
- static constexpr PrimitiveType PTypeA = TypeA;
- static constexpr PrimitiveType PTypeB = TypeA;
- using ArgNativeTypeA = typename
PrimitiveTypeTraits<TypeA>::CppType::NativeType;
- using ArgNativeTypeB = typename
PrimitiveTypeTraits<TypeB>::CppType::NativeType;
+ static constexpr PrimitiveType PTypeA = Type;
+ static constexpr PrimitiveType PTypeB = Type;
+ using ArgNativeTypeA = typename
PrimitiveTypeTraits<Type>::CppType::NativeType;
+ using ArgNativeTypeB = typename
PrimitiveTypeTraits<Type>::CppType::NativeType;
template <PrimitiveType Result>
requires(is_decimal(Result) && Result != TYPE_DECIMALV2)
@@ -69,44 +71,17 @@ struct PlusDecimalImpl {
};
void register_function_plus(SimpleFunctionFactory& factory) {
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMALV2,
TYPE_DECIMALV2>>>>();
-
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL32,
TYPE_DECIMAL32>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL32,
TYPE_DECIMAL64>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL32,
TYPE_DECIMAL128I>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL32,
TYPE_DECIMAL256>>>>();
-
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL64,
TYPE_DECIMAL32>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL64,
TYPE_DECIMAL64>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL64,
TYPE_DECIMAL128I>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL64,
TYPE_DECIMAL256>>>>();
-
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL128I,
TYPE_DECIMAL32>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL128I,
TYPE_DECIMAL64>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL128I,
TYPE_DECIMAL128I>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL128I,
TYPE_DECIMAL256>>>>();
+ factory.register_function<
+
FunctionPlusMinus<PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMALV2>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL256,
TYPE_DECIMAL32>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL256,
TYPE_DECIMAL64>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL256,
TYPE_DECIMAL128I>>>>();
- factory.register_function<FunctionPlusMinus<
- PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL256,
TYPE_DECIMAL256>>>>();
+ factory.register_function<
+
FunctionPlusMinus<PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL32>>>>();
+ factory.register_function<
+
FunctionPlusMinus<PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL64>>>>();
+ factory.register_function<
+
FunctionPlusMinus<PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL128I>>>>();
+ factory.register_function<
+
FunctionPlusMinus<PlusMinusDecimalImpl<PlusDecimalImpl<TYPE_DECIMAL256>>>>();
factory.register_function<FunctionPlusMinus<PlusMinusIntegralImpl<PlusImpl<TYPE_TINYINT>>>>();
factory.register_function<FunctionPlusMinus<PlusMinusIntegralImpl<PlusImpl<TYPE_SMALLINT>>>>();
diff --git a/be/test/exprs/function/binary_arithmetic_registration_test.cpp
b/be/test/exprs/function/binary_arithmetic_registration_test.cpp
new file mode 100644
index 00000000000..78e53a05579
--- /dev/null
+++ b/be/test/exprs/function/binary_arithmetic_registration_test.cpp
@@ -0,0 +1,116 @@
+// 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 <gtest/gtest.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "core/data_type/data_type_decimal.h"
+#include "core/data_type/data_type_nullable.h"
+#include "exprs/function/simple_function_factory.h"
+
+namespace doris {
+
+// Pins the decimal registration surface of the binary arithmetic functions.
+//
+// FE casts both children of Add/Subtract/Mod to exactly the return type
+// (TypeCoercionUtils#processDecimalV3BinaryArithmetic), so BE registers only
+// same-width decimal pairs for them. Multiply is exempt from that cast and
+// must keep the full width cross product. If someone re-adds mixed-width
+// add/subtract/mod registrations (paying ~2/3 of the template instantiations
+// of those TUs for unreachable code) or drops a reachable signature, these
+// tests fail.
+class BinaryArithmeticRegistrationTest : public testing::Test {
+protected:
+ static FunctionBasePtr lookup(const std::string& name, const DataTypePtr&
left,
+ const DataTypePtr& right, const DataTypePtr&
return_type) {
+ ColumnsWithTypeAndName arguments {{nullptr, left, ""}, {nullptr,
right, ""}};
+ return SimpleFunctionFactory::instance().get_function(name, arguments,
return_type);
+ }
+
+ static std::vector<DataTypePtr> v3_widths() {
+ return {std::make_shared<DataTypeDecimal<TYPE_DECIMAL32>>(),
+ std::make_shared<DataTypeDecimal<TYPE_DECIMAL64>>(),
+ std::make_shared<DataTypeDecimal<TYPE_DECIMAL128I>>(),
+ std::make_shared<DataTypeDecimal<TYPE_DECIMAL256>>()};
+ }
+};
+
+TEST_F(BinaryArithmeticRegistrationTest, same_width_add_subtract_resolvable) {
+ for (const auto& name : {std::string("add"), std::string("subtract")}) {
+ for (const auto& type : v3_widths()) {
+ auto function = lookup(name, type, type, type);
+ ASSERT_NE(function, nullptr) << name << " " << type->get_name();
+ EXPECT_EQ(function->get_name(), name) << type->get_name();
+ }
+ }
+}
+
+TEST_F(BinaryArithmeticRegistrationTest, same_width_mod_resolvable) {
+ for (const auto& type : v3_widths()) {
+ // FunctionMod always infers a nullable return type (mod by zero ->
NULL).
+ auto function = lookup("mod", type, type, make_nullable(type));
+ ASSERT_NE(function, nullptr) << type->get_name();
+ EXPECT_EQ(function->get_name(), "mod") << type->get_name();
+ }
+}
+
+TEST_F(BinaryArithmeticRegistrationTest,
mixed_width_add_subtract_mod_unresolvable) {
+ auto widths = v3_widths();
+ for (const auto& left : widths) {
+ for (const auto& right : widths) {
+ if (left->get_primitive_type() == right->get_primitive_type()) {
+ continue;
+ }
+ for (const auto& name : {std::string("add"),
std::string("subtract")}) {
+ EXPECT_EQ(lookup(name, left, right, left), nullptr)
+ << name << " " << left->get_name() << " x " <<
right->get_name();
+ }
+ EXPECT_EQ(lookup("mod", left, right, make_nullable(left)), nullptr)
+ << "mod " << left->get_name() << " x " <<
right->get_name();
+ }
+ }
+}
+
+TEST_F(BinaryArithmeticRegistrationTest, multiply_full_cross_product_retained)
{
+ auto widths = v3_widths();
+ for (const auto& left : widths) {
+ for (const auto& right : widths) {
+ auto function = lookup("multiply", left, right, left);
+ ASSERT_NE(function, nullptr)
+ << "multiply " << left->get_name() << " x " <<
right->get_name();
+ EXPECT_EQ(function->get_name(), "multiply");
+ }
+ }
+}
+
+TEST_F(BinaryArithmeticRegistrationTest, decimalv2_still_resolvable) {
+ auto type = std::make_shared<DataTypeDecimalV2>();
+ for (const auto& name :
+ {std::string("add"), std::string("subtract"),
std::string("multiply")}) {
+ auto function = lookup(name, type, type, type);
+ ASSERT_NE(function, nullptr) << name;
+ EXPECT_EQ(function->get_name(), name);
+ }
+ auto mod_function = lookup("mod", type, type, make_nullable(type));
+ ASSERT_NE(mod_function, nullptr);
+ EXPECT_EQ(mod_function->get_name(), "mod");
+}
+
+} // namespace doris
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]