PostgreSQL: Pemrograman sisi server dalam bahasa manusia (PL / Perl, PL / Python, PL / v8)

Postgres dikenal karena ekstensibilitasnya, yang juga berlaku untuk mendukung bahasa prosedural (PL). Tidak ada yang bisa membanggakan bahasa dengan daftar bahasa yang panjangnya seperti ini, dan kemungkinan daftar ini sama sekali tidak terbatas: untuk menghubungkan bahasa ke server, tidak diperlukan usaha ekstra. Anda bahkan dapat membuat bahasa Anda sendiri dan menjadikannya bahasa prosedural server. Perubahan dalam DBMS tidak akan membutuhkan ini. Seperti banyak lagi, ekstensibilitas ini telah dibangun ke dalam arsitektur Postgres sejak awal.

Adalah mungkin dan terkadang diperlukan untuk menulis bahasa PL untuk tugas-tugas. Lebih baik lagi, jika seseorang menulis kerangka kerja seperti itu untuk menulis bahasa sehingga Anda tidak bisa menulis dalam bahasa C, tetapi memilih bahasa yang lebih nyaman bagi pengembang bahasa. Seperti halnya FDW, yang dapat ditulis dengan Python .

Artikel ini ditulis berdasarkan sejumlah laporan dan kelas master pada topik ini yang dibuat oleh penulis di konferensi PgConf.Russia 2019 , PgConf.Russia 2018 dan DevConf 2017 .

Ini bukan tentang eksotis, tetapi tentang bahasa prosedural yang paling umum PL / Perl, PL / Python dan PL / V8 (mis. JavaScript) dan membandingkan kemampuan mereka dengan PL / pgSQL.

Kapan bahasa-bahasa ini layak digunakan? Kapan SQL dan PL / pgSQL hilang?

  • Kemudian, ketika Anda perlu bekerja dengan struktur yang kompleks, dengan algoritma: melintasi pohon, misalnya, atau ketika penguraian HTML atau XML diperlukan, terutama ketika mengekstraksi mereka dari arsip;
  • Ketika Anda perlu secara dinamis menghasilkan SQL kompleks (laporan, ORM). Pada PL / pgSQL, ini tidak hanya merepotkan, tetapi juga akan bekerja lebih lambat dalam beberapa kasus;
  • Perl Python, C/C++, Perl Python . . , Oracle. , Postgres . Perl Python .
  • โ€” . , , untrusted- ( โ€” . ), Perlu Python(3)u, PL/V8. Postgres , , FDW, , . . !
  • Dan satu hal lagi: jika Anda akan menulis sesuatu dalam bahasa C, maka Anda dapat membuat prototipe dalam bahasa-bahasa ini yang lebih disesuaikan dengan perkembangan yang cepat.

Cara menanamkan bahasa di Postgres


Untuk mengimplementasikan bahasa yang Anda butuhkan: tulis dalam C dari satu hingga tiga fungsi:

  • HANDLER - penangan panggilan yang akan menjalankan fungsi dalam bahasa (ini adalah bagian yang diperlukan);
  • INLINE - penangan blok anonim (jika Anda ingin bahasa mendukung blok anonim);
  • VALIDATOR - fungsi verifikasi kode saat membuat fungsi (jika Anda ingin verifikasi ini dilakukan).

Ini dijelaskan secara rinci dalam dokumentasi di sini dan di sini .

โ€œBahasa di luar kotakโ€ dan bahasa lainnya


Hanya ada empat bahasa yang didukung "out of the box": PL / pgSQL , PL / Perl , PL / Python dan PL / Tcl , tetapi gelitiknya lebih merupakan penghormatan kepada sejarah: beberapa orang menggunakannya sekarang, kami tidak akan membicarakannya lagi.
PL / Perl, PL / Python dan, tentu saja, PL / pgSQL didukung oleh komunitas Postgres. Dukungan untuk bahasa non-kotak lain jatuh pada pengelola mereka - perusahaan, komunitas, atau pengembang tertentu yang tertarik untuk membuat bahasa berfungsi di dalam DBMS. PL / V8 mempromosikan Google. Tetapi dari waktu ke waktu ada alasannyameragukan masa depan tanpa awan dari PL / V8. Pemelihara proyek PL / V8 Google saat ini, Jerry Sievert, sedang mempertimbangkan dukungan JS berbasis server postgres berdasarkan mesin yang berbeda (seperti QuickJS), karena PL / V8 sulit untuk dibangun dan membutuhkan 3-5 GB segala macam hal di Linux saat membangun, dan ini sering menyebabkan masalah pada OS yang berbeda. Tetapi PL / V8 digunakan secara luas dan diuji secara menyeluruh. Ada kemungkinan bahwa PL / JS akan muncul sebagai alternatif untuk mesin JS lain, atau untuk saat ini hanya sebagai nama, yang akan kita gunakan selama periode transisi.

PL / Java jarang digunakan. Saya pribadi tidak perlu menulis di PL / Java karena di PL / Perl dan di PL / V8 ada cukup fungsionalitas untuk hampir semua tugas. Bahkan Python tidak terlalu menambahkan fitur. PL / RBerguna untuk mereka yang menyukai statistik dan menyukai bahasa ini. Kami juga tidak akan membicarakannya di sini.

Bahasa populer belum tentu populer dengan penyimpanan penulisan: ada PL / PHP, tetapi sekarang secara praktis tidak didukung oleh siapa pun - ada beberapa yang ingin menulis prosedur server di atasnya. Untuk bahasa PL / Ruby, gambarnya entah bagaimana sama, meskipun bahasanya tampaknya lebih modern.

Bahasa prosedural berbasis-Go sedang dikembangkan, lihat PL / Go , dan sepertinya, PL / Lua . Penting untuk mempelajarinya. Untuk penggemar keras kepala dari shell, bahkan ada PL / Sh , sulit untuk membayangkan untuk apa itu.

Setidaknya ada satu bahasa prosedural khusus domain (DSL) yang khusus dikhususkan untuk tugasnya - PL / Proxy, yang dulunya sangat populer untuk proksi dan menyeimbangkan beban server.

Pada artikel ini, kita akan membahas bahasa utama yang paling umum digunakan. Ini, tentu saja, adalah PL / PgSQL, PL / Perl, PL / Python dan PL / V8, kami akan memanggil mereka PL / * di bawah ini .

Bahasa โ€œout of the boxโ€ benar-benar hampir secara harfiah dipasang di luar kotak - biasanya instalasi tidak menyakitkan. Tetapi untuk menginstal PL / V8, jika Anda tidak menemukan paket dengan versi yang diperlukan di repositori OS Anda, ini hampir merupakan suatu prestasi, karena untuk ini Anda harus benar-benar membangun seluruh V8, atau, dengan kata lain, Chromium. Pada saat yang sama, seluruh infrastruktur pengembangan akan diunduh dari google.com bersama dengan V8 itu sendiri - andalkan beberapa gigabyte traffic. Untuk Postgres 11 di Ubuntu, paket PL / V8 belum muncul, hanya V8 untuk PG 10 yang tersedia di repositori sejauh ini. Jika Anda mau, kumpulkan dengan tangan. Penting juga bahwa versi yang akan Anda temukan di repositori kemungkinan besar sudah cukup tua. Pada saat publikasi artikel, versi terbaru adalah 2.3.14.

