Skip to content

Return the slot's name from ReadReplicationSlot - #6736

Open
Brar wants to merge 6 commits into
npgsql:mainfrom
Brar:repl/read-slot-name
Open

Brar wants to merge 6 commits into
npgsql:mainfrom
Brar:repl/read-slot-name

Conversation

@Brar

@Brar Brar commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

ReadReplicationSlot returned its argument lowercased as the slot's Name, since the result of READ_REPLICATION_SLOT has no slot name. The server reads the name as an identifier, though, so an argument with surrounding whitespace or quotes gave a Name that isn't the slot's: ReadReplicationSlot(" myslot ") returned " myslot ", and ReadReplicationSlot("\"1abc\"") returned "\"1abc\"". It now returns the name the server read: the name as sent (quoted by QuoteName since #6707) without its quotes, and cut to 63 bytes, the default max_identifier_length, the way the server cuts longer identifiers.

Tests

  • ReplicationSlotNameTests<Physical>.ReadReplicationSlot_returns_the_slot_name reads a physical slot by its name with surrounding spaces, in quotes and plain. Without this change the first two fail, returning the name with its spaces or quotes; the plain one passes either way. It needs PostgreSQL 15 or later for READ_REPLICATION_SLOT, and the logical fixture ignores it.
  • ReplicationSlotNameTests<Physical>.ReadReplicationSlot_returns_the_truncated_slot_name reads a slot whose name has the full max_identifier_length with one more character; without the truncation it returned the longer name.
  • QuotingTests.Names_as_the_server_reads_them covers the unquoting, and Identifiers_are_truncated_like_the_server_does the truncation, which never splits a character.
  • These and the other quoting tests pass against PostgreSQL 18.6, in Debug and Release.

Fixes #6735

READ_REPLICATION_SLOT's result has no slot name, so ReadReplicationSlot
returned its argument lowercased. That isn't the slot's name if the
argument has surrounding whitespace, which the server skips, or quotes.
It now returns the name the server read: the name it was sent, without
its quotes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 1, 2026 13:40
@Brar
Brar requested review from roji and vonzshik as code owners November 1, 2026 13:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Overlong slot names can return a name that differs from the server-resolved identifier, and a regression test is needed.

1 open issue
What changed in this PR

This PR addresses #6735 by deriving ReadReplicationSlot’s returned name from the identifier sent to PostgreSQL, removing surrounding whitespace and identifier quotes.

Changes:

  • Reuses the quoted command identifier to construct the returned slot name.
  • Adds an internal helper to remove identifier quoting and unescape doubled quotes.
  • Adds unit tests and PostgreSQL 15+ integration coverage for name normalization.
File Description
test/​Npgsql.Tests/​Replication/​ReplicationQuotingTests.cs Tests returned slot names for padded, quoted, and plain inputs.
test/​Npgsql.Tests/​QuotingTests.cs Adds coverage for identifier unquoting.
src/​Npgsql/​Util/​Statics.cs Adds the UnquoteName helper.
src/​Npgsql/​Replication/​ReplicationConnection.cs Derives the returned slot name from the quoted command identifier.

Review effort: Balanced

The server truncates an identifier longer than max_identifier_length
bytes, 63 by default, before it looks up the slot, so reading a name of
63 "a"s and a "b" found the slot of 63 "a"s but returned the longer
name. Such a name is now truncated the same way. Asking the server for
its max_identifier_length isn't worth a round trip, so this assumes the
default, which only a build with another NAMEDATALEN changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 1, 2026 14:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Unquoted slot names containing non-ASCII characters are not ASCII-folded, so the returned truncated name can have incorrect capitalization.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity ASCII-fold unquoted identifiers after truncation

src/​Npgsql/​Replication/​ReplicationConnection.cs:402

The server ASCII-folds unquoted identifiers even when they contain non-ASCII characters. For a slot named 63 lowercase as, reading new string('A', 63) + "é" finds that slot, but this returns 63 uppercase As because UnquoteName leaves the unquoted input unchanged. Fold the retained name for unquoted identifiers, and add a regression test with a non-ASCII character beyond the truncation boundary.

1 resolved since last review

Review effort: Balanced

