diff --git a/src/pl/ob_pl.cpp b/src/pl/ob_pl.cpp index e216cc57f..60af988a0 100644 --- a/src/pl/ob_pl.cpp +++ b/src/pl/ob_pl.cpp @@ -102,6 +102,8 @@ int ObPL::init(common::ObMySQLProxy &sql_proxy) WRAP_SPI_CALL(sql::ObSPIService::spi_cursor_close)); jit::ObLLVMHelper::add_symbol(ObString("spi_process_resignal"), WRAP_SPI_CALL(sql::ObSPIService::spi_process_resignal)); + jit::ObLLVMHelper::add_symbol(ObString("spi_pl_set_user_error_msg"), + WRAP_SPI_CALL(sql::ObSPIService::spi_pl_set_user_error_msg)); jit::ObLLVMHelper::add_symbol(ObString("spi_destruct_collection"), WRAP_SPI_CALL(sql::ObSPIService::spi_destruct_collection)); jit::ObLLVMHelper::add_symbol(ObString("spi_reset_composite"), diff --git a/src/pl/ob_pl_code_generator.cpp b/src/pl/ob_pl_code_generator.cpp index 69b03da0d..29fd10b00 100644 --- a/src/pl/ob_pl_code_generator.cpp +++ b/src/pl/ob_pl_code_generator.cpp @@ -2051,6 +2051,38 @@ int ObPLCodeGenerateVisitor::visit(const ObPLSignalStmt &s) OZ (generator_.get_helper().get_int64(s.get_stmt_id(), stmt_id)); // Temporarily use stmtid, this id is the combination of col and line OZ (generator_.get_helper().get_int64(s.get_stmt_id(), loc)); + // For deferred resolver errors (e.g. OB_ERR_BAD_TABLE captured in a + // trigger/SP body), the formatted user message — which embeds the + // offending object name — was preserved on the stmt. Push it back into + // the thread-local warning buffer before raising so the runtime error + // text matches the original LOG_USER_ERROR output instead of the bare + // error template. + if (s.has_user_msg()) { + ObLLVMValue user_err_code; + ObLLVMValue user_sql_state; + ObLLVMValue user_sql_state_len; + ObLLVMValue user_msg_val; + ObLLVMValue user_msg_len; + ObSEArray set_msg_args; + ObLLVMValue set_msg_ret; + OZ (set_msg_args.push_back(generator_.get_vars().at(generator_.CTX_IDX))); + // Use the OB-internal error code (e.g. -5201) so the value matches the + // err code that ObMPPacketSender::send_error_packet compares against + // ObWarningBuffer::get_err_code() when deciding whether to read the + // formatted user message. + OZ (generator_.get_helper().get_int64(s.get_ob_error_code(), user_err_code)); + OZ (set_msg_args.push_back(user_err_code)); + OZ (generator_.generate_global_string(ObString(s.get_str_len(), s.get_sql_state()), + user_sql_state, user_sql_state_len)); + OZ (set_msg_args.push_back(user_sql_state)); + OZ (generator_.generate_global_string(s.get_user_msg(), user_msg_val, user_msg_len)); + OZ (set_msg_args.push_back(user_msg_val)); + OZ (set_msg_args.push_back(user_msg_len)); + OZ (generator_.get_helper().create_call(ObString("spi_pl_set_user_error_msg"), + generator_.get_spi_service().spi_pl_set_user_error_msg_, + set_msg_args, + set_msg_ret)); + } OZ (generator_.generate_destruct_out_params()); OZ (generator_.generate_exception(type, ob_err_code, err_code, sql_state, str_len, stmt_id, normal, loc, s.get_block()->in_notfound(), @@ -3462,6 +3494,17 @@ int ObPLCodeGenerator::init_spi_service() OZ (ObLLVMFunctionType::get(int32_type, arg_types, ft)); OZ (helper_.create_function(ObString("spi_process_resignal"), ft, spi_service_.spi_process_resignal_error_)); } + if (OB_SUCC(ret)) { + // spi_pl_set_user_error_msg(ctx, err_code, sql_state, msg, msg_len) + arg_types.reset(); + OZ (arg_types.push_back(pl_exec_context_pointer_type)); + OZ (arg_types.push_back(int64_type)); + OZ (arg_types.push_back(char_type)); + OZ (arg_types.push_back(char_type)); + OZ (arg_types.push_back(int64_type)); + OZ (ObLLVMFunctionType::get(int32_type, arg_types, ft)); + OZ (helper_.create_function(ObString("spi_pl_set_user_error_msg"), ft, spi_service_.spi_pl_set_user_error_msg_)); + } if (OB_SUCC(ret)) { arg_types.reset(); OZ (arg_types.push_back(pl_exec_context_pointer_type)); diff --git a/src/pl/ob_pl_code_generator.h b/src/pl/ob_pl_code_generator.h index ec12eff54..1872625a3 100644 --- a/src/pl/ob_pl_code_generator.h +++ b/src/pl/ob_pl_code_generator.h @@ -100,6 +100,7 @@ friend class ObPLCGBufferGuard; jit::ObLLVMFunction spi_update_package_change_info_; jit::ObLLVMFunction spi_check_composite_not_null_; jit::ObLLVMFunction spi_process_resignal_error_; + jit::ObLLVMFunction spi_pl_set_user_error_msg_; jit::ObLLVMFunction spi_check_autonomous_trans_; jit::ObLLVMFunction spi_opaque_assign_null_; jit::ObLLVMFunction spi_pl_profiler_before_record_; diff --git a/src/pl/ob_pl_resolver.cpp b/src/pl/ob_pl_resolver.cpp index b3b14eeb1..e9faaeed9 100644 --- a/src/pl/ob_pl_resolver.cpp +++ b/src/pl/ob_pl_resolver.cpp @@ -611,6 +611,27 @@ int ObPLResolver::resolve(const ObStmtNodeTree *parse_tree, ObPLFunctionAST &fun && lib::is_mysql_mode()) { ObPLSignalStmt *signal_stmt = NULL; int save_ret = ret; + // Capture the formatted user error message from the thread-local + // warning buffer before we destroy the failed stmt. Some deferred + // errors (e.g. OB_ERR_BAD_TABLE) embed object names in the message + // template via LOG_USER_ERROR; without preserving the formatted text + // here, the runtime SIGNAL would only carry the bare error template + // and the object name (e.g. "'t.notable'") would be lost. + common::ObString saved_user_msg; + { + common::ObWarningBuffer *wb = common::ob_get_tsi_warning_buffer(); + if (OB_NOT_NULL(wb) && wb->get_err_code() == save_ret) { + const char *err_msg = wb->get_err_msg(); + if (OB_NOT_NULL(err_msg) && '\0' != err_msg[0]) { + const int64_t msg_len = STRLEN(err_msg); + char *buf = static_cast(resolve_ctx_.allocator_.alloc(msg_len)); + if (OB_NOT_NULL(buf)) { + MEMCPY(buf, err_msg, msg_len); + saved_user_msg.assign_ptr(buf, static_cast(msg_len)); + } + } + } + } if (NULL != stmt) { stmt->~ObPLStmt(); stmt = NULL; @@ -630,6 +651,9 @@ int ObPLResolver::resolve(const ObStmtNodeTree *parse_tree, ObPLFunctionAST &fun signal_stmt->set_ob_error_code(save_ret); signal_stmt->set_sql_state(ob_sqlstate(save_ret)); signal_stmt->set_str_len(STRLEN(ob_sqlstate(save_ret))); + if (!saved_user_msg.empty()) { + signal_stmt->set_user_msg(saved_user_msg); + } func.set_has_incomplete_rt_dep_error(true); func.set_is_all_sql_stmt(false); if (lib::is_mysql_mode() && !func.is_reads_sql_data() && !func.is_modifies_sql_data()) { diff --git a/src/pl/ob_pl_stmt.h b/src/pl/ob_pl_stmt.h index 0ea25fe5c..6051320ee 100644 --- a/src/pl/ob_pl_stmt.h +++ b/src/pl/ob_pl_stmt.h @@ -2592,7 +2592,8 @@ class ObPLSignalStmt : public ObPLStmt item_to_expr_idx_(), ob_error_code_(0), is_signal_null_(false), - is_resignal_stmt_(false) {} + is_resignal_stmt_(false), + user_msg_() {} virtual ~ObPLSignalStmt() { item_to_expr_idx_.destroy(); } int accept(ObPLStmtVisitor &visitor) const; @@ -2622,6 +2623,13 @@ class ObPLSignalStmt : public ObPLStmt inline void set_is_resignal_stmt() { is_resignal_stmt_ = true; } inline bool is_resignal_stmt() const { return is_resignal_stmt_; } + // Carries a pre-formatted user error message captured at PL resolve time + // for deferred errors (e.g. OB_ERR_BAD_TABLE) that were turned into a + // PL_SIGNAL stub. Empty when the SIGNAL was written by the user. + inline const common::ObString &get_user_msg() const { return user_msg_; } + inline void set_user_msg(const common::ObString &msg) { user_msg_ = msg; } + inline bool has_user_msg() const { return !user_msg_.empty(); } + TO_STRING_KV(K_(type), K_(label), K_(value)); private: @@ -2630,6 +2638,9 @@ class ObPLSignalStmt : public ObPLStmt int ob_error_code_; bool is_signal_null_; // In Oracle mode, RAISE; statement without specifying an exception name, in this case, the current exception needs to be thrown bool is_resignal_stmt_; + // Pre-formatted user error message preserved across compile->runtime for + // deferred errors. Memory is owned by the PL function's allocator. + common::ObString user_msg_; }; class ObPLCallStmt : public ObPLStmt diff --git a/src/sql/ob_spi.cpp b/src/sql/ob_spi.cpp index 8813fbe96..234ef2e20 100644 --- a/src/sql/ob_spi.cpp +++ b/src/sql/ob_spi.cpp @@ -4448,6 +4448,34 @@ int ObSPIService::spi_process_resignal(pl::ObPLExecCtx *ctx, } #undef GET_INTEGER_FROM_OBJ +int ObSPIService::spi_pl_set_user_error_msg(pl::ObPLExecCtx *ctx, + int64_t err_code, + const char *sql_state, + const char *msg, + int64_t msg_len) +{ + int ret = OB_SUCCESS; + UNUSED(ctx); + if (OB_NOT_NULL(msg) && msg_len > 0) { + ObWarningBuffer *wb = common::ob_get_tsi_warning_buffer(); + if (OB_NOT_NULL(wb)) { + // ObWarningBuffer caps each item at WarningItem::STR_LEN, so truncation + // here matches the runtime behaviour of LOG_USER_ERROR. + char buf[ObWarningBuffer::WarningItem::STR_LEN] = {0}; + int64_t copy_len = msg_len < (ObWarningBuffer::WarningItem::STR_LEN - 1) + ? msg_len + : (ObWarningBuffer::WarningItem::STR_LEN - 1); + MEMCPY(buf, msg, copy_len); + buf[copy_len] = '\0'; + wb->set_error(buf, static_cast(err_code)); + if (OB_NOT_NULL(sql_state)) { + wb->set_sql_state(sql_state); + } + } + } + return ret; +} + int ObSPIService::spi_destruct_collection(ObPLExecCtx *ctx, int64_t idx) { int ret = OB_SUCCESS; diff --git a/src/sql/ob_spi.h b/src/sql/ob_spi.h index a634f21b0..822578425 100644 --- a/src/sql/ob_spi.h +++ b/src/sql/ob_spi.h @@ -752,6 +752,17 @@ class ObSPIService const char *resignal_sql_state, bool is_signal); + // Populate the current thread-local warning buffer with a pre-formatted + // user error message captured at PL resolve time. Used by deferred + // OB_ERR_BAD_TABLE (and similar) signals emitted from trigger/SP bodies so + // that the runtime error text matches the resolver-time text (which + // contains object names) instead of the bare error template. + static int spi_pl_set_user_error_msg(pl::ObPLExecCtx *ctx, + int64_t err_code, + const char *sql_state, + const char *msg, + int64_t msg_len); + static int acquire_spi_conn(ObMySQLProxy &sql_proxy, ObSQLSessionInfo &session_info, observer::ObInnerSQLConnection *&spi_conn);