Node.js 的 PostgreSQL 介面
該庫建立在 node-postgres 之上,並添加了以下內容:
該庫於 2015 年成立時僅向基本驅動程式添加了 Promise,因此得名pg-promise
。雖然保留了原來的名稱,但該庫的功能得到了極大的擴展,而 Promise 現在只是其中的一小部分。
我在這裡和 StackOverflow 上提供免費支援。
如果你想幫助這個項目,我可以接受比特幣: 1yki7MXMkuDw8qqe5icVdh1GJZSQSzKZp
下面的「用法」一章解釋了您需要了解的基礎知識,而官方文件則幫助您入門,並提供了所有其他資源的連結。
在開啟任何新問題或 PR 之前,請先閱讀貢獻說明。
建立資料庫物件後,根據官方文件中的步驟,您可以存取下面記錄的方法。
庫的所有查詢方法都基於通用方法查詢。
通常,您應該只使用派生的、特定於結果的方法來執行查詢,所有這些方法都是根據查詢預期返回的資料行數來命名的,因此對於每個查詢,您應該選擇正確的方法:無、一、 oneOrNone、many、manyOrNone = 任意。不要將方法名稱與查詢影響的行數混淆,這是完全不相關的。
透過依賴特定於結果的方法,您可以保護程式碼免受意外數量的資料行的影響,從而被自動拒絕(視為錯誤)。
還有一些您經常需要的具體方法:
該協議可透過事件擴展完全客製化/擴展。
重要的:
從一開始就需要理解的最重要的方法是任務和 tx/txIf(請參閱任務和事務)。正如方法查詢的文檔所述,它獲取並釋放連接,這使得它成為一次執行多個查詢的糟糕選擇。因此,連結查詢是必讀的,以避免編寫濫用連接的程式碼。
透過範例學習是基於範例的初學者教程。
該程式庫配備了嵌入式查詢格式化引擎,可提供高效能的值轉義、靈活性和可擴充性。預設情況下,它與所有查詢方法一起使用,除非您透過初始化選項中的選項pgFormatting
完全選擇退出它。
內部使用的所有格式化方法都可以從格式化命名空間中獲得,因此也可以在需要時直接使用。主要方法是 format,每個查詢方法都使用 format 來格式化查詢。
變數的格式化語法取決於傳入values
的類型:
values
是數組或單一基本類型時的索引變數;values
是物件( Array
或null
除外)時的命名參數。注意:切勿使用 ES6 模板字串或手動連接來產生查詢,因為兩者都很容易導致查詢損壞!只有該函式庫的格式化引擎知道如何正確轉義 PostgreSQL 的變數值。
最簡單的(經典)格式使用$1, $2, ...
語法根據值數組中的索引(從$1
到$100000
)將值注入到查詢字串中:
await db . any ( 'SELECT * FROM product WHERE price BETWEEN $1 AND $2' , [ 1 , 10 ] )
格式化引擎也支援僅使用變數$1
查詢的單值參數化:
await db . any ( 'SELECT * FROM users WHERE name = $1' , 'John' )
然而,這僅適用於number
、 bigint
、 string
、 boolean
、 Date
和null
類型,因為Array
和Object
等類型改變了參數的解釋方式。這就是為什麼建議在數組中傳遞索引變數更安全,以避免歧義。
當使用values
作為物件對查詢方法進行參數化時,格式化引擎期望查詢使用命名參數語法$*propName*
,其中*
是以下任意開閉對: {}
、 ()
、 <>
、 []
, //
。
// 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
} ) ;
重要提示:切勿在 ES6 範本字串中使用保留的${}
語法,因為它們不知道如何格式化 PostgreSQL 的值。在 ES6 範本字串中,您應該只使用 4 個替代方案之一 - $()
、 $<>
、 $[]
或$//
。一般來說,您應該使用 SQL 的標準字串,或將 SQL 放入外部文件 - 請參閱查詢文件。
有效的變數名稱僅限於開放名稱 JavaScript 變數的語法。 this
名稱具有特殊意義 - 它指的是格式化物件本身(見下文)。
請記住,雖然屬性值null
和undefined
都格式化為null
,但當屬性不存在時會引發錯誤。
this
參考
屬性this
指的是格式化物件本身,將作為 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"}')
命名參數支援任意深度的屬性名稱嵌套。
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'
決議中的姓氏可以是任何名稱,包括:
即解析鏈無限靈活,支援無限遞迴。
但請注意,助手命名空間內不支援嵌套參數。
預設情況下,所有值均根據其 JavaScript 類型進行格式化。格式化過濾器(或修飾符),更改它,使值的格式不同。
請注意,格式化過濾器僅適用於普通查詢,並且在PreparedStatement或ParameterizedQuery中不可用,因為根據定義,它們是在伺服器端格式化的。
過濾器對索引變數和命名參數使用相同的語法,緊跟著變數名稱:
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"
支援以下過濾器:
:name
/ ~
- SQL 名稱:alias
- 別名過濾器:raw
/ ^
- 原始文本:value
/ #
- 開放值:csv
/ :list
- CSV 過濾器:json
- JSON 過濾器當變數名稱以:name
或更短的語法~
(波形符)結尾時,它表示 SQL 名稱或標識符,需要相應地轉義:
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(...)
通常,SQL 名稱變數是文字字串,長度必須至少為 1 個字元。然而, pg-promise
支援多種提供 SQL 名稱的方式:
*
(星號)的字串會自動辨識為所有欄位: 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"
此外,語法this
從格式化物件中列舉列名:
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)
依靠這種類型的 SQL 名稱和標識符格式以及常規變數格式可以保護您的應用程式免受 SQL 注入。
方法 as.name 實作格式化。
別名是:name
過濾器的一個更簡單、不太嚴格的版本,它只支援文字字串,即它不支援*
、 this
、陣列或物件作為輸入,就像:name
一樣。但是,它支援其他不太嚴格的流行案例,但至少覆蓋了所有用例的 99%,如下所示。
await db . any ( 'SELECT full_name as $1:alias FROM $2:name' , [ 'name' , 'table' ] ) ;
//=> SELECT full_name as name FROM "table"
.
,然後分別轉義每個部分,從而支援自動組合 SQL 名稱: await db . any ( 'SELECT * FROM $1:alias' , [ 'schemaName.table' ] ) ;
//=> SELECT * FROM "schemaName".table
有關更多詳細信息,請參閱實現格式化的方法 as.alias。
當變數名稱以:raw
或更短語法^
結尾時,該值將作為原始文字注入,而不進行轉義。
此類變數不能為null
或undefined
,因為在這種情況下意義不明確,而這些值將引發錯誤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
支援特殊語法this:raw
/ this^
,將格式化物件作為原始 JSON 字串注入。
警告:
此過濾器不安全,不應用於來自客戶端的值,因為它可能會導致 SQL 注入。
當變數名稱以:value
或較短的語法#
結尾時,它會照常轉義,除非其類型是字串,否則不會添加尾部引號。
開放值主要是能夠在外部SQL檔案中組成完整的LIKE
/ ILIKE
動態語句,而不必在程式碼中產生它們。
即您可以在程式碼中產生這樣的篩選器:
const name = 'John' ;
const filter = '%' + name + '%' ;
然後將其作為常規字串變數傳遞,或者您可以僅傳遞name
,並讓您的查詢使用開放值語法來添加額外的搜尋邏輯:
SELECT * FROM table WHERE name LIKE ' %$1:value% ' )
警告:
此過濾器不安全,不應用於來自客戶端的值,因為它可能會導致 SQL 注入。
方法 as.value 實作格式化。
當變數名稱以:json
結尾時,顯式 JSON 格式將套用於該值。
預設情況下,任何不是Date
、 Array
、 Buffer
、 null
或 Custom-Type (請參閱自訂類型格式)的物件都會自動格式化為 JSON。
as.json 方法實作格式化。
當變數名稱以:csv
或:list
結尾時,它將被格式化為逗號分隔值列表,每個值根據其 JavaScript 類型進行格式化。
通常,您可以將其用於數組值,但它也適用於單一值。請參閱下面的範例。
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)
使用自動屬性枚舉:
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')
as.csv 方法實作格式化。
此函式庫支援CTF (自訂類型格式)的雙重語法:
該程式庫始終首先檢查符號 CTF,如果沒有使用此類語法,則僅檢查明確 CTF。
任何實作toPostgres
函數的值/物件都被視為自訂格式類型。然後呼叫該函數來取得實際值,透過this
上下文將物件傳遞給它,並加上作為單一參數(如果toPostgres
是 ES6 箭頭函數):
const obj = {
toPostgres ( self ) {
// self = this = obj
// return a value that needs proper escaping
}
}
函數toPostgres
可以傳回任何內容,包括另一個具有自己的toPostgres
函數的對象,即支援巢狀自訂類型。
從toPostgres
傳回的值會根據其 JavaScript 類型進行轉義,除非該物件還包含設定為真值的屬性rawType
,在這種情況下,傳回的值被認為是預先格式化的,因此直接注入為原始文字:
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
}
下面的範例實作了一個從座標自動格式化ST_MakePoint
的類別:
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 ] ) ;
}
}
這種類別的經典語法甚至更簡單:
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 ] ) ;
}
透過此類,您可以使用new STPoint(12, 34)
作為將正確注入的格式值。
您也可以使用CTF覆寫任何標準類型:
Date . prototype . toPostgres = a => a . getTime ( ) ;
與 Explicit CTF 的唯一區別是我們將toPostgres
和rawType
設定為 ES6 Symbol 屬性,在 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
} ;
由於 CTF 符號是全域的,您也可以獨立於該程式庫配置物件:
const ctf = {
toPostgres : Symbol . for ( 'ctf.toPostgres' ) ,
rawType : Symbol . for ( 'ctf.rawType' )
} ;
除此之外,它的運作方式與顯式 CTF 完全相同,但不會更改物件的簽名。
如果您不知道它的含義,請閱讀 ES6 Symbol API 及其對唯一屬性名稱的使用。但簡而言之,Symbol 屬性不是透過for(name in obj)
枚舉的,即它們在 JavaScript 中通常不可見,只能透過特定的 API Object.getOwnPropertySymbols
來查看。
使用外部 SQL 檔案(透過 QueryFile)具有許多優點:
debug
),無需重新啟動應用程式;params
),自動執行兩步驟 SQL 格式化;minify
+ compress
),用於早期錯誤檢測和緊湊查詢。 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
}
} ) ;
檔案findUser.sql
:
/*
multi-line comments are supported
*/
SELECT name, dob -- single-line comments are supported
FROM Users
WHERE id = ${id}
庫的每個查詢方法都可以接受 QueryFile 類型作為其query
參數。類型 QueryFile 永遠不會拋出任何錯誤,留給查詢方法以 QueryFileError 優雅地拒絕。
建議在外部 SQL 檔案中使用命名參數而不是索引變量,因為它使 SQL 更容易閱讀和理解,並且因為它還允許嵌套命名參數,因此大型複雜 SQL 檔案中的變數可以分組在命名空間中以便更輕鬆地進行視覺分離。
一個任務代表一個用於執行多個查詢的共用連線:
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
} ) ;
任務為其回調函數提供一個共享連接上下文,在完成時釋放,並且每次執行多個查詢時都必須使用它們。另請參閱連結查詢以了解使用任務的重要性。
您可以選擇標記任務(請參閱標籤),並使用 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