Interface PostgreSQL pour Node.js
Construite sur node-postgres, cette bibliothèque ajoute les éléments suivants :
Lors de sa création en 2015, cette bibliothèque ajoutait uniquement des promesses au pilote de base, d'où le nom pg-promise
. Et même si le nom d'origine a été conservé, les fonctionnalités de la bibliothèque ont été considérablement étendues, les promesses ne représentant désormais qu'une infime partie.
Je fais du support gratuit ici et sur StackOverflow.
Et si vous souhaitez aider ce projet, je peux accepter Bitcoin : 1yki7MXMkuDw8qqe5icVdh1GJZSQSzKZp
Le chapitre Utilisation ci-dessous explique les bases que vous devez connaître, tandis que la documentation officielle vous aide à démarrer et fournit des liens vers toutes les autres ressources.
Veuillez lire les notes de contribution avant d'ouvrir tout nouveau numéro ou PR.
Une fois que vous avez créé un objet Base de données, selon les étapes de la Documentation officielle, vous avez accès aux méthodes documentées ci-dessous.
Toutes les méthodes de requête de la bibliothèque sont basées sur une requête de méthode générique.
Vous devez normalement utiliser uniquement les méthodes dérivées spécifiques aux résultats pour exécuter des requêtes, qui sont toutes nommées en fonction du nombre de lignes de données que la requête est censée renvoyer. Ainsi, pour chaque requête, vous devez choisir la bonne méthode : aucune, une, oneOrNone, many, manyOrNone = n'importe lequel. Ne confondez pas le nom de la méthode avec le nombre de lignes affectées par la requête, ce qui n'a aucune importance.
En vous appuyant sur les méthodes spécifiques aux résultats, vous protégez votre code d'un nombre inattendu de lignes de données, qui seront automatiquement rejetées (traitées comme des erreurs).
Il existe également quelques méthodes spécifiques dont vous aurez souvent besoin :
Le protocole est entièrement personnalisable/extensible via l'extension d'événement.
IMPORTANT:
Les méthodes les plus importantes à comprendre dès le départ sont task et tx/txIf (voir Tâches et transactions). Comme indiqué pour la méthode de requête, elle acquiert et libère la connexion, ce qui en fait un mauvais choix pour exécuter plusieurs requêtes à la fois. Pour cette raison, Chaining Queries est une lecture incontournable, pour éviter d'écrire du code qui utilise mal les connexions.
Apprendre par l'exemple est un didacticiel pour débutants basé sur des exemples.
Cette bibliothèque est livrée avec un moteur de formatage de requêtes intégré qui offre un échappement de valeurs hautes performances, une flexibilité et une extensibilité. Il est utilisé par défaut avec toutes les méthodes de requête, sauf si vous le désactivez entièrement via l'option pgFormatting
dans les options d'initialisation.
Toutes les méthodes de formatage utilisées en interne sont disponibles à partir de l'espace de noms de formatage, elles peuvent donc également être utilisées directement en cas de besoin. La méthode principale est le format, utilisé par chaque méthode de requête pour formater la requête.
La syntaxe de formatage des variables dépend du type de values
transmis :
values
sont un tableau ou un seul type de base ;values
est un objet (autre que Array
ou null
).ATTENTION : n'utilisez jamais de chaînes de modèles ES6 ou de concaténation manuelle pour générer des requêtes, car les deux peuvent facilement entraîner des requêtes interrompues ! Seul le moteur de formatage de cette bibliothèque sait comment échapper correctement les valeurs des variables pour PostgreSQL.
Le formatage le plus simple (classique) utilise la syntaxe $1, $2, ...
pour injecter des valeurs dans la chaîne de requête, en fonction de leur index (de $1
à $100000
) à partir du tableau de valeurs :
await db . any ( 'SELECT * FROM product WHERE price BETWEEN $1 AND $2' , [ 1 , 10 ] )
Le moteur de formatage prend également en charge la paramétrisation à valeur unique pour les requêtes qui utilisent uniquement la variable $1
:
await db . any ( 'SELECT * FROM users WHERE name = $1' , 'John' )
Cela ne fonctionne cependant que pour les types number
, bigint
, string
, boolean
, Date
et null
, car des types comme Array
et Object
changent la façon dont les paramètres sont interprétés. C'est pourquoi il est conseillé de transmettre des variables d'index dans un tableau comme étant plus sûr, afin d'éviter les ambiguïtés.
Lorsqu'une méthode de requête est paramétrée avec values
en tant qu'objet, le moteur de formatage s'attend à ce que la requête utilise la syntaxe de paramètre nommé $*propName*
, *
étant l'une des paires ouverture-fermeture suivantes : {}
, ()
, <>
, []
, //
.
// 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
} ) ;
IMPORTANT : n'utilisez jamais la syntaxe réservée ${}
dans les chaînes de modèle ES6, car celles-ci ne savent pas comment formater les valeurs pour PostgreSQL. Dans les chaînes de modèle ES6, vous ne devez utiliser qu'une des 4 alternatives - $()
, $<>
, $[]
ou $//
. En général, vous devez soit utiliser les chaînes standard pour SQL, soit placer SQL dans des fichiers externes - voir Fichiers de requête.
Les noms de variables valides sont limités à la syntaxe des variables JavaScript à nom ouvert. Et this
nom a une signification particulière - il fait référence à l'objet de formatage lui-même (voir ci-dessous).
Gardez à l'esprit que même si les valeurs de propriété null
et undefined
sont toutes deux formatées comme null
, une erreur est générée lorsque la propriété n'existe pas.
this
référence
Propriété this
fait référence à l'objet de formatage lui-même, à insérer sous forme de chaîne au format 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"}')
Les paramètres nommés prennent en charge l’imbrication des noms de propriétés de n’importe quelle profondeur.
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'
Le nom de famille dans la résolution peut être n'importe quoi, notamment :
c'est-à-dire que la chaîne de résolution est infiniment flexible et prend en charge la récursivité sans limites.
Veuillez noter cependant que les paramètres imbriqués ne sont pas pris en charge dans l'espace de noms des assistants.
Par défaut, toutes les valeurs sont formatées en fonction de leur type JavaScript. Les filtres de formatage (ou modificateurs) modifient cela pour que la valeur soit formatée différemment.
Notez que les filtres de formatage ne fonctionnent que pour les requêtes normales et ne sont pas disponibles dans PreparedStatement ou ParameterizedQuery, car ceux-ci sont, par définition, formatés côté serveur.
Les filtres utilisent la même syntaxe pour les variables d'index et les paramètres nommés, en suivant immédiatement le nom de la variable :
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"
Les filtres suivants sont pris en charge :
:name
/ ~
- Noms SQL:alias
- Filtre d'alias:raw
/ ^
- Texte brut:value
/ #
- Valeurs ouvertes:csv
/ :list
- Filtre CSV:json
- Filtre JSON Lorsqu'un nom de variable se termine par :name
, ou une syntaxe plus courte ~
(tilde), il représente un nom ou un identifiant SQL, à échapper en conséquence :
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(...)
En règle générale, une variable de nom SQL est une chaîne de texte qui doit comporter au moins 1 caractère. Cependant, pg-promise
prend en charge diverses manières de fournir les noms SQL :
*
(astérisques) est automatiquement reconnue comme toutes les colonnes : 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"
De plus, la syntaxe prend en charge this
pour énumérer les noms de colonnes à partir de l'objet de formatage :
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)
S'appuyer sur ce type de formatage pour les noms et identifiants SQL, ainsi que sur le formatage régulier des variables, protège votre application contre l'injection SQL.
La méthode as.name implémente le formatage.
Un alias est une version plus simple et moins stricte du filtre :name
, qui ne prend en charge qu'une chaîne de texte, c'est-à-dire qu'il ne prend pas en charge *
, this
, un tableau ou un objet comme entrées, comme le fait :name
. Cependant, il prend en charge d'autres cas courants qui sont moins stricts, mais couvrent au moins 99 % de tous les cas d'utilisation, comme indiqué ci-dessous.
await db . any ( 'SELECT full_name as $1:alias FROM $2:name' , [ 'name' , 'table' ] ) ;
//=> SELECT full_name as name FROM "table"
.
, puis échappez chaque partie séparément, prenant ainsi en charge les noms SQL auto-composites : await db . any ( 'SELECT * FROM $1:alias' , [ 'schemaName.table' ] ) ;
//=> SELECT * FROM "schemaName".table
Pour plus de détails, voir la méthode as.alias qui implémente le formatage.
Lorsqu'un nom de variable se termine par :raw
, ou une syntaxe plus courte ^
, la valeur doit être injectée sous forme de texte brut, sans s'échapper.
De telles variables ne peuvent pas être null
ou undefined
, en raison de la signification ambiguë dans ce cas, et ces valeurs généreront une erreur. 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
La syntaxe spéciale this:raw
/ this^
est prise en charge, pour injecter l'objet de formatage sous forme de chaîne JSON brute.
AVERTISSEMENT:
Ce filtre n'est pas sécurisé et ne doit pas être utilisé pour les valeurs provenant du côté client, car il pourrait entraîner une injection SQL.
Lorsqu'un nom de variable se termine par :value
, ou une syntaxe plus courte #
, il est échappé comme d'habitude, sauf lorsque son type est une chaîne, les guillemets de fin ne sont pas ajoutés.
Les valeurs ouvertes visent principalement à pouvoir composer des instructions dynamiques LIKE
/ ILIKE
complètes dans des fichiers SQL externes, sans avoir à les générer dans le code.
c'est-à-dire que vous pouvez soit générer un filtre comme celui-ci dans votre code :
const name = 'John' ;
const filter = '%' + name + '%' ;
puis transmettez-la en tant que variable de chaîne normale, ou vous pouvez transmettre uniquement name
et demander à votre requête d'utiliser la syntaxe open-value pour ajouter la logique de recherche supplémentaire :
SELECT * FROM table WHERE name LIKE ' %$1:value% ' )
AVERTISSEMENT:
Ce filtre n'est pas sécurisé et ne doit pas être utilisé pour les valeurs provenant du côté client, car il pourrait entraîner une injection SQL.
La méthode as.value implémente le formatage.
Lorsqu'un nom de variable se termine par :json
, un formatage JSON explicite est appliqué à la valeur.
Par défaut, tout objet autre que Date
, Array
, Buffer
, null
ou Custom-Type (voir Formatage de type personnalisé) est automatiquement formaté en JSON.
La méthode as.json implémente le formatage.
Lorsqu'un nom de variable se termine par :csv
ou :list
, il est formaté sous la forme d'une liste de valeurs séparées par des virgules, chaque valeur étant formatée en fonction de son type JavaScript.
En règle générale, vous l'utiliserez pour une valeur qui est un tableau, bien que cela fonctionne également pour des valeurs uniques. Voir les exemples ci-dessous.
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)
Utilisation de l'énumération automatique des propriétés :
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')
La méthode as.csv implémente le formatage.
La bibliothèque prend en charge la double syntaxe pour CTF (Custom Type Formatting) :
La bibliothèque vérifie toujours d'abord le CTF symbolique, et si aucune syntaxe de ce type n'est utilisée, elle vérifie ensuite le CTF explicite.
Toute valeur/objet qui implémente la fonction toPostgres
est traité comme un type de formatage personnalisé. La fonction est ensuite appelée pour obtenir la valeur réelle, en lui transmettant l'objet via this
contexte, et plus en tant que paramètre unique (dans le cas où toPostgres
est une fonction fléchée ES6) :
const obj = {
toPostgres ( self ) {
// self = this = obj
// return a value that needs proper escaping
}
}
La fonction toPostgres
peut renvoyer n'importe quoi, y compris un autre objet avec sa propre fonction toPostgres
, c'est-à-dire que les types personnalisés imbriqués sont pris en charge.
La valeur renvoyée par toPostgres
est échappée en fonction de son type JavaScript, sauf si l'objet contient également la propriété rawType
définie sur une valeur véridique, auquel cas la valeur renvoyée est considérée comme pré-formatée, et donc injectée directement, sous forme de texte brut :
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
}
L'exemple ci-dessous implémente une classe qui formate automatiquement ST_MakePoint
à partir des coordonnées :
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 ] ) ;
}
}
Et une syntaxe classique pour une telle classe est encore plus simple :
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 ] ) ;
}
Avec cette classe, vous pouvez utiliser new STPoint(12, 34)
comme valeur de formatage qui sera injectée correctement.
Vous pouvez également utiliser CTF pour remplacer n'importe quel type standard :
Date . prototype . toPostgres = a => a . getTime ( ) ;
La seule différence avec Explicit CTF est que nous définissons toPostgres
et rawType
comme propriétés de symbole ES6, définies dans l'espace de noms 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
} ;
Les symboles CTF étant globaux, vous pouvez également configurer des objets indépendamment de cette bibliothèque :
const ctf = {
toPostgres : Symbol . for ( 'ctf.toPostgres' ) ,
rawType : Symbol . for ( 'ctf.rawType' )
} ;
En dehors de cela, il fonctionne exactement comme le CTF explicite, mais sans changer la signature de l'objet.
Si vous ne savez pas ce que cela signifie, lisez l'API Symbol ES6 et son utilisation pour les noms de propriétés uniques. Mais en bref, les propriétés Symbol ne sont pas énumérées via for(name in obj)
, c'est-à-dire qu'elles ne sont généralement pas visibles dans JavaScript, uniquement via l'API spécifique Object.getOwnPropertySymbols
.
L'utilisation de fichiers SQL externes (via QueryFile) offre de nombreux avantages :
debug
), sans redémarrer l'application ;params
), automatisation du formatage SQL en deux étapes ;minify
+ compress
), pour une détection précoce des erreurs et des requêtes compactes. 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
}
} ) ;
Fichier findUser.sql
:
/*
multi-line comments are supported
*/
SELECT name, dob -- single-line comments are supported
FROM Users
WHERE id = ${id}
Chaque méthode de requête de la bibliothèque peut accepter le type QueryFile comme paramètre query
. Tapez QueryFile ne génère jamais d'erreur, laissant les méthodes de requête le rejeter gracieusement avec QueryFileError.
L'utilisation de paramètres nommés dans des fichiers SQL externes est recommandée par rapport aux variables d'index, car cela rend le SQL beaucoup plus facile à lire et à comprendre, et parce qu'il permet également des paramètres nommés imbriqués, de sorte que les variables d'un fichier SQL volumineux et complexe peuvent être regroupées dans des espaces de noms. pour une séparation visuelle encore plus facile.
Une tâche représente une connexion partagée pour exécuter plusieurs requêtes :
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
} ) ;
Les tâches fournissent un contexte de connexion partagé pour sa fonction de rappel, qui doit être libéré une fois terminée, et elles doivent être utilisées lors de l'exécution de plusieurs requêtes à la fois. Voir également Chaînage des requêtes pour comprendre l’importance de l’utilisation des tâches.
Vous pouvez éventuellement baliser les tâches (voir Balises) et utiliser la syntaxe asynchrone 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