Setelah bahasa itu sendiri diinstal, Anda juga harus โ€œmembuatโ€ bahasa - mendaftarkannya di direktori sistem. Ini harus dilakukan oleh tim.

CREATE EXTENSION plperl;

(Alih-alih plperl, Anda dapat mengganti nama bahasa lain, ada nuansa tertentu, lihat di bawah).
Kami melihat apa yang terjadi:

test_langs=# \x
test_langs=# \dL+
List of languages
-[ RECORD 1 ]-----+---------------------------------
Name              | plperl
Owner             | postgres
Trusted           | t
Internal language | f
Call handler      | plperl_call_handler()
Validator         | plperl_validator(oid)
Inline handler    | plperl_inline_handler(internal)
Access privileges |
Description       | PL/Perl procedural language
-[ RECORD 2 ]-----+---------------------------------
Name              | plpgsql
Owner             | postgres
Trusted           | t
Internal language | f
Call handler      | plpgsql_call_handler()
Validator         | plpgsql_validator(oid)
Inline handler    | plpgsql_inline_handler(internal)
Access privileges |
Description       | PL/pgSQL procedural language
[ RECORD 3 ]-----+---------------------------------
Name              | plv8
Owner             | postgres
Trusted           | t
Internal language | f
Call handler      | plv8_call_handler()
Validator         | plv8_call_validator(oid)
Inline handler    | plv8_inline_handler(internal)
Access privileges |
Description       |

PL / pgSQL tidak perlu dibuat secara khusus, selalu ada di database.

Perhatian! PL / pgSQL tidak harus bingung dengan SQL. Ini bahasa yang berbeda. Namun, Postgres juga dapat menulis fungsi dalam SQL biasa.

Standar


Dalam dunia DBMS, mereka sering berbicara tentang kepatuhan dengan standar SQL. Bahasa prosedural juga memiliki standar, meskipun tidak sering dibicarakan. Standar SQL / PSM sangat sesuai dengan bahasa prosedural DB2. Implementasinya jauh dari PL / pgSQL, meskipun secara konseptual keduanya dekat.

SQL / JRT adalah standar untuk prosedur Java, dan PL / Java adalah pasangan yang baik.

Bahasa Tepercaya dan Tidak Dipercaya


Bahasa prosedural Postgres dipercaya (TRUSTED) dan tidak dipercaya (TIDAK DIPERCAYA).
Dalam bahasa TRUSTED, tidak ada kemungkinan bekerja langsung dengan I / O, termasuk jaringan, dan memang dengan sumber daya sistem. Oleh karena itu, fungsi-fungsi tersebut dapat dibuat oleh pengguna basis data apa pun, merusak sesuatu dan ia tidak akan dapat belajar terlalu banyak. Fungsi dalam bahasa UNTRUSTED hanya dapat dibuat oleh penyelia.

Jika penerjemah bahasa mendukung pembatasan tersebut, maka dapat digunakan untuk membuat bahasa TRUSTED dan UNTRUSTED. Jadi dengan Perl, jadi ada berbagai bahasa plperldan plperlu. Huruf upada akhirnya itu memberikan karakter bahasa yang tidak dipercaya. Python hanya ada dalam versi yang tidak terpercaya. PL / v8 - sebaliknya, hanya di tepercaya. Akibatnya, PL / v8 tidak dapat memuat modul atau pustaka dari disk, hanya dari database.

Fungsi dalam bahasa UNTRUSTED dapat melakukan apa saja: mengirim email, ping situs, masuk ke database asing, dan menjalankan permintaan HTTP. Bahasa TRUSTED terbatas untuk memproses data dari database.

Dengan TRUSTED meliputi: plpgsql, plperl, plv8, pljava.

Dengan tidak dipercaya meliputi: plperlu, pljavau, plpython2u, plpython3u.

Harap dicatat: PL / Python tidak ada sebagai TRUSTED (karena Anda tidak dapat menetapkan batasan akses ke sumber daya di sana), dan PLpgSQL dan PL / V8 adalah sebaliknya: mereka tidak UNTRUSTED.

Tetapi Perl dan Java tersedia di kedua versi.

PL / pgSQL vs PL / *


Kode PL / pgSQL asli bekerja dengan semua tipe data yang Postgres miliki. Bahasa lain tidak memiliki banyak tipe Postgres, dan juru bahasa menangani konversi data menjadi representasi internal bahasa, menggantikan jenis yang tidak jelas dengan teks. Namun, dia dapat dibantu dengan bantuan TRANSFORM, yang akan saya bicarakan lebih dekat dengan akhir artikel.

Panggilan fungsi dalam PL / pgSQL seringkali lebih mahal. Fungsi dalam bahasa lain dapat mengakses perpustakaan mereka tanpa melihat katalog sistem. PL / pgSQL tidak bisa bekerja seperti itu. Beberapa pertanyaan dalam PL / pgSQL bekerja untuk waktu yang lama karena fakta bahwa banyak jenis yang didukung: untuk menambahkan dua bilangan bulat, penerjemah perlu menyadari bahwa ia berurusan dengan bilangan bulat dan bukan beberapa jenis eksotis lainnya, kemudian memutuskan cara melipatnya, dan hanya setelah itu benar-benar melipatnya.

Karena PL / pgSQL TRUSTED, Anda tidak dapat bekerja dengan jaringan dan disk darinya.

Ketika datang untuk bekerja dengan struktur data bersarang, PL / pgSQL hanya memiliki alat Postgres untuk bekerja dengan JSON, yang sangat rumit dan tidak produktif, dalam bahasa lain, bekerja dengan struktur bersarang jauh lebih sederhana dan lebih ekonomis.

PL / * memiliki manajemen memori sendiri, dan Anda perlu memonitor memori, atau mungkin membatasinya.

Anda harus memantau penanganan kesalahan dengan hati-hati, yang juga berbeda untuk semua orang.

Tetapi dalam PL / * ada konteks juru bahasa global, dan itu dapat digunakan, misalnya, untuk menyimpan data, termasuk rencana kueri. Jika bahasa ini TIDAK DIPERCAYA, maka jaringan dan drive tersedia. Semua bahasa ini bekerja dengan database, sebagai aturan, melalui SPI, tetapi lebih lanjut tentang itu nanti.

Mari kita lihat lebih dekat fitur-fitur bahasa PL / *.

PL / Perl


Interpreter Perl adalah sepotong besar kode dalam memori, tetapi untungnya itu tidak dibuat ketika koneksi dibuka, tetapi hanya ketika prosedur / fungsi pertama yang disimpan PL / Perl diluncurkan. Ketika diinisialisasi, kode yang ditentukan dalam parameter konfigurasi Postgres dieksekusi. Biasanya, modul dimuat dan perhitungan dilakukan. Jika Anda menambahkan ke file konfigurasi ketika database sedang berjalan, buat Postgres membaca kembali konfigurasi. Pada artikel ini, contoh-contoh menggunakan modul untuk memvisualisasikan struktur data. Ada beberapa parameter untuk inisialisasi terpisah dari TRUSTED dan UNTRUSTED Perl dan, tentu saja, sebuah parameter . Mereka yang memprogram di Perl tahu bahwa tanpanya bukanlah bahasa, tetapi satu kesalahpahaman.

