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();