fix(dameng): handle DBMS_OUTPUT messages safely

This commit is contained in:
zipg authored and GitHub committed 2026-08-03 15:07:18 +08:00
1 parent 5325c42637
commit a537565875
4 files changed
+430 -13

No files matched your search

@@ -87,6 +87,21 @@ public final class JdbcExecutor {
Integer fetchSize,
int timeoutSecs,
ResultValueReader valueReader
) {
return execute(conn, sql, schema, setSchemaSql, resetSchemaSql, maxRows, fetchSize, timeoutSecs, valueReader, StatementMessageReader.NONE);
}
public QueryResult execute(
Connection conn,
String sql,
String schema,
Function<String, String> setSchemaSql,
Supplier<String> resetSchemaSql,
int maxRows,
Integer fetchSize,
int timeoutSecs,
ResultValueReader valueReader,
StatementMessageReader statementMessageReader
) {
return unchecked(() -> {
String trimmedSql = trimSql(sql);
@@ -122,7 +137,7 @@ public final class JdbcExecutor {
false
);
}
return withStatementWarnings(result, stmt);
return withStatementMessages(result, stmt, effectiveMaxRows, statementMessageReader);
} finally {
activeStatements.remove(stmt);
}
@@ -198,7 +213,19 @@ public final class JdbcExecutor {
QueryPageOptions options,
ResultValueReader valueReader
) {
return executePage(conn, sql, schema, setSchemaSql, () -> "", options, valueReader, sessions);
return executePage(conn, sql, schema, setSchemaSql, () -> "", options, valueReader, StatementMessageReader.NONE, sessions);
}
public QueryPageResult executePage(
Connection conn,
String sql,
String schema,
Function<String, String> setSchemaSql,
QueryPageOptions options,
ResultValueReader valueReader,
StatementMessageReader statementMessageReader
) {
return executePage(conn, sql, schema, setSchemaSql, () -> "", options, valueReader, statementMessageReader, sessions);
}
public QueryPageResult executePage(
@@ -210,7 +237,7 @@ public final class JdbcExecutor {
QueryPageOptions options,
ResultValueReader valueReader
) {
return executePage(conn, sql, schema, setSchemaSql, resetSchemaSql, options, valueReader, sessions);
return executePage(conn, sql, schema, setSchemaSql, resetSchemaSql, options, valueReader, StatementMessageReader.NONE, sessions);
}
public QueryPageResult startTableRead(
@@ -221,7 +248,7 @@ public final class JdbcExecutor {
QueryPageOptions options,
ResultValueReader valueReader
) {
return executePage(conn, sql, schema, setSchemaSql, () -> "", options, valueReader, tableReadSessions);
return executePage(conn, sql, schema, setSchemaSql, () -> "", options, valueReader, StatementMessageReader.NONE, tableReadSessions);
}
public QueryPageResult startTableRead(
@@ -233,7 +260,7 @@ public final class JdbcExecutor {
QueryPageOptions options,
ResultValueReader valueReader
) {
return executePage(conn, sql, schema, setSchemaSql, resetSchemaSql, options, valueReader, tableReadSessions);
return executePage(conn, sql, schema, setSchemaSql, resetSchemaSql, options, valueReader, StatementMessageReader.NONE, tableReadSessions);
}
private QueryPageResult executePage(
@@ -244,6 +271,7 @@ public final class JdbcExecutor {
Supplier<String> resetSchemaSql,
QueryPageOptions options,
ResultValueReader valueReader,
StatementMessageReader statementMessageReader,
ConcurrentHashMap<String, QuerySession> targetSessions
) {
return unchecked(() -> {
@@ -267,13 +295,32 @@ public final class JdbcExecutor {
long elapsed = System.currentTimeMillis() - start;
if (!hasResultSet) {
int updateCount = stmt.getUpdateCount();
activeStatements.remove(stmt);
stmt.close();
return new QueryPageResult(
QueryResult result = new QueryResult(
Collections.emptyList(),
Collections.emptyList(),
updateCount >= 0 ? updateCount : 0,
elapsed
elapsed,
false
);
if (statementMessageReader != StatementMessageReader.NONE) {
result = withStatementMessages(
result,
stmt,
Math.max(options.getMaxRows(), 1),
statementMessageReader
);
}
activeStatements.remove(stmt);
stmt.close();
return new QueryPageResult(
result.getColumns(),
result.getColumn_types(),
result.getRows(),
result.getAffected_rows(),
result.getExecution_time_ms(),
result.getTruncated(),
null,
false
);
}
@@ -700,17 +747,28 @@ public final class JdbcExecutor {
}
}
private static QueryResult withStatementWarnings(QueryResult result, Statement stmt) {
private static QueryResult withStatementMessages(
QueryResult result,
Statement stmt,
int maxRows,
StatementMessageReader statementMessageReader
) {
if (!result.getColumns().isEmpty() || !result.getRows().isEmpty()) {
return result;
}
List<List<Object>> rows = new ArrayList<>();
int effectiveMaxRows = Math.max(maxRows, 1);
boolean truncated = result.getTruncated();
try {
Set<SQLWarning> seen = Collections.newSetFromMap(new IdentityHashMap<>());
for (SQLWarning warning = stmt.getWarnings(); warning != null && seen.add(warning); warning = warning.getNextWarning()) {
String message = warning.getMessage();
if (message != null && !message.trim().isEmpty()) {
if (rows.size() >= effectiveMaxRows) {
truncated = true;
break;
}
rows.add(Collections.singletonList(message));
}
}
@@ -720,6 +778,24 @@ public final class JdbcExecutor {
// successfully executed statement into a query failure.
}
try {
List<String> messages = statementMessageReader.read(stmt);
if (messages != null) {
for (String message : messages) {
if (message == null) {
continue;
}
if (rows.size() >= effectiveMaxRows) {
truncated = true;
break;
}
rows.add(Collections.singletonList(message));
}
}
} catch (Exception ignored) {
// Driver-specific informational output is advisory, like SQLWarning.
}
if (rows.isEmpty()) {
return result;
}
@@ -729,7 +805,7 @@ public final class JdbcExecutor {
rows,
result.getAffected_rows(),
result.getExecution_time_ms(),
result.getTruncated()
truncated
);
}
@@ -800,6 +876,14 @@ public final class JdbcExecutor {
Object read(ResultSet rs, int index, int sqlType) throws SQLException;
}
/** Reads driver-specific informational output that is not exposed as {@link SQLWarning}. */
@FunctionalInterface
public interface StatementMessageReader {
StatementMessageReader NONE = statement -> Collections.emptyList();
List<String> read(Statement statement) throws SQLException;
}
/**
* Optional extension of {@link ResultValueReader} that exposes the JDBC
* {@code getColumnTypeName} alongside the SQL type code, allowing per-driver