plperl.on_init= 'use Data::Dumper;'
plperl.on_plperl_init= ' ... '
plperl.on_plperlu_init= ' ... '
plperl.use_strict= on


Data::Dumper

use_strict=onstrict

PL / Python


Di dalamnya, interpreter dibuat dengan cara yang sama saat pertama kali diakses. Dan di sini penting untuk segera memutuskan python mana yang Anda inginkan: kedua atau ketiga. Seperti yang Anda ketahui, Python ada dalam dua versi populer (Python 2 dan Python 3), tetapi masalahnya adalah bahwa s-shki mereka tidak rukun dalam satu proses: ada konflik dengan nama. Jika Anda bekerja dengan v2 dalam satu sesi dan kemudian memanggil v3, maka Postgres akan macet, dan untuk proses server (backend) ini akan menjadi kesalahan fatal. Untuk mengakses versi yang berbeda, Anda perlu membuka sesi lain.

Tidak seperti Perl, python tidak dapat diberitahu apa yang harus dilakukan selama inisialisasi. Ketidaknyamanan lain: single-liner tidak nyaman untuk dilakukan.

Dalam semua fungsi Python, dua kamus didefinisikan - statis SDdan global GD. Global memungkinkanbertukar data dengan semua fungsi dalam satu backend - yang menarik dan berbahaya pada saat bersamaan. Setiap fungsi memiliki kamus statis.

Dalam PL / Python, Anda dapat membuat subtransaksi, yang akan kita bahas di bawah ini.

PL / V8


Itu hanya DIPERCAYA.

Secara mudah, data JSON secara otomatis dikonversi ke struktur JS. Di PL / V8, seperti pada PL / Python, Anda dapat melakukan subtransaksi. Ada antarmuka untuk panggilan fungsi yang disederhanakan. Ini adalah satu-satunya bahasa prosedural yang dipertanyakan di mana fungsi jendela dapat didefinisikan . Mereka menyarankan bahwa mereka dapat didefinisikan pada PL / R , tetapi bahasa ini berada di luar cakupan artikel ini.

Dan hanya di PL / V8 apakah ada batas waktu eksekusi. Benar, ini tidak dihidupkan secara default, dan jika Anda membangun PL / V8 dengan tangan, Anda harus mengatakan bahwa itu dihidupkan selama perakitan, dan kemudian Anda dapat mengatur batas waktu untuk panggilan fungsi dengan parameter konfigurasi.

Inisialisasi dalam PL / V8 terlihat menarik: karena dipercaya, ia tidak dapat membaca perpustakaan dari disk, ia tidak dapat memuat apa pun dari mana saja. Dia dapat mengambil semua yang dia butuhkan hanya dari pangkalan. Oleh karena itu, fungsi penginisialisasi yang tersimpan didefinisikan yang dipanggil ketika juru bahasa mulai. Nama fungsi ditentukan dalam parameter konfigurasi khusus:

plv8.start_proc=my_init # ( PL/V8-)

Selama inisialisasi, variabel dan fungsi global dapat dibuat dengan menetapkan nilainya ke atribut variabel ini. Misalnya, seperti ini:

CREATE OR REPLACE FUNCTION my_init()
RETURNS void LANGUAGE plv8 AS $$
     this.get_57 = function() { return 57; }; //   
     this.pi_square = 9.8696044;  //   
$$;
SET plv8.start_proc = 'my_init';
DO LANGUAGE plv8 $$
     plv8.elog(NOTICE, pi_square, get_57() );
$$;

Perbandingan PL / Perl vs PL / Python vs PL / V8 dalam prakteknya


Halo Dunia!


Mari kita lakukan latihan sederhana dengan output frasa ini dalam ketiga bahasa, pertama dalam PL / Perl . Dan biarkan dia melakukan hal lain yang bermanfaat, misalnya, memberi tahu versinya:

DO $$
     elog(NOTICE,"Hello World! $]");
$$ LANGUAGE plperl;

NOTICE:  Hello World!
DO

Anda juga dapat menggunakan fungsi Perl warndan die.

Sekarang dalam PL / Python . Lebih tepatnya pada PL / Python3u (tidak dipercaya) - untuk kepastian.

DO $$
     import sys
     plpy.notice('Hello World! ' , hint=" ", detail=sys.version_info)
$$ LANGUAGE plpython3u;


NOTICE:  Hello World! 
DETAIL:  sys.version_info(major=3, minor=6, micro=9, releaselevel='final', serial=0)
HINT:   
DO

Bisa menggunakan throw 'Errmsg'. Ada banyak hal yang dapat Anda ekstrak dari pesan Postgres: mereka berisi Petunjuk, Detail, nomor baris, dan banyak parameter lainnya. Dalam PL / Python, mereka dapat dilewatkan, tetapi tidak dalam bahasa lain yang sedang dipertimbangkan: cara mereka hanya dapat dikutuk dengan baris teks biasa.

Dalam PL / Python, setiap level logging postgres memiliki fungsinya sendiri: PEMBERITAHUAN, PERINGATAN, DEBUG, LOG, INFO, FATAL. Jika KESALAHAN, maka transaksi telah jatuh, jika FATAL, seluruh backend telah jatuh. Untungnya, masalahnya tidak mencapai PANIC. Anda bisa baca di sini .

PL / V8

Dalam bahasa ini, Hello world sangat mirip dengan mutiara. Anda bisa berhenti exceptionmenggunakan throw, dan ini juga akan menjadi penanganan kesalahan, meskipun alat tidak semaju di Python. Jika kamu menulisplv8.elog(ERROR), efeknya akan sama.

DO $$
     plv8.elog(NOTICE, 'Hello World!', plv8.version);
$$ LANGUAGE plv8;

NOTICE:  Hello World! 2.3.14
DO

Bekerja dengan pangkalan


Sekarang mari kita lihat bagaimana cara bekerja dengan database dari prosedur tersimpan. Postgres memiliki SPI (Server Programming Interface). Ini adalah serangkaian fungsi C yang tersedia untuk semua penulis ekstensi. Hampir semua bahasa PL menyediakan pembungkus untuk SPI, tetapi setiap bahasa melakukannya sedikit berbeda.

Fungsi yang ditulis dalam C tetapi menggunakan SPI tidak mungkin memberikan keuntungan yang signifikan dibandingkan dengan PL / PgSQL dan bahasa prosedural lainnya. Tetapi fungsi C yang memintas SPI dan bekerja dengan data tanpa perantara (misalnya table_beginscan/heap_getnext) akan bekerja dengan urutan yang lebih cepat.

PL / Java juga menggunakan SPI. Tetapi bekerja dengan database masih terjadi dalam gaya JDBC dan standar JDBC. Untuk pembuat kode dalam PL / Java, semuanya terjadi seolah-olah Anda bekerja dari aplikasi klien, tetapi JNI (Java Native Interface) menerjemahkan panggilan ke database ke dalam fungsi SPI yang sama. Sangat mudah, dan tidak ada kendala mendasar untuk menerjemahkan prinsip ini menjadi PL / Perl dan PL / Python, tetapi untuk beberapa alasan ini belum dilakukan, dan sejauh ini tidak terlihat dalam rencana.

