Skip to content

docs(spi): clarify SpiClient::select vs update for read-only vs writable SPI#2328

Open
uded wants to merge 1 commit into
pgcentralfoundation:developfrom
uded:docs/spi-select-vs-update
Open

docs(spi): clarify SpiClient::select vs update for read-only vs writable SPI#2328
uded wants to merge 1 commit into
pgcentralfoundation:developfrom
uded:docs/spi-select-vs-update

Conversation

@uded

@uded uded commented Jun 18, 2026

Copy link
Copy Markdown

Pure docs change. No behaviour change.

Motivation

The current docstrings on SpiClient::select and SpiClient::update frame the distinction as "read query vs write query":

/// Perform a SELECT statement.
pub fn select<…>() -> SpiResult<…> {}

/// Perform any query (including utility statements) that modify the database in some way.
pub fn update<…>() -> SpiResult<…> { Spi::mark_mutable();}

That framing is incomplete. select runs the underlying SPI_execute* with read_only = is_xact_still_immutable(), which on a fresh BGW transaction means read_only = true. PostgreSQL then rejects any locking clause — including on a plain SELECT — with:

ERROR: SELECT FOR UPDATE is not allowed in a non-volatile function

A reader who writes client.select("SELECT id FROM t ORDER BY id LIMIT 5 FOR UPDATE SKIP LOCKED", …) because it "is a SELECT" hits this error. In a BGW the abort path turns it into a panic-while-handling-panic, the worker SIGABRTs, and the postmaster crash-recovers. The fix is mechanical (connectconnect_mut, selectupdate), but the docs don't currently nudge the reader toward it.

I had this exact misread last week and filed it as a bug (#2327) before re-reading client.update and realising pgrx had the right shape all along. Closed the issue as user error. This PR is a follow-up to make the next person less likely to make the same mistake.

What this PR does

  • Adds a paragraph to SpiClient::select describing the read-only-SPI execution mode, the specific error message a misuse produces, and the pointer to SpiClient::update.
  • Adds a paragraph to SpiClient::update explicitly listing the locking clauses (FOR UPDATE, FOR SHARE, SKIP LOCKED, NOWAIT) as cases that need writable SPI even when the statement looks like a SELECT.
  • Cross-links to [Spi::is_xact_still_immutable] and [Spi::mark_mutable] from both docstrings so the read_only flag's lifecycle is discoverable.

Intra-doc link format matches the existing pgrx convention ([Spi::foo]). No code changes; rustfmt --check clean.

Why not a code change

The behaviour is correct — client.update calls Spi::mark_mutable() first, so locking clauses work through that path. The gap is purely doc-level.

If you'd prefer different wording (terser, or with a runnable example, or moved to Spi::connect / Spi::connect_mut instead of the client methods), happy to revise.

Closes the docs side of #2327.

…ble SPI

Expand the docstrings on SpiClient::select and SpiClient::update so that
callers can tell which to reach for without having to read the underlying
SPI_execute call site.

The previous one-line docstrings ("Perform a SELECT statement" /
"Perform any query that modifies the database in some way") frame the
distinction purely as "read query vs write query." That framing is
incomplete: SpiClient::select runs in read-only SPI mode, which also
forbids locking clauses on a plain SELECT (FOR UPDATE / SKIP LOCKED /
FOR SHARE / NOWAIT). A reader who uses select for "SELECT id FROM t
ORDER BY id LIMIT 5 FOR UPDATE SKIP LOCKED" hits

    ERROR: SELECT FOR UPDATE is not allowed in a non-volatile function

and then a BGW context turns that ERROR into a panic in the abort path
and the worker SIGABRTs. The fix is mechanical (switch to
SpiClient::update which calls Spi::mark_mutable() first), but the
docstrings don't currently nudge the reader toward it.

This patch:

  * Adds a paragraph to SpiClient::select describing read-only mode, the
    error message a misuse produces, and the pointer to SpiClient::update.
  * Adds a paragraph to SpiClient::update explicitly listing the locking
    clauses as cases that need writable SPI even when the statement
    looks like a SELECT.
  * Links to Spi::is_xact_still_immutable and Spi::mark_mutable from
    both docstrings so the read_only flag's lifecycle is discoverable.

No behaviour changes. Intra-doc links use the existing pgrx convention.
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