Interface PostgreSQL para Node.js
Construída sobre node-postgres, esta biblioteca adiciona o seguinte:
No seu início em 2015, esta biblioteca apenas adicionava promessas ao driver base, daí o nome pg-promise
. E embora o nome original tenha sido mantido, a funcionalidade da biblioteca foi amplamente ampliada, com as promessas sendo agora apenas uma pequena parte.
Ofereço suporte gratuito aqui e no StackOverflow.
E se você quiser ajudar neste projeto, posso aceitar Bitcoin: 1yki7MXMkuDw8qqe5icVdh1GJZSQSzKZp
O capítulo Uso abaixo explica o básico que você precisa saber, enquanto a Documentação Oficial ajuda você a começar e fornece links para todos os outros recursos.
Por favor, leia as Notas de Contribuição antes de abrir qualquer nova edição ou PR.
Depois de criar um objeto Banco de Dados, de acordo com os passos da Documentação Oficial, você terá acesso aos métodos documentados abaixo.
Todos os métodos de consulta da biblioteca são baseados em métodos genéricos de consulta.
Normalmente, você deve usar apenas os métodos derivados e específicos de resultados para executar consultas, todos nomeados de acordo com quantas linhas de dados a consulta deve retornar, portanto, para cada consulta, você deve escolher o método correto: nenhum, um, umOuNone, muitos, muitosOuNone = qualquer. Não confunda o nome do método com o número de linhas a serem afetadas pela consulta, o que é completamente irrelevante.
Ao confiar nos métodos específicos de resultados, você protege seu código contra um número inesperado de linhas de dados, que serão rejeitadas automaticamente (tratadas como erros).
Existem também alguns métodos específicos que você precisará com frequência:
O protocolo é totalmente personalizável/extensível por meio de extensão de evento.
IMPORTANTE:
Os métodos mais importantes para entender desde o início são task e tx/txIf (consulte Tarefas e Transações). Conforme documentado para o método de consulta, ele adquire e libera a conexão, o que o torna uma escolha ruim para executar várias consultas ao mesmo tempo. Por esse motivo, Chaining Queries é uma leitura obrigatória, para evitar escrever o código que faz uso indevido de conexões.
Aprenda por exemplo é um tutorial para iniciantes baseado em exemplos.
Esta biblioteca vem com mecanismo de formatação de consulta integrado que oferece escape de valor de alto desempenho, flexibilidade e extensibilidade. Ele é usado por padrão com todos os métodos de consulta, a menos que você desative-o totalmente por meio da opção pgFormatting
em Opções de inicialização.
Todos os métodos de formatação usados internamente estão disponíveis no namespace de formatação, portanto, também podem ser usados diretamente quando necessário. O método principal é o formato, usado por todos os métodos de consulta para formatar a consulta.
A sintaxe de formatação para variáveis é decidida a partir do tipo de values
passados:
values
são uma matriz ou um único tipo básico;values
são um objeto (diferente de Array
ou null
).ATENÇÃO: Nunca use strings de template ES6 ou concatenação manual para gerar consultas, pois ambos podem facilmente resultar em consultas quebradas! Somente o mecanismo de formatação desta biblioteca sabe como escapar adequadamente dos valores das variáveis para o PostgreSQL.
A formatação mais simples (clássica) usa a sintaxe $1, $2, ...
para injetar valores na string de consulta, com base em seu índice (de $1
a $100000
) da matriz de valores:
await db . any ( 'SELECT * FROM product WHERE price BETWEEN $1 AND $2' , [ 1 , 10 ] )
O mecanismo de formatação também oferece suporte à parametrização de valor único para consultas que usam apenas a variável $1
:
await db . any ( 'SELECT * FROM users WHERE name = $1' , 'John' )
No entanto, isso funciona apenas para os tipos number
, bigint
, string
, boolean
, Date
e null
, porque tipos como Array
e Object
alteram a forma como os parâmetros são interpretados. É por isso que passar variáveis de índice dentro de um array é considerado mais seguro, para evitar ambigüidades.
Quando um método de consulta é parametrizado com values
como um objeto, o mecanismo de formatação espera que a consulta use a sintaxe de parâmetro nomeado $*propName*
, com *
sendo qualquer um dos seguintes pares de abertura e fechamento: {}
, ()
, <>
, []
, //
.
// We can use every supported variable syntax at the same time, if needed:
await db . none ( 'INSERT INTO users(first_name, last_name, age) VALUES(${name.first}, $, $/age/)' , {
name : { first : 'John' , last : 'Dow' } ,
age : 30
} ) ;
IMPORTANTE: Nunca use a sintaxe reservada ${}
dentro de strings de template ES6, pois eles não têm conhecimento de como formatar valores para PostgreSQL. Dentro das strings de modelo ES6 você deve usar apenas uma das 4 alternativas - $()
, $<>
, $[]
ou $//
. Em geral, você deve usar as strings padrão para SQL ou colocar o SQL em arquivos externos - consulte Arquivos de Consulta.
Os nomes de variáveis válidos são limitados à sintaxe das variáveis JavaScript de nome aberto. E o nome this
tem um significado especial - refere-se ao próprio objeto de formatação (veja abaixo).
Lembre-se de que, embora os valores de propriedade null
e undefined
sejam formatados como null
, um erro será gerado quando a propriedade não existir.
this
referência
Propriedade this
refere-se ao próprio objeto de formatação, a ser inserido como uma string formatada em JSON.
await db . none ( 'INSERT INTO documents(id, doc) VALUES(${id}, ${this})' , {
id : 123 ,
body : 'some text'
} )
//=> INSERT INTO documents(id, doc) VALUES(123, '{"id":123,"body":"some text"}')
Parâmetros nomeados suportam aninhamento de nomes de propriedades de qualquer profundidade.
const obj = {
one : {
two : {
three : {
value1 : 123 ,
value2 : a => {
// a = obj.one.two.three
return 'hello' ;
} ,
value3 : function ( a ) {
// a = this = obj.one.two.three
return 'world' ;
} ,
value4 : {
toPostgres : a => {
// Custom Type Formatting
// a = obj.one.two.three.value4
return a . text ;
} ,
text : 'custom'
}
}
}
}
} ;
await db . one ( 'SELECT ${one.two.three.value1}' , obj ) ; //=> SELECT 123
await db . one ( 'SELECT ${one.two.three.value2}' , obj ) ; //=> SELECT 'hello'
await db . one ( 'SELECT ${one.two.three.value3}' , obj ) ; //=> SELECT 'world'
await db . one ( 'SELECT ${one.two.three.value4}' , obj ) ; //=> SELECT 'custom'
O sobrenome na resolução pode ser qualquer coisa, incluindo:
ou seja, a cadeia de resolução é infinitamente flexível e suporta recursão sem limites.
Observe, entretanto, que parâmetros aninhados não são suportados no namespace dos auxiliares.
Por padrão, todos os valores são formatados de acordo com seu tipo JavaScript. Filtros de formatação (ou modificadores), altere isso, para que o valor seja formatado de forma diferente.
Observe que os filtros de formatação funcionam apenas para consultas normais e não estão disponíveis em PreparedStatement ou ParameterizedQuery, porque são, por definição, formatados no lado do servidor.
Os filtros usam a mesma sintaxe para variáveis de índice e parâmetros nomeados, seguindo imediatamente o nome da variável:
await db . any ( 'SELECT $1:name FROM $2:name' , [ 'price' , 'products' ] )
//=> SELECT "price" FROM "products"
await db . any ( 'SELECT ${column:name} FROM ${table:name}' , {
column : 'price' ,
table : 'products'
} ) ;
//=> SELECT "price" FROM "products"
Os seguintes filtros são suportados:
:name
/ ~
- Nomes SQL:alias
- Filtro de Alias:raw
/ ^
- Texto bruto:value
/ #
- Valores abertos:csv
/ :list
- Filtro CSV:json
- Filtro JSON Quando um nome de variável termina com :name
, ou sintaxe mais curta ~
(til), ele representa um nome ou identificador SQL, para ser escapado de acordo:
await db . query ( 'INSERT INTO $1~($2~) VALUES(...)' , [ 'Table Name' , 'Column Name' ] ) ;
//=> INSERT INTO "Table Name"("Column Name") VALUES(...)
await db . query ( 'INSERT INTO $1:name($2:name) VALUES(...)' , [ 'Table Name' , 'Column Name' ] ) ;
//=> INSERT INTO "Table Name"("Column Name") VALUES(...)
Normalmente, uma variável de nome SQL é uma sequência de texto que deve ter pelo menos 1 caractere. No entanto, pg-promise
oferece suporte a diversas maneiras pelas quais os nomes SQL podem ser fornecidos:
*
(asteriscos) é automaticamente reconhecida como todas as colunas : await db . query ( 'SELECT $1:name FROM $2:name' , [ '*' , 'table' ] ) ;
//=> SELECT * FROM "table"
await db . query ( 'SELECT ${columns:name} FROM ${table:name}' , {
columns : [ 'column1' , 'column2' ] ,
table : 'table'
} ) ;
//=> SELECT "column1","column2" FROM "table"
const obj = {
one : 1 ,
two : 2
} ;
await db . query ( 'SELECT $1:name FROM $2:name' , [ obj , 'table' ] ) ;
//=> SELECT "one","two" FROM "table"
Além disso, a sintaxe suporta this
para enumerar nomes de colunas do objeto de formatação:
const obj = {
one : 1 ,
two : 2
} ;
await db . query ( 'INSERT INTO table(${this:name}) VALUES(${this:csv})' , obj ) ;
//=> INSERT INTO table("one","two") VALUES(1, 2)
Contar com esse tipo de formatação para nomes e identificadores SQL, juntamente com a formatação regular de variáveis, protege seu aplicativo contra injeção de SQL.
O método as.name implementa a formatação.
Um alias é uma versão mais simples e menos estrita do filtro :name
, que suporta apenas uma string de texto, ou seja, não suporta *
, this
, array ou objeto como entradas, como :name
faz. No entanto, suporta outros casos populares que são menos rigorosos, mas que cobrem pelo menos 99% de todos os casos de uso, conforme mostrado abaixo.
await db . any ( 'SELECT full_name as $1:alias FROM $2:name' , [ 'name' , 'table' ] ) ;
//=> SELECT full_name as name FROM "table"
.
e, em seguida, escape cada parte separadamente, suportando nomes SQL autocompostos: await db . any ( 'SELECT * FROM $1:alias' , [ 'schemaName.table' ] ) ;
//=> SELECT * FROM "schemaName".table
Para mais detalhes veja o método as.alias que implementa a formatação.
Quando o nome de uma variável termina com :raw
, ou sintaxe mais curta ^
, o valor deve ser injetado como texto bruto, sem escape.
Essas variáveis não podem ser null
ou undefined
, devido ao significado ambíguo neste caso, e esses valores gerarão erros. Values null/undefined cannot be used as raw text.
const where = pgp . as . format ( 'WHERE price BETWEEN $1 AND $2' , [ 5 , 10 ] ) ; // pre-format WHERE condition
await db . any ( 'SELECT * FROM products $1:raw' , where ) ;
//=> SELECT * FROM products WHERE price BETWEEN 5 AND 10
Sintaxe especial this:raw
/ this^
é suportada, para injetar o objeto de formatação como string JSON bruta.
AVISO:
Este filtro não é seguro e não deve ser usado para valores provenientes do lado do cliente, pois pode resultar em injeção de SQL.
Quando o nome de uma variável termina com :value
, ou sintaxe mais curta #
, ela é escapada normalmente, exceto quando seu tipo é uma string, as aspas finais não são adicionadas.
Os valores abertos servem principalmente para poder compor instruções dinâmicas LIKE
/ ILIKE
completas em arquivos SQL externos, sem precisar gerá-las no código.
ou seja, você pode gerar um filtro como este em seu código:
const name = 'John' ;
const filter = '%' + name + '%' ;
e depois passá-lo como uma variável de string regular, ou você pode passar apenas name
e fazer com que sua consulta use a sintaxe de valor aberto para adicionar a lógica de pesquisa extra:
SELECT * FROM table WHERE name LIKE ' %$1:value% ' )
AVISO:
Este filtro não é seguro e não deve ser usado para valores provenientes do lado do cliente, pois pode resultar em injeção de SQL.
O método as.value implementa a formatação.
Quando o nome de uma variável termina com :json
, a formatação JSON explícita é aplicada ao valor.
Por padrão, qualquer objeto que não seja Date
, Array
, Buffer
, null
ou Custom-Type (consulte Formatação de tipo personalizado) é automaticamente formatado como JSON.
O método as.json implementa a formatação.
Quando o nome de uma variável termina com :csv
ou :list
, ela é formatada como uma lista de valores separados por vírgula, com cada valor formatado de acordo com seu tipo JavaScript.
Normalmente, você usaria isso para um valor que é uma matriz, embora também funcione para valores únicos. Veja os exemplos abaixo.
const ids = [ 1 , 2 , 3 ] ;
await db . any ( 'SELECT * FROM table WHERE id IN ($1:csv)' , [ ids ] )
//=> SELECT * FROM table WHERE id IN (1,2,3)
const ids = [ 1 , 2 , 3 ] ;
await db . any ( 'SELECT * FROM table WHERE id IN ($1:list)' , [ ids ] )
//=> SELECT * FROM table WHERE id IN (1,2,3)
Usando enumeração automática de propriedades:
const obj = { first : 123 , second : 'text' } ;
await db . none ( 'INSERT INTO table($1:name) VALUES($1:csv)' , [ obj ] )
//=> INSERT INTO table("first","second") VALUES(123,'text')
await db . none ( 'INSERT INTO table(${this:name}) VALUES(${this:csv})' , obj )
//=> INSERT INTO table("first","second") VALUES(123,'text')
const obj = { first : 123 , second : 'text' } ;
await db . none ( 'INSERT INTO table($1:name) VALUES($1:list)' , [ obj ] )
//=> INSERT INTO table("first","second") VALUES(123,'text')
await db . none ( 'INSERT INTO table(${this:name}) VALUES(${this:list})' , obj )
//=> INSERT INTO table("first","second") VALUES(123,'text')
O método as.csv implementa a formatação.
A biblioteca suporta sintaxe dupla para CTF (Custom Type Formatting):
A biblioteca sempre verifica primeiro o CTF Simbólico e, se nenhuma sintaxe for usada, só então verifica o CTF Explícito.
Qualquer valor/objeto que implemente a função toPostgres
é tratado como um tipo de formatação personalizada. A função é então chamada para obter o valor real, passando o objeto por meio this
contexto e plus como um único parâmetro (caso toPostgres
seja uma função de seta ES6):
const obj = {
toPostgres ( self ) {
// self = this = obj
// return a value that needs proper escaping
}
}
A função toPostgres
pode retornar qualquer coisa, incluindo outro objeto com sua própria função toPostgres
, ou seja, tipos personalizados aninhados são suportados.
O valor retornado de toPostgres
é escapado de acordo com seu tipo JavaScript, a menos que o objeto também contenha a propriedade rawType
definida como um valor verdadeiro, caso em que o valor retornado é considerado pré-formatado e, portanto, injetado diretamente, como Texto Bruto:
const obj = {
toPostgres ( self ) {
// self = this = obj
// return a pre-formatted value that does not need escaping
} ,
rawType : true // use result from toPostgres directly, as Raw Text
}
O exemplo abaixo implementa uma classe que formata automaticamente ST_MakePoint
a partir de coordenadas:
class STPoint {
constructor ( x , y ) {
this . x = x ;
this . y = y ;
this . rawType = true ; // no escaping, because we return pre-formatted SQL
}
toPostgres ( self ) {
return pgp . as . format ( 'ST_MakePoint($1, $2)' , [ this . x , this . y ] ) ;
}
}
E uma sintaxe clássica para tal classe é ainda mais simples:
function STPoint ( x , y ) {
this . rawType = true ; // no escaping, because we return pre-formatted SQL
this . toPostgres = ( ) => pgp . as . format ( 'ST_MakePoint($1, $2)' , [ x , y ] ) ;
}
Com esta classe você pode usar new STPoint(12, 34)
como valor de formatação que será injetado corretamente.
Você também pode usar CTF para substituir qualquer tipo padrão:
Date . prototype . toPostgres = a => a . getTime ( ) ;
A única diferença do CTF explícito é que definimos toPostgres
e rawType
como propriedades do símbolo ES6, definidas no namespace ctf:
const { toPostgres , rawType } = pgp . as . ctf ; // Global CTF symbols
const obj = {
[ toPostgres ] ( self ) {
// self = this = obj
// return a pre-formatted value that does not need escaping
} ,
[ rawType ] : true // use result from toPostgres directly, as Raw Text
} ;
Como os símbolos CTF são globais, você também pode configurar objetos independentemente desta biblioteca:
const ctf = {
toPostgres : Symbol . for ( 'ctf.toPostgres' ) ,
rawType : Symbol . for ( 'ctf.rawType' )
} ;
Fora isso, funciona exatamente como o CTF Explícito, mas sem alterar a assinatura do objeto.
Se você não sabe o que isso significa, leia a API de símbolos ES6 e seu uso para nomes de propriedades exclusivos. Mas, resumindo, as propriedades do Símbolo não são enumeradas via for(name in obj)
, ou seja, geralmente não são visíveis no JavaScript, apenas através da API específica Object.getOwnPropertySymbols
.
O uso de arquivos SQL externos (via QueryFile) oferece muitas vantagens:
debug
), sem reiniciar o aplicativo;params
), automatizando a formatação SQL em duas etapas;minify
+ compress
), para detecção precoce de erros e consultas compactas. const { join : joinPath } = require ( 'path' ) ;
// Helper for linking to external query files:
function sql ( file ) {
const fullPath = joinPath ( __dirname , file ) ;
return new pgp . QueryFile ( fullPath , { minify : true } ) ;
}
// Create a QueryFile globally, once per file:
const sqlFindUser = sql ( './sql/findUser.sql' ) ;
db . one ( sqlFindUser , { id : 123 } )
. then ( user => {
console . log ( user ) ;
} )
. catch ( error => {
if ( error instanceof pgp . errors . QueryFileError ) {
// => the error is related to our QueryFile
}
} ) ;
Arquivo findUser.sql
:
/*
multi-line comments are supported
*/
SELECT name, dob -- single-line comments are supported
FROM Users
WHERE id = ${id}
Cada método de consulta da biblioteca pode aceitar o tipo QueryFile como parâmetro query
. O tipo QueryFile nunca gera nenhum erro, deixando que os métodos de consulta sejam rejeitados normalmente com QueryFileError.
O uso de parâmetros nomeados em arquivos SQL externos é recomendado em vez de variáveis de índice, porque torna o SQL muito mais fácil de ler e entender e porque também permite parâmetros nomeados aninhados, para que variáveis em um arquivo SQL grande e complexo possam ser agrupadas em namespaces para uma separação visual ainda mais fácil.
Uma tarefa representa uma conexão compartilhada para executar múltiplas consultas:
db . task ( t => {
// execute a chain of queries against the task context, and return the result:
return t . one ( 'SELECT count(*) FROM events WHERE id = $1' , 123 , a => + a . count )
. then ( count => {
if ( count > 0 ) {
return t . any ( 'SELECT * FROM log WHERE event_id = $1' , 123 )
. then ( logs => {
return { count , logs } ;
} )
}
return { count } ;
} ) ;
} )
. then ( data => {
// success, data = either {count} or {count, logs}
} )
. catch ( error => {
// failed
} ) ;
As tarefas fornecem um contexto de conexão compartilhado para sua função de retorno de chamada, a ser liberada quando finalizada, e devem ser utilizadas sempre que executar mais de uma consulta por vez. Consulte também Encadeamento de consultas para entender a importância do uso de tarefas.
Opcionalmente, você pode marcar tarefas (consulte Tags) e usar a sintaxe assíncrona ES7:
db . task ( async t => {
const count = await t . one ( 'SELECT count(*) FROM events WHERE id = $1' , 123 , a => + a . count ) ;
if ( count > 0 ) {
const logs = await t . any ( 'SELECT * FROM log WHERE event_id = $1' , 123 ) ;
return { count