A name with non-ASCII letters is sent unquoted, so the server lowercases
its A-Z before it truncates it. Reading 63 uppercase "A"s and an "é"
finds the slot of 63 lowercase "a"s, since the server cuts the "é" off,
but returned the uppercase name. UnquoteName now lowercases A-Z for such
names too. Slot names consist of a-z, 0-9 and _ only, so a name with
non-ASCII letters can only find a slot if they're cut off, and then this
is exactly the server's name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 1, 2026 15:00
@Brar

Brar commented Nov 1, 2026

Copy link
Copy Markdown
Member Author

On Copilot's latest review: slot names can't contain non-ASCII characters. ReplicationSlotCreate validates every name (slot.c:362) and accepts only [a-z0-9_] (slot.c:305-330). So a name with non-ASCII letters only finds a slot if the server cuts them off, as in the review's example, and what remains is plain ASCII, for which lowercasing A-Z gives exactly the server's name. Done in ab44691c8: names sent unquoted are now folded like that too, and ReadReplicationSlot_returns_the_truncated_slot_name(True) reads the uppercase name with an é appended and gets the lowercase one.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Single-byte Turkish casing may fold non-ASCII names to ASCII, but the required locale was unavailable for validation.

No open issues 🎉

Review effort: Balanced

@Brar

Brar commented Nov 1, 2026

Copy link
Copy Markdown
Member Author

On Copilot's latest review (Turkish casing): the server lowercases a non-ASCII letter in an unquoted name with the locale's tolower() only if the database encoding is single-byte (scansup.c:55-71), and in a Turkish locale that turns İ into an ASCII i. That doesn't affect ReadReplicationSlot: READ_REPLICATION_SLOT only exists on physical replication connections, which have no database, so the encoding stays SQL_ASCII (mbutils.c:83) and the server lowercases the bytes of the UTF-8 name, which don't turn into a valid slot name, except in a contrived case where the length limit cuts a character in half. Slot names consist of a-z, 0-9 and _ only (slot.c:305-330), so lowercasing A-Z, as ab44691c8 does, gives exactly the server's name for every name that finds a slot.

The ÄBC case tested a name that can't be a slot name, and its expected
result isn't what every server reads: one with a single-byte locale also
lowercases the Ä. A name with non-ASCII letters is sent unquoted, and it
can only find a slot if the server cuts those letters off. So the new
test reads 63 uppercase letters and an é the way ReadReplicationSlot
does, and expects 63 lowercase ones, which is what every server reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 1, 2026 15:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The 63-byte assumption can make the returned slot object target a different slot on PostgreSQL builds with a larger NAMEDATALEN, creating a replication-safety tradeoff that warrants human review.

No open issues 🎉

Review effort: Balanced

ReadReplicationSlot_returns_the_truncated_slot_name(True) failed on the
Windows and macOS jobs with 22021, invalid byte sequence for encoding
"UTF8": 0xe3 0xa9 0x22. A physical walsender keeps the locale of the
postmaster's environment and the SQL_ASCII encoding, which counts as
single-byte, so it lowercases each byte above 0x7F that the locale
calls uppercase. On these servers that hit the first byte of é (Ã in
Latin-1), and the notice that the name will be truncated wasn't valid
UTF-8 any more, which failed the command. None of the bytes of 中 is an
uppercase letter in Latin-1 or Windows-1252.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 1, 2026 15:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

A locale-dependent encoding edge case may cause the replication test to fail before its assertion, warranting human assessment.

No open issues 🎉

Review effort: Balanced

TruncateIdentifier's summary and its test said that the server truncates
identifiers without splitting a character. A physical walsender cuts
them after max_identifier_length bytes, since its encoding is SQL_ASCII,
but if that splits a character, its notice about the truncation isn't
valid in the client encoding UTF8, and the command fails. So when
READ_REPLICATION_SLOT succeeds, the server read the name that
TruncateIdentifier returns. The summary and the test now say so, and
the test is named after what it checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 1, 2026 16:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Locale- and encoding-dependent identifier handling and test behavior warrant human assessment.

No open issues 🎉

Review effort: Balanced

This branch has not been deployed

No deployments
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.

ReadReplicationSlot returns the slot name as passed, not the one the server read

3 participants