PostgreSQL-Schnittstelle für Node.js
Diese Bibliothek basiert auf Node-Postgres und fügt Folgendes hinzu:
Bei ihrer Einführung im Jahr 2015 fügte diese Bibliothek nur Versprechungen zum Basistreiber hinzu, daher der Name pg-promise
. Und während der ursprüngliche Name beibehalten wurde, wurde die Funktionalität der Bibliothek erheblich erweitert, wobei Versprechungen nur noch einen winzigen Teil ausmachen.
Ich biete kostenlosen Support hier und auf StackOverflow an.
Und wenn Sie diesem Projekt helfen möchten, kann ich Bitcoin akzeptieren: 1yki7MXMkuDw8qqe5icVdh1GJZSQSzKZp
Das folgende Kapitel „Verwendung“ erläutert die Grundlagen, die Sie wissen müssen, während die offizielle Dokumentation Ihnen den Einstieg erleichtert und Links zu allen anderen Ressourcen bereitstellt.
Bitte lesen Sie die Beitragshinweise, bevor Sie eine neue Ausgabe oder PR eröffnen.
Sobald Sie ein Datenbankobjekt gemäß den Schritten in der offiziellen Dokumentation erstellt haben, erhalten Sie Zugriff auf die unten dokumentierten Methoden.
Alle Abfragemethoden der Bibliothek basieren auf der generischen Methodenabfrage.
Normalerweise sollten Sie zum Ausführen von Abfragen nur die abgeleiteten, ergebnisspezifischen Methoden verwenden, die alle danach benannt sind, wie viele Datenzeilen die Abfrage voraussichtlich zurückgeben wird. Daher sollten Sie für jede Abfrage die richtige Methode auswählen: keine, eine, oneOrNone, viele, vieleOrNone = beliebig. Verwechseln Sie den Methodennamen nicht mit der Anzahl der Zeilen, die von der Abfrage betroffen sind, da er völlig irrelevant ist.
Indem Sie sich auf die ergebnisspezifischen Methoden verlassen, schützen Sie Ihren Code vor einer unerwarteten Anzahl von Datenzeilen, die automatisch abgelehnt (als Fehler behandelt) werden.
Es gibt auch einige spezifische Methoden, die Sie häufig benötigen:
Das Protokoll ist über Event Extend vollständig anpassbar/erweiterbar.
WICHTIG:
Die wichtigsten Methoden, die Sie von Anfang an verstehen sollten, sind task und tx/txIf (siehe Aufgaben und Transaktionen). Wie für die Methodenabfrage dokumentiert, erwirbt und gibt sie die Verbindung frei, was sie zu einer schlechten Wahl für die gleichzeitige Ausführung mehrerer Abfragen macht. Aus diesem Grund ist Chaining Queries ein Muss, um zu vermeiden, dass Code geschrieben wird, der Verbindungen missbraucht.
„Learn by Sample“ ist ein Tutorial für Anfänger, das auf Beispielen basiert.
Diese Bibliothek verfügt über eine eingebettete Abfrageformatierungs-Engine, die leistungsstarkes Value-Escape, Flexibilität und Erweiterbarkeit bietet. Es wird standardmäßig mit allen Abfragemethoden verwendet, es sei denn, Sie deaktivieren es vollständig über die Option pgFormatting
in den Initialisierungsoptionen.
Alle intern verwendeten Formatierungsmethoden sind im Formatierungsnamensraum verfügbar und können daher bei Bedarf auch direkt verwendet werden. Die wichtigste Methode dort ist format, die von jeder Abfragemethode zum Formatieren der Abfrage verwendet wird.
Die Formatierungssyntax für Variablen wird anhand der Art der übergebenen values
bestimmt:
values
um ein Array oder einen einzelnen Basistyp handelt;values
ein Objekt ist (außer Array
oder null
).ACHTUNG: Verwenden Sie niemals ES6-Vorlagenzeichenfolgen oder manuelle Verkettung zum Generieren von Abfragen, da beides leicht zu fehlerhaften Abfragen führen kann! Nur die Formatierungs-Engine dieser Bibliothek weiß, wie Variablenwerte für PostgreSQL richtig maskiert werden.
Die einfachste (klassische) Formatierung verwendet die Syntax $1, $2, ...
, um Werte basierend auf ihrem Index (von $1
bis $100000
) aus dem Wertearray in die Abfragezeichenfolge einzufügen:
await db . any ( 'SELECT * FROM product WHERE price BETWEEN $1 AND $2' , [ 1 , 10 ] )
Die Formatierungs-Engine unterstützt auch die Einzelwertparametrisierung für Abfragen, die nur die Variable $1
verwenden:
await db . any ( 'SELECT * FROM users WHERE name = $1' , 'John' )
Dies funktioniert jedoch nur für die Typen number
, bigint
, string
, boolean
, Date
und null
, da Typen wie Array
und Object
die Art und Weise ändern, wie Parameter interpretiert werden. Aus diesem Grund wird die Übergabe von Indexvariablen innerhalb eines Arrays als sicherer empfohlen, um Mehrdeutigkeiten zu vermeiden.
Wenn eine Abfragemethode mit values
als Objekt parametrisiert wird, erwartet die Formatierungs-Engine, dass die Abfrage die benannte Parametersyntax $*propName*
verwendet, wobei *
eines der folgenden Open-Close-Paare ist: {}
, ()
, <>
, []
, //
.
// 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
} ) ;
WICHTIG: Verwenden Sie niemals die reservierte ${}
-Syntax in ES6-Vorlagenzeichenfolgen, da diese nicht wissen, wie Werte für PostgreSQL formatiert werden. Innerhalb von ES6-Vorlagenzeichenfolgen sollten Sie nur eine der vier Alternativen verwenden – $()
, $<>
, $[]
oder $//
. Im Allgemeinen sollten Sie entweder die Standardzeichenfolgen für SQL verwenden oder SQL in externen Dateien platzieren – siehe Abfragedateien.
Gültige Variablennamen sind auf die Syntax von Open-Name-JavaScript-Variablen beschränkt. Und this
Name hat eine besondere Bedeutung – er bezieht sich auf das Formatierungsobjekt selbst (siehe unten).
Beachten Sie, dass die Eigenschaftswerte null
und undefined
zwar beide als null
formatiert sind, ein Fehler jedoch ausgegeben wird, wenn die Eigenschaft nicht vorhanden ist.
this
Referenz
this
Eigenschaft bezieht sich auf das Formatierungsobjekt selbst, das als JSON-formatierte Zeichenfolge eingefügt werden soll.
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"}')
Benannte Parameter unterstützen die Verschachtelung von Eigenschaftsnamen beliebiger Tiefe.
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'
Der Nachname in der Auflösung kann beliebig sein, einschließlich:
Das heißt, die Auflösungskette ist unendlich flexibel und unterstützt eine unbegrenzte Rekursion.
Bitte beachten Sie jedoch, dass verschachtelte Parameter im Namensraum der Helfer nicht unterstützt werden.
Standardmäßig werden alle Werte entsprechend ihrem JavaScript-Typ formatiert. Formatierungsfilter (oder Modifikatoren) ändern dies, sodass der Wert anders formatiert wird.
Beachten Sie, dass Formatierungsfilter nur für normale Abfragen funktionieren und nicht in PreparedStatement oder ParameterizedQuery verfügbar sind, da diese per Definition auf der Serverseite formatiert werden.
Filter verwenden für Indexvariablen und benannte Parameter dieselbe Syntax und folgen unmittelbar auf den Variablennamen:
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"
Die folgenden Filter werden unterstützt:
:name
/ ~
– SQL-Namen:alias
– Alias-Filter:raw
/ ^
– Rohtext:value
/ #
– Werte öffnen:csv
/ :list
– CSV-Filter:json
– JSON-Filter Wenn ein Variablenname mit :name
oder einer kürzeren Syntax ~
(Tilde) endet, stellt er einen SQL-Namen oder Bezeichner dar, der entsprechend maskiert werden muss:
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(...)
Normalerweise ist eine SQL-Namensvariable eine Textzeichenfolge, die mindestens 1 Zeichen lang sein muss. Allerdings unterstützt pg-promise
verschiedene Möglichkeiten, wie SQL-Namen bereitgestellt werden können:
*
(Sternchen) enthält, wird automatisch als alle Spalten erkannt: 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"
Darüber hinaus unterstützt die Syntax this
, um Spaltennamen aus dem Formatierungsobjekt aufzuzählen:
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)
Wenn Sie sich auf diese Art der Formatierung für SQL-Namen und Bezeichner sowie auf die reguläre Variablenformatierung verlassen, wird Ihre Anwendung vor SQL-Injection geschützt.
Die Methode as.name implementiert die Formatierung.
Ein Alias ist eine einfachere, weniger strenge Version des :name
Filters, der nur eine Textzeichenfolge unterstützt, dh er unterstützt nicht *
, this
, Array oder Objekt als Eingaben, wie dies bei :name
der Fall ist. Es unterstützt jedoch andere beliebte Fälle, die weniger streng sind, aber mindestens 99 % aller Anwendungsfälle abdecken, wie unten gezeigt.
await db . any ( 'SELECT full_name as $1:alias FROM $2:name' , [ 'name' , 'table' ] ) ;
//=> SELECT full_name as name FROM "table"
.
, und maskieren Sie dann jeden Teil separat, um so automatisch zusammengesetzte SQL-Namen zu unterstützen: await db . any ( 'SELECT * FROM $1:alias' , [ 'schemaName.table' ] ) ;
//=> SELECT * FROM "schemaName".table
Weitere Details finden Sie in der Methode as.alias, die die Formatierung implementiert.
Wenn ein Variablenname mit :raw
oder einer kürzeren Syntax ^
endet, muss der Wert als Rohtext ohne Escapezeichen eingefügt werden.
Solche Variablen können aufgrund der mehrdeutigen Bedeutung in diesem Fall nicht null
oder undefined
sein und diese Werte lösen einen Fehler aus 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
Die spezielle Syntax this:raw
/ this^
wird unterstützt, um das Formatierungsobjekt als rohen JSON-String einzufügen.
WARNUNG:
Dieser Filter ist unsicher und sollte nicht für Werte verwendet werden, die von der Clientseite stammen, da dies zu einer SQL-Injection führen kann.
Wenn ein Variablenname mit :value
oder der kürzeren Syntax #
endet, wird er wie üblich maskiert, außer wenn es sich bei seinem Typ um eine Zeichenfolge handelt, werden die abschließenden Anführungszeichen nicht hinzugefügt.
Offene Werte dienen in erster Linie dazu, komplette dynamische LIKE
/ ILIKE
-Anweisungen in externen SQL-Dateien verfassen zu können, ohne diese im Code generieren zu müssen.
Das heißt, Sie können entweder einen Filter wie diesen in Ihrem Code generieren:
const name = 'John' ;
const filter = '%' + name + '%' ;
und übergeben Sie es dann als reguläre Zeichenfolgenvariable. Alternativ können Sie auch nur name
übergeben und Ihre Abfrage mit der Open-Value-Syntax versehen, um die zusätzliche Suchlogik hinzuzufügen:
SELECT * FROM table WHERE name LIKE ' %$1:value% ' )
WARNUNG:
Dieser Filter ist unsicher und sollte nicht für Werte verwendet werden, die von der Clientseite stammen, da dies zu einer SQL-Injection führen kann.
Die Methode as.value implementiert die Formatierung.
Wenn ein Variablenname mit :json
endet, wird eine explizite JSON-Formatierung auf den Wert angewendet.
Standardmäßig wird jedes Objekt, das nicht Date
, Array
, Buffer
, null
oder Custom-Type ist (siehe benutzerdefinierte Typformatierung), automatisch als JSON formatiert.
Die Methode as.json implementiert die Formatierung.
Wenn ein Variablenname mit :csv
oder :list
endet, wird er als Liste durch Kommas getrennter Werte formatiert, wobei jeder Wert entsprechend seinem JavaScript-Typ formatiert wird.
Normalerweise verwenden Sie dies für einen Wert, der ein Array ist, obwohl es auch für einzelne Werte funktioniert. Sehen Sie sich die Beispiele unten an.
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)
Verwenden der automatischen Eigenschaftenaufzählung:
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')
Die Methode as.csv implementiert die Formatierung.
Die Bibliothek unterstützt die duale Syntax für CTF (Custom Type Formatting):
Die Bibliothek prüft immer zuerst, ob das symbolische CTF vorliegt, und wenn keine solche Syntax verwendet wird, prüft sie erst dann, ob das explizite CTF vorhanden ist.
Jeder Wert/jedes Objekt, das die Funktion toPostgres
implementiert, wird als benutzerdefinierter Formatierungstyp behandelt. Die Funktion wird dann aufgerufen, um den tatsächlichen Wert abzurufen, und übergibt ihr das Objekt über this
Kontext und plus als einzelnen Parameter (falls toPostgres
eine ES6-Pfeilfunktion ist):
const obj = {
toPostgres ( self ) {
// self = this = obj
// return a value that needs proper escaping
}
}
Die Funktion toPostgres
kann alles zurückgeben, einschließlich eines anderen Objekts mit einer eigenen toPostgres
-Funktion, dh verschachtelte benutzerdefinierte Typen werden unterstützt.
Der von toPostgres
zurückgegebene Wert wird entsprechend seinem JavaScript-Typ maskiert, es sei denn, das Objekt enthält auch die Eigenschaft rawType
die auf einen wahren Wert gesetzt ist. In diesem Fall gilt der zurückgegebene Wert als vorformatiert und wird daher direkt als Rohtext eingefügt:
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
}
Das folgende Beispiel implementiert eine Klasse, die ST_MakePoint
automatisch aus Koordinaten formatiert:
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 ] ) ;
}
}
Und eine klassische Syntax für eine solche Klasse ist noch einfacher:
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 ] ) ;
}
Mit dieser Klasse können Sie new STPoint(12, 34)
als Formatierungswert verwenden, der korrekt eingefügt wird.
Sie können CTF auch verwenden, um jeden Standardtyp zu überschreiben:
Date . prototype . toPostgres = a => a . getTime ( ) ;
Der einzige Unterschied zu Explicit CTF besteht darin, dass wir toPostgres
und rawType
als ES6-Symboleigenschaften festlegen, die im ctf-Namespace definiert sind:
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
} ;
Da CTF-Symbole global sind, können Sie Objekte auch unabhängig von dieser Bibliothek konfigurieren:
const ctf = {
toPostgres : Symbol . for ( 'ctf.toPostgres' ) ,
rawType : Symbol . for ( 'ctf.rawType' )
} ;
Ansonsten funktioniert es genau wie das explizite CTF, ohne jedoch die Signatur des Objekts zu ändern.
Wenn Sie nicht wissen, was es bedeutet, lesen Sie die ES6-Symbol-API und ihre Verwendung für eindeutige Eigenschaftsnamen. Aber kurz gesagt, Symboleigenschaften werden nicht über for(name in obj)
aufgezählt, dh sie sind in JavaScript nicht allgemein sichtbar, sondern nur über die spezifische API Object.getOwnPropertySymbols
.
Die Verwendung externer SQL-Dateien (über QueryFile) bietet viele Vorteile:
debug
), ohne die App neu zu starten;params
), Automatisierung der zweistufigen SQL-Formatierung;minify
+ compress
), zur frühzeitigen Fehlererkennung und kompakten Abfragen. 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
}
} ) ;
Datei findUser.sql
:
/*
multi-line comments are supported
*/
SELECT name, dob -- single-line comments are supported
FROM Users
WHERE id = ${id}
Jede Abfragemethode der Bibliothek kann den Typ QueryFile als query
akzeptieren. Der Typ QueryFile löst niemals einen Fehler aus und überlässt es den Abfragemethoden, ihn ordnungsgemäß mit QueryFileError abzulehnen.
Die Verwendung von benannten Parametern in externen SQL-Dateien wird gegenüber den Indexvariablen empfohlen, da die SQL dadurch viel einfacher zu lesen und zu verstehen ist und weil auch verschachtelte benannte Parameter möglich sind, sodass Variablen in einer großen und komplexen SQL-Datei in Namespaces gruppiert werden können für noch einfachere optische Trennung.
Eine Aufgabe stellt eine gemeinsame Verbindung zum Ausführen mehrerer Abfragen dar:
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
} ) ;
Aufgaben stellen einen gemeinsamen Verbindungskontext für ihre Rückruffunktion bereit, der nach Abschluss freigegeben wird, und sie müssen immer dann verwendet werden, wenn mehr als eine Abfrage gleichzeitig ausgeführt wird. Siehe auch Verketten von Abfragen, um zu verstehen, wie wichtig die Verwendung von Aufgaben ist.
Sie können Aufgaben optional mit Tags versehen (siehe Tags) und die asynchrone ES7-Syntax verwenden:
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