Skip to content

fix(largeobject): correct BlobInputStream mark/reset position and add efficient skip - #4204

Merged
vlsi merged 3 commits into
pgjdbc:masterfrom
vlsi:feat/blobinputstream-skip-reset
Jun 26, 2026
Merged

vlsi merged 3 commits into
pgjdbc:masterfrom
vlsi:feat/blobinputstream-skip-reset

Conversation

@vlsi

@vlsi vlsi commented Jun 19, 2026

Copy link
Copy Markdown
Member

Why

What

  • Initialise the stream position lazily from the LargeObject's current position (lo_tell64, or lo_tell below 9.3) and make the read limit relative to that position.
  • Override skip(long) to seek with lo_lseek/lo_lseek64; skips that stay within the current buffer advance the buffer pointer without a round-trip. Large objects are sparse, so skipping past the end is allowed and later reads return -1, as the InputStream.skip contract permits.
  • Select the 64-bit fastpath functions by server version through the new Fastpath#getServerVersionNum and LargeObject#supports64BitOffsets, rather than calling lo_tell64 and recovering from the failure. Older servers pay no extra round-trip, and the public tell64 semantics are unchanged.

This takes over and reworks #3148 by @OrangeDog, whose commits are credited via Co-authored-by. Differences from that PR:

  • version-based dispatch instead of try { tell64 } catch { tell }, which removes the extra round-trip on pre-9.3 servers that the original review flagged;
  • drop the castNonNull calls — assigning the field to a local satisfies the nullness checker;
  • simplify the skip-past-max test: the original asserted that a second close() throws, but LargeObject.close() is idempotent, so the assertion could not hold.

Closes #3144
Closes #3149

How to verify

./gradlew :postgresql:test --tests "org.postgresql.test.jdbc2.BlobTest"

All 27 BlobTest cases pass locally against PostgreSQL 18; autostyleCheck, checkstyleMain, and checkstyleTest are clean.

@vlsi vlsi mentioned this pull request Jun 19, 2026
9 tasks done
vlsi and others added 3 commits June 26, 2026 09:42
lo_tell64 and lo_lseek64 require PostgreSQL 9.3+. Expose the cached
server version through Fastpath#getServerVersionNum and add
LargeObject#supports64BitOffsets so callers can pick the 64-bit
fastpath functions by version instead of calling them and recovering
from the failure on older servers. The version is negotiated at
connection startup, so the check costs no extra round-trip.
…osition

BlobInputStream assumed the underlying LargeObject was positioned at the
start, so mark() and reset() computed offsets from the wrong origin when
the stream was created from a LargeObject that had already been seeked.
reset() then repositioned to the wrong byte.

Initialise the stream position from the LargeObject's current position
(lo_tell64, or lo_tell on servers older than 9.3) on first use, and make
the read limit relative to that position. The initialisation is deferred
out of the constructor so the failure surfaces as a checked IOException.

Also invalidate the read buffer before seeking in reset(), so a failed
seek cannot leave stale buffered data behind.

Closes pgjdbc#3149

Co-authored-by: James Howe <[email protected]>
InputStream.skip on a BlobInputStream used the default implementation,
which reads and discards the bytes, so skipping a large distance cost a
full read. Override skip(long) to seek with lo_lseek/lo_lseek64, making
the cost independent of the distance.

Large objects are sparse, so skipping past the current end is allowed:
later reads return -1 and the returned count still reflects the
requested distance, as the InputStream.skip contract permits. Skips that
stay within the current buffer advance the buffer pointer without a
round-trip.

Closes pgjdbc#3144

Co-authored-by: James Howe <[email protected]>
@vlsi
vlsi force-pushed the feat/blobinputstream-skip-reset branch from 83c4afe to 070636d Compare June 26, 2026 06:43
@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.08696% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.88%. Comparing base (40b6b07) to head (070636d).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #4204      +/-   ##
============================================
+ Coverage     70.86%   70.88%   +0.02%     
- Complexity     5323     5333      +10     
============================================
  Files           244      244              
  Lines         21932    21973      +41     
  Branches       3685     3692       +7     
