Skip to content

Commit

Permalink
Merge branch 'master' into docs
Browse files Browse the repository at this point in the history
  • Loading branch information
rijkvanzanten committed May 2, 2023
2 parents fcd53f9 + 249182e commit 9c2697c
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 36 deletions.
3 changes: 2 additions & 1 deletion docs/pages/apis/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"pool": "pg.Pool",
"result": "pg.Result",
"types": "pg.Types",
"cursor": "Cursor"
"cursor": "Cursor",
"utilities": "Utilities"
}
1 change: 1 addition & 0 deletions docs/pages/apis/client.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { Client } from 'pg'
const client = new Client({
host: 'my.database-server.com',
port: 5334,
database: 'database-name',
user: 'database-user',
password: 'secretpassword!!',
})
Expand Down
6 changes: 6 additions & 0 deletions docs/pages/apis/pool.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,12 @@ The error listener is passed the error as the first argument and the client upon
uncaught error and potentially crash your node process.
</Alert>

### release

`pool.on('release', (err: Error, client: Client) => void) => void`

Whenever a client is released back into the pool, the pool will emit the `release` event.

### remove

`pool.on('remove', (client: Client) => void) => void`
Expand Down
4 changes: 2 additions & 2 deletions docs/pages/apis/result.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ await client.end()

The command type last executed: `INSERT` `UPDATE` `CREATE` `SELECT` etc.

### `result.rowCount: int`
### `result.rowCount: int | null`

The number of rows processed by the last command.
The number of rows processed by the last command. Can be `null` for commands that never affect rows, such as the `LOCK`-command. More specifically, some commands, including `LOCK`, only return a command tag of the form `COMMAND`, without any `[ROWS]`-field to parse. For such commands `rowCount` will be `null`.

_note: this does not reflect the number of rows __returned__ from a query. e.g. an update statement could update many rows (so high `result.rowCount` value) but `result.rows.length` would be zero. To check for an empty query reponse on a `SELECT` query use `result.rows.length === 0`_.

Expand Down
30 changes: 30 additions & 0 deletions docs/pages/apis/utilities.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
title: Utilities
---
import { Alert } from '/components/alert.tsx'

## Utility Functions
### pg.escapeIdentifier

Escapes a string as a [SQL identifier](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS).

```js
const { escapeIdentifier } = require('pg')
const escapedIdentifier = escapeIdentifier('FooIdentifier')
console.log(escapedIdentifier) // '"FooIdentifier"'
```


### pg.escapeLiteral

