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
55 changes: 55 additions & 0 deletions docs/pages/apis/client.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,61 @@ 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 (before first query)

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()
```

## events

### error
Expand Down
8 changes: 8 additions & 0 deletions packages/pg-native/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ const types = require('pg-types')
const buildResult = require('./lib/build-result')
const CopyStream = require('./lib/copy-stream')

// https://www.postgresql.org/docs/current/libpq-status.html#LIBPQ-PQTRANSACTIONSTATUS
// 0=IDLE, 1=ACTIVE, 2=INTRANS, 3=INERROR
const statusMap = { 0: 'I', 2: 'T', 3: 'E' }

const Client = (module.exports = function (config) {
if (!(this instanceof Client)) {
return new Client(config)
Expand Down Expand Up @@ -145,6 +149,10 @@ Client.prototype.escapeIdentifier = function (value) {
return this.pq.escapeIdentifier(value)
}

Client.prototype.getTransactionStatus = function () {
return statusMap[this.pq.transactionStatus()] ?? null
}

// export the version number so we can check it in node-postgres
module.exports.version = require('./package.json').version

Expand Down
2 changes: 1 addition & 1 deletion packages/pg-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
},
"homepage": "https://github.com/brianc/node-postgres/tree/master/packages/pg-native",
"dependencies": {
"libpq": "^1.8.15",
"libpq": "^1.10.0",
"pg-types": "2.2.0"
},
"devDependencies": {
Expand Down
6 changes: 6 additions & 0 deletions packages/pg/lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class Client extends EventEmitter {
this._connectionError = false
this._queryable = true
this._activeQuery = null
this._txStatus = null

this.enableChannelBinding = Boolean(c.enableChannelBinding) // set true to use SCRAM-SHA-256-PLUS when offered
this.connection =
Expand Down Expand Up @@ -359,6 +360,7 @@ class Client extends EventEmitter {
}
const activeQuery = this._getActiveQuery()
this._activeQuery = null
this._txStatus = msg?.status ?? null
this.readyForQuery = true
if (activeQuery) {
activeQuery.handleReadyForQuery(this.connection)
Expand Down Expand Up @@ -703,6 +705,10 @@ class Client extends EventEmitter {
this.connection.unref()
}

getTransactionStatus() {
return this._txStatus
}

end(cb) {
this._ending = true

Expand Down
4 changes: 4 additions & 0 deletions packages/pg/lib/native/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,7 @@ Client.prototype.getTypeParser = function (oid, format) {
Client.prototype.isConnected = function () {
return this._connected
}

Client.prototype.getTransactionStatus = function () {
return this.native.getTransactionStatus()
}
82 changes: 82 additions & 0 deletions packages/pg/test/integration/client/txstatus-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
'use strict'
const helper = require('./test-helper')
const suite = new helper.Suite()
const pg = helper.pg
const assert = require('assert')

suite.test('txStatus tracking', function (done) {
const client = new pg.Client()
client.connect(
assert.success(function () {
// Run a simple query to initialize txStatus
client.query(
'SELECT 1',
assert.success(function () {
// Test 1: Initial state after query (should be idle)
assert.equal(client.getTransactionStatus(), 'I', 'should start in idle state')

// Test 2: BEGIN transaction
client.query(
'BEGIN',
assert.success(function () {
assert.equal(client.getTransactionStatus(), 'T', 'should be in transaction state')

// Test 3: COMMIT
client.query(
'COMMIT',
assert.success(function () {
assert.equal(client.getTransactionStatus(), 'I', 'should return to idle after commit')

client.end(done)
})
)
})
)
})
)
})
)
})

suite.test('txStatus error state', function (done) {
const client = new pg.Client()
client.connect(
assert.success(function () {
// Run a simple query to initialize txStatus
client.query(
'SELECT 1',
assert.success(function () {
client.query(
'BEGIN',
assert.success(function () {
// Execute invalid SQL to trigger error state
client.query('INVALID SQL SYNTAX', function (err) {
assert(err, 'should receive error from invalid query')

// Issue a sync query to ensure ReadyForQuery has been processed
// This guarantees transaction status has been updated
client.query('SELECT 1', function () {
// This callback fires after ReadyForQuery is processed
assert.equal(client.getTransactionStatus(), 'E', 'should be in error state')

// Rollback to recover
client.query(
'ROLLBACK',
assert.success(function () {
assert.equal(
client.getTransactionStatus(),
'I',
'should return to idle after rollback from error'
)
client.end(done)
})
)
})
})
})
)
})
)
})
)
})
15 changes: 10 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5955,13 +5955,13 @@ levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"

libpq@^1.8.15:
version "1.8.15"
resolved "https://registry.yarnpkg.com/libpq/-/libpq-1.8.15.tgz#bf9cea8e59e1a4a911d06df01d408213a09925ad"
integrity sha512-4lSWmly2Nsj3LaTxxtFmJWuP3Kx+0hYHEd+aNrcXEWT0nKWaPd9/QZPiMkkC680zeALFGHQdQWjBvnilL+vgWA==
libpq@^1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/libpq/-/libpq-1.10.0.tgz#238d01d416abca8768aab09bc82d81af9c7ffa23"
integrity sha512-PHY+JGD3+9X5b2emXLh+WJEnz1jhczO1xs25ZH0xbMWvQi+Hd9X/mTZOrGA99Rcw/DvNjsBRlegroqigpNfaJA==
dependencies:
bindings "1.5.0"
nan "~2.22.2"
nan "~2.23.1"

lines-and-columns@^1.1.6:
version "1.1.6"
Expand Down Expand Up @@ -6686,6 +6686,11 @@ nan@~2.22.2:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.2.tgz#6b504fd029fb8f38c0990e52ad5c26772fdacfbb"
integrity sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==

nan@~2.23.1:
version "2.23.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.23.1.tgz#6f86a31dd87e3d1eb77512bf4b9e14c8aded3975"
integrity sha512-r7bBUGKzlqk8oPBDYxt6Z0aEdF1G1rwlMcLk8LCOMbOzf0mG+JUfUzG4fIMWwHWP0iyaLWEQZJmtB7nOHEm/qw==

nanoid@^3.3.11:
version "3.3.11"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
Expand Down
Loading