mirror of
https://github.com/apache/doris.git
synced 2026-11-04 16:03:23 +00:00
### What problem does this PR solve? Backport #68308 / #68401 to branch-4.1. The repair is present on branch-4.1.4, but branch-4.1 atc8c0ddca4dstill has the same three unnormalized OR paths as 4.1.5-rc01 (7fda449b7a). A nullable Boolean can contain an arbitrary hidden byte. When the other operand makes OR non-null, preserving the bitwise result can expose a value other than 0 or 1. Release regression reproduced incorrect Boolean grouping on the original candidate in both LOCAL and S3 modes. This is an exact backport offc344110d1, preserving its author and all five changed files: normalize FunctionOr and both compound OR paths, add three BE unit tests, and include the original regression suite and unchanged golden output. No unrelated NULL/CASE implementation changes are included. ### Release note Fix noncanonical logical OR results involving hidden NULL payloads. ### Check List (For Author) - Validation: patch applies cleanly to the stated branch-4.1 base; all five resulting files are byte-identical to the original fixed commit; git diff --check passes. - Runtime validation: this new backport has not run BE unit tests or live regression yet. Required validation is FunctionsLogicalTest.* and test_json_extract_bool_null_payload in LOCAL/S3, with both short-circuit settings, against a binary built from this PR's exact SHA. - The existing candidate failure remains a release blocker. This PR does not replace the candidate artifact or establish a passing release result. - Duplicate check: currently open branch-4.1 PR file lists and matching repair references were checked; no equivalent open backport was found. Co-authored-by: Mingyu Chen (Rayner) <morningman.cmy@gmail.com>
144 lines
5.5 KiB
C++
144 lines
5.5 KiB
C++
// 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 "exprs/function/functions_logical.h"
|
|
|
|
#include <gen_cpp/Exprs_types.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "core/block/block.h"
|
|
#include "core/column/column_const.h"
|
|
#include "core/column/column_nullable.h"
|
|
#include "core/data_type/data_type_nullable.h"
|
|
#include "core/data_type/data_type_number.h"
|
|
#include "core/data_type/primitive_type.h"
|
|
#include "exprs/vcompound_pred.h"
|
|
#include "exprs/vexpr_context.h"
|
|
#include "testutil/column_helper.h"
|
|
|
|
namespace doris {
|
|
|
|
namespace {
|
|
|
|
ColumnWithTypeAndName nullable_boolean_column(std::vector<UInt8> data, std::vector<UInt8> null_map,
|
|
std::string name) {
|
|
return {ColumnHelper::create_nullable_column<DataTypeUInt8>(data, null_map),
|
|
make_nullable(std::make_shared<DataTypeUInt8>()), std::move(name)};
|
|
}
|
|
|
|
ColumnPtr execute_or(ColumnWithTypeAndName left, ColumnWithTypeAndName right, size_t rows) {
|
|
auto result_type = make_nullable(std::make_shared<DataTypeUInt8>());
|
|
Block block({std::move(left), std::move(right), {nullptr, result_type, "result"}});
|
|
auto status = FunctionOr::create()->execute_impl(nullptr, block, {0, 1}, 2, rows);
|
|
EXPECT_TRUE(status.ok()) << status.to_string();
|
|
return block.get_by_position(2).column;
|
|
}
|
|
|
|
class ColumnExpr final : public VExpr {
|
|
public:
|
|
ColumnExpr(ColumnPtr column, DataTypePtr type)
|
|
: VExpr(std::move(type), false), _column(std::move(column)) {}
|
|
|
|
bool is_constant() const override { return false; }
|
|
|
|
const std::string& expr_name() const override {
|
|
static const std::string name = "ColumnExpr";
|
|
return name;
|
|
}
|
|
|
|
Status execute_column_impl(VExprContext*, const Block*, const Selector*, size_t,
|
|
ColumnPtr& result_column) const override {
|
|
result_column = _column;
|
|
return Status::OK();
|
|
}
|
|
|
|
private:
|
|
ColumnPtr _column;
|
|
};
|
|
|
|
ColumnPtr execute_compound_or(ColumnWithTypeAndName left, ColumnWithTypeAndName right,
|
|
size_t rows) {
|
|
TExprNode node;
|
|
node.__set_type(create_type_desc(TYPE_BOOLEAN));
|
|
node.__set_node_type(TExprNodeType::COMPOUND_PRED);
|
|
node.__set_opcode(TExprOpcode::COMPOUND_OR);
|
|
node.__set_num_children(2);
|
|
node.__set_is_nullable(true);
|
|
|
|
auto compound = VCompoundPred::create_shared(node);
|
|
compound->add_child(std::make_shared<ColumnExpr>(std::move(left.column), left.type));
|
|
compound->add_child(std::make_shared<ColumnExpr>(std::move(right.column), right.type));
|
|
VExprContext context(compound);
|
|
ColumnPtr result;
|
|
auto status = compound->execute_column(&context, nullptr, nullptr, rows, result);
|
|
EXPECT_TRUE(status.ok()) << status.to_string();
|
|
return result;
|
|
}
|
|
|
|
void expect_boolean(const ColumnNullable& result, size_t row, UInt8 value) {
|
|
EXPECT_FALSE(result.is_null_at(row));
|
|
EXPECT_EQ(assert_cast<const ColumnUInt8&>(result.get_nested_column()).get_data()[row], value);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(FunctionsLogicalTest, NullableOrIgnoresNullPayload) {
|
|
auto result =
|
|
execute_or(nullable_boolean_column({65, 0, 65, 1, 65}, {1, 0, 1, 0, 1}, "left"),
|
|
nullable_boolean_column({1, 0, 0, 65, 127}, {0, 0, 0, 1, 1}, "right"), 5);
|
|
|
|
const auto& nullable_result = assert_cast<const ColumnNullable&>(*result);
|
|
expect_boolean(nullable_result, 0, 1);
|
|
expect_boolean(nullable_result, 1, 0);
|
|
EXPECT_TRUE(nullable_result.is_null_at(2));
|
|
expect_boolean(nullable_result, 3, 1);
|
|
EXPECT_TRUE(nullable_result.is_null_at(4));
|
|
}
|
|
|
|
TEST(FunctionsLogicalTest, NullableOrWithTrueConstantProducesCanonicalTrue) {
|
|
constexpr size_t rows = 4;
|
|
auto left = nullable_boolean_column({3, 65, 0, 255}, {1, 1, 1, 1}, "left");
|
|
ColumnWithTypeAndName right {
|
|
ColumnConst::create(ColumnHelper::create_column<DataTypeUInt8>({1}), rows),
|
|
std::make_shared<DataTypeUInt8>(), "right"};
|
|
|
|
auto result = execute_or(std::move(left), std::move(right), rows);
|
|
const auto& nullable_result = assert_cast<const ColumnNullable&>(*result);
|
|
for (size_t row = 0; row < rows; ++row) {
|
|
expect_boolean(nullable_result, row, 1);
|
|
}
|
|
}
|
|
|
|
TEST(FunctionsLogicalTest, CompoundNullableOrIgnoresNullPayload) {
|
|
auto result = execute_compound_or(
|
|
nullable_boolean_column({65, 0, 65, 1, 65}, {1, 0, 1, 0, 1}, "left"),
|
|
nullable_boolean_column({1, 0, 0, 65, 127}, {0, 0, 0, 1, 1}, "right"), 5);
|
|
|
|
const auto& nullable_result = assert_cast<const ColumnNullable&>(*result);
|
|
expect_boolean(nullable_result, 0, 1);
|
|
expect_boolean(nullable_result, 1, 0);
|
|
EXPECT_TRUE(nullable_result.is_null_at(2));
|
|
expect_boolean(nullable_result, 3, 1);
|
|
EXPECT_TRUE(nullable_result.is_null_at(4));
|
|
}
|
|
|
|
} // namespace doris
|