porsager / postgres

Postgres.js - The Fastest full featured PostgreSQL client for Node.js, Deno, Bun and CloudFlare
The Unlicense
7.04k stars 257 forks source link

arbitrary UNSAFE_TRANSACTION errors when using sql.begin #823

Open JuHwon opened 4 months ago

JuHwon commented 4 months ago

The following query arbitrary causes an UNSAVE_TRANSACTION error.

await this.sql.begin(async (tsql) => {
  await tsql`DELETE
            FROM mapping_table
            WHERE a_id = ${aId}`;
  if (items.length === 0) {
    return;
  }
  await tsql`INSERT INTO mapping_table ${
    // eslint-disable-next-line @ts-safeql/check-sql
    tsql(items, 'a_id', 'b_id')
  }`;
});

the error most likely happens when this code is executed very frequently and items is usually an array betwenn 0 and a few thousand items.

error:

Error: UNSAFE_TRANSACTION: Only use sql.begin, sql.reserved or max: 1
    at CommandComplete (/Users/juhwon/Projects/xx/xx/node_modules/postgres/cjs/src/connection.js:580:29)
    at handle (/Users/juhwon/Projects/xx/xx/node_modules/postgres/cjs/src/connection.js:476:6)
    at Socket.data (/Users/juhwon/Projects/xx/xx/node_modules/postgres/cjs/src/connection.js:315:9)
    at Socket.emit (node:events:514:28)
    at addChunk (node:internal/streams/readable:376:12)
    at readableAddChunk (node:internal/streams/readable:349:9)
    at Socket.Readable.push (node:internal/streams/readable:286:10)
    at TCP.onStreamRead (node:internal/stream_base_commons:190:23)
    at TCP.callbackTrampoline (node:internal/async_hooks:130:17)

to my understanding using sql.begin for something like this should be fine. have we found a bug here or is there something wrong with our code?

JuHwon commented 3 months ago

error object of notice callback:

{
   "severity_local":"WARNING",
   "severity":"WARNING",
   "code":"25001",
   "message":"there is already a transaction in progress",
   "file":"xact.c",
   "line":"3799",
   "routine":"BeginTransactionBlock"
}

maybe this is similar to this one: https://github.com/porsager/postgres/issues/274

according to the debug logs the client executes a BEGIN statement on connection id e.g. 4, and then a few ms laters it executes a BEGIN statement with the same connection id again, before the first transaction was handled. there were no other statements between with the affected connection id.