From a1c9ea26ce13c6c76dd1a545a3af387e8fded368 Mon Sep 17 00:00:00 2001 From: Paul Trowbridge Date: Wed, 17 Jun 2026 15:37:47 -0400 Subject: [PATCH] fix: stream PostgreSQL source (migration mode) instead of buffering it all MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setFetchSize(10000) is a no-op on the PostgreSQL JDBC driver while autoCommit is true — the driver loads the entire ResultSet into memory, OOM/GC-thrashing on large source tables (a pg->SQL Server pull pinned the box: 4GB heap, swap full, 0 rows written). PG only uses a server-side cursor when autoCommit is false AND fetchSize > 0. Set the source connection to manual commit ONLY in migration mode: the migration source is read-only so never committing is harmless. Query mode is excluded on purpose — callers (pipekit's run_dest_sql) run committed DDL/DML through query mode, and autoCommit=false would roll those back on close. Co-Authored-By: Claude Opus 4.8 --- jrunner/src/main/java/jrunner/jrunner.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/jrunner/src/main/java/jrunner/jrunner.java b/jrunner/src/main/java/jrunner/jrunner.java index 53a0a87..f786052 100644 --- a/jrunner/src/main/java/jrunner/jrunner.java +++ b/jrunner/src/main/java/jrunner/jrunner.java @@ -208,6 +208,15 @@ public class jrunner { } try { scon = DriverManager.getConnection(scu, scn, scp); + // Migration mode only: PostgreSQL ignores setFetchSize unless autoCommit + // is false — without this it buffers the ENTIRE result set into memory + // (OOM on big tables). The migration source is read-only, so never + // committing is harmless. Do NOT do this in query mode: callers run + // committed DDL/DML through query mode, and autoCommit=false would roll + // those statements back on connection close. + if (!queryMode) { + scon.setAutoCommit(false); + } } catch (SQLException e) { System.out.println("issue connecting to source:"); e.printStackTrace(); @@ -303,6 +312,7 @@ public class jrunner { for (int i = 1; i <= cols; i++){ switch (dtn[i].toUpperCase()){ case "VARCHAR": + case "NVARCHAR": nc = rs.getString(i); if (rs.wasNull() || nc == null) { nc = "NULL"; @@ -313,6 +323,7 @@ public class jrunner { nc = "'" + nc + "'"; break; case "TEXT": + case "NTEXT": nc = rs.getString(i); if (rs.wasNull() || nc == null) { nc = "NULL"; @@ -323,6 +334,7 @@ public class jrunner { nc = "'" + nc + "'"; break; case "CHAR": + case "NCHAR": nc = rs.getString(i); if (rs.wasNull() || nc == null) { nc = "NULL"; @@ -333,6 +345,7 @@ public class jrunner { nc = "'" + nc + "'"; break; case "CLOB": + case "NCLOB": nc = rs.getString(i); if (rs.wasNull() || nc == null) { nc = "NULL";