-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Add pg-transaction module #3518
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a new pg-transaction
module that provides a simplified async/await wrapper for PostgreSQL transactions. The module handles automatic transaction management with BEGIN/COMMIT/ROLLBACK operations and supports both individual clients and connection pools.
Key changes:
- Creates a transaction utility function that accepts either a PostgreSQL client or pool
- Implements automatic transaction lifecycle management with proper error handling and resource cleanup
- Provides comprehensive test coverage for various transaction scenarios
Reviewed Changes
Copilot reviewed 4 out of 6 changed files in this pull request and generated 3 comments.
File | Description |
---|---|
packages/pg-transaction/src/index.ts | Core transaction implementation with type guards and transaction management logic |
packages/pg-transaction/src/index.test.ts | Comprehensive test suite covering client/pool usage, commit/rollback scenarios, and bound functions |
packages/pg-transaction/package.json | Package configuration with dependencies and build scripts |
packages/pg-transaction/tsconfig.json | TypeScript configuration for the module |
Deploying node-postgres with
|
Latest commit: |
bc627be
|
Status: | ✅ Deploy successful! |
Preview URL: | https://e885314f.node-postgres.pages.dev |
Branch Preview URL: | https://bmc-pg-transaction-2.node-postgres.pages.dev |
throw error | ||
} finally { | ||
if (isPoolClient(client)) { | ||
client.release() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the rollback failed, client
could be returned to the pool while still in a transaction or in a broken-connection state. I don’t know if the former is actually possible in practice (ROLLBACK
failing without breaking the connection), but the latter definitely is (when error
doesn’t crash the process, e.g. because the user is attaching error
listeners to all clients using the pool’s connect
event). Might want to add another catch
for the ROLLBACK
and .release(rollbackError)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh that's a good point. I'll take stab at this...I think I can repro by pg_terminate_backend
the active in transaction client backed from within the transaction itself. That should disconnect it and make it fail to be able to send rollback
- anyways....I'll test a few more failure modes for both this comment and the other one about the error listener dance and see if I can make it throw an uncaught anywhere else.
let client: Client | PoolClient | ||
if (isPool(clientOrPool)) { | ||
// It's a Pool | ||
client = await clientOrPool.connect() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn’t do the error
listener dance, so connection errors can crash a Node process here. It might be surprising that the behavior is different from pool.query
. But maybe pg should add a default handler for the error
event in a minor version update anyway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn’t do the error listener dance, so connection errors can crash a Node process here
Sorry I'm not following! What do you mean by error listener dance? I thought await pool.connect()
would reject on connection errors? not have to result to listeners?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pool.connect()
itself does, but if there’s a connection-level error after connecting (e.g. during the BEGIN
query), the default behavior is to crash the process (because it’s an error
event). (I think a lot of people probably use pg without doing this part strictly right…)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a lot of people probably use pg without doing this part strictly right…)
yeah, honestly myself included....for yeras, in multiple apps. 😬
the whole async/evented error stuff is so so tricky to get "really just right" - would love to see an example if you have one. I'm totally cool working on some kind of "buffer the error(s) if no default .on('error')
listener is added and throw it on the next call to the library" feature instead of what it is now which is effectively "if its not an in-flight query, then you're probably just gonna crash, which doesn't happen much, but async race conditions are their own kind of personal hell." I do think that's a minor version bump fix too because it could probably be backwards-comp where if there is an error listener things proceede as normal but if there isn't we don't just explode the universe w/ an unhandledError
and instead buffer it.
Honestly it could potentially be a lot to take on and needs some thinking about where the right place to handle these errors is (because legacy reasons we do pool -> client -> connection -> pg-protocol) callstacks, but its probably worth spending more time there myself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I already did the error replay part in #1503; it’d just be a matter of adding *actually, I don’t remember if the pool automatically drops broken clients that are released to it without an explicit error, and there might be a question of whether this.on('error', () => {})
to Client
’s constructor (and Pool
’s too).end()
should start rejecting with the connection error… maybe only if there are no error
listeners… it might not be that simple, and a new major version could be the safest option.
Co-authored-by: Charmander <[email protected]>
…s into bmc/pg-transaction-2
I'm taking over the old pg-transaction module, and turning it into a simpler async/await affair. I basically write this same utility in every project I do, so thought it'd be helpful. Pass the
transaction
function a connected client or an instance of a pool and it will call back with the client (or one checked out from the pool) already within aBEGIN
block. If your async callback doesn't throw, it willCOMMIT
after, and if it does throw, it willROLLBACK
. It also releases the client back to the pool regardless of if the your async callback function throws or not.