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]

Reply via email to