-
Notifications
You must be signed in to change notification settings - Fork 217
Learn by Example
This tutorial is to be used for quick reference and to help first time users get started right away. For complete overview of the library use the official documentation.
You can use the complete examples for a quick copy'n paste of the code snippets provided here. For all recommended reads see the project's WiKi.
- ES5
db.any('SELECT * FROM users WHERE active = $1', [true])
.then(function(data) {
// success;
})
.catch(function(error) {
// error;
});
- ES7
try {
const users = await db.any('SELECT * FROM users WHERE active = $1', [true]);
// success
}
catch(e) {
// error
}
See: any
db.none('INSERT INTO users(name, active) VALUES($1, $2)', ['John', true])
.then(() => {
// success;
})
.catch(error => {
// error;
});
See: none
db.one('INSERT INTO users(name, active) VALUES($1, $2) RETURNING id', ['John', true])
.then(data => {
console.log(data.id); // print new user id;
})
.catch(error => {
console.log('ERROR:', error); // print error;
});
See: one
db.func('myFuncName', [123, new Date()])
.then(data => {
console.log('DATA:', data); // print data;
})
.catch(error => {
console.log('ERROR:', error); // print the error;
});
See: func
db.proc('myProcName', [123, new Date()])
.then(data => {
console.log('DATA:', data); // print data, if any;
})
.catch(error => {
console.log('ERROR:', error.message || error); // print the error;
});
See: proc
Postgres Prepared Statements serve two purposes:
- Performance optimization (for complex queries only), via cached execution plan;
- SQL injection prevention, as data and queries are passed in separately.
db.one({
name: 'find-user',
text: 'SELECT * FROM users WHERE id = $1', // can also be a QueryFile object
values: [1]
})
.then(user => {
// user found;
})
.catch(error => {
// error;
});
See also: PreparedStatement, QueryFile.
db.one({
text: 'SELECT * FROM users WHERE id = $1', // can also be a QueryFile object
values: [1]
})
.then(user => {
// user found;
})
.catch(error => {
// error;
});
See also: ParameterizedQuery, QueryFile.
When you want to access the original Result object for properties like rowCount (number of affected rows) or the fields (column details), use method result(query, values)
to bypass the result verification and resolve with the Result object passed from PG.
// delete all inactive users;
db.result('DELETE FROM users WHERE active = $1', false)
.then(result => {
// rowCount = number of rows affected by the query
console.log(result.rowCount); // print how many records were deleted;
})
.catch(error => {
console.log('ERROR:', error);
});
See: result
db.one('SELECT * FROM users WHERE id = $1', 123)
.then(user => {
console.log(user); // print user object;
})
.catch(error => {
// error;
});
db.any('SELECT * FROM users WHERE created < $1 AND active = $2', [new Date(), true])
.then(data => {
console.log('DATA:', data); // print data;
})
.catch(error => {
console.log('ERROR:', error); // print the error;
});
Named Parameters are defined using syntax $*propName*
, where *
is any of the following open-close pairs: {}
, ()
, []
, <>
, //
, so you can use one to your liking, but remember that ${}
is also used by ES6 template strings.
db.any('SELECT * FROM users WHERE name = ${name} AND active = $/active/',
{
name: 'John',
active: true
})
.then(data => {
console.log('DATA:', data); // print data;
})
.catch(error => {
console.log('ERROR:', error); // print the error;
});
Property with name this
refers to the formatting object itself, to be injected as a JSON-formatted string.
Combinations of different open-close symbols are not allowed.
const account = {
balance: 123.45,
expenses: 2.7,
margin: 0.1,
total: a => {
const t = a.balance + a.expenses;
return a.margin ? (t + t * a.margin / 10) : t;
}
};
db.none('INSERT INTO activity VALUES(${balance}, ${total})', account)
.then(() => {
// success;
})
.catch(error => {
// error;
});
Functions are good for returning any type of value that needs to be created in-line.
Raw (pre-formatted) text is injected by adding either :raw
or symbol ^
to the end of the variable name:
-
$1:raw, $2:raw,...
or$*propName:raw*
-
$1^, $2^,...
or$*propName^*
where *
is any of: {}
, ()
, []
, <>
, //
.
Unlike a regular variable, the value for a raw-text variable:
- doesn't get inner single-quotes fixed (replaced with two);
- isn't wrapped in single quotes;
- cannot be
null
; - cannot be
undefined
inside array of parameters or as an object property.
Open Values simplify string concatenation for escaped values. They are injected by adding either :value
or symbol #
to the end of the variable name:
-
$1:value, $2:value,...
or$*propName:value*
-
$1#, $2#,...
or$*propName#*
where *
is any of: {}
, ()
, []
, <>
, //
.
// using variable $1#
db.any('SELECT * FROM users WHERE name LIKE \'%$1#%\'', 'John')
.then(data => {
// success;
})
.catch(error => {
// error;
});
Unlike a regular variable, an open-value variable:
- isn't wrapped in single quotes;
- cannot be
null
; - cannot be
undefined
inside array of parameters or as an object property.
SQL Names and identifiers should be formatted using either :name
or symbol ~
(tilde):
-
$1:name
,$2:name
, or${propName:name}
-
$1~
,$2~
, or${propName~}
db.any('INSERT INTO $1~($2~) VALUES(...)', ['Table Name', 'Column Name']);
// => INSERT INTO "Table Name"("Column Name") VALUES(...)
db.any('SELECT ${column~} FROM ${table~}', {
column: 'Column Name',
table: 'Table Name'
});
// => SELECT "Column Name" FROM "Table Name"
See also: as.name
- as a property of an object:
db.none('INSERT INTO data(point) VALUES(${vector})',
{
// 2D array of integers: int[][];
vector: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
})
.then(() => {
// success;
})
.catch(error => {
// error;
});
- as an element inside the array of parameters:
db.none('INSERT INTO data(labels) VALUES($1)',
[
// 3D array of strings: text[][][];
[[['one'], ['two']], [['three'], ['four']]]
])
.then(() => {
// success;
})
.catch(error => {
// error;
});
You can also pass an array as the return result from a function, if the array needs to be created in-line.
Tasks are for executing multiple queries against the same connection. Also see method task and Chaining Queries.
db.task(async t => {
// t.ctx = task config + state context;
const user = await t.one('SELECT * FROM users WHERE id = $1', 123);
return t.any('SELECT * FROM events WHERE login = $1', user.name);
})
.then(events => {
// success;
})
.catch(error => {
// error
});
See method task.
A transaction starts with tx, and considered a task surrounded by BEGIN
and COMMIT
/ROLLBACK
queries. See method tx, Chaining Queries.
db.tx(async t => {
// t.ctx = transaction config + state context;
await t.none('UPDATE users SET active = $1 WHERE id = $2', [true, 123]);
await t.none('INSERT INTO audit(status, id) VALUES($1, $2)', ['active', 123]);
})
.then(() => {
// success;
})
.catch(error => {
console.log('ERROR:', error);
});
db.tx(async t => {
// `t` and `this` here are the same;
// this.ctx = transaction config + state context;
const userId = await t.one('INSERT INTO users(name) VALUES($1) RETURNING id', 'John', a => a.id);
const eventId = await t.one('INSERT INTO events(code) VALUES($1) RETURNING id', 123, a => a.id);
return {userId, eventId};
})
.then(data => {
console.log(data.userId);
console.log(data.eventId);
})
.catch(error => {
// error
});
See method tx.
db.tx(async t1 => {
// t1 = transaction protocol context;
// t1.ctx = transaction config + state context;
await t1.none('UPDATE users SET active = $1 WHERE id = $2', [true, 123]);
await t1.none('INSERT INTO audit(status, id) VALUES($1, $2)', ['active', 123]);
return t1.tx(async t2 => {
// t2 != t1
const userId = await t2.one('INSERT INTO users(name) VALUES($1) RETURNING id', 'John', a => a.id);
const eventId = await t2.one('INSERT INTO events(code) VALUES($1) RETURNING id', 123, a => a.id);
return {userId, eventId};
});
})
.then(data => {
console.log(data.userId);
console.log(data.eventId);
})
.catch(error => {
console.log('ERROR:', error);
});
NOTE: You can use this
instead of parameters t1
and t2
, when using a regular function.
Please be aware of the limitations in the nested transactions support.
Use method sequence when executing massive transactions, like bulk inserts, with way over 1,000 records. See Data Imports with a complete example.
You can use pg-query-stream - high-performance, read-only query streaming via cursor (doesn't work with pgNative option). Its code example can be re-implemented via pg-promise as follows:
import QueryStream from 'pg-query-stream';
import JSONStream from 'JSONStream';
// you can also use pgp.as.format(query, values, options)
// to format queries properly, via pg-promise;
const qs = new QueryStream('SELECT * FROM users');
await db.stream(qs, s => {
s.pipe(JSONStream.stringify()).pipe(process.stdout);
});
//=> resolves with: {processed, duration}
Here's another example, of streaming a query result into a CSV file:
import QueryStream from 'pg-query-stream';
import CsvWriter from 'csv-write-stream';
import {createWriteStream} from 'fs';
const csv = new CsvWriter();
const file = createWriteStream('out.csv');
const qs = new QueryStream('select * from my_table');
await db.stream(query, s => {
s.pipe(csv).pipe(file);
});
//=> resolves with: {processed, duration}
This can be very useful for large data outputs, like table exports. See stream method.
If you want to stream data into the database, have a look at stream support within the spex library. Here's an example:
import {createReadStream} from 'fs';
const streamRead = pgp.spex.stream.read;
const rs = createReadStream('primes.txt');
function receiver(_, data) {
function source(index) {
if (index < data.length) {
return data[index];
}
}
function dest(index, data) {
return this.none('INSERT INTO primes VALUES($1)', data);
}
return this.sequence(source, {dest});
}
await db.tx(t => streamRead.call(t, rs, receiver));
And you should be aware of the considerations made in the Performance Boost.
- Temporary listener, using the connection pool: listening will stop when the connection pool releases the physical connection, due to inactivity (see idleTimeoutMillis) or a connectivity error.
let sco; // shared connection object
db.connect()
.then(obj => {
sco = obj;
sco.client.on('notification', data => {
console.log('Received:', data);
// data.payload = 'my payload string'
});
return sco.none('LISTEN $1:name', 'my-channel');
})
.catch(error => {
console.log('Error:', error);
})
.finally(() => {
if (sco) {
sco.done(); // releasing the connection back to the pool
}
});
Sending a notification example:
db.none('NOTIFY $1:name, $2', ['my-channel', 'my payload string'])
.then(() => {
console.log('Notification sent.');
})
.catch(error => {
console.log('NOTIFY error:', error);
});
-
Permanent listener, outside of the connection pool: listening will never stop, unless the physical connection fails, or if you call
sco.done()
to release it. See also Robust Listeners.
db.connect({direct: true})
.then(sco => {
sco.client.on('notification', data => {
console.log('Received:', data);
// data.payload = 'my payload string'
});
return sco.none('LISTEN $1:name', 'my-channel');
})
.catch(error => {
console.log('Error:', error);
});
i.e. the difference is that we use {direct: true}
to create a separate Client
, plus we never release the connection.
However, we can still save sco
and call sco.done()
at some point, if we want to shut down the channel. For example, if the physical connection fails, you will need to re-create the connection and set up your listeners again. For a complete example, see Robust Listeners.
const data = [1, 'two', 3, 'four'];
db.any('SELECT * FROM table WHERE id IN ($1:csv)', [data])
.then(data => {
// success;
})
.catch(error => {
console.log('ERROR:', error);
});
NOTE: data
must be a parameter inside an array. See CSV Filter.
Floating-point types can accept special strings: NaN
, +Infinity
and -Infinity
:
db.none('INSERT INTO test VALUES($1, $2, $3, $4)', [123, NaN, 1/0, -1/0])
.then(() => {
// success;
})
.catch(error => {
console.log('ERROR:', error);
});
// This will execute the following query:
// INSERT INTO test VALUES(123, 'NaN', '+Infinity', '-Infinity')
The library automatically formats type Buffer
for columns of type bytea
.
const fs = require('fs');
// read in image in raw format (as type Buffer):
fs.readFile('image.jpg', (err, imgData) => {
// inserting data into column 'img' of type 'bytea':
db.none('INSERT INTO images(img) VALUES($1)', imgData)
.then(() => {
// success;
})
.catch(error => {
// error;
});
});
const user = {
name: 'John',
age: 30,
active: true
};
- as an array element:
// 'info' column is of type json;
db.none('INSERT INTO users(info) VALUES($1)', [user])
.then(() => {
// success;
})
.catch(error => {
console.log('ERROR:', error);
});
- as an object property:
db.none('INSERT INTO users(info) VALUES(${obj})', {
obj: user
})
.then(() => {
// success;
})
.catch(error => {
// error;
});
- via
this
reference:
db.none('INSERT INTO users(info) VALUES(${this})', user)
.then(() => {
// success;
})
.catch(error => {
// error;
});
This library provides a flexible event system for you to be able to track every aspect of the query execution, while pg-monitor makes full use of it:
pg-promise