<Alert>
**Note**: Instead of manually escaping SQL literals, it is recommended to use parameterized queries. Refer to [parameterized queries](/features/queries#parameterized-query) and the [client.query](/apis/client#clientquery) API for more information.
</Alert>

Escapes a string as a [SQL literal](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS).

```js
const { escapeLiteral } = require('pg')
const escapedLiteral = escapeLiteral("hello 'world'")
console.log(escapedLiteral) // "'hello ''world'''"
```
4 changes: 2 additions & 2 deletions docs/theme.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export default {
head: (
<>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Nextra: the next docs builder" />
<meta name="og:title" content="Nextra: the next docs builder" />
<meta name="description" content="node-postgres is a collection of node.js modules for interfacing with your PostgreSQL database." />
<meta name="og:title" content="node-postgres" />
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-100138145-1"></script>
<script
dangerouslySetInnerHTML={{
Expand Down
14 changes: 8 additions & 6 deletions packages/pg-pool/test/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,20 @@ describe('events', function () {
expect(client).to.be.ok()
releaseCount++
})
const promises = []
for (let i = 0; i < 10; i++) {
pool.connect(function (err, client, release) {
if (err) return done(err)
release()
})
pool.query('SELECT now()')
promises.push(pool.query('SELECT now()'))
}
setTimeout(function () {
expect(releaseCount).to.be(20)
pool.end(done)
}, 100)
Promise.all(promises).then(() => {
pool.end(() => {
expect(releaseCount).to.be(20)
done()
})
})
})

it('emits release with an error if client is released due to an error', function (done) {
Expand All @@ -87,7 +90,6 @@ describe('events', function () {
expect(err).to.equal(undefined)
const releaseError = new Error('problem')
pool.once('release', function (err, errClient) {
console.log(err, errClient)
expect(err).to.equal(releaseError)
expect(errClient).to.equal(client)
pool.end(done)
Expand Down
30 changes: 5 additions & 25 deletions packages/pg/lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -456,35 +456,15 @@ class Client extends EventEmitter {
return this._types.getTypeParser(oid, format)
}

// Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c
// escapeIdentifier and escapeLiteral moved to utility functions & exported
// on PG
// re-exported here for backwards compatibility
escapeIdentifier(str) {
return '"' + str.replace(/"/g, '""') + '"'
return utils.escapeIdentifier(str)
}

// Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c
escapeLiteral(str) {
var hasBackslash = false
var escaped = "'"

for (var i = 0; i < str.length; i++) {
var c = str[i]
if (c === "'") {
escaped += c + c
} else if (c === '\\') {
escaped += c + c
hasBackslash = true
} else {
escaped += c
}
}

escaped += "'"

if (hasBackslash === true) {
escaped = ' E' + escaped
}

return escaped
return utils.escapeLiteral(str)
}

_pulseQueryQueue() {
Expand Down
3 changes: 3 additions & 0 deletions packages/pg/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var defaults = require('./defaults')
var Connection = require('./connection')
var Pool = require('pg-pool')
const { DatabaseError } = require('pg-protocol')
const { escapeIdentifier, escapeLiteral } = require('./utils')

const poolFactory = (Client) => {
return class BoundPool extends Pool {
Expand All @@ -23,6 +24,8 @@ var PG = function (clientConstructor) {
this.Connection = Connection
this.types = require('pg-types')
this.DatabaseError = DatabaseError
this.escapeIdentifier = escapeIdentifier
this.escapeLiteral = escapeLiteral
}

if (typeof process.env.NODE_PG_FORCE_NATIVE !== 'undefined') {
Expand Down
34 changes: 34 additions & 0 deletions packages/pg/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,38 @@ const postgresMd5PasswordHash = function (user, password, salt) {
return 'md5' + outer
}

// Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c
const escapeIdentifier = function (str) {
return '"' + str.replace(/"/g, '""') + '"'
}

const escapeLiteral = function (str) {
var hasBackslash = false
var escaped = "'"

for (var i = 0; i < str.length; i++) {
var c = str[i]
if (c === "'") {
escaped += c + c
} else if (c === '\\') {
escaped += c + c
hasBackslash = true
} else {
escaped += c
}
}

escaped += "'"

if (hasBackslash === true) {
escaped = ' E' + escaped
}

return escaped
}



module.exports = {
prepareValue: function prepareValueWrapper(value) {
// this ensures that extra arguments do not get passed into prepareValue
Expand All @@ -184,4 +216,6 @@ module.exports = {
normalizeQueryConfig,
postgresMd5PasswordHash,
md5,
escapeIdentifier,
escapeLiteral
}
23 changes: 23 additions & 0 deletions packages/pg/test/unit/client/escape-tests.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'
var helper = require('./test-helper')
var utils = require('../../../lib/utils')

function createClient(callback) {
var client = new Client(helper.config)
Expand All @@ -14,6 +15,17 @@ var testLit = function (testName, input, expected) {
var actual = client.escapeLiteral(input)
assert.equal(expected, actual)
})

test('Client.prototype.' + testName, function () {
var actual = Client.prototype.escapeLiteral(input)
assert.equal(expected, actual)
})


test('utils.' + testName, function () {
var actual = utils.escapeLiteral(input)
assert.equal(expected, actual)
})
}

var testIdent = function (testName, input, expected) {
Expand All @@ -22,6 +34,17 @@ var testIdent = function (testName, input, expected) {
var actual = client.escapeIdentifier(input)
assert.equal(expected, actual)
})

test('Client.prototype.' + testName, function () {
var actual = Client.prototype.escapeIdentifier(input)
assert.equal(expected, actual)
})


test('utils.' + testName, function () {
var actual = utils.escapeIdentifier(input)
assert.equal(expected, actual)
})
}

testLit('escapeLiteral: no special characters', 'hello world', "'hello world'")
Expand Down
53 changes: 53 additions & 0 deletions packages/pg/test/unit/utils-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,56 @@ test('prepareValue: can safely be used to map an array of values including those
var out = values.map(utils.prepareValue)
assert.deepEqual(out, [1, 'test', 'zomgcustom!'])
})

var testEscapeLiteral = function (testName, input, expected) {
test(testName, function () {
var actual = utils.escapeLiteral(input)
assert.equal(expected, actual)
})
}
testEscapeLiteral('escapeLiteral: no special characters', 'hello world', "'hello world'")

testEscapeLiteral('escapeLiteral: contains double quotes only', 'hello " world', "'hello \" world'")

testEscapeLiteral('escapeLiteral: contains single quotes only', "hello ' world", "'hello '' world'")

testEscapeLiteral('escapeLiteral: contains backslashes only', 'hello \\ world', " E'hello \\\\ world'")

testEscapeLiteral('escapeLiteral: contains single quotes and double quotes', 'hello \' " world', "'hello '' \" world'")

testEscapeLiteral('escapeLiteral: contains double quotes and backslashes', 'hello \\ " world', " E'hello \\\\ \" world'")

testEscapeLiteral('escapeLiteral: contains single quotes and backslashes', "hello \\ ' world", " E'hello \\\\ '' world'")

testEscapeLiteral(
'escapeLiteral: contains single quotes, double quotes, and backslashes',
'hello \\ \' " world',
" E'hello \\\\ '' \" world'"
)

var testEscapeIdentifier = function (testName, input, expected) {
test(testName, function () {
var actual = utils.escapeIdentifier(input)
assert.equal(expected, actual)
})
}

testEscapeIdentifier('escapeIdentifier: no special characters', 'hello world', '"hello world"')

testEscapeIdentifier('escapeIdentifier: contains double quotes only', 'hello " world', '"hello "" world"')

testEscapeIdentifier('escapeIdentifier: contains single quotes only', "hello ' world", '"hello \' world"')

testEscapeIdentifier('escapeIdentifier: contains backslashes only', 'hello \\ world', '"hello \\ world"')

testEscapeIdentifier('escapeIdentifier: contains single quotes and double quotes', 'hello \' " world', '"hello \' "" world"')

testEscapeIdentifier('escapeIdentifier: contains double quotes and backslashes', 'hello \\ " world', '"hello \\ "" world"')

testEscapeIdentifier('escapeIdentifier: contains single quotes and backslashes', "hello \\ ' world", '"hello \\ \' world"')

testEscapeIdentifier(
'escapeIdentifier: contains single quotes, double quotes, and backslashes',
'hello \\ \' " world',
'"hello \\ \' "" world"'
)

0 comments on commit 9c2697c

Please sign in to comment.