fix(agent): read JDBC blobs through Blob values

This commit is contained in:
t8y2
2026-07-31 21:41:07 +08:00
parent 486a73ef09
commit ac49b28cab
2 changed files with 76 additions and 8 deletions
@@ -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;
@@ -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":