This is an automated email from the ASF dual-hosted git repository.
hello-stephen 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 477cb0c6bba [improvement](be) Add release-enabled Doris check macros
(#63730)
477cb0c6bba is described below
commit 477cb0c6bbaad03e7e65b4589560a3b8a1bf659a
Author: Jerry Hu <[email protected]>
AuthorDate: Fri May 29 19:11:17 2026 +0800
[improvement](be) Add release-enabled Doris check macros (#63730)
### What problem does this PR solve?
Issue Number: None
Related PR: None
Problem Summary: Add a dedicated `common/check.h` header for Doris check
macros. `DORIS_CHECK` accepts streamed context through the usual `<<`
syntax while avoiding evaluation of streamed operands on successful
checks. `DORIS_CHECK_EQ/NE/LT/LE/GT/GE` are intended for invariants that
should remain checked in Release builds: Debug builds map them to the
corresponding `DCHECK_*` macros, while Release builds evaluate each
operand once, compare with the requested operator, and throw through the
existing `DORIS_CHECK`-style fatal error path with a message that
includes both compared expressions and their actual values. Release
comparison checks also accept streamed context. `status.h` re-exports
`common/check.h` to keep existing includes compatible. The JSONB
function call sites that rely on these invariants are switched from
`DCHECK` to the new release-enabled Doris checks. The added
`DorisCheckTest` coverage exercises `check.cpp` failure handling,
`check.h` value formatting helpers, binary-op result formatting,
streamed messages, stream-operand laziness, comparison success, and
single-evaluation behavior.
### Release note
None
### Check List (For Author)
- Test: Unit Test / Manual test
- `build-support/clang-format.sh
be/src/exprs/function/function_jsonb.cpp be/test/common/check_test.cpp`
- `DORIS_HOME=$PWD ninja -C be/ut_build_ASAN
src/exprs/CMakeFiles/Exprs.dir/function/function_jsonb.cpp.o
test/CMakeFiles/doris_be_test.dir/common/check_test.cpp.o`
- `./run-be-ut.sh --run --filter=DorisCheckTest.*`
- `build-support/check-format.sh`
- `git diff --cached --check`
- Behavior changed: No
- Does this need documentation: No
---
be/src/common/check.cpp | 29 ++++++
be/src/common/check.h | 154 +++++++++++++++++++++++++++++
be/src/common/status.h | 9 +-
be/src/exprs/function/function_jsonb.cpp | 17 ++--
be/test/common/check_test.cpp | 161 +++++++++++++++++++++++++++++++
5 files changed, 353 insertions(+), 17 deletions(-)
diff --git a/be/src/common/check.cpp b/be/src/common/check.cpp
new file mode 100644
index 00000000000..99408738af1
--- /dev/null
+++ b/be/src/common/check.cpp
@@ -0,0 +1,29 @@
+// 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 "common/check.h"
+
+#include "common/exception.h"
+#include "common/status.h"
+
+namespace doris {
+
+void doris_check_fail(std::string_view message) {
+ throw Exception(Status::FatalError("{}", message));
+}
+
+} // namespace doris
diff --git a/be/src/common/check.h b/be/src/common/check.h
new file mode 100644
index 00000000000..1b4e6d51992
--- /dev/null
+++ b/be/src/common/check.h
@@ -0,0 +1,154 @@
+// 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 <fmt/format.h>
+#include <glog/logging.h>
+
+#include <cstddef>
+#include <ios>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+
+namespace doris {
+
+[[noreturn]] void doris_check_fail(std::string_view message);
+
+namespace detail {
+template <typename T>
+concept OstreamPrintable = requires(std::ostream& os, const T& value) { os <<
value; };
+
+class DorisCheckMessage {
+public:
+ explicit DorisCheckMessage(std::string_view message) { _stream << message;
}
+
+ template <typename T>
+ DorisCheckMessage& operator<<(const T& value) {
+ _stream << value;
+ return *this;
+ }
+
+ DorisCheckMessage& operator<<(std::ostream& (*func)(std::ostream&)) {
+ func(_stream);
+ return *this;
+ }
+
+ DorisCheckMessage& operator<<(std::ios& (*func)(std::ios&)) {
+ func(_stream);
+ return *this;
+ }
+
+ DorisCheckMessage& operator<<(std::ios_base& (*func)(std::ios_base&)) {
+ func(_stream);
+ return *this;
+ }
+
+ [[noreturn]] void fail() { doris_check_fail(_stream.str()); }
+
+private:
+ std::ostringstream _stream;
+};
+
+class DorisCheckMessageVoidify {
+public:
+ [[noreturn]] void operator&(DorisCheckMessage& message) const {
message.fail(); }
+ [[noreturn]] void operator&(DorisCheckMessage&& message) const {
message.fail(); }
+};
+
+class DorisCheckResult {
+public:
+ explicit DorisCheckResult(bool ok) : _ok(ok) {}
+ explicit DorisCheckResult(std::string message) : _ok(false),
_message(std::move(message)) {}
+
+ bool ok() const { return _ok; }
+ const std::string& message() const { return _message; }
+
+private:
+ bool _ok;
+ std::string _message;
+};
+
+template <typename T>
+std::string doris_check_value_to_string(const T& value) {
+ if constexpr (std::is_same_v<std::decay_t<T>, std::nullptr_t>) {
+ return "nullptr";
+ } else if constexpr (std::is_same_v<std::decay_t<T>, bool>) {
+ return value ? "true" : "false";
+ } else if constexpr (OstreamPrintable<T>) {
+ std::ostringstream oss;
+ oss << std::boolalpha << value;
+ return oss.str();
+ } else {
+ return "<unprintable>";
+ }
+}
+
+template <typename Lhs, typename Rhs, typename Comparator>
+DorisCheckResult doris_check_binary_op_result(const Lhs& lhs, const Rhs& rhs,
+ std::string_view lhs_expr,
std::string_view rhs_expr,
+ std::string_view op_expr,
Comparator comparator) {
+ if (static_cast<bool>(comparator(lhs, rhs))) {
+ return DorisCheckResult(true);
+ }
+ return DorisCheckResult(fmt::format("Check failed: {} {} {} ({} vs {})",
lhs_expr, op_expr,
+ rhs_expr,
doris_check_value_to_string(lhs),
+ doris_check_value_to_string(rhs)));
+}
+} // namespace detail
+
+} // namespace doris
+
+// core in Debug mode, exception in Release mode.
+#define DORIS_CHECK(stmt) \
+ if (bool _doris_check_ok = static_cast<bool>(stmt); _doris_check_ok) { \
+ } else [[unlikely]] \
+ ::doris::detail::DorisCheckMessageVoidify() & \
+ ::doris::detail::DorisCheckMessage("Check failed: " #stmt)
+
+// Use DORIS_CHECK_* only for invariants that must also be checked in Release
builds.
+// Keep DCHECK_* in loops or other hot paths where Release checks would add
overhead.
+#ifndef NDEBUG
+#define DORIS_CHECK_EQ(val1, val2) DCHECK_EQ(val1, val2)
+#define DORIS_CHECK_NE(val1, val2) DCHECK_NE(val1, val2)
+#define DORIS_CHECK_LT(val1, val2) DCHECK_LT(val1, val2)
+#define DORIS_CHECK_LE(val1, val2) DCHECK_LE(val1, val2)
+#define DORIS_CHECK_GT(val1, val2) DCHECK_GT(val1, val2)
+#define DORIS_CHECK_GE(val1, val2) DCHECK_GE(val1, val2)
+#else
+#define DORIS_CHECK_BINARY_OP(val1, val2, op, op_str)
\
+ if (auto _doris_check_result =
::doris::detail::doris_check_binary_op_result( \
+ (val1), (val2), #val1, #val2, op_str,
\
+ [](const auto& _doris_check_lhs, const auto& _doris_check_rhs)
{ \
+ return _doris_check_lhs op _doris_check_rhs;
\
+ });
\
+ _doris_check_result.ok()) {
\
+ } else [[unlikely]]
\
+ ::doris::detail::DorisCheckMessageVoidify() &
\
+
::doris::detail::DorisCheckMessage(_doris_check_result.message())
+
+#define DORIS_CHECK_EQ(val1, val2) DORIS_CHECK_BINARY_OP(val1, val2, ==, "==")
+#define DORIS_CHECK_NE(val1, val2) DORIS_CHECK_BINARY_OP(val1, val2, !=, "!=")
+#define DORIS_CHECK_LT(val1, val2) DORIS_CHECK_BINARY_OP(val1, val2, <, "<")
+#define DORIS_CHECK_LE(val1, val2) DORIS_CHECK_BINARY_OP(val1, val2, <=, "<=")
+#define DORIS_CHECK_GT(val1, val2) DORIS_CHECK_BINARY_OP(val1, val2, >, ">")
+#define DORIS_CHECK_GE(val1, val2) DORIS_CHECK_BINARY_OP(val1, val2, >=, ">=")
+#endif
diff --git a/be/src/common/status.h b/be/src/common/status.h
index baf2cf8caa4..8563c09910b 100644
--- a/be/src/common/status.h
+++ b/be/src/common/status.h
@@ -17,6 +17,7 @@
#include <type_traits>
#include <utility>
+#include "common/check.h" // IWYU pragma: export
#include "common/compiler_util.h" // IWYU pragma: keep
#include "common/config.h"
#include "common/expected.h"
@@ -769,14 +770,6 @@ using ResultError = unexpected<Status>;
std::forward<_result_t>(_result_).error();
\
})
-// core in Debug mode, exception in Release mode.
-#define DORIS_CHECK(stmt)
\
- do {
\
- if (!static_cast<bool>(stmt)) [[unlikely]] {
\
- throw Exception(Status::FatalError(fmt::format("Check failed: {}",
#stmt))); \
- }
\
- } while (false)
-
} // namespace doris
// specify formatter for Status
diff --git a/be/src/exprs/function/function_jsonb.cpp
b/be/src/exprs/function/function_jsonb.cpp
index 1f11e496877..04b902f7933 100644
--- a/be/src/exprs/function/function_jsonb.cpp
+++ b/be/src/exprs/function/function_jsonb.cpp
@@ -374,7 +374,7 @@ public:
Status execute_impl(FunctionContext* context, Block& block, const
ColumnNumbers& arguments,
uint32_t result, size_t input_rows_count) const
override {
- DCHECK_GE(arguments.size(), 2);
+ DORIS_CHECK_GE(arguments.size(), 2);
ColumnPtr jsonb_data_column;
bool jsonb_data_const = false;
@@ -430,7 +430,7 @@ public:
path_null_maps, path_const, res_data, res_offsets,
null_map->get_data()));
} else {
// not support other extract type for now (e.g. int, double, ...)
- DCHECK_EQ(jsonb_path_columns.size(), 1);
+ DORIS_CHECK_EQ(jsonb_path_columns.size(), 1);
const auto& rdata = jsonb_path_columns[0]->get_chars();
const auto& roffsets = jsonb_path_columns[0]->get_offsets();
@@ -491,8 +491,8 @@ public:
Status execute_impl(FunctionContext* context, Block& block, const
ColumnNumbers& arguments,
uint32_t result, size_t input_rows_count) const
override {
- DCHECK_GE(arguments.size(), 1);
- DCHECK(arguments.size() == 1 || arguments.size() == 2)
+ DORIS_CHECK_GE(arguments.size(), 1);
+ DORIS_CHECK(arguments.size() == 1 || arguments.size() == 2)
<< "json_keys should have 1 or 2 arguments, but got " <<
arguments.size();
const NullMap* data_null_map = nullptr;
@@ -696,7 +696,7 @@ public:
path_col = assert_cast<const ColumnString*>(path_column.get());
}
- DCHECK(!(jsonb_data_const && path_const))
+ DORIS_CHECK(!(jsonb_data_const && path_const))
<< "jsonb_data_const and path_const should not be both const";
auto create_all_null_result = [&]() {
@@ -1367,7 +1367,7 @@ struct JsonbLengthUtil {
static Status jsonb_length_execute(FunctionContext* context, Block& block,
const ColumnNumbers& arguments,
uint32_t result,
size_t input_rows_count) {
- DCHECK_GE(arguments.size(), 2);
+ DORIS_CHECK_GE(arguments.size(), 2);
ColumnPtr jsonb_data_column;
bool jsonb_data_const = false;
// prepare jsonb data column
@@ -1482,7 +1482,7 @@ struct JsonbContainsUtil {
static Status jsonb_contains_execute(FunctionContext* context, Block&
block,
const ColumnNumbers& arguments,
uint32_t result,
size_t input_rows_count) {
- DCHECK_GE(arguments.size(), 3);
+ DORIS_CHECK_GE(arguments.size(), 3);
auto jsonb_data1_column = block.get_by_position(arguments[0]).column;
auto jsonb_data2_column = block.get_by_position(arguments[1]).column;
@@ -2053,7 +2053,6 @@ public:
replace = true;
if
(!build_parents_by_path(json_documents[row_idx]->getValue(),
json_path[path_index],
parents)) {
- DCHECK(false);
continue;
}
} else {
@@ -2725,7 +2724,7 @@ public:
Status execute_impl(FunctionContext* context, Block& block, const
ColumnNumbers& arguments,
uint32_t result, size_t input_rows_count) const
override {
- DCHECK_GE(arguments.size(), 2);
+ DORIS_CHECK_GE(arguments.size(), 2);
// Check if arguments count is valid (json_doc + at least one path)
if (arguments.size() < 2) {
diff --git a/be/test/common/check_test.cpp b/be/test/common/check_test.cpp
new file mode 100644
index 00000000000..37272e76ff9
--- /dev/null
+++ b/be/test/common/check_test.cpp
@@ -0,0 +1,161 @@
+// 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 "common/check.h"
+
+#include <gtest/gtest.h>
+
+#include <ios>
+#include <string>
+#include <utility>
+
+#include "common/exception.h"
+
+namespace doris {
+
+namespace {
+
+struct NotOstreamPrintable {};
+
+std::ios& TouchIos(std::ios& ios) {
+ return ios;
+}
+
+#ifdef NDEBUG
+template <typename Fn>
+std::string CaptureDorisCheckException(Fn&& fn) {
+ try {
+ std::forward<Fn>(fn)();
+ } catch (const Exception& e) {
+ return e.to_string();
+ }
+ ADD_FAILURE() << "Expected doris::Exception";
+ return {};
+}
+#endif
+
+} // namespace
+
+TEST(DorisCheckTest, CheckFailHandlesFatalError) {
+#ifndef NDEBUG
+ EXPECT_DEATH(doris_check_fail("manual failure"), "manual failure");
+#else
+ const auto message = CaptureDorisCheckException([] {
doris_check_fail("manual failure"); });
+ EXPECT_NE(message.find("[FATAL_ERROR]"), std::string::npos) << message;
+ EXPECT_NE(message.find("manual failure"), std::string::npos) << message;
+#endif
+}
+
+TEST(DorisCheckTest, ValueToString) {
+ EXPECT_EQ(detail::doris_check_value_to_string(nullptr), "nullptr");
+ EXPECT_EQ(detail::doris_check_value_to_string(true), "true");
+ EXPECT_EQ(detail::doris_check_value_to_string(false), "false");
+ EXPECT_EQ(detail::doris_check_value_to_string(123), "123");
+ EXPECT_EQ(detail::doris_check_value_to_string(std::string("value")),
"value");
+ EXPECT_EQ(detail::doris_check_value_to_string(NotOstreamPrintable {}),
"<unprintable>");
+}
+
+TEST(DorisCheckTest, BinaryOpResult) {
+ auto ok_result = detail::doris_check_binary_op_result(
+ 1, 2, "lhs", "rhs", "<", [](const auto& lhs, const auto& rhs) {
return lhs < rhs; });
+ EXPECT_TRUE(ok_result.ok());
+ EXPECT_TRUE(ok_result.message().empty());
+
+ auto failed_result = detail::doris_check_binary_op_result(
+ 1, 2, "lhs", "rhs", ">", [](const auto& lhs, const auto& rhs) {
return lhs > rhs; });
+ EXPECT_FALSE(failed_result.ok());
+ EXPECT_EQ(failed_result.message(), "Check failed: lhs > rhs (1 vs 2)");
+
+ auto bool_failed_result = detail::doris_check_binary_op_result(
+ true, false, "lhs", "rhs",
+ "==", [](const auto& lhs, const auto& rhs) { return lhs == rhs; });
+ EXPECT_FALSE(bool_failed_result.ok());
+ EXPECT_EQ(bool_failed_result.message(), "Check failed: lhs == rhs (true vs
false)");
+}
+
+TEST(DorisCheckTest, CheckMessageSupportsStreaming) {
+#ifndef NDEBUG
+ EXPECT_DEATH(DORIS_CHECK(false) << " with context " << std::boolalpha <<
false << std::endl
+ << "done",
+ "Check failed: false with context false");
+
+ EXPECT_DEATH(
+ {
+ detail::DorisCheckMessage check_message("lvalue message ");
+ check_message << TouchIos << 42;
+ detail::DorisCheckMessageVoidify() & check_message;
+ },
+ "lvalue message 42");
+
+ EXPECT_DEATH(detail::DorisCheckMessageVoidify() &
detail::DorisCheckMessage("rvalue message"),
+ "rvalue message");
+#else
+ const auto message = CaptureDorisCheckException([] {
+ DORIS_CHECK(false) << " with context " << std::boolalpha << false <<
std::endl << "done";
+ });
+ EXPECT_NE(message.find("Check failed: false with context false"),
std::string::npos) << message;
+ EXPECT_NE(message.find("done"), std::string::npos) << message;
+
+ const auto lvalue_message = CaptureDorisCheckException([] {
+ detail::DorisCheckMessage check_message("lvalue message ");
+ check_message << TouchIos << 42;
+ detail::DorisCheckMessageVoidify() & check_message;
+ });
+ EXPECT_NE(lvalue_message.find("lvalue message 42"), std::string::npos) <<
lvalue_message;
+
+ const auto rvalue_message = CaptureDorisCheckException([] {
+ detail::DorisCheckMessageVoidify() & detail::DorisCheckMessage("rvalue
message");
+ });
+ EXPECT_NE(rvalue_message.find("rvalue message"), std::string::npos) <<
rvalue_message;
+#endif
+}
+
+TEST(DorisCheckTest, CheckMacroSkipsMessageWhenOk) {
+ int stream_value = 0;
+ DORIS_CHECK(true) << ++stream_value;
+ EXPECT_EQ(stream_value, 0);
+}
+
+TEST(DorisCheckTest, ComparisonMacrosEvaluateOnceAndCheckSuccess) {
+ DORIS_CHECK_EQ(1, 1);
+ DORIS_CHECK_NE(1, 2);
+ DORIS_CHECK_LT(1, 2);
+ DORIS_CHECK_LE(1, 1);
+ DORIS_CHECK_GT(2, 1);
+ DORIS_CHECK_GE(1, 1);
+
+ int lhs = 0;
+ int rhs = 0;
+ DORIS_CHECK_EQ(++lhs, ++rhs);
+ EXPECT_EQ(lhs, 1);
+ EXPECT_EQ(rhs, 1);
+}
+
+#ifdef NDEBUG
+TEST(DorisCheckTest, ComparisonMacrosThrowInRelease) {
+ const auto message =
+ CaptureDorisCheckException([] { DORIS_CHECK_EQ(1, 2) << " with
context " << 43; });
+ EXPECT_NE(message.find("Check failed: 1 == 2 (1 vs 2) with context 43"),
std::string::npos)
+ << message;
+}
+#elif DCHECK_IS_ON()
+TEST(DorisCheckTest, ComparisonMacrosDcheckInDebug) {
+ EXPECT_DEATH(DORIS_CHECK_EQ(1, 2), "Check failed");
+}
+#endif
+
+} // namespace doris
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]