You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Return the slot's name from ReadReplicationSlot - #6736
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.
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>
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.
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>
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.
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>
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.
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>
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.
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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ReadReplicationSlotreturned its argument lowercased as the slot'sName, 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 aNamethat isn't the slot's:ReadReplicationSlot(" myslot ")returned" myslot ", andReadReplicationSlot("\"1abc\"")returned"\"1abc\"". It now returns the name the server read: the name as sent (quoted byQuoteNamesince #6707) without its quotes, and cut to 63 bytes, the defaultmax_identifier_length, the way the server cuts longer identifiers.Tests
ReplicationSlotNameTests<Physical>.ReadReplicationSlot_returns_the_slot_namereads 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_namereads a slot whose name has the fullmax_identifier_lengthwith one more character; without the truncation it returned the longer name.QuotingTests.Names_as_the_server_reads_themcovers the unquoting, andIdentifiers_are_truncated_like_the_server_doesthe truncation, which never splits a character.Fixes #6735