Tentu saja, jika mau, Anda dapat pergi ke pangkalan asing dengan cara biasa - melalui DBI atau Psycopg . Dimungkinkan untuk database lokal, tetapi mengapa.

Jika Anda tidak masuk ke topik holistik "proses dalam basis vs proses pada klien", dan segera melanjutkan dari pemrosesan maksimum lebih dekat ke data (setidaknya agar tidak mendorong sampel raksasa melalui jaringan), maka solusi untuk menggunakan fungsi yang tersimpan di server terlihat tentu saja.

Kinerja : perlu diingat bahwa SPI memiliki beberapa overhead, dan query SQL dalam fungsi mungkin lebih lambat daripada tanpa fungsi. Postgres ke-13 termasuk tambalan oleh Konstantin Knizhnik , yang mengurangi biaya ini. Tetapi, tentu saja, pemrosesan hasil query dalam fungsi yang disimpan tidak memerlukan transfer hasil ke klien, dan karenanya dapat bermanfaat dalam hal kinerja.

Keamanan: satu set fungsi debugged dan diuji mengisolasi struktur database dari pengguna, melindungi terhadap suntikan SQL dan kerusakan lainnya. Kalau tidak, itu akan tetap sakit kepala untuk setiap pengembang aplikasi. Penggunaan kembali

kode : jika sejumlah besar aplikasi rumit bekerja dengan database, akan lebih mudah untuk menyimpan fungsi yang berguna di server, daripada menulisnya lagi di setiap aplikasi.

Bagaimana dan dalam bentuk apa kita mendapatkan data dari database


Di Perl , semuanya sederhana dan jelas. Panggilan spi_exec_querymengembalikan jumlah baris yang diproses, status dan array baris yang dipilih oleh permintaan SQL:

DO $$ 
     warn Data::Dumper::Dumper(
          spi_exec_query('SELECT 57 AS x')
     )
$$ LANGUAGE plperl;

WARNING:  $VAR1 = {
          'rows' => [
                    {
                      'x' => '57'
                    }
                  ],
          'processed' => 1,
          'status' => 'SPI_OK_SELECT'
        };

Dalam Python, kueri dan hasilnya terlihat seperti ini, tetapi di sini fungsinya tidak mengembalikan struktur data, tetapi objek khusus yang dapat Anda kerjakan dengan cara yang berbeda. Biasanya itu berpura-pura menjadi sebuah array dan, karenanya, Anda dapat mengekstrak string dari itu.

DO $$ 
     plpy.notice(
          plpy.execute('SELECT 57 AS x')
     )
$$ LANGUAGE plpython3u;

NOTICE:  <PLyResult status=5 nrows=1 rows=[{'x': 57}]>
DO

Dan sekarang kita ambil baris 1, keluar dari sana X dan dapatkan nilai - jumlahnya.

DO $$ 
     plpy.notice(
          plpy.execute('SELECT 57 AS x')[0]['x']
      )
$$ LANGUAGE plpython3u;

NOTICE:  57
DO

Dalam PL / V8 :

DO $$ 
     plv8.elog(NOTICE, JSON.stringify(
          plv8.execute('SELECT 57 as x'))
     );
$$ LANGUAGE plv8;

NOTICE:  [{"x":57}]
DO

Untuk melihat strukturnya, kami menggunakan fungsi pustaka JSON.stringify, yang tidak perlu dimuat secara khusus, sudah siap untuk digunakan sebagai bagian dari PL / v8 secara default.

Perisai


Untuk menghindari suntikan jahat SQL, beberapa karakter dalam kueri harus melarikan diri. Untuk melakukan ini, pertama, ada fungsi SPI dan fungsi terkait (ditulis dalam C) dalam bahasa yang bekerja seperti pembungkus SPI. Sebagai contoh, dalam PL / Perl:

quote_literal- mengambil tanda kutip dan ganda 'dan \. Dirancang untuk menyaring data teks.
quote_nullable- sama, tetapi undefdikonversi ke NULL.
quote_ident- mengutip nama tabel atau bidang, jika perlu. Berguna dalam kasus ketika Anda membangun kueri SQL dan mengganti nama-nama objek database di dalamnya.

PL / Perl

DO $$
     warn "macy's";
     warn quote_literal("macy's");
$$ LANGUAGE plperl;

WARNING:  macy's at line 2.
WARNING:  'macy''s' at line 3.
DO

Perlu diingat: nama tabel tidak boleh diloloskan seperti garis teks. Itu sebabnya ada fungsi quote_ident.

Tetapi dalam PL / Perl ada fungsi-fungsi lain untuk melindungi data tipe post-gres individu: Suatu fungsi harus menerima tipe apa pun dan mengubah karakter ragu atipikal menjadi sesuatu yang jelas aman. Ini bekerja dengan sejumlah besar jenis, tetapi, bagaimanapun, tidak dengan semua. Dia, misalnya, tidak akan memahami tipe rentang dan menganggapnya hanya sebagai string teks.

encode_bytea
decode_bytea
encode_array_literal
encode_typed_literal
encode_array_constructor


quote_typed_literal

DO $$
     warn encode_typed_literal(
          ["", " "], "text[]"
     );
$$ LANGUAGE plperl;

WARNING:  {," "} at line 2.
DO

Ada tiga fungsi serupa di PL / Python , dan mereka bekerja dengan cara yang sama:

plpy.quote_literal
plpy.quote_nullable
plpy.quote_ident


DO $$ plpy.notice(
     plpy.quote_literal("Macy's"));
$$ LANGUAGE plpython3u;
NOTICE:  'Macy''s'
DO

Apakah fungsi dalam PL / V8 sama ?

Tentu saja! Semuanya sama hingga fitur sintaksis.

plv8.quote_literal
plv8.quote_nullable
plv8.quote_ident


DO $$
    plv8.elog(NOTICE, plv8.quote_nullable("Macy's"));
$$ LANGUAGE plv8;

NOTICE:  'Macy''s'

Performa


Bahasa mana yang paling cepat? Biasanya mereka menjawab: C. Tetapi jawaban yang benar adalah C atau SQL. Mengapa SQL Faktanya adalah bahwa fungsi dalam bahasa ini tidak selalu dilakukan secara eksplisit. Itu bisa menjadi tertanam dalam permintaan (scheduler akan menanamkan fungsi di tubuh permintaan utama), mengoptimalkan dengan baik dengan permintaan, dan hasilnya akan lebih cepat. Tetapi dalam kondisi apa kode dapat disematkan dalam permintaan? Ada beberapa kondisi sederhana yang dapat Anda baca, katakanlah, di sini . Misalnya, suatu fungsi tidak boleh dieksekusi dengan hak pemilik (untuk menjadi DEFINER KEAMANAN). Sebagian besar fungsi sederhana akan sesuai dengan kondisi ini.

Pada artikel ini kita akan mengukur "pada lutut", tidak serius. Kami membutuhkan perbandingan kasar. Pertama, nyalakan waktu:

\timing

Mari kita coba SQL (Waktu pelaksanaan perintah di bawah ini adalah nilai rata-rata bulat yang diterima penulis pada PC berusia enam tahun yang dibongkar. Mereka dapat dibandingkan satu sama lain, tetapi mereka tidak mengklaim sebagai ilmiah):

SELECT count(*) FROM pg_class;
0.5 ms

