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