Skip to content

Commit ad6a6b9

Browse files
committed
fix(sql-execute): fall back to exception type when error message is null
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.
1 parent 27e7baa commit ad6a6b9

2 files changed

Lines changed: 23 additions & 1 deletion

File tree

‎data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,9 @@ private Map<String, Object> executeSqlQuery(OverAllState state, Integer currentS
152152
.flatMapMany(executed -> Flux.concat(baseResultResponses(executed.resultSet()),
153153
chartUpdateResponses(state, executed.resultSet())))
154154
.onErrorResume(e -> {
155-
String errorMessage = e.getMessage();
155+
// NPE / StackOverflowError and several JDBC wrappers carry a null message;
156+
// fall back to the exception type so the user never sees the literal "null"
157+
String errorMessage = StringUtils.isNotBlank(e.getMessage()) ? e.getMessage() : e.getClass().getName();
156158
log.error("SQL execution failed - SQL as follows: \n {} \n ", sqlQuery, e);
157159
result.put(SQL_REGENERATE_REASON, SqlRetryDto.sqlExecute(errorMessage));
158160
return Flux.just(ChatResponseUtil.createResponse("SQL执行失败: " + errorMessage));

‎data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/workflow/node/sql/SqlExecuteNodeTest.java‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,26 @@ void apply_sqlExecutionError_setsRetryReason() throws Exception {
237237
assertFalse(execution.finalResult().containsKey(PLAN_CURRENT_STEP));
238238
}
239239

240+
@Test
241+
void apply_sqlExecutionErrorWithoutMessage_doesNotLeakNullIntoUserFacingText() throws Exception {
242+
OverAllState state = createTestState();
243+
setupBasicState(state);
244+
setupBasicMocks();
245+
246+
// NPE / StackOverflowError and several JDBC wrappers carry a null message
247+
when(accessor.executeSqlAndReturnObject(any(), any())).thenThrow(new RuntimeException((String) null));
248+
249+
NodeExecution execution = execute(sqlExecuteNode.apply(state), SQL_EXECUTE_NODE_OUTPUT);
250+
251+
String streamed = execution.streamedText();
252+
assertFalse(streamed.contains("null"),
253+
"user-facing text must not leak the literal 'null', was: " + streamed);
254+
assertTrue(streamed.contains("SQL执行失败"), "failure notice must still be emitted, was: " + streamed);
255+
SqlRetryDto retryReason = (SqlRetryDto) execution.finalResult().get(SQL_REGENERATE_REASON);
256+
assertNotNull(retryReason);
257+
assertTrue(retryReason.sqlExecuteFail());
258+
}
259+
240260
@Test
241261
void apply_connectionFailure_throwsException() throws Exception {
242262
OverAllState state = createTestState();

0 commit comments

Comments
 (0)