Ini bekerja sangat cepat. Dalam bahasa lain, fungsi panggilan dari bahasa tersebut terbuang. Tentu saja, pertama kali permintaan akan berjalan lebih lambat karena inisialisasi penerjemah. Kemudian stabil.

Mari kita coba PL / pgSQL :

DO $$
     DECLARE a int;
     BEGIN
          SELECT count(*) INTO a FROM pg_class;
     END;
$$ LANGUAGE plpgsql;
0.7 ms

PL / Perl :

DO $$
     my $x = spi_exec_query('SELECT count(*) FROM pg_class');
$$ LANGUAGE plperl;
0.7 ms

PL / Python:

DO $$
     x = plpy.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plpythonu;
0.8 ms

Itu Python 2. Sekarang Python 3 (ingat: Python2 dan Python3 tidak hidup damai dalam sesi yang sama, konflik nama mungkin terjadi):

DO $$
     x = plpy.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plpython3u;
0.9ms

Dan akhirnya, PL / V8 :

DO $$
     var x = plv8.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plv8 ;
0.9 ms

Tapi entah kenapa sangat cepat. Mari kita coba jalankan query 1000 kali atau 1 juta kali, tiba-tiba perbedaannya akan lebih terlihat:

PL / pgSQL :

DO $$
     DECLARE a int; i int;
     BEGIN FOR i IN 0..999999 LOOP
          SELECT count(*) INTO a FROM pg_class;
    END LOOP;
END;
$$ LANGUAGE plpgsql;
53s

PL / Perl :

DO $$
     for (0..999999) {
          spi_exec_query('SELECT count(*) FROM pg_class');
     }
$$ LANGUAGE plperl;
102s

PL / Python 3 :

DO $$
     for i in range (0,1000000) :
          plpy.execute('SELECT count(*) FROM pg_class')
$$ LANGUAGE plpython3u;
98s

PL / V8 :

DO $$
     for(var i=0;i<1000;i++)
          plv8.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plv8;
100ms

Perhatikan bahwa dengan PL / V8, percobaan dilakukan dengan seribu, bukan satu juta iterasi. Dengan sumber daya moderat, PL / V8 dalam siklus 1 juta operasi akan memakan semua memori dan sepenuhnya menggantung mobil. Sudah di seribu iterasi, proses postgres memilih memori 3,5GB dan 100% menulis ke disk. Bahkan, postgres meluncurkan lingkungan V8, dan tentu saja itu memakan memori. Setelah mengeksekusi permintaan, monster turbo ini tidak akan mengembalikan memori. Untuk mengosongkan memori, Anda harus mengakhiri sesi.

Kita melihat bahwa PL / pgSQL sudah 2 kali lebih cepat dari PL / Perl dan PL / Python. PL / V8 masih sedikit di belakang mereka, tetapi menjelang akhir artikel dia sebagian direhabilitasi.

Secara umum, Perl dengan Python dalam percobaan ini menunjukkan hasil yang kira-kira sama. Perl dulu sedikit lebih rendah daripada Python, dalam versi modern, ini sedikit lebih cepat. Python ketiga sedikit lebih lambat dari yang kedua. Seluruh perbedaannya adalah dalam 15%.

Performa dengan SIAPKAN


Orang yang tahu akan mengerti: ada sesuatu yang salah. PL / pgSQL dapat secara otomatis me- cache rencana kueri , dan dalam PL / *, setiap kali kueri dijadwalkan kembali. Dengan cara yang baik, Anda perlu menyiapkan permintaan, membangun rencana permintaan, dan kemudian menurut rencana ini, permintaan tersebut harus dieksekusi sebanyak yang diperlukan. Di PL / *, Anda dapat bekerja secara eksplisit dengan paket kueri, yang akan kami coba mulai dengan PL / Perl :

DO $$
     my $h = spi_prepare('SELECT count(*) FROM pg_class');
     for (0..999999) {
          spi_exec_prepared($h);
     }
     spi_freeplan($h);
$$ LANGUAGE plperl;
60s

PL / Python 3 :

DO $$
     h = plpy.prepare('SELECT count(*) FROM pg_class')
     for i in range (0,1000000): plpy.execute(h)
$$ LANGUAGE plpython3u;
62s

PL / V8 :

DO $$
     var h=plv8.prepare('SELECT count(*) FROM pg_class');
     for(var i=0;i<1000;i++) h.execute();
$$ LANGUAGE plv8;
53ms

Dengan preparedua bahasa kami, kami hampir terjebak dengan PL / pgSQL, sementara yang ketiga juga ingin, tetapi tidak mencapai garis akhir karena meningkatnya permintaan untuk memori.

Tetapi jika Anda tidak memperhitungkan memori akun, maka jelas bahwa semua bahasa hampir saling berhadapan - dan tidak secara kebetulan. Kemacetan mereka sekarang umum - bekerja dengan database melalui SPI.

Kinerja komputasi


Kami melihat bahwa kinerja bahasa telah beristirahat dalam bekerja dengan database. Untuk membandingkan bahasa satu sama lain, mari kita coba menghitung sesuatu tanpa menggunakan database, misalnya, jumlah kuadrat.

PL / pgSQL :

DO $$
     DECLARE i bigint; a bigint;
     BEGIN a=0;
     FOR i IN 0..1000000 LOOP
          a=a+i*i::bigint;
     END LOOP;
END;
$$ LANGUAGE plpgsql;
280ms

PL / Perl :

DO $$
     my $a=0;
     for my $i (0..1000000) { $a+=$i*$i; };
     warn $a;
$$ LANGUAGE plperl;
63ms

PL / Python 3 :

DO $$
a=0
for i in range(1,1000001): a=a+i*i
$$ LANGUAGE plpython3u;
73ms

PL / V8 :

DO $$
     var a=0;
     for(var i=0;i<=1000000;i++) a+=i*i;
     plv8.elog(NOTICE, a);
$$ language plv8;
7.5ms

Kita melihat bahwa PL / Perl dan PL / Python menyusul dan menyusul PL / pgSQL, mereka 4 kali lebih cepat. Dan delapan merobek semua orang! Tetapi apakah ini benar-benar gratis? Atau akankah kita mendapatkannya untuk kepala? Ya kita akan.

Angka dalam JavaScript adalah float, dan hasilnya cepat, tetapi tidak akurat: 333333833333127550, bukan 333333833333500000.

Berikut adalah rumus yang digunakan untuk menghitung hasil pasti:

โˆ‘ = n*(n+1)*(2n+1)/6

Sebagai latihan, Anda bisa membuktikannya menggunakan induksi matematika.

Dalam urutan tawa

DO LANGUAGE plv8 $$
plv8.elog(NOTICE, parseInt(33333383333312755033)) $$;

NOTICE:
33333383333312754000

Dalam Javascript, parseIntitu masih float, bukan Int.

Namun demikian, BigInt muncul di V8 pada tahun 2018 , dan sekarang dapat dihitung dengan pasti, tetapi dengan merugikan kecepatan, karena itu bukan bilangan bulat 64-bit, tetapi bilangan bulat dari kedalaman bit sewenang-wenang. Namun, dalam PL / V8 inovasi ini belum mencapai. Dalam bahasa prosedural lain, angka bit sewenang-wenang (analog SQL numeric) didukung melalui perpustakaan khusus.