============================================
+ Hits          15542    15576      +34     
- Misses         4741     4742       +1     
- Partials       1649     1655       +6     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@vlsi
vlsi merged commit 007d4c2 into pgjdbc:master Jun 26, 2026
17 checks passed
davecramer added a commit to davecramer/pgjdbc that referenced this pull request Jul 6, 2026
Rename the release notes to 2026-07-06 to reflect the release date, and
add the remaining PR links (flushCacheOnDdl pgjdbc#4067, connectExecutor
pgjdbc#4165, reWriteBatchedInserts cap pgjdbc#4207, OID constants pgjdbc#4220, 64-bit LO
pgjdbc#4204, and the refactor entries pgjdbc#4160/pgjdbc#4166/pgjdbc#4215).
davecramer added a commit that referenced this pull request Jul 6, 2026
* docs: add 42.7.12 release changelog

Covers all changes since REL42.7.11: search_path GUC_REPORT cache
invalidation (PG 18+), flushCacheOnDdl, connectExecutor,
classLoaderStrategy, reWriteBatchedInserts protocol-limit cap,
large object 64-bit API, updatable ResultSet search_path fix,
XA autoCommit fix, FIPS trust anchors, SSL key-format auto-detection,
batching improvements, and numerous other bug fixes.

docs: prepare 42.7.13 release notes after 42.7.12 security release

42.7.12 shipped as a security-only release for the silent channel-binding
downgrade (CVE-2026-54291), so the feature and bug-fix notes that had been
drafted for 42.7.12 now belong to 42.7.13.

- bump pgjdbc.version to 42.7.13
- add a published 42.7.12 changelog entry covering CVE-2026-54291
- retitle the drafted maintenance notes from 42.7.12 to 42.7.13 (CHANGELOG.md
  and the website changelog)
- record PR #4248 (LargeObject.close() flushes before marking closed, and
  lo_close no longer masks a flush failure) under 42.7.13

docs: fix PR links and add missing entries in 42.7.13 release notes

