From ac49b28cab770a5ba938faab0747b8fdd589ac6d Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Fri, 31 Jul 2026 20:51:07 +0800 Subject: [PATCH] fix(agent): read JDBC blobs through Blob values --- .../main/java/com/dbx/agent/JdbcExecutor.java | 19 +++++- .../java/com/dbx/agent/JdbcExecutorTest.java | 65 +++++++++++++++++-- 2 files changed, 76 insertions(+), 8 deletions(-) diff --git a/agents/common/src/main/java/com/dbx/agent/JdbcExecutor.java b/agents/common/src/main/java/com/dbx/agent/JdbcExecutor.java index a7e2898d83..acd69e65f5 100644 --- a/agents/common/src/main/java/com/dbx/agent/JdbcExecutor.java +++ b/agents/common/src/main/java/com/dbx/agent/JdbcExecutor.java @@ -455,9 +455,11 @@ public final class JdbcExecutor { case Types.BINARY: case Types.VARBINARY: case Types.LONGVARBINARY: - case Types.BLOB: value = bytesToHex(rs.getBytes(index)); break; + case Types.BLOB: + value = blobResultValue(rs, index); + break; case Types.SQLXML: value = sqlXmlToString(rs.getSQLXML(index)); break; @@ -474,9 +476,11 @@ public final class JdbcExecutor { case Types.BINARY: case Types.VARBINARY: case Types.LONGVARBINARY: - case Types.BLOB: value = bytesToHex(rs.getBytes(index)); break; + case Types.BLOB: + value = blobResultValue(rs, index); + break; case Types.SQLXML: value = sqlXmlToString(rs.getSQLXML(index)); break; @@ -487,6 +491,17 @@ public final class JdbcExecutor { return rs.wasNull() ? null : value; } + private static Object blobResultValue(ResultSet rs, int index) throws SQLException { + Object value = rs.getObject(index); + if (value instanceof Blob || value instanceof byte[]) { + return normalizeResultValue(value); + } + if (value == null && rs.wasNull()) { + return null; + } + return bytesToHex(rs.getBytes(index)); + } + public static Object normalizeResultValue(Object value) throws SQLException { if (value == null) { return null; diff --git a/agents/common/src/test/java/com/dbx/agent/JdbcExecutorTest.java b/agents/common/src/test/java/com/dbx/agent/JdbcExecutorTest.java index 45955dc00c..ca659389c8 100644 --- a/agents/common/src/test/java/com/dbx/agent/JdbcExecutorTest.java +++ b/agents/common/src/test/java/com/dbx/agent/JdbcExecutorTest.java @@ -20,21 +20,60 @@ import java.util.concurrent.atomic.AtomicInteger; import javax.sql.rowset.serial.SerialBlob; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; class JdbcExecutorTest { @Test - void stringResultValueFormatsBlobWithoutUsingStringConversion() throws Exception { + void blobResultValuesPreferBlobObjectsWhenGetBytesFails() throws Exception { ResultSet rs = resultSet( - new byte[]{0x01, 0x2A, (byte) 0xFF}, - () -> { - throw new AssertionError("BLOB columns should not be read with getString"); - } + new SerialBlob(new byte[]{0x01, 0x2A, (byte) 0xFF}), + null, + null, + false, + new SQLException("ORA-00932: expected NUMBER got BLOB") ); + assertEquals("0x012aff", JdbcExecutor.INSTANCE.defaultResultValue(rs, 1, Types.BLOB)); assertEquals("0x012aff", JdbcExecutor.stringResultValue(rs, 1, Types.BLOB)); } + @Test + void blobResultValuesFallBackToBytesWhenObjectIsUnavailable() throws Exception { + ResultSet rs = resultSet(null, new byte[]{0x0A, 0x0B}, null, false, null); + + assertEquals("0x0a0b", JdbcExecutor.INSTANCE.defaultResultValue(rs, 1, Types.BLOB)); + assertEquals("0x0a0b", JdbcExecutor.stringResultValue(rs, 1, Types.BLOB)); + } + + @Test + void blobResultValuesPreserveSqlNullWithoutReadingBytes() throws Exception { + ResultSet rs = resultSet( + null, + null, + null, + true, + new SQLException("getBytes should not be called for SQL NULL") + ); + + assertNull(JdbcExecutor.INSTANCE.defaultResultValue(rs, 1, Types.BLOB)); + assertNull(JdbcExecutor.stringResultValue(rs, 1, Types.BLOB)); + } + + @Test + void binaryResultValuesContinueReadingBytesDirectly() throws Exception { + ResultSet rs = resultSet( + new SerialBlob(new byte[]{0x01}), + new byte[]{0x02}, + null, + false, + null + ); + + assertEquals("0x02", JdbcExecutor.INSTANCE.defaultResultValue(rs, 1, Types.BINARY)); + assertEquals("0x02", JdbcExecutor.stringResultValue(rs, 1, Types.BINARY)); + } + @Test void defaultResultValueReadsClobColumnsAsText() throws Exception { ResultSet rs = resultSet(null, () -> "hello clob"); @@ -262,12 +301,26 @@ class JdbcExecutorTest { } private static ResultSet resultSet(Object objectValue, StringSupplier stringSupplier, boolean wasNull) { + byte[] bytesValue = objectValue instanceof byte[] ? (byte[]) objectValue : null; + return resultSet(objectValue, bytesValue, stringSupplier, wasNull, null); + } + + private static ResultSet resultSet( + Object objectValue, + byte[] bytesValue, + StringSupplier stringSupplier, + boolean wasNull, + SQLException getBytesFailure + ) { InvocationHandler handler = (Object unused, Method method, Object[] args) -> { switch (method.getName()) { case "getObject": return objectValue; case "getBytes": - return objectValue instanceof byte[] ? objectValue : null; + if (getBytesFailure != null) { + throw getBytesFailure; + } + return bytesValue; case "getString": return stringSupplier == null ? null : stringSupplier.get(); case "wasNull":