Di Perl, ada modul Math :: BigFloat untuk aritmatika dengan ketepatan arbitrer, dan dalam Python paket Bigfloat adalah pembungkus Cython di sekitar pustaka GNU MPFR .

Fungsi kinerja untuk menyortir


Berikut ini adalah contoh praktis, yang menunjukkan perbedaan dalam kinerja penyortiran berdasarkan fungsi, jika fungsi ini ditulis dalam bahasa yang berbeda. Tugas: untuk mengurutkan bidang teks yang berisi jumlah masalah jurnal, yang mungkin sebagai berikut:

1
2
3
4-5
6
6A
6
11
12

Itu itu sebenarnya sebuah string, tetapi dimulai dengan angka, dan Anda perlu mengurutkan berdasarkan angka-angka ini. Oleh karena itu, untuk mengurutkan dengan benar sebagai string, kami menambah bagian numerik dengan nol di sebelah kiri untuk mendapatkan:

0000000001
0000000002
0000000003
0000000004-5
0000000006
0000000006A
0000000006
0000000011
0000000012

Ya, saya tahu bahwa ini bukan satu-satunya solusi untuk masalah (dan bahkan tidak benar). Tapi misalnya, itu akan dilakukan.

Untuk meminta suatu tipe, SELECT ... ORDER BY nsort(n)kami menulis fungsi dalam PL / Perl, SQL, PL / Python, dan PL / V8 yang mengonversi nomor jurnal menjadi bentuk ini:

CREATE OR REPLACE FUNCTION nsort(text) RETURNS text 
   LANGUAGE PLPERL IMMUTABLE AS $$
    my $x = shift;
    return ($x =~ /^\s*(\d+)(.*)$/)
        ? sprintf("%010d", $1).$2
        : $x;
$$;

CREATE OR REPLACE FUNCTION _nsort(x text) RETURNS text
     LANGUAGE SQL  IMMUTABLE  AS $$
 WITH y AS (
    SELECT regexp_match(x,'^\s*(\d*)(.*)$') as z
 )
 SELECT CASE WHEN z[1] = '' THEN x ELSE lpad(z[1],10,'0') || z[2] END FROM y;
$$;

CREATE OR REPLACE FUNCTION py_nsort(x text) RETURNS text 
   LANGUAGE plpython2u IMMUTABLE AS $$
import re
r = re.match('^\s*(\d+)(.*)$', x)
return x if r == None else ('%010d' % int(r.group(1))) + r.group(2)
$$;

CREATE OR REPLACE FUNCTION js_nsort(x text) RETURNS text 
   LANGUAGE plv8 IMMUTABLE AS $$
var m = x.match(/^\s*(\d+)(.*)$/);
if(m) { return m[1].padStart(10-m[1].length,'0') + m[2]; }
else { return x; } 
$$;

Di perpustakaan saya yang berisi 15,5 ribu artikel jurnal, kueri yang menggunakan fungsi dalam PL / Perl membutuhkan sekitar 64 ms terhadap 120 ms dalam PL / Python dan 200 ms dalam PL / PgSQL. Tetapi yang tercepat - PL / v8: 54ms.

Catatan: saat bereksperimen dengan penyortiran, berikan jumlah memori kerja yang diperlukan sehingga penyortiran masuk dalam memori (EXPLAIN kemudian akan ditampilkan Sort Method: quicksort). Jumlah memori diatur oleh parameter work_mem:

set work_mem = '20MB';

Penyimpanan


Perl tidak suka struktur yang dililitkan, ia tidak tahu cara membersihkannya. Jika Anda amemiliki pointer ke b, dan bpointer ke a, maka penghitung referensi tidak akan pernah diatur ulang dan memori tidak akan dibebaskan.

Bahasa pengumpulan sampah memiliki masalah lain. Tidak diketahui, misalnya, kapan memori akan dibebaskan atau apakah akan dibebaskan sama sekali. Atau - jika Anda tidak sengaja menangani hal ini - pengumpul akan mengumpulkan sampah pada saat yang paling tidak tepat.

Tetapi ada juga fitur manajemen memori yang berhubungan langsung dengan Postgres. Ada struktur yang mengalokasikan SPI, dan Perl tidak selalu menyadari bahwa mereka perlu dibebaskan.

PL / Perl

Ini BUKANNYA:

CREATE OR REPLACE function cr()
RETURNS int LANGUAGE plperl AS
$$
     return spi_exec_query(
           'SELECT count(*) FROM pg_class'
     )->{rows}->[0]->{count};
$$;

Begitulah yang terjadi:

CREATE OR REPLACE function cr()
RETURNS int LANGUAGE plperl AS
$$
     my $h = spi_prepare(
          'SELECT count(*) FROM pg_class'
     );
     return spi_exec_prepared($h)->{rows}->[0]->{count};
$$;

Setelah eksekusi, pawang $hakan tetap hidup, terlepas dari kenyataan bahwa tidak ada satu pun link hidup dengannya yang tersisa.

Tidak apa-apa, Anda hanya perlu mengingat kebutuhan untuk secara eksplisit melepaskan sumber daya dengan spi_freeplan($h):

CREATE OR REPLACE function cr()
RETURNS int LANGUAGE plperl AS
$$
     my $h = spi_prepare(
          'select count(*) from pg_class'
     );
     my $res = spi_exec_prepared($h)->{rows}->[0]->{count};
     spi_freeplan($h);
     return $res;
$$;

PL / Python:

Python tidak pernah mengalir , paket ini secara otomatis dirilis:

CREATE OR REPLACE function cr3() RETURNS int
LANGUAGE plpythonu as
$$
     return plpy.execute(
           'select count(*) from pg_class'
     )[0]['count']
$$;

PL / V8

Kisah yang sama dengan Perl. Tidak mengalir seperti ini:

CREATE OR REPLACE FUNCTION crq() RETURNS int
LANGUAGE plv8 AS
$$
     return plv8.execute(
          'select count(*) from pg_classโ€˜
     )[0].count;
$$;

Begitulah yang terjadi:

CREATE OR REPLACE FUNCTION crq() RETURNS int
LANGUAGE plv8 AS
$$
     var h = plv8.prepare(
          'select count(*) from pg_class'
     );
     return h.execute()[0].count;
$$;

Sekali lagi: jangan lupa tentang membebaskan sumber daya. Ini dia. h.free();

Itu tidak mengalir:

CREATE OR REPLACE FUNCTION crq() RETURNS int
LANGUAGE plv8 AS
$$
     var h = plv8.prepare(
          'select count(*) from pg_class'
     );
     var r = h.execute()[0].count;
     h.free();
     return r;
$$;

Parameter


Saatnya untuk memahami bagaimana argumen dilewatkan ke fungsi. Dalam contoh, kita akan melewatkan 4 parameter dengan tipe ke fungsi:

  • seluruh;
  • sebuah array;
  • bytea dan
  • jsonb

Bagaimana mereka masuk ke PL / Perl ?

CREATE OR REPLACE FUNCTION crq(a int, b
bytea, c int[], d jsonb ) RETURNS void
LANGUAGE plperl AS
$$
    warn Dumper(@_);
$$;

SELECT crq(1,'abcd', ARRAY[1,2,3],'{"a":2,"b":3}');