Point each changelog entry at the PR that merged the change rather than
at the issue or at the superseded #3400. Add two entries that were
missing: the reWriteBatchedInserts parameterless-INSERT fix (#4207) and
the GitHub release jar/signature fix (#3814). Drop the stray
connectThreadFactory entry from CHANGELOG.md, which connectExecutor
replaced before release.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

docs: credit translators and attribute large-object commits to James Howe

Move the BlobInputStream skip and reset commits to James Howe, who
co-authored them, and add a short contributors note crediting the
Italian, Simplified Chinese, and Russian-review translators.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

docs: add the empty-timestamp fix (#4278) to the 42.7.13 release notes

Reflect PR #4278, merged after the release-notes branch was opened: an
empty timestamp/timestamptz/date value now yields a clear SQLException
instead of ArrayIndexOutOfBoundsException.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* docs: diarize all 42.7.13 changes in CHANGELOG.md

The root CHANGELOG.md [42.7.13] section listed only 20 entries while
about 50 user-facing changes have landed since the 42.7.12 baseline.
Bring it in line with the reviewed website changelog: add the missing
feat/fix/refactor entries and correct PR references.

Newly diarized items include getSchemas() on a hot standby (#4274),
getColumnPrivileges()/CHAR_OCTET_LENGTH (#4231), FIPS PKIX trust
anchors (#4193), SSL key-format auto-detection (#3946), GSS response
timeout, autosave savepoints for SET LOCAL (#4203), BatchResultHandler
on a closed connection (#4187), SQL_TSI_FRAC_SECOND (#4229), the
batching/describe fixes, NumberParser/PGInterval validation,
connectThreadFactory, OID constants, and getPrimaryKeys derivation.

Docs, i18n, build, ci, test, and dependency-bump commits are omitted,
consistent with the file's existing "Notable changes" scope.

* docs: date the 42.7.13 website changelog to its release date

Rename the release notes to 2026-07-06 to reflect the release date, and
add the remaining PR links (flushCacheOnDdl #4067, connectExecutor
#4165, reWriteBatchedInserts cap #4207, OID constants #4220, 64-bit LO
#4204, and the refactor entries #4160/#4166/#4215).

* docs: correct the 42.7.13 commits-by-author counts

Reconcile the "Commits by author" section with git history:
- Vladimir Sitnikov: 100 -> 104 (stale count carried from the 42.7.12 draft)
- Dave Cramer: 4 -> 5, add the missing "maintainer vs committer" (#4234) entry
- add Michael Baumgartner (1 commit), previously omitted

James Howe remains credited for the two co-authored large-object commits.

* remove connectThread as it was replaced by connectExecutor

---------

Co-authored-by: Vladimir Sitnikov <[email protected]>
vlsi added a commit to vlsi/pgjdbc that referenced this pull request Jul 8, 2026
* docs: add 42.7.12 release changelog

Covers all changes since REL42.7.11: search_path GUC_REPORT cache
invalidation (PG 18+), flushCacheOnDdl, connectExecutor,
classLoaderStrategy, reWriteBatchedInserts protocol-limit cap,
large object 64-bit API, updatable ResultSet search_path fix,
XA autoCommit fix, FIPS trust anchors, SSL key-format auto-detection,
batching improvements, and numerous other bug fixes.

docs: prepare 42.7.13 release notes after 42.7.12 security release

42.7.12 shipped as a security-only release for the silent channel-binding
downgrade (CVE-2026-54291), so the feature and bug-fix notes that had been
drafted for 42.7.12 now belong to 42.7.13.

- bump pgjdbc.version to 42.7.13
- add a published 42.7.12 changelog entry covering CVE-2026-54291
- retitle the drafted maintenance notes from 42.7.12 to 42.7.13 (CHANGELOG.md
  and the website changelog)
- record PR pgjdbc#4248 (LargeObject.close() flushes before marking closed, and
  lo_close no longer masks a flush failure) under 42.7.13

docs: fix PR links and add missing entries in 42.7.13 release notes

Point each changelog entry at the PR that merged the change rather than
at the issue or at the superseded pgjdbc#3400. Add two entries that were
missing: the reWriteBatchedInserts parameterless-INSERT fix (pgjdbc#4207) and
the GitHub release jar/signature fix (pgjdbc#3814). Drop the stray
connectThreadFactory entry from CHANGELOG.md, which connectExecutor
replaced before release.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

docs: credit translators and attribute large-object commits to James Howe

Move the BlobInputStream skip and reset commits to James Howe, who
co-authored them, and add a short contributors note crediting the
Italian, Simplified Chinese, and Russian-review translators.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

docs: add the empty-timestamp fix (pgjdbc#4278) to the 42.7.13 release notes

Reflect PR pgjdbc#4278, merged after the release-notes branch was opened: an
empty timestamp/timestamptz/date value now yields a clear SQLException
instead of ArrayIndexOutOfBoundsException.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* docs: diarize all 42.7.13 changes in CHANGELOG.md

The root CHANGELOG.md [42.7.13] section listed only 20 entries while
about 50 user-facing changes have landed since the 42.7.12 baseline.
Bring it in line with the reviewed website changelog: add the missing
feat/fix/refactor entries and correct PR references.

Newly diarized items include getSchemas() on a hot standby (pgjdbc#4274),
getColumnPrivileges()/CHAR_OCTET_LENGTH (pgjdbc#4231), FIPS PKIX trust
anchors (pgjdbc#4193), SSL key-format auto-detection (pgjdbc#3946), GSS response
timeout, autosave savepoints for SET LOCAL (pgjdbc#4203), BatchResultHandler
on a closed connection (pgjdbc#4187), SQL_TSI_FRAC_SECOND (pgjdbc#4229), the
batching/describe fixes, NumberParser/PGInterval validation,
connectThreadFactory, OID constants, and getPrimaryKeys derivation.

Docs, i18n, build, ci, test, and dependency-bump commits are omitted,
consistent with the file's existing "Notable changes" scope.

* docs: date the 42.7.13 website changelog to its release date

Rename the release notes to 2026-07-06 to reflect the release date, and
add the remaining PR links (flushCacheOnDdl pgjdbc#4067, connectExecutor
pgjdbc#4165, reWriteBatchedInserts cap pgjdbc#4207, OID constants pgjdbc#4220, 64-bit LO
pgjdbc#4204, and the refactor entries pgjdbc#4160/pgjdbc#4166/pgjdbc#4215).

* docs: correct the 42.7.13 commits-by-author counts

Reconcile the "Commits by author" section with git history:
- Vladimir Sitnikov: 100 -> 104 (stale count carried from the 42.7.12 draft)
- Dave Cramer: 4 -> 5, add the missing "maintainer vs committer" (pgjdbc#4234) entry
- add Michael Baumgartner (1 commit), previously omitted

James Howe remains credited for the two co-authored large-object commits.

* remove connectThread as it was replaced by connectExecutor

---------

Co-authored-by: Vladimir Sitnikov <[email protected]>
vlsi added a commit to vlsi/pgjdbc that referenced this pull request Jul 8, 2026
* docs: add 42.7.12 release changelog

Covers all changes since REL42.7.11: search_path GUC_REPORT cache
invalidation (PG 18+), flushCacheOnDdl, connectExecutor,
classLoaderStrategy, reWriteBatchedInserts protocol-limit cap,
large object 64-bit API, updatable ResultSet search_path fix,
XA autoCommit fix, FIPS trust anchors, SSL key-format auto-detection,
batching improvements, and numerous other bug fixes.

docs: prepare 42.7.13 release notes after 42.7.12 security release

42.7.12 shipped as a security-only release for the silent channel-binding
downgrade (CVE-2026-54291), so the feature and bug-fix notes that had been
drafted for 42.7.12 now belong to 42.7.13.

- bump pgjdbc.version to 42.7.13
- add a published 42.7.12 changelog entry covering CVE-2026-54291
- retitle the drafted maintenance notes from 42.7.12 to 42.7.13 (CHANGELOG.md
  and the website changelog)
- record PR pgjdbc#4248 (LargeObject.close() flushes before marking closed, and
  lo_close no longer masks a flush failure) under 42.7.13

docs: fix PR links and add missing entries in 42.7.13 release notes

Point each changelog entry at the PR that merged the change rather than
at the issue or at the superseded pgjdbc#3400. Add two entries that were
missing: the reWriteBatchedInserts parameterless-INSERT fix (pgjdbc#4207) and
the GitHub release jar/signature fix (pgjdbc#3814). Drop the stray
connectThreadFactory entry from CHANGELOG.md, which connectExecutor
replaced before release.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

docs: credit translators and attribute large-object commits to James Howe

Move the BlobInputStream skip and reset commits to James Howe, who
co-authored them, and add a short contributors note crediting the
Italian, Simplified Chinese, and Russian-review translators.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

docs: add the empty-timestamp fix (pgjdbc#4278) to the 42.7.13 release notes

Reflect PR pgjdbc#4278, merged after the release-notes branch was opened: an
empty timestamp/timestamptz/date value now yields a clear SQLException
instead of ArrayIndexOutOfBoundsException.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* docs: diarize all 42.7.13 changes in CHANGELOG.md

The root CHANGELOG.md [42.7.13] section listed only 20 entries while
about 50 user-facing changes have landed since the 42.7.12 baseline.
Bring it in line with the reviewed website changelog: add the missing
feat/fix/refactor entries and correct PR references.

Newly diarized items include getSchemas() on a hot standby (pgjdbc#4274),
getColumnPrivileges()/CHAR_OCTET_LENGTH (pgjdbc#4231), FIPS PKIX trust
anchors (pgjdbc#4193), SSL key-format auto-detection (pgjdbc#3946), GSS response
timeout, autosave savepoints for SET LOCAL (pgjdbc#4203), BatchResultHandler
on a closed connection (pgjdbc#4187), SQL_TSI_FRAC_SECOND (pgjdbc#4229), the
batching/describe fixes, NumberParser/PGInterval validation,
connectThreadFactory, OID constants, and getPrimaryKeys derivation.

Docs, i18n, build, ci, test, and dependency-bump commits are omitted,
consistent with the file's existing "Notable changes" scope.

* docs: date the 42.7.13 website changelog to its release date

Rename the release notes to 2026-07-06 to reflect the release date, and
add the remaining PR links (flushCacheOnDdl pgjdbc#4067, connectExecutor
pgjdbc#4165, reWriteBatchedInserts cap pgjdbc#4207, OID constants pgjdbc#4220, 64-bit LO
pgjdbc#4204, and the refactor entries pgjdbc#4160/pgjdbc#4166/pgjdbc#4215).

* docs: correct the 42.7.13 commits-by-author counts

Reconcile the "Commits by author" section with git history:
- Vladimir Sitnikov: 100 -> 104 (stale count carried from the 42.7.12 draft)
- Dave Cramer: 4 -> 5, add the missing "maintainer vs committer" (pgjdbc#4234) entry
- add Michael Baumgartner (1 commit), previously omitted

James Howe remains credited for the two co-authored large-object commits.

* remove connectThread as it was replaced by connectExecutor

---------

Co-authored-by: Vladimir Sitnikov <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BlobInputStream does not reset() to correct position if LargeObject was not at position 0. BlobInputStream should implement skip

1 participant