Skip to content

Abort a transaction when a parameter fails to serialize after its describe - #1238

Open
chrbala wants to merge 1 commit into
porsager:masterfrom
chrbala:fix-serialize-error-after-describe
Open

chrbala wants to merge 1 commit into
porsager:masterfrom
chrbala:fix-serialize-error-after-describe

Conversation

@chrbala

@chrbala chrbala commented Sep 30, 2026

Copy link
Copy Markdown

A query with parameters that is new to a connection is sent as Parse and Describe first, and only bound once the server has described its parameter types. If a parameter then fails to serialize (for example new Date(NaN), whose serializer throws a RangeError), the query is rejected and a bare Sync is sent. The server answers that Sync without an error, so a transaction the query was pipelined in goes on and commits without it:

const reserved = await sql.reserve()
await Promise.all([
  reserved`begin`,
  reserved`insert into test values (1)`,
  reserved`select ${ new Date(NaN) }::timestamptz`, // rejects
  reserved`insert into test values (2)`,
  reserved`commit`                                  // commits 1 and 2
])

Bind the query with no parameters instead. The server refuses it, since the query takes at least one parameter (or it would not have been described first). That aborts the transaction, the refusal answers in the query's place, and the query is rejected with its own error, as a retried query already is. It is no longer a cursor either, whose error would send a Sync of its own and shift the next answer.

…cribe

A query with parameters that is new to a connection is sent as Parse and
Describe first, and only bound once the server has described its
parameter types. If a parameter then fails to serialize (for example
`new Date(NaN)`, whose serializer throws a RangeError), the query is
rejected and a bare Sync is sent. The server answers that Sync without an
error, so a transaction the query was pipelined in goes on and commits
without it:

    const reserved = await sql.reserve()
    await Promise.all([
      reserved`begin`,
      reserved`insert into test values (1)`,
      reserved`select ${ new Date(NaN) }::timestamptz`, // rejects
      reserved`insert into test values (2)`,
      reserved`commit`                                  // commits 1 and 2
    ])

Bind the query with no parameters instead. The server refuses it, since
the query takes at least one parameter (or it would not have been
described first). That aborts the transaction, the refusal answers in the
query's place, and the query is rejected with its own error, as a retried
query already is. It is no longer a cursor either, whose error would send
a Sync of its own and shift the next answer.

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.

1 participant