WARNING:  $VAR1 = '1';
$VAR2 = '\\x61626364';
$VAR3 = bless( {
                 'array' => [
                              '1',
                              '2',
                              '3'
                            ],
                 'typeoid' => 1007
               }, 'PostgreSQL::InServer::ARRAY' );
$VAR4 = '{"a": 2, "b": 3}';
 crq 
-----
(1 row)

Apakah itu JSON atau JSONB - dalam hal ini, tidak ada bedanya: mereka masih berupa string. Ini adalah biaya untuk keserbagunaan: Postgres memiliki banyak jenis, dengan berbagai tingkat "bawaan". Untuk menuntut dari pengembang bahwa dengan tipe baru ia segera menyediakan dan fungsi konversi untuk semua PL / * akan terlalu banyak. Secara default, banyak tipe dilewatkan sebagai string. Tapi ini tidak selalu nyaman, Anda harus menguraikan persyaratan ini. Tentu saja, saya ingin data Postgres segera berubah menjadi struktur Perl yang sesuai. Secara default, ini tidak terjadi, tetapi mulai dari 9.6, mekanisme TRANSFORM muncul - kemampuan untuk mendefinisikan fungsi konversi tipe: CREATE TRANSFORM .

Untuk membuat TRANSFORM, Anda perlu menulis dua fungsi dalam C: satu akan mengonversi data dari jenis tertentu ke satu sisi, yang lain kembali. Harap dicatat, TRANSFORM bekerja di empat tempat:

  • Saat melewatkan parameter ke suatu fungsi;
  • Saat mengembalikan nilai fungsi;
  • Saat mengirimkan parameter ke panggilan SPI di dalam suatu fungsi;
  • Setelah menerima hasil panggilan SPI di dalam fungsi.

TRANSFORMASI JSONB untuk Perl dan Python, yang dikembangkan oleh Anton Bykov, muncul dalam versi 11 Postgres. Sekarang Anda tidak perlu mengurai JSONB, ia masuk ke Perl segera sebagai struktur yang sesuai. Anda harus membuat ekstensi jsonb_plperl, dan kemudian Anda dapat menggunakan TRANSFORM:

CREATE EXTENSION jsonb_plperl;
CREATE OR REPLACE FUNCTION crq2(d jsonb)
RETURNS void LANGUAGE plperl
TRANSFORM FOR TYPE jsonb AS $$
     warn Dumper(@_);
$$;

Anda dapat memanggil fungsi ini untuk memverifikasi bahwa JSONB telah berubah menjadi hash mutiara:

SELECT crq2( '{"a":2,"b":3}');


WARNING:  $VAR1 = {
          'a' => '2',
          'b' => '3'
        };
 crq2 
------
(1 row)

Masalah yang sama sekali berbeda!

Penulis artikel ini juga memiliki andil dalam mengembangkan TRANSFORMs. Ternyata tipe data sederhana seperti itu, seperti booleanditeruskan ke PL / Perl dalam bentuk yang tidak nyaman, seperti string teks 't'atau 'f'. Namun dalam pemahaman Perl, string 'f' benar. Untuk menghilangkan ketidaknyamanan, sebuah patch diciptakan yang mendefinisikan konversi untuk tipe Boolean . Patch ini mengenai PostgreSQL 13 dan akan segera tersedia. Karena kesederhanaannya, bool_plperl dapat berfungsi sebagai model awal minimal untuk menulis konversi lainnya.

Saya harap seseorang akan mengembangkan TRANSFORM untuk tipe data lain (bytea, array, tanggal, numerik).

Sekarang mari kita lihat bagaimana parameter dilewatkan dalam Python .

CREATE EXTENSION jsonb_plpython3u;
CREATE OR REPLACE FUNCTION pdump(a int, b bytea, c int[], d jsonb ) RETURNS void
LANGUAGE plpython3u
TRANSFORM FOR TYPE jsonb AS $$
      plpy.warning(a,b,c,d)
$$;

SELECT pdump(1,'abcd', ARRAY[1,2,3],'{"a":2,"b":3}');


WARNING:  (1, b'abcd', [1, 2, 3], {'a': Decimal('2'), 'b': Decimal('3')})
 pdump 
-------
(1 row)

Array dikonversi ke array - ini bagus (dimulai dengan versi PG10, array multidimensi juga ditransfer dengan benar ke python). Di Perl, sebuah array dikonversi ke objek kelas khusus. Yah, sudah jsonbberubah. Tanpa TRANSFORM, jsonb akan diteruskan sebagai string.

Sekarang mari kita lihat dalam bentuk apa parameter masuk ke JS .

CREATE OR REPLACE FUNCTION jsdump(a int, b bytea, c int[], d jsonb) RETURNS void
LANGUAGE plv8 AS $$
     plv8.elog(WARNING,a,b,c,d)
$$;

SELECT jsdump(1,'abcd', ARRAY[1,2,3],'{"a":2,"b":3}');


WARNING:  1 97,98,99,100 1,2,3 [object Object]
jsdump 
-------
(1 row)

JSONB dikonversi ke objek JavaScript tanpa TRANSFORM! Jenis Postgres Sementara juga dikonversi ke tipe JS Tanggal. Sama halnya dengan boolean. Semua transformasi sudah dibangun ke dalam PL / V8.

Bekerja dengan tak terhingga


Konstanta INFINITY tidak sering digunakan, tetapi pekerjaan ceroboh dengannya berbahaya. Dalam PostgreSQL, Infinity dan -Infinity ada sebagai nilai khusus untuk beberapa tipe sementara dan floating-point. Tetapi transfer Infinity ke bahasa prosedural dan sebaliknya harus dibahas secara rinci, karena bekerja dengan mereka tidak hanya bergantung pada bahasa, tetapi juga pada perpustakaan, pada OS dan bahkan pada perangkat keras.

Python memiliki modul Numpy yang mendefinisikan angka tak terhingga:

import numpy as nm
a = nm.inf
b = -nm.inf
print(a, b)

inf -inf

Perl juga memiliki infinity, menggunakan string "infinity"yang dapat disingkat "inf". Misalnya, Anda bisa mengatakan:

perl -e 'print 1 * "inf"'

Inf

atau

perl -e 'print 1/"inf"'

0

Dalam PL / Perl, PL / Python, PL / v8, angka tak terhingga dari Postgres diteruskan dengan benar, tetapi tanggal tak terbatas tidak tepat. Sebaliknya, dalam PL / Perl dan PL / Python tidak ada tipe data bawaan untuk waktu, sebuah string muncul di sana. Di PL / V8, ada Date type bawaan, dan tanggal yang biasa dari postgres berubah menjadi Date. Tapi V8 tidak tahu tanggal yang tidak ada habisnya, dan ketika ditransfer, itu berubah menjadi Invalid Date.

Melewati parameter ke permintaan yang disiapkan


Kembali ke prepare, pertimbangkan bagaimana parameter dilewatkan di sana. Bahasa yang berbeda memiliki banyak kesamaan, karena semuanya didasarkan pada SPI.

Saat Anda menyiapkan kueri dalam PL / Perl , Anda perlu menentukan jenis parameter yang dilewati, dan saat Anda menjalankan kueri, Anda hanya menentukan nilai-nilai parameter ini (parameter dilewatkan ke PL / pgSQL dengan cara yang sama).

