From cbaccb3a3ab88beb90863f838e4f8e75ecd6abe9 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Sat, 5 Sep 2026 14:38:03 +0800 Subject: [PATCH] fix(iris): raise jdbc query prefetch for large results Refs: #7953 --- .../java/com/dbx/agent/iris/IrisAgent.java | 33 +++++++ .../iris/IrisAgentQueryPrefetchTest.java | 86 +++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 agents/drivers/iris/src/test/java/com/dbx/agent/iris/IrisAgentQueryPrefetchTest.java diff --git a/agents/drivers/iris/src/main/java/com/dbx/agent/iris/IrisAgent.java b/agents/drivers/iris/src/main/java/com/dbx/agent/iris/IrisAgent.java index 81c5016816..155790ccc5 100644 --- a/agents/drivers/iris/src/main/java/com/dbx/agent/iris/IrisAgent.java +++ b/agents/drivers/iris/src/main/java/com/dbx/agent/iris/IrisAgent.java @@ -2,11 +2,14 @@ package com.dbx.agent.iris; import com.dbx.agent.ConfiguredJdbcAgent; import com.dbx.agent.ColumnInfo; +import com.dbx.agent.ConnectParams; import com.dbx.agent.JdbcAgentProfile; import com.dbx.agent.JdbcExecutor; import com.dbx.agent.MultiSessionJsonRpcServer; import com.dbx.agent.StandardJdbcMetadata; +import com.intersystems.jdbc.IRISConnection; + import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; @@ -32,6 +35,36 @@ public final class IrisAgent extends ConfiguredJdbcAgent { super(IRIS_PROFILE); } + // The InterSystems driver's default query prefetch block (32768 bytes) means + // one server round trip per ~32KB of result data. Large result sets over + // high-latency links then spend most of their wall time waiting on the + // network instead of transferring rows, while other clients that negotiate + // larger fetch blocks finish several times faster. Statement.setFetchSize + // does not help here: intersystems-jdbc stores that value but never sends + // it to the server. The connection-level query prefetch size is the only + // effective knob, so raise it for every DBX IRIS connection. This is + // advisory: servers that reject the value keep the driver default, and the + // driver only round-trips when the value actually changes. + static final int IRIS_QUERY_PREFETCH_SIZE = 262144; + + @Override + protected void afterConnect(ConnectParams params, Connection connection) { + super.afterConnect(params, connection); + applyQueryPrefetchSize(connection); + } + + static void applyQueryPrefetchSize(Connection connection) { + if (!(connection instanceof IRISConnection)) { + return; + } + try { + ((IRISConnection) connection).setQueryPrefetchSize(IRIS_QUERY_PREFETCH_SIZE); + } catch (Exception ignored) { + // Performance tuning only; keep the connection usable with the + // driver default when a server refuses the larger prefetch. + } + } + @Override protected Object resultValue(ResultSet rs, int index, int sqlType) { if (sqlType == Types.OTHER) { diff --git a/agents/drivers/iris/src/test/java/com/dbx/agent/iris/IrisAgentQueryPrefetchTest.java b/agents/drivers/iris/src/test/java/com/dbx/agent/iris/IrisAgentQueryPrefetchTest.java new file mode 100644 index 0000000000..bc318d7d69 --- /dev/null +++ b/agents/drivers/iris/src/test/java/com/dbx/agent/iris/IrisAgentQueryPrefetchTest.java @@ -0,0 +1,86 @@ +package com.dbx.agent.iris; + +import com.dbx.agent.ConnectParams; +import com.intersystems.jdbc.IRISConnection; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class IrisAgentQueryPrefetchTest { + /** Records setQueryPrefetchSize calls without touching driver internals. */ + static final class RecordingIrisConnection extends IRISConnection { + final List prefetchSizes = new ArrayList<>(); + + @Override + public void setQueryPrefetchSize(int size) { + prefetchSizes.add(size); + } + } + + static final class FailingIrisConnection extends IRISConnection { + @Override + public void setQueryPrefetchSize(int size) throws SQLException { + throw new SQLException("server rejected prefetch size"); + } + } + + @Test + void afterConnectAppliesLargerQueryPrefetch() { + RecordingIrisConnection connection = new RecordingIrisConnection(); + + new IrisAgent().afterConnect(new ConnectParams(), connection); + + assertEquals(Collections.singletonList(IrisAgent.IRIS_QUERY_PREFETCH_SIZE), connection.prefetchSizes); + } + + @Test + void afterConnectKeepsNonIrisConnectionsUntouched() { + Connection connection = proxyConnection(); + + assertDoesNotThrow(() -> new IrisAgent().afterConnect(new ConnectParams(), connection)); + } + + @Test + void prefetchFailureDoesNotBreakConnect() { + FailingIrisConnection connection = new FailingIrisConnection(); + + assertDoesNotThrow(() -> new IrisAgent().afterConnect(new ConnectParams(), connection)); + } + + @Test + void directApplyIsBoundedToIrisConnections() { + // A plain java.sql.Connection (for example a test or wrapper instance) + // must not be cast or fail when the prefetch tuning runs. + assertDoesNotThrow(() -> IrisAgent.applyQueryPrefetchSize(proxyConnection())); + } + + private static Connection proxyConnection() { + return (Connection) Proxy.newProxyInstance( + IrisAgentQueryPrefetchTest.class.getClassLoader(), + new Class[] { Connection.class }, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) { + Class returnType = method.getReturnType(); + if (returnType == boolean.class) { + return false; + } + if (returnType.isPrimitive() && returnType != void.class) { + return 0; + } + return null; + } + } + ); + } +}