From ad6a6b94b27c21a3245e3187ea76afbe2518f71c Mon Sep 17 00:00:00 2001 From: ydflow <314143294+ydflow@users.noreply.github.com> Date: Tue, 22 Sep 2026 22:47:32 +0800 Subject: [PATCH] fix(sql-execute): fall back to exception type when error message is null MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SqlExecuteNode#executeSqlQuery reads e.getMessage() straight into both the user-facing streamed text ("SQL执行失败: " + errorMessage) and SqlRetryDto.sqlExecute(). NPE, StackOverflowError and several JDBC driver wrappers carry a null message, so the UI rendered the literal "SQL执行失败: null" and the retry reason stored null. Fall back to the exception class name when the message is blank, so the user always sees an actionable reason. The logged stack trace is unchanged. Add a regression test that throws a message-less RuntimeException and asserts no "null" leaks into the streamed text while the failure notice and retry reason are still produced. --- .../workflow/node/SqlExecuteNode.java | 4 +++- .../workflow/node/sql/SqlExecuteNodeTest.java | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java index 91c760a5c..90eb2ed36 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java @@ -152,7 +152,9 @@ private Map executeSqlQuery(OverAllState state, Integer currentS .flatMapMany(executed -> Flux.concat(baseResultResponses(executed.resultSet()), chartUpdateResponses(state, executed.resultSet()))) .onErrorResume(e -> { - String errorMessage = e.getMessage(); + // NPE / StackOverflowError and several JDBC wrappers carry a null message; + // fall back to the exception type so the user never sees the literal "null" + String errorMessage = StringUtils.isNotBlank(e.getMessage()) ? e.getMessage() : e.getClass().getName(); log.error("SQL execution failed - SQL as follows: \n {} \n ", sqlQuery, e); result.put(SQL_REGENERATE_REASON, SqlRetryDto.sqlExecute(errorMessage)); return Flux.just(ChatResponseUtil.createResponse("SQL执行失败: " + errorMessage)); diff --git a/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/workflow/node/sql/SqlExecuteNodeTest.java b/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/workflow/node/sql/SqlExecuteNodeTest.java index 0b741b8bc..547eb33ff 100644 --- a/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/workflow/node/sql/SqlExecuteNodeTest.java +++ b/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/workflow/node/sql/SqlExecuteNodeTest.java @@ -237,6 +237,26 @@ void apply_sqlExecutionError_setsRetryReason() throws Exception { assertFalse(execution.finalResult().containsKey(PLAN_CURRENT_STEP)); } + @Test + void apply_sqlExecutionErrorWithoutMessage_doesNotLeakNullIntoUserFacingText() throws Exception { + OverAllState state = createTestState(); + setupBasicState(state); + setupBasicMocks(); + + // NPE / StackOverflowError and several JDBC wrappers carry a null message + when(accessor.executeSqlAndReturnObject(any(), any())).thenThrow(new RuntimeException((String) null)); + + NodeExecution execution = execute(sqlExecuteNode.apply(state), SQL_EXECUTE_NODE_OUTPUT); + + String streamed = execution.streamedText(); + assertFalse(streamed.contains("null"), + "user-facing text must not leak the literal 'null', was: " + streamed); + assertTrue(streamed.contains("SQL执行失败"), "failure notice must still be emitted, was: " + streamed); + SqlRetryDto retryReason = (SqlRetryDto) execution.finalResult().get(SQL_REGENERATE_REASON); + assertNotNull(retryReason); + assertTrue(retryReason.sqlExecuteFail()); + } + @Test void apply_connectionFailure_throwsException() throws Exception { OverAllState state = createTestState();