#8793·citus

PG19 Beta3: fix nested psql COPY transaction in intermediate_results

Author: ihalatciCreated Aug 24, 2026Updated Aug 24, 2026
Labelstype/bug

Problem

The PostgreSQL 19 Beta3 integration run exposes a distinct failure in intermediate_results: an outer COPY (...) TO PROGRAM starts a child psql using one -c argument containing:

sql
BEGIN;
COPY squares FROM STDIN WITH (FORMAT binary);
CREATE TABLE squares AS SELECT * FROM read_intermediate_result('squares', 'binary') AS res (x int, x2 int);
END;

The outer COPY streams binary rows to the child process. Under PostgreSQL 19 Beta3, the child psql exits with status 2, the outer program reports failure, and the squares table is absent.

Evidence

Observed in the PostgreSQL 19 Beta3 integration run and artifacts:

  • child command: one psql -c containing BEGIN; COPY ... FROM STDIN; CREATE TABLE ...; END;
  • child exit status: 2
  • outer COPY (...) TO PROGRAM: fails because the child program failed
  • postcondition: squares does not exist

Why this is separate from #8781

#8781 handles psql's new draining behavior after an inline COPY ... FROM STDIN is rejected before COPY mode starts. This failure occurs in a nested child psql process whose COPY succeeds far enough to consume the outer program's stdin, but whose single multi-statement -c invocation no longer preserves the intended COPY-then-CREATE execution. The two failures may originate from the same upstream psql transition, but their failure boundaries and fixes are different.

Proposed minimal fix

Keep one child psql process and transaction atomicity, but invoke it as:

psql -1 -c "COPY squares FROM STDIN WITH (FORMAT binary)" -c "CREATE TABLE squares AS ..."

This lets COPY consume the program stdin and then executes CREATE TABLE in the same transaction. Prefer this unconditional cross-version form if it works on PostgreSQL 16-19, without version gates or alternative expected-output files.

Acceptance criteria

  • intermediate_results passes on PostgreSQL 19 Beta3, Beta2, and PostgreSQL 18.4.
  • The owning full schedule passes on those versions.
  • Existing transaction atomicity and assertions remain intact.
  • Scope stays limited to intermediate_results SQL and canonical expected output unless validation proves otherwise.