X Tutup
Skip to content

Simplify field reads#5480

Open
NinoFloris wants to merge 9 commits intomainfrom
simplify-field-reads
Open

Simplify field reads#5480
NinoFloris wants to merge 9 commits intomainfrom
simplify-field-reads

Conversation

@NinoFloris
Copy link
Copy Markdown
Member

@NinoFloris NinoFloris commented Dec 6, 2023

This moves the stream handling into the Ado resolver.

Based on #5476
Any reviews should start from the last commit Simplify by resolving Stream by default as a converter

After this pack of changes (though that ideally includes #5471) we are able to fully inline everything on the hot path into GetFieldValueCore.

DPGO was even happy to make one huge 8000 bytes method (arm64) out of my local reproduction of the fortunes scenario, inlining even GetFieldValueCore itself.

Imprecise local benchmarking shows about a 3% improvement in fortunes RPS.

@NinoFloris NinoFloris force-pushed the simplify-field-reads branch 2 times, most recently from 368cc98 to c9212f3 Compare December 8, 2023 13:26
@NinoFloris NinoFloris force-pushed the simplify-field-reads branch from c9212f3 to a22a718 Compare March 17, 2024 16:17
This was referenced Mar 17, 2024
@NinoFloris NinoFloris force-pushed the simplify-field-reads branch 2 times, most recently from 5b91717 to cf122e5 Compare March 26, 2024 00:59
@NinoFloris NinoFloris force-pushed the simplify-field-reads branch 4 times, most recently from aab2833 to e950992 Compare June 28, 2024 14:52
@roji roji removed the enhancement label Jun 14, 2025
@NinoFloris NinoFloris force-pushed the simplify-field-reads branch 2 times, most recently from 5a8a530 to 86af6d4 Compare October 29, 2025 23:56
@NinoFloris NinoFloris added this to the 10.0.0 milestone Nov 3, 2025
@NinoFloris NinoFloris modified the milestones: 10.0.0, 11.0.0 Nov 19, 2025
Copilot AI review requested due to automatic review settings March 12, 2026 06:31
@NinoFloris NinoFloris force-pushed the simplify-field-reads branch from 86af6d4 to 078b193 Compare March 12, 2026 06:31
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR simplifies field reads in NpgsqlDataReader by removing Stream-specific special-casing and moving Stream handling into the ADO type info resolver/converter pipeline, with additional reader state tweaks to support the new flow and improve inlining/hot-path performance.

Changes:

  • Replace Stream special-casing in NpgsqlDataReader.GetFieldValue* with resolver-based Stream conversion (StreamByteaConverter).
  • Introduce PgReader.StreamCanSeek and propagate sequential/non-sequential seekability decisions into PgReader.GetStream().
  • Cleanup and small refactors: unify -1 null sentinel usage, tighten cleanup/reset behavior, and rename nested-read revert API.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/Npgsql/ThrowHelper.cs Changes ThrowIndexOutOfRangeException formatting overload to take an int argument.
src/Npgsql/Replication/PgOutput/ReplicationValue.cs Refactors replication value reads to use a shared generic Get<T>(async, ...) path; exposes sync GetStream/GetTextReader via blocking.
src/Npgsql/NpgsqlDataReader.cs Removes Stream fast path from GetFieldValue*, introduces DbNullSentinel, threads DataFormat through GetInfo, sets stream seekability via PgReader.StreamCanSeek, and resets reader/buffer references on cleanup.
src/Npgsql/Internal/ResolverFactories/AdoTypeInfoResolverFactory.cs Adds fallback Stream type info resolution and enables Stream converter text-format support for relevant mappings.
src/Npgsql/Internal/PgReader.cs Adds StreamCanSeek, replaces GetStream(canSeek, ...) with GetStream() using the new flag, and renames nested-scope revert method.
src/Npgsql/Internal/Converters/Primitive/TextConverters.cs Adjusts ConsumeChars end-of-stream condition logic.
src/Npgsql/Internal/Converters/Primitive/ByteaConverters.cs Implements StreamByteaConverter as an actual Stream reader via PgReader.GetStream(), with format support controlled by supportsTextFormat.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@NinoFloris NinoFloris force-pushed the simplify-field-reads branch 3 times, most recently from 8365094 to dd543b8 Compare March 12, 2026 07:34
Copilot AI review requested due to automatic review settings March 25, 2026 12:06
@NinoFloris NinoFloris force-pushed the simplify-field-reads branch from dd543b8 to eadb2f1 Compare March 25, 2026 12:06
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/Npgsql/Replication/PgOutput/ReplicationValue.cs:169

  • ThrowIfInitialized() only checks PgReader.Initialized, but Consume() sets _isConsumed = true after committing (which clears PgReader.Initialized). After Consume() has been called, Get<T>() can be invoked again and will re-Init and read from the wrong position. Consider also checking _isConsumed (or setting a separate flag) and throwing the same "Column has already been consumed" exception when true.
    void ThrowIfInitialized()
    {
        if (PgReader.Initialized)
            throw new InvalidOperationException("Column has already been consumed");
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings March 25, 2026 14:06
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

src/Npgsql/Replication/PgOutput/ReplicationValue.cs:169

  • ThrowIfInitialized() only checks PgReader.Initialized, but Consume(...) sets _isConsumed = true after committing the reader (so Initialized becomes false). That allows Get* methods to be called again after consumption, potentially reading from an already-consumed buffer position. Add a guard for _isConsumed (or reintroduce the previous active-state check) and ensure the exception message matches the condition.
    void ThrowIfInitialized()
    {
        if (PgReader.Initialized)
            throw new InvalidOperationException("Column has already been consumed");
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@NinoFloris NinoFloris force-pushed the simplify-field-reads branch from 34efa01 to dcbfdd5 Compare March 25, 2026 14:16
Copilot AI review requested due to automatic review settings March 25, 2026 14:19
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings March 25, 2026 14:37
@NinoFloris NinoFloris force-pushed the simplify-field-reads branch from fe41bb8 to 5a9300d Compare March 25, 2026 14:37
@NinoFloris NinoFloris force-pushed the simplify-field-reads branch from 5a9300d to 6e1cf1e Compare March 25, 2026 14:43
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@NinoFloris NinoFloris force-pushed the simplify-field-reads branch from 6e1cf1e to 6d4e08b Compare March 25, 2026 14:53
if ((uint)ordinal > (uint)ColumnCount)
ThrowHelper.ThrowIndexOutOfRangeException("Ordinal must be between 0 and " + (ColumnCount - 1));
if ((uint)ordinal >= (uint)ColumnCount)
ThrowHelper.ThrowIndexOutOfRangeException("Ordinal is out of range, value must be between 0 and {0} (exclusive).", ColumnCount);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guard condition change was a copilot suggestion, as we were dropping through to the array indexer bounds check for the last column. To do so the message needed a rework as well (otherwise you'd read e.g. "must be between 0 and 0" for a one column row).

get
{
if ((uint)ordinal < (uint)Count)
if ((uint)ordinal >= (uint)Count)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guard condition change was a copilot suggestion, as we were dropping through to the array indexer bounds check for the last column. To do so the message needed a rework as well (otherwise you'd read e.g. "must be between 0 and 0" for a one column row).

@NinoFloris
Copy link
Copy Markdown
Member Author

I've rebased and minimized the diff @roji @vonzshik PTAL.

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.

3 participants

X Tutup