DO LANGUAGE plperl $$
     my $h= spi_prepare('SELECT * FROM pg_class WHERE
          relname ~ $1', 'text' );                     #   
     warn Dumper(spi_exec_prepared($h, 'pg_language')); #   
     spi_freeplan($h);
$$;

Dalam PL / Python, esensinya sama, tetapi sintaksinya sedikit berbeda:

DO LANGUAGE plpython3u $$
     h= plpy.prepare('SELECT relname FROM pg_class WHERE relname ~ $1', ['text'] )
     plpy.notice(.execute (['pg_language']))
$$;

Dalam PL / V8, perbedaannya minimal:

DO LANGUAGE plv8 $$
    var h= plv8.prepare('SELECT relname FROM pg_class WHERE relname ~ $1', ['text'] );
    plv8.elog(NOTICE, h.execute (['pg_language']));
    h.free();
$$;

Dalam PL / Java, semuanya berbeda. Di sana, SPI jelas tidak digunakan, tetapi koneksi pseudo-JDBC ke database terbentuk. Untuk programmer PL / Java, semuanya terjadi seolah-olah dia sedang membuat aplikasi klien. Ini nyaman, dan orang juga bisa mendekati desain PL / Perl dan PL / Python, tetapi untuk beberapa alasan ini tidak dilakukan (namun, tidak ada yang melarang membuat beberapa implementasi lebih dari PL / Perl dan PL / Python).

Bekerja dengan kursor


Semua fungsi SPI yang kami gunakan ketika kami pergi ke database - spi_exec_query()dan yang lain - memiliki parameter yang membatasi jumlah baris yang dikembalikan. Jika Anda membutuhkan banyak baris yang dikembalikan, maka Anda tidak dapat melakukannya tanpa kursor untuk menariknya sedikit.

Kursor bekerja dalam semua bahasa ini. Di PL / Perl,
spi_exec_query mengembalikan kursor tempat Anda dapat mengekstraksi string satu per satu. Tidak perlu menutup kursor, itu akan menutup sendiri. Tetapi jika Anda ingin menemukan kembali lagi, Anda dapat secara eksplisit menutupnya dengan perintah close().

DO LANGUAGE plperl $$
    my $cursor = spi_query('SELECT * FROM pg_class');
    my $row;
    while(defined($row = spi_fetchrow($cursor))) {
         warn $row->{relname};
    }
$$;

WARNING:  pg_statistic at line 5.
WARNING:  pg_toast_2604 at line 5.
WARNING:  pg_toast_2604_index at line 5.
WARNING:  pg_toast_2606 at line 5.
WARNING:  pg_toast_2606_index at line 5.
WARNING:  pg_toast_2609 at line 5.
WARNING:  pg_toast_2609_index at line 5.
...

Dalam PL / Python, semuanya sangat mirip, tetapi kursor disajikan sebagai objek yang dapat Anda siklus:

h = plpy.prepare('SELECT ...');
cursor = plpy.cursor(h);
for row in cursor:
...
cursor.close() //  

Di PL / v8, semuanya juga sangat mirip, tetapi jangan lupa untuk membebaskan paket permintaan yang sudah disiapkan:

var h = plv.prepare('SELECT ...');
var cursor = h.cursor();
var row;
while(row = cursor.fetch()) {
...
}
cursor.close();
h.free();

PL / V8: Akses cepat ke fitur


Di PL / V8, Anda dapat memanggil fungsi bukan dari SELECT biasa, tetapi temukan namanya dan segera luncurkan dengan plv8.find_function(name);. Tetapi perlu diingat bahwa dalam JS suatu fungsi tidak bisa polimorfik, seperti pada PostgreSQL, di mana fungsi dengan nama yang sama tetapi dengan parameter yang berbeda dapat hidup berdampingan. Dalam PL / v8, tentu saja, kita dapat membuat fungsi polimorfik, tetapi find_functionakan ada kesalahan saat mencoba menggunakannya .

ERROR:  Error: more than one function named "jsdump"

Jika fungsi dengan nama tidak ambigu, maka itu bisa dipanggil tanpa SPI dan ketik konversi, mis. lebih cepat. Misalnya, seperti ini:

DO LANGUAGE plv8 $$
plv8.find_function('jsdump')(1, 'abc');
$$;

Transaksi


Postgres 11 memiliki banyak kesenangan: prosedur nyata telah muncul . Postgres dulu hanya memiliki fitur. Kegembiraan bukan hanya karena kompatibilitas dan kepatuhan dengan standar SQL, tetapi mengapa: dalam prosedur Anda dapat melakukan dan memutar kembali transaksi.

PL / Perl dan PL / Python sudah memiliki fungsi SPI untuk mengelola transaksi, sementara PL / V8 belum. Dalam PL / Perl, fungsi-fungsi ini dipanggil spi_commit()dan spi_rollback(), dan contoh penggunaannya ada dalam dokumentasi . Dalam PL / Python, ini plpy.commit()dan plpy.rollback().

Subtransaksi


Subtransaksi nyaman untuk penanganan kesalahan yang benar dalam logika multi-level yang kompleks.

Dalam PL / pgSQL di dalam transaksi, setiap blok dengan kata kunci PENGECUALIAN adalah subtransaksi. Anda dapat membaca tentang beberapa masalah kinerja dan keandalan yang mungkin muncul dalam kasus ini, misalnya, di sini .

Tidak ada subtransaksi eksplisit dalam PL / Perl , tetapi mereka dapat disimulasikan melalui savaepoints. Rupanya, jika Anda mau, mudah untuk menulis modul mutiara yang mengimplementasikan subtransaksi dalam bentuk eksplisit.

Dalam PL / Python, sub-transaksi muncul sejak lama: dari 9,5 eksplisit , sebelum itu ada yang implisit . Anda dapat mendefinisikan transaksi, membungkusnyatry-dan jalankan. Jika subtransaksi jatuh, maka kita jatuh ke dalam blok except, jika tidak jatuh, kemudian ke dalam blok elsedan melanjutkan.

try:
     with plpy.subtransaction():
          plpy.execute("...")
          plpy.execute("...")
except plpy.SPIError, e:
. . .
else:
. . .

Desain serupa ada di PL / V8 , hanya dalam sintaks JS.

try {
plv8.subtransaction(function() {
plv8.execute('UPDATE...');
plv8.execute('UPDATE...');
});
}
catch(e) {
...
}

Kesimpulan


Coba, tetapi jangan menyalahgunakan :) Pengetahuan PL / * dapat membawa beberapa manfaat. Seperti alat apa pun, alat ini suka digunakan untuk tujuan yang dimaksudkan.

PL / v8 sangat menjanjikan, tetapi kadang-kadang berperilaku tak terduga dan memiliki sejumlah masalah. Oleh karena itu, lebih baik mengeluarkan bahasa dari kotak jika cocok untuk tugas Anda.

Saya ingin mengucapkan terima kasih kepada Igor Levshin (Igor_Le), yang banyak membantu saya dengan menyiapkan bahan untuk artikel, dan melemparkan beberapa ide yang berguna, serta Evgeny Sergeev dan Alexey Fadeev untuk koreksi yang mereka usulkan.

All Articles