Antarmuka PostgreSQL untuk Node.js
Dibangun di atas node-postgres, perpustakaan ini menambahkan yang berikut:
Pada permulaannya pada tahun 2015, perpustakaan ini hanya menambahkan janji ke driver dasar, oleh karena itu dinamakan pg-promise
. Meskipun nama aslinya tetap dipertahankan, fungsi perpustakaan telah diperluas secara luas, dengan janji-janji yang kini hanya merupakan bagian kecil dari perpustakaan tersebut.
Saya melakukan dukungan gratis di sini dan di StackOverflow.
Dan jika Anda ingin membantu proyek ini, saya dapat menerima Bitcoin: 1yki7MXMkuDw8qqe5icVdh1GJZSQSzKZp
Bab Penggunaan di bawah ini menjelaskan dasar-dasar yang perlu Anda ketahui, sedangkan Dokumentasi Resmi membantu Anda memulai, dan menyediakan tautan ke semua sumber daya lainnya.
Harap baca Catatan Kontribusi sebelum membuka terbitan atau PR baru.
Setelah Anda membuat objek Database, sesuai dengan langkah-langkah dalam Dokumentasi Resmi, Anda mendapatkan akses ke metode yang didokumentasikan di bawah ini.
Semua metode kueri perpustakaan didasarkan pada kueri metode umum.
Biasanya Anda sebaiknya hanya menggunakan metode turunan yang spesifik untuk mengeksekusi kueri, yang semuanya diberi nama berdasarkan berapa banyak baris data yang diharapkan dihasilkan oleh kueri, jadi untuk setiap kueri, Anda harus memilih metode yang tepat: tidak ada, satu, oneOrNone, banyak, manyOrNone = apa saja. Jangan bingung dengan nama metode untuk jumlah baris yang akan dipengaruhi oleh kueri, yang sama sekali tidak relevan.
Dengan mengandalkan metode khusus hasil, Anda melindungi kode Anda dari jumlah baris data yang tidak terduga, yang akan ditolak secara otomatis (dianggap sebagai kesalahan).
Ada juga beberapa metode khusus yang sering Anda perlukan:
Protokol ini sepenuhnya dapat disesuaikan/diperluas melalui perluasan acara.
PENTING:
Metode yang paling penting untuk dipahami sejak awal adalah task dan tx/txIf (lihat Tugas dan Transaksi). Seperti yang didokumentasikan untuk kueri metode, ia memperoleh dan melepaskan koneksi, sehingga menjadikannya pilihan yang buruk untuk mengeksekusi beberapa kueri sekaligus. Oleh karena itu, Chaining Query harus dibaca, untuk menghindari penulisan kode yang menyalahgunakan koneksi.
Belajar dengan Contoh adalah tutorial pemula berdasarkan contoh.
Pustaka ini dilengkapi dengan mesin pemformatan kueri tertanam yang menawarkan pelolosan nilai kinerja tinggi, fleksibilitas, dan ekstensibilitas. Ini digunakan secara default dengan semua metode kueri, kecuali Anda memilih untuk tidak menggunakannya sepenuhnya melalui opsi pgFormatting
dalam Opsi Inisialisasi.
Semua metode pemformatan yang digunakan secara internal tersedia dari namespace pemformatan, sehingga metode tersebut juga dapat digunakan secara langsung bila diperlukan. Metode utama di sana adalah format, yang digunakan oleh setiap metode kueri untuk memformat kueri.
Sintaks pemformatan untuk variabel ditentukan dari jenis values
yang diteruskan:
values
berupa array atau tipe dasar tunggal;values
adalah objek (selain Array
atau null
).PERHATIAN: Jangan pernah menggunakan string template ES6 atau penggabungan manual untuk menghasilkan kueri, karena keduanya dapat dengan mudah menghasilkan kueri yang rusak! Hanya mesin pemformatan perpustakaan ini yang mengetahui cara keluar dari nilai variabel untuk PostgreSQL dengan benar.
Pemformatan (klasik) paling sederhana menggunakan sintaks $1, $2, ...
untuk memasukkan nilai ke dalam string kueri, berdasarkan indeksnya (dari $1
hingga $100000
) dari array nilai:
await db . any ( 'SELECT * FROM product WHERE price BETWEEN $1 AND $2' , [ 1 , 10 ] )
Mesin pemformatan juga mendukung parameterisasi nilai tunggal untuk kueri yang hanya menggunakan variabel $1
:
await db . any ( 'SELECT * FROM users WHERE name = $1' , 'John' )
Namun ini hanya berfungsi untuk tipe number
, bigint
, string
, boolean
, Date
dan null
, karena tipe seperti Array
dan Object
mengubah cara parameter diinterpretasikan. Itu sebabnya meneruskan variabel indeks dalam array disarankan lebih aman, untuk menghindari ambiguitas.
Ketika metode kueri diparameterisasi dengan values
sebagai objek, mesin pemformatan mengharapkan kueri menggunakan sintaks Parameter Bernama $*propName*
, dengan *
menjadi salah satu dari pasangan buka-tutup berikut: {}
, ()
, <>
, []
, //
.
// 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
} ) ;
PENTING: Jangan pernah menggunakan sintaks ${}
yang dicadangkan di dalam string templat ES6, karena mereka tidak memiliki pengetahuan tentang cara memformat nilai untuk PostgreSQL. Di dalam string templat ES6 Anda hanya boleh menggunakan salah satu dari 4 alternatif - $()
, $<>
, $[]
atau $//
. Secara umum, Anda harus menggunakan string standar untuk SQL, atau menempatkan SQL ke dalam file eksternal - lihat File Kueri.
Nama variabel yang valid terbatas pada sintaks variabel JavaScript nama terbuka. Dan nama this
memiliki arti khusus - ini mengacu pada objek pemformatan itu sendiri (lihat di bawah).
Perlu diingat bahwa meskipun nilai properti null
dan undefined
keduanya diformat sebagai null
, kesalahan akan terjadi saat properti tidak ada.
referensi this
Properti this
mengacu pada objek pemformatan itu sendiri, untuk dimasukkan sebagai string berformat 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"}')
Parameter Bernama mendukung penyarangan nama properti dengan kedalaman apa pun.
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'
Nama belakang dalam resolusi bisa apa saja, antara lain:
yaitu rantai resolusi sangat fleksibel, dan mendukung rekursi tanpa batas.
Namun perlu diingat bahwa parameter bertumpuk tidak didukung dalam namespace helper.
Secara default, semua nilai diformat sesuai dengan tipe JavaScript-nya. Filter pemformatan (atau pengubah), ubah itu, sehingga nilainya diformat berbeda.
Perhatikan bahwa filter pemformatan hanya berfungsi untuk kueri normal, dan tidak tersedia dalam PreparedStatement atau ParameterizedQuery, karena menurut definisi, diformat di sisi server.
Filter menggunakan sintaks yang sama untuk Variabel Indeks dan Parameter Bernama, yang langsung mengikuti nama variabel:
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"
Filter berikut ini didukung:
:name
/ ~
- Nama SQL:alias
- Filter Alias:raw
/ ^
- Teks Mentah:value
/ #
- Nilai Terbuka:csv
/ :list
- Filter CSV:json
- Filter JSON Ketika nama variabel diakhiri dengan :name
, atau sintaks yang lebih pendek ~
(tilde), itu mewakili nama atau pengidentifikasi SQL, yang akan di-escape:
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(...)
Biasanya, variabel nama SQL adalah string teks, yang panjangnya minimal harus 1 karakter. Namun, pg-promise
mendukung berbagai cara pemberian nama SQL:
*
(tanda bintang) secara otomatis dikenali sebagai semua kolom : 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"
Selain itu, sintaks mendukung this
untuk menghitung nama kolom dari objek pemformatan:
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)
Mengandalkan jenis pemformatan ini untuk nama dan pengidentifikasi sql, bersama dengan pemformatan variabel reguler akan melindungi aplikasi Anda dari injeksi SQL.
Metode as.name mengimplementasikan pemformatan.
Alias adalah versi filter :name
yang lebih sederhana dan tidak terlalu ketat, yang hanya mendukung string teks, yakni tidak mendukung *
, this
, array, atau objek sebagai input, seperti halnya :name
. Namun, ini mendukung kasus populer lainnya yang tidak terlalu ketat, namun mencakup setidaknya 99% dari semua kasus penggunaan, seperti yang ditunjukkan di bawah ini.
await db . any ( 'SELECT full_name as $1:alias FROM $2:name' , [ 'name' , 'table' ] ) ;
//=> SELECT full_name as name FROM "table"
.
, lalu keluarkan setiap bagian secara terpisah, sehingga mendukung nama SQL komposit otomatis: await db . any ( 'SELECT * FROM $1:alias' , [ 'schemaName.table' ] ) ;
//=> SELECT * FROM "schemaName".table
Untuk lebih jelasnya lihat metode as.alias yang mengimplementasikan pemformatan.
Ketika nama variabel diakhiri dengan :raw
, atau sintaksis yang lebih pendek ^
, nilainya akan dimasukkan sebagai teks mentah, tanpa keluar.
Variabel tersebut tidak boleh null
atau undefined
, karena maknanya ambigu dalam kasus ini, dan nilai tersebut akan menimbulkan kesalahan. 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
Sintaks khusus this:raw
/ this^
didukung, untuk memasukkan objek pemformatan sebagai string JSON mentah.
PERINGATAN:
Filter ini tidak aman, dan tidak boleh digunakan untuk nilai yang berasal dari sisi klien, karena dapat mengakibatkan injeksi SQL.
Ketika nama variabel diakhiri dengan :value
, atau sintaksis yang lebih pendek #
, variabel tersebut akan di-escape seperti biasa, kecuali jika tipenya adalah string, tanda kutip di akhir tidak ditambahkan.
Nilai terbuka terutama untuk dapat membuat pernyataan dinamis LIKE
/ ILIKE
lengkap dalam file SQL eksternal, tanpa harus membuatnya dalam kode.
yaitu Anda dapat membuat filter seperti ini di kode Anda:
const name = 'John' ;
const filter = '%' + name + '%' ;
lalu meneruskannya sebagai variabel string biasa, atau Anda hanya dapat meneruskannya name
, dan minta kueri Anda menggunakan sintaks nilai terbuka untuk menambahkan logika pencarian tambahan:
SELECT * FROM table WHERE name LIKE ' %$1:value% ' )
PERINGATAN:
Filter ini tidak aman, dan tidak boleh digunakan untuk nilai yang berasal dari sisi klien, karena dapat mengakibatkan injeksi SQL.
Metode as.value mengimplementasikan pemformatan.
Jika nama variabel diakhiri dengan :json
, pemformatan JSON eksplisit diterapkan pada nilai.
Secara default, objek apa pun yang bukan Date
, Array
, Buffer
, null
atau Custom-Type (lihat Pemformatan Tipe Kustom), secara otomatis diformat sebagai JSON.
Metode as.json mengimplementasikan pemformatan.
Jika nama variabel diakhiri dengan :csv
atau :list
, maka variabel tersebut akan diformat sebagai daftar Nilai yang Dipisahkan Koma, dengan setiap nilai diformat sesuai dengan tipe JavaScript-nya.
Biasanya, Anda akan menggunakan ini untuk nilai yang berupa array, meskipun ini juga berfungsi untuk nilai tunggal. Lihat contoh di bawah ini.
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)
Menggunakan enumerasi properti otomatis:
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')
Metode as.csv mengimplementasikan pemformatan.
Perpustakaan mendukung sintaks ganda untuk CTF (Custom Type Formatting):
Perpustakaan selalu terlebih dahulu memeriksa KKP Simbolik, dan jika tidak ada sintaks seperti itu yang digunakan, barulah perpustakaan memeriksa KKP Eksplisit.
Nilai/objek apa pun yang mengimplementasikan fungsi toPostgres
diperlakukan sebagai tipe pemformatan khusus. Fungsi tersebut kemudian dipanggil untuk mendapatkan nilai sebenarnya, meneruskan objek melalui konteks this
, dan ditambah sebagai parameter tunggal (jika toPostgres
adalah fungsi panah ES6):
const obj = {
toPostgres ( self ) {
// self = this = obj
// return a value that needs proper escaping
}
}
Fungsi toPostgres
dapat mengembalikan apa pun, termasuk objek lain dengan fungsi toPostgres
sendiri, yaitu tipe kustom bersarang yang didukung.
Nilai yang dikembalikan dari toPostgres
di-escape sesuai dengan tipe JavaScript-nya, kecuali objek tersebut juga berisi properti rawType
yang disetel ke nilai sebenarnya, dalam hal ini nilai yang dikembalikan dianggap telah diformat sebelumnya, dan dengan demikian dimasukkan secara langsung, sebagai Teks Mentah:
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
}
Contoh di bawah mengimplementasikan kelas yang memformat ST_MakePoint
secara otomatis dari koordinat:
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 ] ) ;
}
}
Dan sintaks klasik untuk kelas seperti itu bahkan lebih sederhana:
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 ] ) ;
}
Dengan kelas ini Anda dapat menggunakan new STPoint(12, 34)
sebagai nilai format yang akan dimasukkan dengan benar.
Anda juga dapat menggunakan CTF untuk mengganti tipe standar apa pun:
Date . prototype . toPostgres = a => a . getTime ( ) ;
Satu-satunya perbedaan dari CTF Eksplisit adalah kita menetapkan toPostgres
dan rawType
sebagai properti Simbol ES6, yang didefinisikan dalam 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
} ;
Karena simbol CTF bersifat global, Anda juga dapat mengonfigurasi objek secara independen dari pustaka ini:
const ctf = {
toPostgres : Symbol . for ( 'ctf.toPostgres' ) ,
rawType : Symbol . for ( 'ctf.rawType' )
} ;
Selain itu, ia bekerja persis seperti KKP Eksplisit, namun tanpa mengubah tanda tangan objek.
Jika Anda tidak tahu artinya, baca ES6 Symbol API dan penggunaannya untuk nama properti unik. Namun singkatnya, properti Simbol tidak disebutkan melalui for(name in obj)
, yaitu properti tersebut umumnya tidak terlihat dalam JavaScript, hanya melalui API Object.getOwnPropertySymbols
tertentu.
Penggunaan file SQL eksternal (melalui QueryFile) menawarkan banyak keuntungan:
debug
), tanpa memulai ulang aplikasi;params
opsi), mengotomatiskan pemformatan SQL dua langkah;minify
+ compress
), untuk deteksi kesalahan dini dan kueri ringkas. 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
}
} ) ;
File findUser.sql
:
/*
multi-line comments are supported
*/
SELECT name, dob -- single-line comments are supported
FROM Users
WHERE id = ${id}
Setiap metode kueri perpustakaan dapat menerima tipe QueryFile sebagai parameter query
. Ketik QueryFile tidak pernah menimbulkan kesalahan apa pun, membiarkan metode kueri menolak dengan baik dengan QueryFileError.
Penggunaan Parameter Bernama dalam file SQL eksternal lebih disarankan daripada Variabel Indeks, karena ini membuat SQL lebih mudah dibaca dan dipahami, dan karena ini juga memungkinkan Parameter Bernama Bersarang, sehingga variabel dalam file SQL yang besar dan kompleks dapat dikelompokkan dalam ruang nama untuk pemisahan visual yang lebih mudah.
Tugas mewakili koneksi bersama untuk menjalankan beberapa kueri:
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
} ) ;
Tugas menyediakan konteks koneksi bersama untuk fungsi panggilan baliknya, yang akan dilepaskan setelah selesai, dan tugas tersebut harus digunakan setiap kali menjalankan lebih dari satu kueri dalam satu waktu. Lihat juga Merangkai Kueri untuk memahami pentingnya menggunakan tugas.
Anda juga dapat menandai tugas (lihat Tag), dan menggunakan sintaksis asinkron 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