-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
feat(sql) transactions, savepoints, connection pooling and reserve #16381
Conversation
Updated 4:36 PM PT - Jan 18th, 2025
✅ @cirospaciari, your commit 66dd28e has passed in 🧪 try this PR locally: bunx bun-pr 16381 |
130076c
to
ff50783
Compare
0ea2f06
to
7b88aec
Compare
872492b
to
71a5e32
Compare
9eb2d9c
to
a06f824
Compare
class PooledConnection { | ||
pool: ConnectionPool; | ||
connection: ReturnType<typeof createConnection>; | ||
state: PooledConnectionState = PooledConnectionState.pending; |
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.
technically the flags and the connection state can be the same property since the connection state is 2 bits and the flags are 3 bits and we can go up to 32 bit int
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.
but its probably fine
What does this PR do?
How did you verify your code works?
Add support for transactions:
BEGIN / COMMIT
await sql.begin([options = ''], fn) -> fn()
SAVEPOINT
await sql.savepoint([name], fn) -> fn()
ERR_POSTGRES_UNSAFE_TRANSACTION
error is throw if BEGIN command is used without.begin()
,max: 1
or.reserve()
.Like
postgres
package if returned an array of Promises on .begin or .savepoint it willPromise.all
all promises before COMMIT.sql.transaction
is a alias forsql.begin
RESERVE
await sql.reserve()
The reserve method pulls out a connection from the pool, and returns a client that wraps the single connection. This can be used for running queries on an isolated connection.
To make it simpler bun supports
Symbol.dispose
andSymbol.asyncDispose
Calling
.reserve
in a reserved Sql will return a new reserved connection, not the same connection (behavior matchespostgres
package).DISTRIBUTED TRANSACTIONS
await sql.beginDistributed(name, fn) -> fn()
Also know as Two-Phase Commit, in a distributed transaction, Phase 1 involves the coordinator preparing nodes by ensuring data is written and ready to commit, while Phase 2 finalizes with nodes committing or rolling back based on the coordinator's decision, ensuring durability and releasing locks.
In PostgreSQL and MySQL distributed transactions persist beyond the original session, allowing privileged users or coordinators to commit/rollback them, ensuring support for distributed transactions, recovery, and administrative tasks.
beginDistributed
will automatic rollback if any exception are not caught, and you can commit and rollback later if everything goes well.PostgreSQL natively supports distributed transactions using PREPARE TRANSACTION, while MySQL uses XA Transactions, and MSSQL also supports distributed/XA transactions. However, in MSSQL, distributed transactions are tied to the original session, the DTC coordinator, and the specific connection. These transactions are automatically committed or rolled back following the same rules as regular transactions, with no option for manual intervention from other sessions, in MSSQL distributed transactions are used to coordinate transactions using Linked Servers.
sql.distributed
is a alias forsql.beginDistributed
Connection Pool
No connection will be made until a query is made.
If a connection is closed or not able to connect it will only retry it if no other connection is currently available to be used, this enables Bun to run even if not all connections are able to established minimizing downtimes. Bun will open as many connections until max number of connections is reached. By default max is 10. This can be changed by setting max in the Bun.sql() call. Example - Bun.sql({ max: 20 }).
Differences from
postgres
package:1 - Error code
UNSAFE_TRANSACTION
isERR_POSTGRES_UNSAFE_TRANSACTION
2 -
postgres
package will accept 0 inmax
value, we will throw a error, the default value will be the same 10,postgres
will use 0 with means hanging when trying to do a query.3 -
postgres
is conservative and will lazy open each connection only if enough concurrency is achieved, untilmax
is reached,Bun.sql
try to keep the pool full (untilmax
is reached) when possible after the first query is called or when retries are needed to maximize throughput.4 - Features present in
postgres
and not inBun.sql
:sql.readable()
sql.writable()
sql.cursor
sql.file
sql.simple
sql.listen
sql.notify
sql.subscribe
sql.unsafe
sql.prepare
in transactions seesql.beginDistributed
options.types
options.transform