Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions docs/pages/apis/client.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,63 @@ await client.end()
console.log('client has disconnected')
```

## client.getTransactionStatus

`client.getTransactionStatus() => string | null`

Returns the current transaction status of the client connection. This can be useful for debugging transaction state issues or implementing custom transaction management logic.

**Return values:**

- `'I'` - Idle (not in a transaction)
- `'T'` - Transaction active (BEGIN has been issued)
- `'E'` - Error (transaction aborted, requires ROLLBACK)
- `null` - Initial state or not supported (native client)

The transaction status is updated after each query completes based on the PostgreSQL backend's `ReadyForQuery` message.

**Example: Checking transaction state**

```js
import { Client } from 'pg'
const client = new Client()
await client.connect()

await client.query('BEGIN')
console.log(client.getTransactionStatus()) // 'T' - in transaction

await client.query('SELECT * FROM users')
console.log(client.getTransactionStatus()) // 'T' - still in transaction

await client.query('COMMIT')
console.log(client.getTransactionStatus()) // 'I' - idle

await client.end()
```

**Example: Handling transaction errors**

```js
import { Client } from 'pg'
const client = new Client()
await client.connect()

await client.query('BEGIN')
try {
await client.query('INVALID SQL')
} catch (err) {
console.log(client.getTransactionStatus()) // 'E' - error state

// Must rollback to recover
await client.query('ROLLBACK')
console.log(client.getTransactionStatus()) // 'I' - idle again
}

await client.end()
```

**Note:** This method is not supported in the native client and will always return `null`.

## events

### error
Expand Down
8 changes: 7 additions & 1 deletion docs/pages/apis/pool.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ type Config = {
// regardless of whether they are idle. It's useful to force rotation of connection pools through
// middleware so that you can rotate the underlying servers. The default is disabled (value of zero)
maxLifetimeSeconds?: number

// When true, automatically removes connections with open or failed transactions from the pool
// when they are released. This provides protection against "connection poisoning" where
// uncommitted transactions leak across different requests. Default is false.
evictOnOpenTransaction?: boolean
}
```

Expand All @@ -70,7 +75,8 @@ const pool = new Pool({
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
maxLifetimeSeconds: 60
maxLifetimeSeconds: 60,
evictOnOpenTransaction: true
})
```

Expand Down
9 changes: 9 additions & 0 deletions packages/pg-pool/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class Pool extends EventEmitter {
this.options.maxUses = this.options.maxUses || Infinity
this.options.allowExitOnIdle = this.options.allowExitOnIdle || false
this.options.maxLifetimeSeconds = this.options.maxLifetimeSeconds || 0
this.options.evictOnOpenTransaction = this.options.evictOnOpenTransaction || false
this.log = this.options.log || function () {}
this.Client = this.options.Client || Client || require('pg').Client
this.Promise = this.options.Promise || global.Promise
Expand All @@ -116,6 +117,10 @@ class Pool extends EventEmitter {
return this._clients.length > this.options.min
}

_hasActiveTransaction(client) {
return client && (client.getTransactionStatus() === 'T' || client.getTransactionStatus() === 'E')
}

_pulseQueue() {
this.log('pulse queue')
if (this.ended) {
Expand Down Expand Up @@ -363,7 +368,11 @@ class Pool extends EventEmitter {
if (client._poolUseCount >= this.options.maxUses) {
this.log('remove expended client')
}
return this._remove(client, this._pulseQueue.bind(this))
}

if (this.options.evictOnOpenTransaction && this._hasActiveTransaction(client)) {
this.log('remove client due to open transaction')
return this._remove(client, this._pulseQueue.bind(this))
}

Expand Down
Loading