Skip to content

Commit dafc9b4

Browse files
committed
feat: add new client.getTransactionStatus() method
Adds a new public method to retrieve the current transaction status of the client connection. Returns 'I' (idle), 'T' (in transaction), 'E' (error/aborted), or null (initial state/native client). The transaction status is tracked from PostgreSQL's ReadyForQuery message after each query completes. Native client returns null as it does not support this feature yet.
1 parent ad36e3c commit dafc9b4

4 files changed

Lines changed: 154 additions & 3 deletions

File tree

docs/pages/apis/client.mdx

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ type Config = {
2323
lock_timeout?: number, // number of milliseconds a query is allowed to be en lock state before it's cancelled due to lock timeout
2424
application_name?: string, // The name of the application that created this Client instance
2525
connectionTimeoutMillis?: number, // number of milliseconds to wait for connection, default is no timeout
26-
keepAlive?: boolean, // if true, enable keepalive on the `net.Socket`
27-
keepAliveInitialDelayMillis?: number, // number of milliseconds between last data packet received and first keepalive probe, default is 0 (keep existing value);
28-
// no effect unless `keepAlive` is true
26+
keepAliveInitialDelayMillis?: number, // set the initial delay before the first keepalive probe is sent on an idle socket
2927
idle_in_transaction_session_timeout?: number, // number of milliseconds before terminating any session with an open idle transaction, default is no timeout
3028
client_encoding?: string, // specifies the character set encoding that the database uses for sending data to the client
3129
fallback_application_name?: string, // provide an application name to use if application_name is not set
@@ -175,6 +173,63 @@ await client.end()
175173
console.log('client has disconnected')
176174
```
177175

176+
## client.getTransactionStatus
177+
178+
`client.getTransactionStatus() => string | null`
179+
180+
Returns the current transaction status of the client connection. This can be useful for debugging transaction state issues or implementing custom transaction management logic.
181+
182+
**Return values:**
183+
184+
- `'I'` - Idle (not in a transaction)
185+
- `'T'` - Transaction active (BEGIN has been issued)
186+
- `'E'` - Error (transaction aborted, requires ROLLBACK)
187+
- `null` - Initial state or not supported (native client)
188+
189+
The transaction status is updated after each query completes based on the PostgreSQL backend's `ReadyForQuery` message.
190+
191+
**Example: Checking transaction state**
192+
193+
```js
194+
import { Client } from 'pg'
195+
const client = new Client()
196+
await client.connect()
197+
198+
await client.query('BEGIN')
199+
console.log(client.getTransactionStatus()) // 'T' - in transaction
200+
201+
await client.query('SELECT * FROM users')
202+
console.log(client.getTransactionStatus()) // 'T' - still in transaction
203+
204+
await client.query('COMMIT')
205+
console.log(client.getTransactionStatus()) // 'I' - idle
206+
207+
await client.end()
208+
```
209+
210+
**Example: Handling transaction errors**
211+
212+
```js
213+
import { Client } from 'pg'
214+
const client = new Client()
215+
await client.connect()
216+
217+
await client.query('BEGIN')
218+
try {
219+
await client.query('INVALID SQL')
220+
} catch (err) {
221+
console.log(client.getTransactionStatus()) // 'E' - error state
222+
223+
// Must rollback to recover
224+
await client.query('ROLLBACK')
225+
console.log(client.getTransactionStatus()) // 'I' - idle again
226+
}
227+
228+
await client.end()
229+
```
230+
231+
**Note:** This method is not supported in the native client and will always return `null`.
232+
178233
## events
179234

180235
### error

packages/pg/lib/client.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class Client extends EventEmitter {
7171
this._connectionError = false
7272
this._queryable = true
7373
this._activeQuery = null
74+
this._txStatus = null
7475

7576
this.enableChannelBinding = Boolean(c.enableChannelBinding) // set true to use SCRAM-SHA-256-PLUS when offered
7677
this.connection =
@@ -359,6 +360,7 @@ class Client extends EventEmitter {
359360
}
360361
const activeQuery = this._getActiveQuery()
361362
this._activeQuery = null
363+
this._txStatus = msg?.status ?? null
362364
this.readyForQuery = true
363365
if (activeQuery) {
364366
activeQuery.handleReadyForQuery(this.connection)
@@ -703,6 +705,10 @@ class Client extends EventEmitter {
703705
this.connection.unref()
704706
}
705707

708+
getTransactionStatus() {
709+
return this._txStatus
710+
}
711+
706712
end(cb) {
707713
this._ending = true
708714

packages/pg/lib/native/client.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,3 +321,8 @@ Client.prototype.getTypeParser = function (oid, format) {
321321
Client.prototype.isConnected = function () {
322322
return this._connected
323323
}
324+
325+
Client.prototype.getTransactionStatus = function () {
326+
// not supported in native client
327+
return null
328+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
'use strict'
2+
const helper = require('./test-helper')
3+
const suite = new helper.Suite()
4+
const pg = helper.pg
5+
const assert = require('assert')
6+
7+
// txStatus tracking is not supported in native client
8+
if (!helper.args.native) {
9+
suite.test('txStatus tracking', function (done) {
10+
const client = new pg.Client()
11+
client.connect(
12+
assert.success(function () {
13+
// Run a simple query to initialize txStatus
14+
client.query(
15+
'SELECT 1',
16+
assert.success(function () {
17+
// Test 1: Initial state after query (should be idle)
18+
assert.equal(client.getTransactionStatus(), 'I', 'should start in idle state')
19+
20+
// Test 2: BEGIN transaction
21+
client.query(
22+
'BEGIN',
23+
assert.success(function () {
24+
assert.equal(client.getTransactionStatus(), 'T', 'should be in transaction state')
25+
26+
// Test 3: COMMIT
27+
client.query(
28+
'COMMIT',
29+
assert.success(function () {
30+
assert.equal(client.getTransactionStatus(), 'I', 'should return to idle after commit')
31+
32+
client.end(done)
33+
})
34+
)
35+
})
36+
)
37+
})
38+
)
39+
})
40+
)
41+
})
42+
43+
suite.test('txStatus error state', function (done) {
44+
const client = new pg.Client()
45+
client.connect(
46+
assert.success(function () {
47+
// Run a simple query to initialize txStatus
48+
client.query(
49+
'SELECT 1',
50+
assert.success(function () {
51+
client.query(
52+
'BEGIN',
53+
assert.success(function () {
54+
// Execute invalid SQL to trigger error state
55+
client.query('INVALID SQL SYNTAX', function (err) {
56+
assert(err, 'should receive error from invalid query')
57+
58+
// Issue a sync query to ensure ReadyForQuery has been processed
59+
// This guarantees transaction status has been updated
60+
client.query('SELECT 1', function () {
61+
// This callback fires after ReadyForQuery is processed
62+
assert.equal(client.getTransactionStatus(), 'E', 'should be in error state')
63+
64+
// Rollback to recover
65+
client.query(
66+
'ROLLBACK',
67+
assert.success(function () {
68+
assert.equal(
69+
client.getTransactionStatus(),
70+
'I',
71+
'should return to idle after rollback from error'
72+
)
73+
client.end(done)
74+
})
75+
)
76+
})
77+
})
78+
})
79+
)
80+
})
81+
)
82+
})
83+
)
84+
})
85+
}

0 commit comments

Comments
 (0)