Skip to content

Skip the COPY header when scanning for row (1.5)#537

Merged
staticlibs merged 1 commit into
duckdb:v1.5-variegatafrom
staticlibs:header_skip_15
Jul 25, 2026
Merged

Skip the COPY header when scanning for row (1.5)#537
staticlibs merged 1 commit into
duckdb:v1.5-variegatafrom
staticlibs:header_skip_15

Conversation

@staticlibs

Copy link
Copy Markdown
Member

This is a backport of the PR #532 to v1.5-variegata stable branch.

PostgresBinaryFileReader::FindLastCompleteRow started scanning at byte zero of the first buffer, so it read the 19 byte binary COPY file header as if it were a tuple: 'PG' became a field count of 20551 and 'COPY' became a field length of 1129270361. No row could ever be completed, the scan returned 0, and the reader threw

Postgres binary file contains a row larger than the read buffer

for any file that did not fit in a single buffer. With the default 32MB buffer that rejected every postgres binary file larger than 32MB, regardless of how small its rows were.

Skip the header while scanning for row boundaries on the first fill, but keep it in the buffer so CheckHeader can still validate it.

When no complete row fits alongside the header, hand the parser just the header and carry the partial row over to the next fill, where the whole buffer is available for it. Otherwise a row larger than buffer_size - 19 but no larger than buffer_size would still be rejected even though it fits. CheckHeader's bound becomes '>' so that a buffer holding exactly the header is accepted.

Files that fit in one buffer were unaffected, which is why the existing tests - all of which use tiny fixtures - did not catch this.

This is a backport of the PR duckdb#532 to `v1.5-variegata` stable branch.

PostgresBinaryFileReader::FindLastCompleteRow started scanning at byte
zero of the first buffer, so it read the 19 byte binary COPY file header
as if it were a tuple: 'PG' became a field count of 20551 and 'COPY'
became a field length of 1129270361. No row could ever be completed, the
scan returned 0, and the reader threw

  Postgres binary file contains a row larger than the read buffer

for any file that did not fit in a single buffer. With the default 32MB
buffer that rejected every postgres binary file larger than 32MB,
regardless of how small its rows were.

Skip the header while scanning for row boundaries on the first fill, but
keep it in the buffer so CheckHeader can still validate it.

When no complete row fits alongside the header, hand the parser just the
header and carry the partial row over to the next fill, where the whole
buffer is available for it. Otherwise a row larger than buffer_size - 19
but no larger than buffer_size would still be rejected even though it
fits. CheckHeader's bound becomes '>' so that a buffer holding exactly
the header is accepted.

Files that fit in one buffer were unaffected, which is why the existing
tests - all of which use tiny fixtures - did not catch this.
@staticlibs
staticlibs merged commit 7c570c4 into duckdb:v1.5-variegata Jul 25, 2026
8 of 12 checks passed
@staticlibs
staticlibs deleted the header_skip_15 branch July 25, 2026 11:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant