Skip to content

Answer a query that fails to build in its place in the pipeline - #1236

Open
chrbala wants to merge 1 commit into
porsager:masterfrom
chrbala:fix-pipelined-build-error
Open

chrbala wants to merge 1 commit into
porsager:masterfrom
chrbala:fix-pipelined-build-error

Conversation

@chrbala

@chrbala chrbala commented Sep 30, 2026

Copy link
Copy Markdown

When a query throws while it is built (an undefined value, more than 65534 parameters, a serializer that throws), execute has already queued it to await an answer, but nothing of it was written. The catch then rejected the connection's current query with the new query's error and left the failed query waiting for an answer that never comes, so every later answer on the connection went to the wrong query:

await sql`select 1`
const a = sql`select pg_sleep(0.1), 1 as x`
    , b = sql`select ${ undefined }`
    , c = sql`select 3 as x`
// a rejects with UNDEFINED_VALUE, b with "Cannot set properties of
// null (setting 'columns')", c never settles, nor does any later query

This is also why sql.begin(sql => [sqlselect 1, sqlselect ${ undefined }]) never settles (#1082). On a reserved connection with the transaction pipelined (begin, insert, failing query, insert, commit), both inserts committed.

Send a query the server is sure to refuse in the failed query's place. The failed query then gets an answer of its own, every later answer stays with its query, and a transaction it was part of is aborted rather than committed without it. The failed query is rejected with its own error, as a retried query already is. It is no longer marked to be described first or as a cursor, since either would make the server's error send a Sync of its own and shift the next answer.

Fixes #1082

When a query throws while it is built (an undefined value, more than
65534 parameters, a serializer that throws), `execute` has already queued
it to await an answer, but nothing of it was written. The catch then
rejected the connection's current query with the new query's error and
left the failed query waiting for an answer that never comes, so every
later answer on the connection went to the wrong query:

    await sql`select 1`
    const a = sql`select pg_sleep(0.1), 1 as x`
        , b = sql`select ${ undefined }`
        , c = sql`select 3 as x`
    // a rejects with UNDEFINED_VALUE, b with "Cannot set properties of
    // null (setting 'columns')", c never settles, nor does any later query

This is also why `sql.begin(sql => [sql`select 1`, sql`select ${ undefined }`])`
never settles (porsager#1082). On a reserved connection with the transaction
pipelined (`begin`, insert, failing query, insert, `commit`), both inserts
committed.

Send a query the server is sure to refuse in the failed query's place.
The failed query then gets an answer of its own, every later answer stays
with its query, and a transaction it was part of is aborted rather than
committed without it. The failed query is rejected with its own error, as
a retried query already is. It is no longer marked to be described first
or as a cursor, since either would make the server's error send a Sync of
its own and shift the next answer.

Fixes porsager#1082

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@chrbala

chrbala commented Sep 30, 2026

Copy link
Copy Markdown
Author

I made a few PRs from Claude which fix some problems I was having. Can you take a look?

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.

Unsettled transaction pipeline promise on parameter validation error

1 participant