Report a machine-readable summary; stop failing on no-result statements
Two coupled changes: the summary is what a caller should read instead of
parsing prose, and the statement fix is what makes a non-zero exit code
usable at all.
Summary: emit one "@summary {json}" line carrying status, row count and
elapsed ms. Deliberately on stderr — migration-mode stdout carries the
progress output pipekit streams to its live log, and query-mode stdout
must stay pure CSV, so structured data on stdout would corrupt one or the
other. Previously the row count reached the caller only because a \r
progress tick and a trailing " rows written" happened to render on the
same line, which pipekit then matched with three different regexes.
Statements: query mode used executeQuery(), which raises when a statement
returns no result set — Postgres "No results were returned by the query",
SQL Server "The statement did not return a result set". DDL, INSERT,
DELETE and TRUNCATE all hit this despite succeeding, so pipekit carries a
_BENIGN_EXCEPTION_SUBSTRINGS allowlist to tell a working TRUNCATE from a
real failure. That made --strict unusable: it would have failed every
merge, staging DDL and hook. Now execute() + getUpdateCount() treats them
as the successes they are and reports rows affected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
61b3967a36
commit
94cd89899a
@ -276,18 +276,34 @@ public class jrunner {
|
|||||||
stmt = scon.createStatement();
|
stmt = scon.createStatement();
|
||||||
stmt.setFetchSize(10000);
|
stmt.setFetchSize(10000);
|
||||||
tsStart = Timestamp.from(Instant.now());
|
tsStart = Timestamp.from(Instant.now());
|
||||||
|
summaryStart = tsStart;
|
||||||
if (!queryMode) {
|
if (!queryMode) {
|
||||||
System.out.println(tsStart);
|
System.out.println(tsStart);
|
||||||
}
|
}
|
||||||
rs = stmt.executeQuery(sq);
|
// execute() rather than executeQuery(): a statement that returns no
|
||||||
//while (rs.next()) {
|
// result set (DDL, INSERT, DELETE, TRUNCATE, MERGE) is a normal,
|
||||||
// System.out.println(rs.getString("x"));
|
// successful outcome, but executeQuery() raises for it — Postgres
|
||||||
//}
|
// "No results were returned by the query", SQL Server "The
|
||||||
|
// statement did not return a result set". Callers had to allowlist
|
||||||
|
// those messages to tell a real failure from a working TRUNCATE,
|
||||||
|
// which made a non-zero exit code impossible to adopt.
|
||||||
|
boolean hasResultSet = stmt.execute(sq);
|
||||||
|
if (!hasResultSet) {
|
||||||
|
int updated = stmt.getUpdateCount();
|
||||||
|
if (!queryMode) {
|
||||||
|
System.out.println("------------no result set---------------------------------");
|
||||||
|
System.out.println("rows affected: " + updated);
|
||||||
|
}
|
||||||
|
emitSummary("ok", updated >= 0 ? updated : -1, summaryElapsedMs());
|
||||||
|
closeQuietly(scon, dcon, queryMode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rs = stmt.getResultSet();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
System.out.println("issue retrieving rows from source:");
|
System.out.println("issue retrieving rows from source:");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------build meta---------------------------------------------------------------
|
//---------------------------------------build meta---------------------------------------------------------------
|
||||||
try {
|
try {
|
||||||
@ -355,6 +371,7 @@ public class jrunner {
|
|||||||
// onto the last progress tick; the trailing " rows written" makes
|
// onto the last progress tick; the trailing " rows written" makes
|
||||||
// it parseable.
|
// it parseable.
|
||||||
System.out.print("\r" + src.rowsWritten());
|
System.out.print("\r" + src.rowsWritten());
|
||||||
|
summaryRows = src.rowsWritten();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
die();
|
die();
|
||||||
@ -397,6 +414,7 @@ public class jrunner {
|
|||||||
}
|
}
|
||||||
cin.endCopy();
|
cin.endCopy();
|
||||||
System.out.print("\r" + rows);
|
System.out.print("\r" + rows);
|
||||||
|
summaryRows = rows;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
die();
|
die();
|
||||||
@ -545,6 +563,7 @@ public class jrunner {
|
|||||||
stmtd = dcon.createStatement();
|
stmtd = dcon.createStatement();
|
||||||
stmtd.executeUpdate(sql);
|
stmtd.executeUpdate(sql);
|
||||||
System.out.print("\r" + t);
|
System.out.print("\r" + t);
|
||||||
|
summaryRows = t;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
System.out.println(sql);
|
System.out.println(sql);
|
||||||
@ -573,6 +592,7 @@ public class jrunner {
|
|||||||
tsEnd = Timestamp.from(Instant.now());
|
tsEnd = Timestamp.from(Instant.now());
|
||||||
System.out.println(tsStart);
|
System.out.println(tsStart);
|
||||||
System.out.println(tsEnd);
|
System.out.println(tsEnd);
|
||||||
|
emitSummary("ok", summaryRows, summaryElapsedMs());
|
||||||
}
|
}
|
||||||
//long time = Duration.between(tsStart, tsEnd).toMillis();
|
//long time = Duration.between(tsStart, tsEnd).toMillis();
|
||||||
//System.out.println("time elapsed: " + time);
|
//System.out.println("time elapsed: " + time);
|
||||||
@ -785,9 +805,52 @@ public class jrunner {
|
|||||||
private static boolean strictExit = false;
|
private static boolean strictExit = false;
|
||||||
|
|
||||||
private static void die() {
|
private static void die() {
|
||||||
|
emitSummary("error", summaryRows, summaryElapsedMs());
|
||||||
System.exit(strictExit ? 1 : 0);
|
System.exit(strictExit ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Row count for the summary. Migration has three write paths (SQLServer
|
||||||
|
// bulk copy, Postgres COPY, batched INSERT) each with its own counter, so
|
||||||
|
// whichever runs records the total here.
|
||||||
|
private static long summaryRows = -1;
|
||||||
|
private static Timestamp summaryStart = null;
|
||||||
|
|
||||||
|
private static long summaryElapsedMs() {
|
||||||
|
if (summaryStart == null) return -1;
|
||||||
|
return Duration.between(summaryStart.toInstant(), Instant.now()).toMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A single machine-readable line for the calling process.
|
||||||
|
//
|
||||||
|
// Deliberately on stderr: migration-mode stdout carries human-readable
|
||||||
|
// progress that pipekit streams to its live log, and query-mode stdout must
|
||||||
|
// stay pure CSV, so anything structured on stdout would corrupt one or the
|
||||||
|
// other. stderr is already captured in full by the caller.
|
||||||
|
//
|
||||||
|
// Replaces having to regex a row count out of prose — the count previously
|
||||||
|
// reached the caller only because a \r progress tick and a trailing
|
||||||
|
// " rows written" happened to render on the same line.
|
||||||
|
private static void emitSummary(String status, long rows, long ms) {
|
||||||
|
StringBuilder sb = new StringBuilder("@summary {\"status\":\"");
|
||||||
|
sb.append(status).append("\"");
|
||||||
|
if (rows >= 0) sb.append(",\"rows\":").append(rows);
|
||||||
|
if (ms >= 0) sb.append(",\"ms\":").append(ms);
|
||||||
|
sb.append("}");
|
||||||
|
System.err.println(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Best-effort connection close for the early return taken when a statement
|
||||||
|
// produced no result set. A failure here cannot invalidate work that the
|
||||||
|
// server already committed, so it is reported but not fatal.
|
||||||
|
private static void closeQuietly(Connection scon, Connection dcon, boolean queryMode) {
|
||||||
|
try {
|
||||||
|
if (scon != null) scon.close();
|
||||||
|
if (!queryMode && dcon != null) dcon.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
System.err.println("issue closing connections: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static String escapeTSV(String value) {
|
private static String escapeTSV(String value) {
|
||||||
return value.replaceAll("\t", " ").replaceAll("\n", " ").replaceAll("\r", " ");
|
return value.replaceAll("\t", " ").replaceAll("\n", " ").replaceAll("\r", " ");
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user