postgres
Advanced tools
Comparing version 2.0.0-beta.8 to 2.0.0-beta.9
@@ -125,8 +125,8 @@ const { errors } = require('./errors.js') | ||
? (row[i] = value) | ||
: (row[column.name] = transform.value ? transform.value(value) : value) | ||
: (row[column.name] = transform.value.from ? transform.value.from(value) : value) | ||
} | ||
backend.query.stream | ||
? backend.query.stream(transform.row ? transform.row(row) : row, backend.query.result) | ||
: (backend.query.result[rows++] = transform.row ? transform.row(row) : row) | ||
? backend.query.stream(transform.row.from ? transform.row.from(row) : row, backend.query.result) | ||
: (backend.query.result[rows++] = transform.row.from ? transform.row.from(row) : row) | ||
} | ||
@@ -212,4 +212,4 @@ | ||
backend.query.statement.columns[i] = { | ||
name: transform.column | ||
? transform.column(x.toString('utf8', start, index - 1)) | ||
name: transform.column.from | ||
? transform.column.from(x.toString('utf8', start, index - 1)) | ||
: x.toString('utf8', start, index - 1), | ||
@@ -216,0 +216,0 @@ parser: parsers[type], |
@@ -36,2 +36,4 @@ const net = require('net') | ||
let connect_timer | ||
let buffers = null | ||
let remaining = 0 | ||
@@ -289,13 +291,27 @@ const queries = Queue() | ||
function data(x) { | ||
buffer = buffer.length === 0 | ||
? x | ||
: Buffer.concat([buffer, x], buffer.length + x.length) | ||
if (buffers) { | ||
buffers.push(x) | ||
remaining -= x.length | ||
if (remaining >= 0) | ||
return | ||
} | ||
buffer = buffers | ||
? Buffer.concat(buffers, length - remaining) | ||
: buffer.length === 0 | ||
? x | ||
: Buffer.concat([buffer, x], buffer.length + x.length) | ||
while (buffer.length > 4) { | ||
length = buffer.readInt32BE(1) | ||
if (length >= buffer.length) | ||
if (length >= buffer.length) { | ||
remaining = length - buffer.length | ||
buffers = [buffer] | ||
break | ||
} | ||
backend[buffer[0]](buffer.slice(0, length + 1)) | ||
buffer = buffer.slice(length + 1) | ||
remaining = 0 | ||
buffers = null | ||
} | ||
@@ -302,0 +318,0 @@ } |
@@ -6,2 +6,3 @@ const fs = require('fs') | ||
const Queue = require('./queue.js') | ||
const Subscribe = require('./subscribe.js') | ||
const { errors, PostgresError } = require('./errors.js') | ||
@@ -12,7 +13,10 @@ const { | ||
arrayParser, | ||
fromPascal, | ||
fromCamel, | ||
fromKebab, | ||
inferType, | ||
toPascal, | ||
entries, | ||
toCamel, | ||
toKebab, | ||
entries, | ||
escape, | ||
@@ -35,5 +39,9 @@ types, | ||
Object.assign(Postgres, { | ||
PostgresError, | ||
toPascal, | ||
toCamel, | ||
toKebab, | ||
fromPascal, | ||
fromCamel, | ||
fromKebab, | ||
BigInt: { | ||
@@ -58,2 +66,4 @@ to: 20, | ||
const max = Math.max(1, options.max) | ||
, subscribe = Subscribe(Postgres, a, b) | ||
, transform = options.transform | ||
, connections = Queue() | ||
@@ -81,2 +91,3 @@ , all = [] | ||
parameters: {}, | ||
subscribe, | ||
listen, | ||
@@ -259,3 +270,3 @@ begin, | ||
new Promise((resolve, reject) => { | ||
send(connection, { resolve, reject, tagged: false, prepare: false, origin: new Error().stack }, ` | ||
send(connection, { resolve, reject, simple: true, tagged: false, prepare: false, origin: new Error().stack }, ` | ||
select b.oid, b.typarray | ||
@@ -398,4 +409,4 @@ from pg_catalog.pg_type a | ||
destroy(error, callback) { | ||
callback(error) | ||
query.writable.push({ error }) | ||
callback(error) | ||
}, | ||
@@ -474,6 +485,12 @@ final(callback) { | ||
return ended = Promise.race([ | ||
Promise.resolve(arrayTypesPromise).then(() => Promise.all(all.map(c => c.end()))) | ||
Promise.resolve(arrayTypesPromise).then(() => Promise.all( | ||
(subscribe.sql ? [subscribe.sql.end({ timeout: 0 })] : []).concat(all.map(c => c.end())) | ||
)) | ||
].concat( | ||
timeout === 0 || timeout > 0 | ||
? new Promise(r => destroy = setTimeout(() => (all.map(c => c.destroy()), r()), timeout * 1000)) | ||
? new Promise(r => destroy = setTimeout(() => ( | ||
subscribe.sql && subscribe.sql.end({ timeout }), | ||
all.map(c => c.destroy()), | ||
r() | ||
), timeout * 1000)) | ||
: [] | ||
@@ -548,3 +565,5 @@ )) | ||
acc + (!columns.length || columns.indexOf(k) > -1 | ||
? (acc ? ',' : '') + parseValue(v, xargs, types) + ' as ' + escape(k) | ||
? (acc ? ',' : '') + parseValue(v, xargs, types) + ' as ' + escape( | ||
transform.column.to ? transform.column.to(k) : k | ||
) | ||
: '' | ||
@@ -570,3 +589,5 @@ ), | ||
return (columns.length ? columns : Object.keys(first)).reduce((acc, k) => | ||
acc + (acc ? ',' : '') + escape(k) + ' = ' + parseValue(first[k], xargs, types), | ||
acc + (acc ? ',' : '') + escape( | ||
transform.column.to ? transform.column.to(k) : k | ||
) + ' = ' + parseValue(first[k], xargs, types), | ||
'' | ||
@@ -577,3 +598,5 @@ ) | ||
function escapeHelper(xs) { | ||
return xs.reduce((acc, x) => acc + (acc ? ',' : '') + escape(x), '') | ||
return xs.reduce((acc, x) => acc + (acc ? ',' : '') + escape( | ||
transform.column.to ? transform.column.to(x) : x | ||
), '') | ||
} | ||
@@ -642,3 +665,3 @@ | ||
onparameter : o.onparameter, | ||
transform : Object.assign({}, o.transform), | ||
transform : parseTransform(o.transform || {}), | ||
connection : Object.assign({ application_name: 'postgres.js' }, o.connection), | ||
@@ -653,2 +676,19 @@ target_session_attrs: o.target_session_attrs || url.query.target_session_attrs || env.PGTARGETSESSIONATTRS, | ||
function parseTransform(x) { | ||
return { | ||
column: { | ||
from: typeof x.column === 'function' ? x.column : x.column && x.column.from, | ||
to: x.column && x.column.to | ||
}, | ||
value: { | ||
from: typeof x.value === 'function' ? x.value : x.value && x.value.from, | ||
to: x.value && x.value.to | ||
}, | ||
row: { | ||
from: typeof x.row === 'function' ? x.row : x.row && x.row.from, | ||
to: x.row && x.row.to | ||
} | ||
} | ||
} | ||
function parseSSL(x) { | ||
@@ -655,0 +695,0 @@ return x !== 'disable' && x !== 'false' && x |
@@ -38,3 +38,3 @@ const char = module.exports.char = (acc, [k, v]) => (acc[k.charCodeAt(0)] = v, acc) | ||
from: 17, | ||
serialize: x => '\\x' + x.toString('hex'), | ||
serialize: x => '\\x' + Buffer.from(x.buffer, x.byteOffset, x.byteLength).toString('hex'), | ||
parse: x => Buffer.from(x.slice(2), 'hex') | ||
@@ -176,2 +176,6 @@ } | ||
module.exports.fromCamel = x => x.replace(/([A-Z])/g, '_$1').toLowerCase() | ||
module.exports.fromPascal = x => (x.slice(0, 1) + x.slice(1).replace(/([A-Z])/g, '_$1')).toLowerCase() | ||
module.exports.fromKebab = x => x.replace(/-/g, '_') | ||
module.exports.errorFields = entries({ | ||
@@ -178,0 +182,0 @@ S: 'severity_local', |
{ | ||
"name": "postgres", | ||
"version": "2.0.0-beta.8", | ||
"version": "2.0.0-beta.9", | ||
"description": "Fastest full featured PostgreSQL client for Node.js", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
185
README.md
@@ -48,30 +48,30 @@ <img align="left" width="440" height="140" alt="Fastest full PostgreSQL nodejs client" src="https://raw.githubusercontent.com/porsager/postgres/master/postgresjs.svg?sanitize=true" /> | ||
const sql = postgres('postgres://username:password@host:port/database', { | ||
host : '', // Postgres ip address[s] or domain name[s] | ||
port : 5432, // Postgres server port[s] | ||
path : '', // unix socket path (usually '/tmp') | ||
database : '', // Name of database to connect to | ||
username : '', // Username of database user | ||
password : '', // Password of database user | ||
ssl : false, // true, prefer, require, tls.connect options | ||
max : 10, // Max number of connections | ||
idle_timeout : 0, // Idle connection timeout in seconds | ||
connect_timeout : 30, // Connect timeout in seconds | ||
no_prepare : false, // No automatic creation of prepared statements | ||
types : [], // Array of custom types, see more below | ||
onnotice : fn // Defaults to console.log | ||
onparameter : fn // (key, value) when server param change | ||
debug : fn // Is called with (connection, query, params) | ||
transform : { | ||
column : fn, // Transforms incoming column names | ||
value : fn, // Transforms incoming row values | ||
row : fn // Transforms entire rows | ||
host : '', // Postgres ip address[s] or domain name[s] | ||
port : 5432, // Postgres server port[s] | ||
path : '', // unix socket path (usually '/tmp') | ||
database : '', // Name of database to connect to | ||
username : '', // Username of database user | ||
password : '', // Password of database user | ||
ssl : false, // true, prefer, require, tls.connect options | ||
max : 10, // Max number of connections | ||
idle_timeout : 0, // Idle connection timeout in seconds | ||
connect_timeout : 30, // Connect timeout in seconds | ||
no_prepare : false, // No automatic creation of prepared statements | ||
types : [], // Array of custom types, see more below | ||
onnotice : fn, // Defaults to console.log | ||
onparameter : fn, // (key, value) when server param change | ||
debug : fn, // Is called with (connection, query, params) | ||
transform : { | ||
column : fn, // Transforms incoming column names | ||
value : fn, // Transforms incoming row values | ||
row : fn // Transforms entire rows | ||
}, | ||
connection : { | ||
application_name : 'postgres.js', // Default application_name | ||
... // Other connection parameters | ||
connection : { | ||
application_name : 'postgres.js', // Default application_name | ||
... // Other connection parameters | ||
}, | ||
target_session_attrs : null // Use 'read-write' with multiple hosts to | ||
// ensure only connecting to primary | ||
fetch_array_types : true // Disable automatically fetching array types | ||
// on initial connection. | ||
target_session_attrs : null, // Use 'read-write' with multiple hosts to | ||
// ensure only connecting to primary | ||
fetch_array_types : true, // Disable automatically fetching array types | ||
// on initial connection. | ||
}) | ||
@@ -147,5 +147,41 @@ ``` | ||
#### Query parameters | ||
#### TypeScript support | ||
Parameters are automatically inferred and handled by Postgres so that SQL injection isn't possible. No special handling is necessary, simply use JS tagged template literals as usual. | ||
```js | ||
let search = 'Mur' | ||
const users = await sql` | ||
select | ||
name, | ||
age | ||
from users | ||
where | ||
name like ${ search + '%' } | ||
` | ||
// users = [{ name: 'Murray', age: 68 }] | ||
``` | ||
> Be careful with quotation marks here. Because Postgres infers the types, you don't need to wrap your interpolated parameters in quotes like `'${name}'`. In fact, this will cause an error because the tagged template replaces `${name}` with `$1` in the query string, leaving Postgres to do the interpolation. If you wrap that in a string, Postgres will see `'$1'` and interpret it as a string as opposed to a parameter. | ||
#### Arrays | ||
Arrays will be handled by replacement parameters too, so `where in` queries are also simple. | ||
```js | ||
const users = await sql` | ||
select | ||
* | ||
from users | ||
where age in (${ [68, 75, 23] }) | ||
` | ||
``` | ||
### TypeScript support | ||
`postgres` has TypeScript support. You can pass a row list type for your queries in this way: | ||
@@ -188,36 +224,2 @@ ```ts | ||
#### Query parameters | ||
Parameters are automatically inferred and handled by Postgres so that SQL injection isn't possible. No special handling is necessary, simply use JS tagged template literals as usual. | ||
```js | ||
let search = 'Mur' | ||
const users = await sql` | ||
select | ||
name, | ||
age | ||
from users | ||
where | ||
name like ${ search + '%' } | ||
` | ||
// users = [{ name: 'Murray', age: 68 }] | ||
``` | ||
Arrays will be handled by replacement parameters too, so `where in` queries are also simple. | ||
```js | ||
const users = await sql` | ||
select | ||
* | ||
from users | ||
where age in (${ [68, 75, 23] }) | ||
` | ||
``` | ||
## Stream ```sql` `.stream(fn) -> Promise``` | ||
@@ -467,2 +469,48 @@ | ||
## Subscribe / Realtime | ||
Postgres.js implements the logical replication protocol of PostgreSQL to support subscription to realtime updates of `insert`, `update` and `delete` operations. | ||
> **NOTE** To make this work you must [create the proper publications in your database](https://www.postgresql.org/docs/current/sql-createpublication.html), enable logical replication by setting `wal_level = logical` in `postgresql.conf` and connect using either a replication or superuser. | ||
### Quick start | ||
#### Create a publication (eg. in migration) | ||
```sql | ||
CREATE PUBLICATION alltables FOR ALL TABLES | ||
``` | ||
#### Subscribe to updates | ||
```js | ||
const sql = postgres({ publications: 'alltables' }) | ||
const { unsubscribe } = await sql.subscribe('insert:events', row => | ||
// tell about new event row over eg. websockets or do something else | ||
) | ||
``` | ||
### Subscribe pattern | ||
You can subscribe to specific operations, tables or even rows with primary keys. | ||
### `operation` `:` `schema` `.` `table` `=` `primary_key` | ||
**`operation`** is one of ``` * | insert | update | delete ``` and defaults to `*` | ||
**`schema`** defaults to `public.` | ||
**`table`** is a specific table name and defaults to `*` | ||
**`primary_key`** can be used to only subscribe to specific rows | ||
#### Examples | ||
```js | ||
sql.subscribe('*', () => /* everything */ ) | ||
sql.subscribe('insert', () => /* all inserts */ ) | ||
sql.subscribe('*:users', () => /* all operations on the public.users table */ ) | ||
sql.subscribe('delete:users', () => /* all deletes on the public.users table */ ) | ||
sql.subscribe('update:users=1', () => /* all updates on the users row with a primary key = 1 */ ) | ||
``` | ||
## Transactions | ||
@@ -538,6 +586,7 @@ | ||
## Types | ||
You can add ergonomic support for custom types, or simply pass an object with a `{ type, value }` signature that contains the Postgres `oid` for the type and the correctly serialized value. | ||
## Custom Types | ||
You can add ergonomic support for custom types, or simply pass an object with a `{ type, value }` signature that contains the Postgres `oid` for the type and the correctly serialized value. _(`oid` values for types can be found in the `pg_catalog.pg_types` table.)_ | ||
Adding Query helpers is the recommended approach which can be done like this: | ||
@@ -547,8 +596,15 @@ | ||
const sql = sql({ | ||
const sql = postgres({ | ||
types: { | ||
rect: { | ||
// The pg_types oid to pass to the db along with the serialized value. | ||
to : 1337, | ||
// An array of pg_types oids to handle when parsing values coming from the db. | ||
from : [1337], | ||
//Function that transform values before sending them to the db. | ||
serialize : ({ x, y, width, height }) => [x, y, width, height], | ||
// Function that transforms values coming from the db. | ||
parse : ([x, y, width, height]) => { x, y, width, height } | ||
@@ -559,2 +615,3 @@ } | ||
// Now you can use sql.types.rect() as specified above | ||
const [custom] = sql` | ||
@@ -561,0 +618,0 @@ insert into rectangles ( |
@@ -106,6 +106,6 @@ /** | ||
type PostgresErrorType = PostgresError | ||
type PostgresErrorType = typeof PostgresError | ||
declare namespace postgres { | ||
type PostgresError = PostgresErrorType | ||
export const PostgresError: PostgresErrorType; | ||
@@ -112,0 +112,0 @@ /** |
96898
14
2279
782